feat: add ability to edit id in flows (#4364)
* all * all * nit mailto * fix * fix
This commit is contained in:
@@ -4737,26 +4737,6 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/flows/input_history/p/{path}:
|
||||
get:
|
||||
summary: list inputs for previous completed flow jobs
|
||||
operationId: getFlowInputHistoryByPath
|
||||
tags:
|
||||
- flow
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
responses:
|
||||
"200":
|
||||
description: input history for completed jobs with this flow path
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Input"
|
||||
|
||||
/w/{workspace}/raw_apps/list:
|
||||
get:
|
||||
|
||||
@@ -51,10 +51,10 @@
|
||||
{#if job.is_flow_step}
|
||||
<div class="flex flex-row gap-2 items-center text-sm">
|
||||
<BarsStaggered size={SMALL_ICON_SIZE} class="text-secondary min-w-3.5" />
|
||||
<span class="whitespace-nowrap">
|
||||
<span class="whitespace-nowrap text-sm">
|
||||
Step of flow
|
||||
<a href={`${base}/run/${job.parent_job}?workspace=${$workspaceStore}`}>
|
||||
{job.parent_job}
|
||||
{truncateRev(job.parent_job, 18)}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
85
frontend/src/lib/components/IdEditorInput.svelte
Normal file
85
frontend/src/lib/components/IdEditorInput.svelte
Normal file
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { ArrowRight } from 'lucide-svelte'
|
||||
import { Button } from './common'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { forbiddenIds } from './flows/idUtils'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
export let initialId: string
|
||||
export let reservedIds: string[] = []
|
||||
export let label: string = 'Component ID'
|
||||
export let value = initialId
|
||||
export let buttonText = ''
|
||||
export let btnClasses = '!p-1 !w-[34px] !ml-1'
|
||||
|
||||
let error = ''
|
||||
const dispatch = createEventDispatcher()
|
||||
const regex = /^[a-zA-Z][a-zA-Z0-9]*$/
|
||||
|
||||
$: validateId(value, reservedIds)
|
||||
|
||||
function validateId(id: string, reservedIds: string[]) {
|
||||
if (id == initialId) {
|
||||
error = ''
|
||||
return
|
||||
}
|
||||
if (!regex.test(value)) {
|
||||
error = 'The ID must include only letters and numbers and start with a letter'
|
||||
} else if (forbiddenIds.includes(value)) {
|
||||
error = 'This ID is reserved'
|
||||
} else if (reservedIds.some((rid) => rid === value)) {
|
||||
error = 'This ID is already in use'
|
||||
} else {
|
||||
error = ''
|
||||
}
|
||||
}
|
||||
|
||||
let inputDiv: HTMLInputElement | undefined = undefined
|
||||
|
||||
$: inputDiv?.focus()
|
||||
</script>
|
||||
|
||||
<label class="block text-primary">
|
||||
{#if label != ''}
|
||||
<div class="pb-1 text-sm text-secondary">{label}</div>
|
||||
{/if}
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
bind:this={inputDiv}
|
||||
autofocus
|
||||
type="text"
|
||||
bind:value
|
||||
class="!w-auto grow"
|
||||
on:click|stopPropagation={() => {}}
|
||||
on:keydown|stopPropagation={({ key }) => {
|
||||
if (key === 'Enter' && error === '' && value !== initialId) {
|
||||
dispatch('save', value)
|
||||
} else if (key == 'Escape') {
|
||||
dispatch('close')
|
||||
}
|
||||
}}
|
||||
on:keypress|stopPropagation
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
{btnClasses}
|
||||
aria-label="Save ID"
|
||||
disabled={error != '' || value === initialId}
|
||||
on:click={() => {
|
||||
dispatch('save', value)
|
||||
}}
|
||||
>
|
||||
{buttonText}<ArrowRight size={18} />
|
||||
</Button>
|
||||
</div>
|
||||
{#if error != ''}
|
||||
<div
|
||||
transition:slide|local={{ duration: 100 }}
|
||||
class="w-full text-sm text-red-600 whitespace-pre-wrap pt-1"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
</label>
|
||||
@@ -1,40 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type { AppViewerContext } from '$lib/components/apps/types'
|
||||
import { allItems } from '$lib/components/apps/utils'
|
||||
import { forbiddenIds } from '$lib/components/flows/idUtils'
|
||||
import { ArrowRight, Pencil } from 'lucide-svelte'
|
||||
import { Pencil } from 'lucide-svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
import { Button, Popup } from '../../../../common'
|
||||
import IdEditorInput from '$lib/components/IdEditorInput.svelte'
|
||||
import { Popup } from '$lib/components/common'
|
||||
|
||||
const { app, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
export let id: string
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const regex = /^[a-zA-Z][a-zA-Z0-9]*$/
|
||||
let value = id
|
||||
let error = ''
|
||||
|
||||
$: if (!regex.test(value)) {
|
||||
error = 'The ID must include only letters and numbers and start with a letter'
|
||||
} else if (forbiddenIds.includes(value)) {
|
||||
error = 'This ID is reserved'
|
||||
} else if (
|
||||
allItems($app.grid, $app.subgrids).some((item) => item.id === value && item.id !== id)
|
||||
) {
|
||||
error = 'This ID is already in use'
|
||||
} else {
|
||||
error = ''
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (error != '') {
|
||||
return
|
||||
}
|
||||
if (value != id) {
|
||||
dispatch('change', value)
|
||||
}
|
||||
}
|
||||
$: reservedIds = allItems($app.grid, $app.subgrids).map((item) => item.id)
|
||||
</script>
|
||||
|
||||
<Popup let:close floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}>
|
||||
@@ -50,44 +28,13 @@
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
<label class="block text-primary">
|
||||
<div class="pb-1 text-sm text-secondary">Component ID</div>
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
type="text"
|
||||
bind:value
|
||||
class="!w-auto grow"
|
||||
on:click|stopPropagation={() => {}}
|
||||
on:keydown|stopPropagation
|
||||
on:keypress|stopPropagation={({ key }) => {
|
||||
if (key === 'Enter') {
|
||||
save()
|
||||
close(null)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Save ID"
|
||||
disabled={error != ''}
|
||||
on:click={() => {
|
||||
save()
|
||||
close(null)
|
||||
}}
|
||||
>
|
||||
<ArrowRight size={18} />
|
||||
</Button>
|
||||
</div>
|
||||
{#if error != ''}
|
||||
<div
|
||||
transition:slide|local={{ duration: 100 }}
|
||||
class="w-full text-sm text-red-600 whitespace-pre-wrap pt-1"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
</label>
|
||||
<IdEditorInput
|
||||
initialId={id}
|
||||
on:close={() => close(null)}
|
||||
on:save={(e) => {
|
||||
dispatch('save', e.detail)
|
||||
close(null)
|
||||
}}
|
||||
{reservedIds}
|
||||
/>
|
||||
</Popup>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
style="width: 275px; height: 34px; background-color: {getStateColor(
|
||||
undefined,
|
||||
darkMode,
|
||||
'#fff'
|
||||
true
|
||||
)};"
|
||||
on:click={() => {
|
||||
selected = true
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
dispatch('node')
|
||||
}}
|
||||
type="button"
|
||||
class="text-primary bg-surface border-[1px] mx-[1px] border-gray-300 dark:border-gray-500 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-full text-sm w-[25px] h-[25px] flex items-center justify-center"
|
||||
class="text-primary bg-surface outline-[1px] outline dark:outline-gray-500 outline-gray-300 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-full text-sm w-[25px] h-[25px] flex items-center justify-center"
|
||||
>
|
||||
<Cross class="mx-[5px]" size={15} />
|
||||
</button>
|
||||
@@ -28,7 +28,7 @@
|
||||
type="button"
|
||||
on:click={() => dispatch('addBranch')}
|
||||
class={twMerge(
|
||||
'text-primary bg-surface border-[1px] mx-[1px] border-gray-300 dark:border-gray-500 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-full text-sm w-[25px] h-[25px] flex items-center justify-center',
|
||||
'text-secondary bg-surface outline-[1px] outline dark:outline-gray-500 outline-gray-300 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-full text-sm w-[25px] h-[25px] flex items-center justify-center',
|
||||
!canAddNode && 'ml-16 mb-2'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<div
|
||||
class={classNames(
|
||||
'fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity',
|
||||
open ? 'ease-out duration-300 opacity-80' : 'ease-in duration-200 opacity-0'
|
||||
open ? 'ease-out duration-300 opacity-100' : 'ease-in duration-200 opacity-0'
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
@@ -29,7 +29,10 @@
|
||||
>
|
||||
Single JavaScript expression. The following functions and objects are available:
|
||||
<ul class="ml-4">
|
||||
<li><b>{'results.<id>'}</b>: the result of step at id 'id'</li>
|
||||
<li
|
||||
><b>{'results.<id>'}</b>: the result of step at id 'id' (use <b>{'results?.<id>'}</b> if id may
|
||||
not exist because branch was not chosen)</li
|
||||
>
|
||||
<li><b>flow_input</b>: the object containing the flow input arguments</li>
|
||||
<li><b>params</b>: the object containing the current step static values</li>
|
||||
<li>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { charsToNumber, numberToChars } from './idUtils'
|
||||
export function nextId(flowState: FlowState, fullFlow: OpenFlow): string {
|
||||
const allIds = dfs(fullFlow.value.modules, (fm) => fm.id)
|
||||
const max = allIds.concat(Object.keys(flowState)).reduce((acc, key) => {
|
||||
if (key === 'failure' || key.includes('branch') || key.includes('loop')) {
|
||||
if (key.length >= 4) {
|
||||
return acc
|
||||
} else {
|
||||
const num = charsToNumber(key)
|
||||
|
||||
@@ -34,3 +34,9 @@ export async function copyFirstStepSchema(flowState: FlowState, flowStore: Writa
|
||||
return flow
|
||||
})
|
||||
}
|
||||
|
||||
export function replaceId(expr: string, id: string, newId: string): string {
|
||||
return expr
|
||||
.replaceAll(`results.${id}`, `results.${newId}`)
|
||||
.replaceAll(`results?.${id}`, `results?.${newId}`)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
Database,
|
||||
Gauge,
|
||||
Move,
|
||||
Pencil,
|
||||
PhoneIncoming,
|
||||
Repeat,
|
||||
Square,
|
||||
@@ -17,9 +18,15 @@
|
||||
} from 'lucide-svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import type { FlowInput } from '../types'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type { FlowEditorContext, FlowInput } from '../types'
|
||||
import { get, type Writable } from 'svelte/store'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import IdEditorInput from '$lib/components/IdEditorInput.svelte'
|
||||
import { dfs } from '../dfs'
|
||||
import { Drawer } from '$lib/components/common'
|
||||
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
||||
import { getDependeeAndDependentComponents } from '../flowExplorer'
|
||||
import { replaceId } from '../flowStore'
|
||||
|
||||
export let selected: boolean = false
|
||||
export let deletable: boolean = false
|
||||
@@ -41,22 +48,85 @@
|
||||
const { flowInputsStore } = getContext<{ flowInputsStore: Writable<FlowInput | undefined> }>(
|
||||
'FlowGraphContext'
|
||||
)
|
||||
|
||||
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const { currentStepStore: copilotCurrentStepStore } =
|
||||
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
|
||||
|
||||
let editId = false
|
||||
|
||||
let newId: string = id ?? ''
|
||||
|
||||
let hover = false
|
||||
</script>
|
||||
|
||||
{#if deletable && id && editId}
|
||||
{@const flowStore = flowEditorContext?.flowStore ? get(flowEditorContext?.flowStore) : undefined}
|
||||
{@const getDeps = getDependeeAndDependentComponents(
|
||||
id,
|
||||
flowStore?.value.modules ?? [],
|
||||
flowStore?.value.failure_module
|
||||
)}
|
||||
<Drawer bind:open={editId}>
|
||||
<DrawerContent title="Edit Step Id {id}" on:close={() => (editId = false)}>
|
||||
<div>
|
||||
<IdEditorInput
|
||||
buttonText="Edit Id "
|
||||
btnClasses="!ml-1"
|
||||
label=""
|
||||
initialId={id}
|
||||
reservedIds={dfs(flowStore?.value.modules ?? [], (x) => x.id)}
|
||||
bind:value={newId}
|
||||
on:save={(e) => {
|
||||
dispatch('changeId', { id, newId: e.detail, deps: getDeps?.dependents ?? {} })
|
||||
editId = false
|
||||
}}
|
||||
on:close={() => {
|
||||
editId = false
|
||||
}}
|
||||
/>
|
||||
<div class="mt-8">
|
||||
<h3>Step Inputs Replacements</h3>
|
||||
<div class="text-2xs text-tertiary pt-0.5">
|
||||
Replace all occurrences of `results.<span class="font-bold">{id}</span>` with{' '}
|
||||
results.<span class="font-bold">{newId}</span> in the step inputs of all steps that depend
|
||||
on it.
|
||||
</div>
|
||||
<div class="pt-8 flex flex-col gap-y-4">
|
||||
{#if Object.keys(getDeps?.dependents ?? {})?.length > 0}
|
||||
{#each Object.entries(getDeps?.dependents ?? {}) as dependents}
|
||||
<div>
|
||||
<h4>{dependents[0]}</h4>
|
||||
{#each dependents?.[1] as d}
|
||||
<span class="font-mono text-sm">{d}</span> →
|
||||
<span class="font-mono text-sm">{replaceId(d, id, newId)}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="text-2xs text-tertiary"> No dependents </div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
{/if}
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full module flex rounded-sm cursor-pointer',
|
||||
selected ? 'outline outline-offset-1 outline-2 outline-gray-600 dark:outline-gray-400' : '',
|
||||
selected ? 'outline outline-offset-0 outline-2 outline-slate-500 dark:outline-gray-400' : '',
|
||||
'flex relative',
|
||||
$copilotCurrentStepStore === id ? 'z-[901]' : ''
|
||||
)}
|
||||
style="width: 275px; height: 34px; background-color: {bgColor};"
|
||||
on:mouseenter={() => (hover = true)}
|
||||
on:mouseleave={() => (hover = false)}
|
||||
on:click
|
||||
>
|
||||
<div class="absolute text-sm right-12 -bottom-3 flex flex-row gap-1 z-10">
|
||||
@@ -67,7 +137,7 @@
|
||||
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
|
||||
>
|
||||
{#if retries}<span class="text-red-400 mr-2">{retries}</span>{/if}
|
||||
<Repeat size={14} />
|
||||
<Repeat size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Retries</svelte:fragment>
|
||||
</Popover>
|
||||
@@ -79,7 +149,7 @@
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
|
||||
>
|
||||
<Gauge size={14} />
|
||||
<Gauge size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Concurrency Limits</svelte:fragment>
|
||||
</Popover>
|
||||
@@ -90,7 +160,7 @@
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
|
||||
>
|
||||
<Database size={14} />
|
||||
<Database size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Cached</svelte:fragment>
|
||||
</Popover>
|
||||
@@ -101,7 +171,7 @@
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
||||
>
|
||||
<Square size={14} />
|
||||
<Square size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Early stop/break</svelte:fragment>
|
||||
</Popover>
|
||||
@@ -112,7 +182,7 @@
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
||||
>
|
||||
<PhoneIncoming size={14} />
|
||||
<PhoneIncoming size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Suspend</svelte:fragment>
|
||||
</Popover>
|
||||
@@ -123,7 +193,7 @@
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
||||
>
|
||||
<Bed size={14} />
|
||||
<Bed size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Sleep</svelte:fragment>
|
||||
</Popover>
|
||||
@@ -134,7 +204,7 @@
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
||||
>
|
||||
<Voicemail size={14} />
|
||||
<Voicemail size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Mocked</svelte:fragment>
|
||||
</Popover>
|
||||
@@ -143,38 +213,49 @@
|
||||
|
||||
<div
|
||||
class="flex gap-1 justify-between items-center w-full overflow-hidden rounded-sm
|
||||
border border-gray-400 dark:border-gray-600 p-2 text-2xs module text-primary"
|
||||
p-2 text-2xs module text-primary"
|
||||
>
|
||||
{#if $$slots.icon}
|
||||
<slot name="icon" />
|
||||
{/if}
|
||||
<div class="truncate" class:font-bold={bold}>{label}</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="flex items-center relative">
|
||||
{#if id}
|
||||
<Badge color="indigo">{id}</Badge>
|
||||
|
||||
{#if deletable}
|
||||
<button
|
||||
class="absolute -left-[20px] z-10 h-[20px] rounded-l rounded-t rounded-s w-[20px] trash center-center text-secondary bg-surface duration-150 hover:bg-blue-400 {editId
|
||||
? '!bg-blue-400'
|
||||
: ''} hover:text-white
|
||||
hover:border-blue-700 hover:!visible {hover ? '' : '!hidden'}"
|
||||
on:click|preventDefault|stopPropagation={(event) => (editId = !editId)}
|
||||
title="Edit Id"><Pencil size={14} /></button
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if deletable}
|
||||
<button
|
||||
class="absolute -top-[10px] -right-[10px] rounded-full h-[20px] w-[20px] trash center-center text-primary
|
||||
border-[1.5px] border-gray-700 bg-surface duration-150 hover:bg-red-400 hover:text-white
|
||||
hover:border-red-700 {selected ? '' : '!hidden'}"
|
||||
class="absolute -top-[10px] -right-[10px] rounded-full h-[20px] w-[20px] trash center-center text-secondary
|
||||
outline-[1px] outline dark:outline-gray-500 outline-gray-300 bg-surface duration-150 hover:bg-red-400 hover:text-white
|
||||
{hover || selected ? '' : '!hidden'}"
|
||||
title="Delete"
|
||||
on:click|preventDefault|stopPropagation={(event) =>
|
||||
dispatch('delete', { event, id, type: modType })}
|
||||
>
|
||||
<X class="mx-[3px]" size={14} strokeWidth={2} />
|
||||
<X class="mx-[3px]" size={12} strokeWidth={2} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="absolute -top-[10px] right-[35px] rounded-full h-[20px] w-[20px] trash center-center text-primary
|
||||
border-[1.5px] border-gray-700 bg-surface duration-150 hover:bg-blue-400 hover:text-white
|
||||
hover:border-blue-700 {selected ? '' : '!hidden'}"
|
||||
class="absolute -top-[10px] right-[60px] rounded-full h-[20px] w-[20px] trash center-center text-secondary
|
||||
outline-[1px] outline dark:outline-gray-500 outline-gray-300 bg-surface duration-150 hover:bg-blue-400 hover:text-white
|
||||
{hover ? '' : '!hidden'}"
|
||||
on:click|preventDefault|stopPropagation={(event) => dispatch('move')}
|
||||
title="Move"
|
||||
>
|
||||
<Move class="mx-[3px]" size={14} strokeWidth={2} />
|
||||
<Move class="mx-[3px]" size={12} strokeWidth={2} />
|
||||
</button>
|
||||
|
||||
{#if (id && Object.values($flowInputsStore?.[id]?.flowStepWarnings || {}).length > 0) || Boolean(warningMessage)}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
import { ignoredTutorials } from '$lib/components/tutorials/ignoredTutorials'
|
||||
import { tutorialInProgress } from '$lib/tutorialUtils'
|
||||
import FlowGraphV2 from '$lib/components/graph/FlowGraphV2.svelte'
|
||||
import { replaceId } from '../flowStore'
|
||||
|
||||
export let modules: FlowModule[] | undefined
|
||||
export let sidebarSize: number | undefined = undefined
|
||||
@@ -328,6 +329,35 @@
|
||||
$flowStore = $flowStore
|
||||
}
|
||||
}}
|
||||
on:changeId={({ detail }) => {
|
||||
let { id, newId, deps } = detail
|
||||
dfs($flowStore.value.modules, (mod) => {
|
||||
if (deps[mod.id]) {
|
||||
deps[mod.id].forEach((dep) => {
|
||||
if (
|
||||
mod.value.type == 'rawscript' ||
|
||||
mod.value.type == 'script' ||
|
||||
mod.value.type == 'flow'
|
||||
) {
|
||||
mod.value.input_transforms = Object.fromEntries(
|
||||
Object.entries(mod.value.input_transforms).map(([k, v]) => {
|
||||
if (v.type == 'javascript') {
|
||||
return [k, { ...v, expr: replaceId(v.expr, id, newId) }]
|
||||
} else {
|
||||
return [k, v]
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (mod.id == id) {
|
||||
mod.id = newId
|
||||
}
|
||||
})
|
||||
$flowStore = $flowStore
|
||||
$selectedId = newId
|
||||
}}
|
||||
on:deleteBranch={async ({ detail }) => {
|
||||
if (detail.module) {
|
||||
await removeBranch(detail.module, detail.index)
|
||||
|
||||
@@ -35,13 +35,13 @@
|
||||
id={`flow-editor-add-step-${index}`}
|
||||
type="button"
|
||||
class={twMerge(
|
||||
'w-6 h-6 flex items-center justify-center',
|
||||
'border border-gray-300 dark:border-gray-500',
|
||||
'text-primary text-sm',
|
||||
'bg-surface focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected rounded-full '
|
||||
'w-5 h-5 flex items-center justify-center',
|
||||
'outline-[1px] outline dark:outline-gray-500 outline-gray-300',
|
||||
'text-secondary',
|
||||
'bg-surface focus:outline-none hover:bg-surface-hover rounded '
|
||||
)}
|
||||
>
|
||||
<Cross size={14} />
|
||||
<Cross size={12} />
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
<div id="flow-editor-insert-module">
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
title="Add a Trigger"
|
||||
slot="trigger"
|
||||
type="button"
|
||||
class="text-primary bg-surface border-[1px] mx-[1px] border-gray-300 dark:border-gray-500 rotate-180 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-gray-200 font-medium rounded-full text-sm w-[25px] h-[25px] flex items-center justify-center"
|
||||
class="text-secondary bg-surface outline-[1px] outline dark:outline-gray-500 outline-gray-300 rotate-180 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-gray-200 font-medium rounded text-sm w-[20px] h-[20px] flex items-center justify-center"
|
||||
>
|
||||
<Zap size={14} />
|
||||
<Zap size={12} />
|
||||
</button>
|
||||
{#if !disableAi}
|
||||
<StepGen {index} bind:funcDesc bind:open {close} {modules} trigger on:insert />
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
mod.value.skip_failures ? '(skip failures)' : ''
|
||||
}`}
|
||||
id={mod.id}
|
||||
on:changeId
|
||||
on:move={() => dispatch('move')}
|
||||
on:delete={onDelete}
|
||||
on:click={() => dispatch('select', mod.id)}
|
||||
@@ -120,6 +121,7 @@
|
||||
{:else if mod.value.type === 'branchone'}
|
||||
<FlowModuleSchemaItem
|
||||
deletable={insertable}
|
||||
on:changeId
|
||||
on:delete={onDelete}
|
||||
on:move={() => dispatch('move')}
|
||||
on:click={() => dispatch('select', mod.id)}
|
||||
@@ -135,6 +137,7 @@
|
||||
{:else if mod.value.type === 'branchall'}
|
||||
<FlowModuleSchemaItem
|
||||
deletable={insertable}
|
||||
on:changeId
|
||||
on:delete={onDelete}
|
||||
on:move={() => dispatch('move')}
|
||||
on:click={() => dispatch('select', mod.id)}
|
||||
@@ -150,6 +153,7 @@
|
||||
{:else}
|
||||
<FlowModuleSchemaItem
|
||||
{retries}
|
||||
on:changeId
|
||||
on:click={() => dispatch('select', mod.id)}
|
||||
on:delete={onDelete}
|
||||
on:move={() => dispatch('move')}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
|
||||
|
||||
export let label: string
|
||||
export let label: string | undefined = undefined
|
||||
export let bgColor: string = ''
|
||||
export let selected: boolean
|
||||
export let selectable: boolean
|
||||
@@ -13,6 +13,7 @@
|
||||
export let center = true
|
||||
export let borderColor: string | undefined = undefined
|
||||
export let hideId: boolean = false
|
||||
export let preLabel: string | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
insert: {
|
||||
@@ -35,8 +36,7 @@
|
||||
'w-full flex relative overflow-hidden rounded-sm',
|
||||
selectable ? 'cursor-pointer' : '',
|
||||
selected ? 'outline outline-offset-1 outline-2 outline-gray-600' : '',
|
||||
label === 'Input' && $copilotCurrentStepStore === 'Input' ? 'z-[901]' : '',
|
||||
'bg-surface'
|
||||
label === 'Input' && $copilotCurrentStepStore === 'Input' ? 'z-[901]' : ''
|
||||
)}
|
||||
style="width: 275px; max-height: 34px; background-color: {bgColor} !important;"
|
||||
on:click={() => {
|
||||
@@ -44,14 +44,15 @@
|
||||
if (id) {
|
||||
dispatch('select', id)
|
||||
} else {
|
||||
dispatch('select', label)
|
||||
dispatch('select', label || label || '')
|
||||
}
|
||||
}
|
||||
}}
|
||||
id={`flow-editor-virtual-${encodeURIComponent(label)}`}
|
||||
title={(label ? label + ' ' : '') + (label ?? '')}
|
||||
id={`flow-editor-virtual-${encodeURIComponent(label || label || '')}`}
|
||||
>
|
||||
<div
|
||||
style={borderColor ? `border-color: ${borderColor};` : ''}
|
||||
style={borderColor ? `border-color: ${borderColor};` : 'border: 0'}
|
||||
class="flex gap-1 justify-between {center
|
||||
? 'items-center'
|
||||
: 'items-baseline'} w-full overflow-hidden rounded-sm border p-2 text-2xs module text-primary border-gray-400 dark:border-gray-600"
|
||||
@@ -61,7 +62,14 @@
|
||||
<span class="mr-2" />
|
||||
{/if}
|
||||
<div />
|
||||
<div class="truncate"><pre>{label}</pre></div>
|
||||
<div class="flex flex-col w-full">
|
||||
{#if label}
|
||||
<div class="truncate text-center">{label}</div>
|
||||
{/if}
|
||||
{#if preLabel}
|
||||
<div class="truncate text-2xs text-center"><pre>{preLabel}</pre></div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
{#if id && !hideId}
|
||||
<Badge color="indigo">{id}</Badge>
|
||||
|
||||
@@ -146,6 +146,9 @@
|
||||
dispatch('select', modId)
|
||||
}
|
||||
},
|
||||
changeId: (detail) => {
|
||||
dispatch('changeId', detail)
|
||||
},
|
||||
delete: (detail, label) => {
|
||||
$selectedId = label
|
||||
|
||||
@@ -274,6 +277,7 @@
|
||||
showLock={false}
|
||||
showZoom={false}
|
||||
showFitView={false}
|
||||
class="!shadow-none"
|
||||
>
|
||||
{#if showDataflow}
|
||||
<Toggle
|
||||
|
||||
@@ -12,6 +12,7 @@ export type GraphEventHandlers = {
|
||||
newBranch: (module: FlowModule) => void
|
||||
move: (module: FlowModule, modules: FlowModule[]) => void
|
||||
selectedIteration: (detail, moduleId: string) => void
|
||||
changeId: (newId: string) => void
|
||||
}
|
||||
|
||||
export default function graphBuilder(
|
||||
@@ -354,11 +355,8 @@ export default function graphBuilder(
|
||||
id: `${module.id}-branch-${branchIndex}`,
|
||||
data: {
|
||||
offset: currentOffset,
|
||||
label:
|
||||
defaultIfEmptyString(branch.summary, 'Branch ' + (branchIndex + 1)) +
|
||||
'\n`' +
|
||||
branch.expr +
|
||||
'`',
|
||||
label: defaultIfEmptyString(branch.summary, 'Branch ' + (branchIndex + 1)),
|
||||
preLabel: branch.summary ? '' : branch.expr,
|
||||
id: module.id,
|
||||
branchIndex: branchIndex,
|
||||
modules: modules,
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<button
|
||||
title="Delete branch"
|
||||
class="z-50 absolute -top-[10px] -right-[10px] rounded-full h-[20px] w-[20px] center-center text-primary
|
||||
border-[1.5px] border-gray-700 bg-surface duration-150 hover:bg-red-400 hover:text-white
|
||||
outline-[1px] outline outline-gray-700 bg-surface duration-150 hover:bg-red-400 hover:text-white
|
||||
hover:border-red-700"
|
||||
on:click|preventDefault|stopPropagation={() => {
|
||||
data.eventHandlers.deleteBranch(
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
export let data: {
|
||||
label: string
|
||||
preLabel: string | undefined
|
||||
insertable: boolean
|
||||
flowModuleStates: Record<string, GraphModuleState> | undefined
|
||||
id: string
|
||||
@@ -30,12 +31,11 @@
|
||||
<NodeWrapper let:darkMode offset={data.offset}>
|
||||
<VirtualItem
|
||||
label={data.label}
|
||||
preLabel={data.preLabel}
|
||||
selectable
|
||||
selected={data.selected}
|
||||
bgColor={getStateColor(undefined, darkMode)}
|
||||
borderColor={borderStatus
|
||||
? getStateColor(borderStatus, darkMode) + (!darkMode ? '; border-width: 3px' : '')
|
||||
: undefined}
|
||||
borderColor={borderStatus ? getStateColor(borderStatus, darkMode) : undefined}
|
||||
on:select={() => {
|
||||
data.eventHandlers.select(data.id)
|
||||
}}
|
||||
|
||||
@@ -46,9 +46,9 @@
|
||||
: undefined}
|
||||
slot="trigger"
|
||||
type="button"
|
||||
class=" bg-surface text-violet-800 dark:text-violet-400 border mx-0.5 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-gray-200 font-medium rounded-full text-sm w-8 h-8 flex items-center justify-center"
|
||||
class=" bg-surface text-violet-800 dark:text-violet-400 border mx-0.5 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-gray-200 font-medium rounded-full text-sm w-7 h-7 flex items-center justify-center"
|
||||
>
|
||||
<Wand2 size={16} />
|
||||
<Wand2 size={12} />
|
||||
</button>
|
||||
{#if !$copilotInfo.exists_openai_resource_path}
|
||||
<div class="text-primary p-4">
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
'/' +
|
||||
(state?.iteration_total ?? '?')
|
||||
: ''}
|
||||
bgColor={getStateColor(type, darkMode, '#fff')}
|
||||
bgColor={getStateColor(type, darkMode, true)}
|
||||
modules={data.modules ?? []}
|
||||
moving={data.moving}
|
||||
duration_ms={state?.duration_ms}
|
||||
@@ -68,6 +68,9 @@
|
||||
on:insert={(e) => {
|
||||
data.eventHandlers.insert(e.detail)
|
||||
}}
|
||||
on:changeId={(e) => {
|
||||
data.eventHandlers.changeId(e.detail)
|
||||
}}
|
||||
on:move={(e) => {
|
||||
data.eventHandlers.move(data.module, data.modules)
|
||||
}}
|
||||
@@ -87,7 +90,7 @@
|
||||
{#if (data.value.type === 'branchall' || data.value.type === 'branchone') && data.insertable}
|
||||
<button
|
||||
title="Add branch"
|
||||
class="rounded-full border hover:bg-surface-hover bg-surface p-1"
|
||||
class="rounded text-secondary border hover:bg-surface-hover bg-surface p-1"
|
||||
on:click={() => {
|
||||
data?.eventHandlers?.newBranch(data.module)
|
||||
}}
|
||||
|
||||
@@ -12,7 +12,7 @@ export const NODE = {
|
||||
export function getStateColor(
|
||||
state: FlowStatusModule['type'] | undefined,
|
||||
isDark: boolean,
|
||||
lightModeBackground: string = '#dfe6ee'
|
||||
nonVirtualItem?: boolean
|
||||
): string {
|
||||
switch (state) {
|
||||
case 'Success':
|
||||
@@ -26,6 +26,10 @@ export function getStateColor(
|
||||
case 'WaitingForExecutor':
|
||||
return isDark ? '#ea580c' : 'rgb(255, 208, 193)'
|
||||
default:
|
||||
return isDark ? '#2e3440' : lightModeBackground
|
||||
if (nonVirtualItem) {
|
||||
return isDark ? '#2E3440' : 'white'
|
||||
} else {
|
||||
return isDark ? '#313742' : '#dfe6ee'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@
|
||||
{#if filteredUsers}
|
||||
{#each filteredUsers.slice(0, nbDisplayed) as { email, username, is_admin, operator, disabled } (email)}
|
||||
<tr class="!hover:bg-surface-hover">
|
||||
<Cell first>{truncate(email, 20)}</Cell>
|
||||
<Cell first><a href="mailto:{email}">{truncate(email, 20)}</a></Cell>
|
||||
<Cell>{truncate(username, 30)}</Cell>
|
||||
<Cell
|
||||
>{#if usage?.[email] != undefined}{usage?.[email]}{:else}<Loader2
|
||||
|
||||
Reference in New Issue
Block a user