diff --git a/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte b/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte index 5301ba27e8..770dcf7b59 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte +++ b/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte @@ -112,7 +112,7 @@ aiChatManager.disableAutomaticScroll() }} > -
+
{#each messages as message, messageIndex} {/if} {#if message.role === 'user' && message.snapshot} -
+
Saved {message.snapshot.type === 'flow' ? 'a flow' : 'an app'} snapshot + {#if message.isLoading && !message.needsConfirmation} + + {:else if message.error} + ✗ + {:else if !message.isLoading && !message.error} + ✓ + {/if} + + {message.content} + +
+ - - {#if isExpanded} -
- - {#if hasParameters || message.needsConfirmation} -
- -
- {/if} + + {#if isExpanded} +
+ + {#if hasParameters || message.needsConfirmation} +
+ +
+ {/if} - - {#if message.needsConfirmation} -
- - -
+ + +
- - {:else if !message.isStreamingArguments} - + + {:else if !message.isStreamingArguments} + - - {/if} -
+ + {/if} +
+ {/if} +
+ + {#if previewInfo} + {@const PreviewIcon = previewInfo.icon} + {@const clickable = previewInfo.kind === 'schedule'} + {/if}
diff --git a/frontend/src/lib/components/copilot/chat/previewModalState.svelte.ts b/frontend/src/lib/components/copilot/chat/previewModalState.svelte.ts new file mode 100644 index 0000000000..8c01acc879 --- /dev/null +++ b/frontend/src/lib/components/copilot/chat/previewModalState.svelte.ts @@ -0,0 +1,35 @@ +// Shared state for the "open resource" modal triggered by clicking a Codex-style +// preview card under a tool call (see ToolExecutionDisplay.svelte). The modal itself +// is mounted at the page level (e.g. /design-sandbox) so it overlays the whole UI. + +export type PreviewModalKind = 'schedule' | 'flow' | 'script' | 'app' + +type State = { + kind: PreviewModalKind | null + path: string + title: string + isFlow: boolean +} + +export const previewModalState = $state({ + kind: null, + path: '', + title: '', + isFlow: false +}) + +export function openPreviewModal(opts: { + kind: PreviewModalKind + path?: string + title?: string + isFlow?: boolean +}) { + previewModalState.kind = opts.kind + previewModalState.path = opts.path ?? '' + previewModalState.title = opts.title ?? '' + previewModalState.isFlow = opts.isFlow ?? false +} + +export function closePreviewModal() { + previewModalState.kind = null +} diff --git a/frontend/src/routes/(root)/(logged)/design-sandbox/+page.svelte b/frontend/src/routes/(root)/(logged)/design-sandbox/+page.svelte index c6b7e6648b..1da883ca3f 100644 --- a/frontend/src/routes/(root)/(logged)/design-sandbox/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/design-sandbox/+page.svelte @@ -8,6 +8,12 @@ import { copilotInfo } from '$lib/aiStore' import type { DisplayMessage } from '$lib/components/copilot/chat/shared' import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui' + import Modal2 from '$lib/components/common/modal/Modal2.svelte' + import ScheduleEditorInner from '$lib/components/triggers/schedules/ScheduleEditorInner.svelte' + import { + previewModalState, + closePreviewModal + } from '$lib/components/copilot/chat/previewModalState.svelte' // Fake conversation: user asks the assistant to build a 3-step user-engagement flow. const fakeMessages: DisplayMessage[] = [ @@ -72,16 +78,6 @@ parameters: { cron: '0 9 * * 1-5', timezone: 'Europe/Paris' }, result: { ok: true, schedule_id: 'sched_001' }, showDetails: false - }, - { - role: 'tool', - tool_call_id: 'tool_edit_c', - toolName: 'edit_step', - content: 'Updated `Notify` to reference `user.name`', - parameters: { id: 'c', diff: '- {{ user.first_name }}\n+ {{ user.name }}' }, - result: { ok: true }, - showDetails: false, - isLoading: true } ] @@ -126,6 +122,23 @@ setTimeout(applyMocks, 1500) }) + // Real schedule used by the schedule preview modal. The path must exist in the + // workspace — ScheduleEditorInner.openEdit fetches it via ScheduleService.getSchedule. + // Synthesizing a `defaultCfg` instead leaves too many fields undefined and crashes + // downstream sub-components (Path, ErrorOrRecoveryHandler, etc.). + const SANDBOX_SCHEDULE_PATH = 'u/guilhempw/fake_flow_schedule' + const SANDBOX_SCHEDULE_IS_FLOW = true + + let scheduleEditor: ScheduleEditorInner | undefined = $state(undefined) + + // When the schedule modal opens (kind = 'schedule'), load the real schedule so + // the editor populates fully (cron, timezone, runnable, handlers, etc.). + $effect(() => { + if (previewModalState.kind === 'schedule' && scheduleEditor) { + scheduleEditor.openEdit(SANDBOX_SCHEDULE_PATH, SANDBOX_SCHEDULE_IS_FLOW) + } + }) + // Minimal fake flow with a few modules so the editor has something to show. let flowStore = $state({ val: { @@ -210,3 +223,25 @@ + + + previewModalState.kind === 'schedule', + (v) => { + if (!v) closePreviewModal() + } + } +> +
+ +
+