Skip to content

Commit

Permalink
Add first step panel
Browse files Browse the repository at this point in the history
  • Loading branch information
Guilhem-lm committed Jan 8, 2025
1 parent c66d22e commit f662e58
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 68 deletions.
11 changes: 9 additions & 2 deletions frontend/src/lib/components/EditableSchemaForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@
export let lightweightMode: boolean = false
export let displayWebhookWarning: boolean = false
export let dndType: string | undefined = undefined
export let editTab: 'inputEditor' | 'history' | 'savedInputs' | 'json' | 'captures' | undefined
export let editTab:
| 'inputEditor'
| 'history'
| 'savedInputs'
| 'json'
| 'captures'
| 'firstStepInputs'
| undefined
export let previewSchema: Record<string, any> | undefined = undefined
export let editPanelInitialSize: number | undefined = undefined
export let editPanelSize = 0
Expand Down Expand Up @@ -274,7 +281,7 @@
{#if editPanelSize > 0}
<Pane
bind:size={editPanelSize}
minSize={noPreview ? 100 : 35}
minSize={noPreview ? 100 : 50}
class={twMerge('border rounded-md', pannelButtonWidth > 0 ? 'rounded-tl-none' : '')}
>
{#if editTab !== 'inputEditor'}
Expand Down
122 changes: 122 additions & 0 deletions frontend/src/lib/components/FirstStepInputs.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<script lang="ts">
import { createEventDispatcher, onDestroy, getContext } from 'svelte'
import { getFirstStepSchema } from '$lib/components/flows/flowStore'
import type { FlowEditorContext } from '$lib/components/flows/types'
import { twMerge } from 'tailwind-merge'
import { clickOutside } from '$lib/utils'
import { Alert } from '$lib/components/common'
import FlowModuleSchemaItemViewer from '$lib/components/flows/map/FlowModuleSchemaItemViewer.svelte'
import LanguageIcon from '$lib/components/common/languageIcons/LanguageIcon.svelte'
import { prettyLanguage } from '$lib/common'
import IconedResourceType from '$lib/components/IconedResourceType.svelte'
import { Building } from 'lucide-svelte'
const { flowStore, flowStateStore } = getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
let schema: Record<string, any> | undefined = undefined
let error: string | undefined = undefined
let mod: any | undefined = undefined
async function loadSchema() {
try {
const res = await getFirstStepSchema($flowStateStore, flowStore)
schema = res.schema
mod = res.mod
dispatch('connectFirstNode', { connectFirstNode: res.connectFirstNode })
} catch (e) {
error = e
}
}
loadSchema()
function handleClick() {
selected = !selected
dispatch('select', selected ? schema : undefined)
}
onDestroy(() => {
selected = false
dispatch('select', undefined)
})
let selected: boolean = false
async function getPropPickerElements(): Promise<HTMLElement[]> {
return Array.from(
document.querySelectorAll('[data-schema-picker], [data-schema-picker] *')
) as HTMLElement[]
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape' && selected) {
selected = false
dispatch('select', undefined)
}
}
let firstUpdate = true
$: if (schema && !selected && firstUpdate) {
firstUpdate = false
setTimeout(() => {
selected = true
dispatch('select', schema)
}, 200)
}
</script>

<svelte:window on:keydown={handleKeydown} />

<div class="h-full">
{#if schema && mod}
<button
class={twMerge(
'w-full shadow-md rounded-sm',
selected ? 'bg-surface-selected' : 'hover:bg-surface-hover'
)}
disabled={!schema}
on:click={handleClick}
use:clickOutside={{ capture: false, exclude: getPropPickerElements }}
on:click_outside={() => {
if (selected) {
selected = false
dispatch('select', undefined)
}
}}
>
<FlowModuleSchemaItemViewer
on:click={handleClick}
deletable={false}
id={mod.id}
label={mod.summary ||
(`path` in mod.value ? mod.value.path : undefined) ||
(mod.value.type === 'rawscript'
? `Inline ${prettyLanguage(mod.value.language)}`
: 'To be defined')}
path={`path` in mod.value && mod.summary ? mod.value.path : ''}
>
<div slot="icon">
{#if mod.value.type === 'rawscript'}
<LanguageIcon lang={mod.value.language} width={16} height={16} />
{:else if mod.value.type === 'script'}
{#if mod.value.path.startsWith('hub/')}
<div>
<IconedResourceType
width="20px"
height="20px"
name={mod.value.path.split('/')[2]}
silent={true}
/>
</div>
{:else}
<Building size={14} />
{/if}
{/if}
</div>
</FlowModuleSchemaItemViewer>
</button>
{:else}
<Alert type="warning" title="Cannot load first step's inputs">
{error}
</Alert>
{/if}
</div>
31 changes: 28 additions & 3 deletions frontend/src/lib/components/flows/content/FlowInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { ButtonType } from '$lib/components/common/button/model'
import { getContext } from 'svelte'
import FlowCard from '../common/FlowCard.svelte'
import { copyFirstStepSchema } from '../flowStore'
import type { FlowEditorContext } from '../types'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
Expand All @@ -14,6 +13,7 @@
import FlowInputViewer from '$lib/components/FlowInputViewer.svelte'
import HistoricInputs from '$lib/components/HistoricInputs.svelte'
import SavedInputsPicker from '$lib/components/SavedInputsPicker.svelte'
import FirstStepInputs from '$lib/components/FirstStepInputs.svelte'
import {
CornerDownLeft,
Pen,
Expand All @@ -39,7 +39,7 @@
export let noEditor: boolean
export let disabled: boolean
const { flowStore, flowStateStore, previewArgs, pathStore, initialPath, flowInputEditorState } =
const { flowStore, previewArgs, pathStore, initialPath, flowInputEditorState } =
getContext<FlowEditorContext>('FlowEditorContext')
let pendingJson: string
Expand Down Expand Up @@ -104,7 +104,7 @@
{
label: "First step's inputs",
onClick: () => {
copyFirstStepSchema($flowStateStore, flowStore)
handleEditSchema('firstStepInputs')
},
icon: Code
},
Expand Down Expand Up @@ -244,8 +244,11 @@
history: 'History',
savedInputs: 'Saved inputs',
json: 'JSON',
firstStepInputs: 'First step',
undefined: ''
}
let connectFirstNode: () => void = () => {}
</script>

<!-- Add svelte:window to listen for keyboard events -->
Expand Down Expand Up @@ -438,6 +441,28 @@
>
<SimpleEditor bind:code={pendingJson} lang="json" class="h-full" />
</FlowInputEditor>
{:else if $flowInputEditorState?.selectedTab === 'firstStepInputs'}
<FlowInputEditor
disabled={!previewSchema}
on:applySchemaAndArgs={() => {
applySchemaAndArgs()
connectFirstNode()
}}
on:applySchema={applySchema}
on:destroy={() => {
$flowInputEditorState.payloadData = undefined
pendingJson = ''
}}
>
<FirstStepInputs
on:connectFirstNode={({ detail }) => {
connectFirstNode = detail.connectFirstNode
}}
on:select={(e) => {
previewSchema = e.detail ?? undefined
}}
/>
</FlowInputEditor>
{/if}
</svelte:fragment>
<svelte:fragment slot="runButton">
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/lib/components/flows/flowStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Flow, OpenFlow } from '$lib/gen'
import { writable, type Writable } from 'svelte/store'
import { initFlowState, type FlowState } from './flowState'
import { sendUserToast } from '$lib/toast'
import { get } from 'svelte/store'

export type FlowMode = 'push' | 'pull'

Expand Down Expand Up @@ -40,6 +41,47 @@ export async function copyFirstStepSchema(flowState: FlowState, flowStore: Writa
})
}

export async function getFirstStepSchema(flowState: FlowState, flowStore: Writable<OpenFlow>) {
const flow = get(flowStore)
const firstModuleId = flow.value.modules[0]?.id

if (!firstModuleId || !flowState[firstModuleId]) {
throw new Error('no first step found')
}

const schema = structuredClone(flowState[firstModuleId].schema)
const v = flow.value.modules[0].value

if (v.type !== 'rawscript' && v.type !== 'script') {
throw new Error('only scripts can be used as a input schema')
}

const simplifiedModule = {
id: flow.value.modules[0].id,
summary: flow.value.modules[0].summary,
value: {
type: flow.value.modules[0].value.type,
...('path' in flow.value.modules[0].value ? { path: flow.value.modules[0].value.path } : {}),
...('language' in flow.value.modules[0].value
? { language: flow.value.modules[0].value.language }
: {})
}
}

return {
schema,
mod: simplifiedModule,
connectFirstNode: () => {
Object.keys(v.input_transforms ?? {}).forEach((key) => {
v.input_transforms[key] = {
type: 'javascript',
expr: `flow_input.${key}`
}
})
}
}
}

export function replaceId(expr: string, id: string, newId: string): string {
return expr
.replaceAll(`results.${id}`, `results.${newId}`)
Expand Down
66 changes: 6 additions & 60 deletions frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script lang="ts">
import Badge from '$lib/components/common/badge/Badge.svelte'
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
import Popover from '$lib/components/Popover.svelte'
import { classNames } from '$lib/utils'
Expand All @@ -9,7 +8,6 @@
Database,
Gauge,
Move,
Pencil,
PhoneIncoming,
Repeat,
Square,
Expand All @@ -28,7 +26,7 @@
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import { getDependeeAndDependentComponents } from '../flowExplorer'
import { replaceId } from '../flowStore'
import FlowModuleSchemaItemViewer from './FlowModuleSchemaItemViewer.svelte'
import FlowPropPicker from '$lib/components/flows/propPicker/FlowPropPicker.svelte'
import type { PropPickerContext } from '$lib/components/prop_picker'
export let selected: boolean = false
Expand Down Expand Up @@ -74,11 +72,6 @@
let newId: string = id ?? ''
let hover = false
let idBadgeWidth: number | undefined = undefined
let iconWidth: number | undefined = undefined
$: marginLeft = Math.max(iconWidth ?? 0, idBadgeWidth ?? 0) * 2 + 32
</script>

{#if deletable && id && editId}
Expand Down Expand Up @@ -250,58 +243,11 @@
{/if}
</div>

<div
class="relative flex gap-1 justify-between items-center w-full overflow-hidden rounded-sm
p-2 text-2xs module text-primary"
>
{#if $$slots.icon}
<div class="flex-none" bind:clientWidth={iconWidth}>
<slot name="icon" />
</div>
{/if}

<Popover
class="absolute left-1/2 transform -translate-x-1/2 center-center"
style="max-width: calc(100% - {marginLeft}px)"
>
<div class="text-center truncate {bold ? '!font-bold' : 'font-normal'}">
{label}
</div>
<svelte:fragment slot="text">
<div>
<div>{label}</div>
{#if path != ''}<div>{path}</div>{/if}
</div>
</svelte:fragment>
</Popover>

<div class="flex items-center space-x-2 relative max-w-[25%]" bind:clientWidth={idBadgeWidth}>
{#if id && id !== 'preprocessor' && !id.startsWith('failure') && !id.startsWith('subflow:')}
<Badge
color="indigo"
wrapperClass="max-w-full"
baseClass="max-w-full truncate !px-1"
title={id}
>
<span class="max-w-full text-2xs truncate">{id}</span></Badge
>
{#if deletable}
<button
class="absolute -left-[28px] 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}
{:else if id?.startsWith('subflow:')}
<Badge color="blue" wrapperClass="max-w-full" baseClass="!px-1" title={id}>
<span class="max-w-full text-2xs truncate">{id.substring('subflow:'.length)}</span></Badge
>
{/if}
</div>
</div>
<FlowModuleSchemaItemViewer {label} {path} {id} {deletable} {bold} bind:editId {hover}>
<svelte:fragment slot="icon">
<slot name="icon" />
</svelte:fragment>
</FlowModuleSchemaItemViewer>

{#if id && $flowPropPickerConfig && pickableIds && Object.keys(pickableIds).includes(id)}
<div class="absolute -bottom-[14px] right-[21px] translate-x-[50%] center-center">
Expand Down
Loading

0 comments on commit f662e58

Please sign in to comment.