From a3f2f8867a4e8c154756d7ea5ad6b213b09ad19c Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:15:35 +0200 Subject: [PATCH] fix: ai button in inline script editor to open AI chat in flow builder (#5989) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: ai button in inline script editor to open AI chat in flow builder - Add openAiChat prop to ScriptGen component to control AI button behavior - When openAiChat=true, AI button opens AI chat manager in script mode - When openAiChat=false (default), AI button shows direct generation popover - Update flow builder (FlowModuleComponent) to use openAiChat=true for inline scripts - App builder continues using direct generation (openAiChat defaults to false) - Import AIMode enum properly to fix TypeScript error Resolves request from @HugoCasa to make flow AI button open chat instead of direct generation while keeping app builder unchanged. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Ruben Fiszel * refactor: extract repeated logic into reusable functions in ScriptGen.svelte - Created determineModeFromEditor() to centralize mode setting logic - Added callCopilot() to unify copilot function calls with proper typing - Added handleAiButtonClick() as single handler for button click scenarios - Created safeLocalStorageOperation() for generic localStorage error handling - Added getPromptStorageKey() for centralized storage key generation Reduces code duplication and improves maintainability. Co-authored-by: HugoCasa * refactor: extract duplicate determineModeFromEditor call in handleAiButtonClick Remove code duplication by calling determineModeFromEditor() once at the beginning of handleAiButtonClick() instead of in both conditional branches. Co-authored-by: HugoCasa * nits * better color --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel Co-authored-by: HugoCasa Co-authored-by: HugoCasa --- frontend/src/lib/components/EditorBar.svelte | 6 +- .../lib/components/copilot/ScriptGen.svelte | 158 ++++++++++-------- .../copilot/chat/AIChatManager.svelte.ts | 1 + .../flows/content/FlowModuleComponent.svelte | 1 + 4 files changed, 91 insertions(+), 75 deletions(-) diff --git a/frontend/src/lib/components/EditorBar.svelte b/frontend/src/lib/components/EditorBar.svelte index 5b5681b3b3..91044ead7a 100644 --- a/frontend/src/lib/components/EditorBar.svelte +++ b/frontend/src/lib/components/EditorBar.svelte @@ -76,6 +76,7 @@ diffMode?: boolean showHistoryDrawer?: boolean right?: import('svelte').Snippet + openAiChat?: boolean } let { @@ -98,7 +99,8 @@ lastDeployedCode = undefined, diffMode = false, showHistoryDrawer = $bindable(false), - right + right, + openAiChat = false }: Props = $props() let contextualVariablePicker: ItemPicker | undefined = $state() @@ -810,7 +812,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS {/if} {#if customUi?.aiGen != false} - + {/if} diff --git a/frontend/src/lib/components/copilot/ScriptGen.svelte b/frontend/src/lib/components/copilot/ScriptGen.svelte index 02e48cf20a..cb7ab6c946 100644 --- a/frontend/src/lib/components/copilot/ScriptGen.svelte +++ b/frontend/src/lib/components/copilot/ScriptGen.svelte @@ -28,6 +28,7 @@ import { twMerge } from 'tailwind-merge' import { onDestroy } from 'svelte' import ProviderModelSelector from './chat/ProviderModelSelector.svelte' + import { aiChatManager, AIMode } from './chat/AIChatManager.svelte' interface Props { // props @@ -38,6 +39,7 @@ inlineScript?: boolean args: Record transformer?: boolean + openAiChat?: boolean } let { @@ -47,7 +49,8 @@ diffEditor, inlineScript = false, args, - transformer = false + transformer = false, + openAiChat = false }: Props = $props() run(() => { @@ -72,6 +75,54 @@ run(() => { trimmedDesc = funcDesc.trim() }) + async function callCopilot() { + if (mode === 'edit') { + await copilot( + { + // @ts-ignore + language: transformer && lang === 'frontend' ? 'transformer' : lang!, + description: trimmedDesc, + code: editor?.getCode() || '', + dbSchema: dbSchema, + type: 'edit', + workspace: $workspaceStore! + }, + generatedCode, + abortController + ) + } else { + await copilot( + { + // @ts-ignore + language: transformer && lang === 'frontend' ? 'transformer' : lang!, + description: trimmedDesc, + dbSchema: dbSchema, + type: 'gen', + workspace: $workspaceStore! + }, + generatedCode, + abortController + ) + } + } + + function handleAiButtonClick() { + if (editor && isInitialCode(editor.getCode())) { + mode = 'gen' + } else { + mode = 'edit' + } + + if (openAiChat) { + // Open the AI chat in script mode + aiChatManager.openChat() + aiChatManager.changeMode(AIMode.SCRIPT) + } else { + setTimeout(() => { + autoResize() + }, 0) + } + } async function onGenerate(closePopup: () => void) { if (trimmedDesc.length <= 0) { @@ -82,34 +133,7 @@ genLoading = true blockPopupOpen = true abortController = new AbortController() - if (mode === 'edit') { - await copilot( - { - // @ts-ignore - language: transformer && lang === 'frontend' ? 'transformer' : lang!, - description: trimmedDesc, - code: editor?.getCode() || '', - dbSchema: dbSchema, - type: 'edit', - workspace: $workspaceStore! - }, - generatedCode, - abortController - ) - } else { - await copilot( - { - // @ts-ignore - language: transformer && lang === 'frontend' ? 'transformer' : lang!, - description: trimmedDesc, - dbSchema: dbSchema, - type: 'gen', - workspace: $workspaceStore! - }, - generatedCode, - abortController - ) - } + await callCopilot() setupDiff() diffEditor?.setModified($generatedCode) blockPopupOpen = false @@ -205,14 +229,27 @@ } let promptHistory: string[] = $state([]) - function getPromptHistory() { + + function getPromptStorageKey() { + return 'prompts-' + lang + } + + function safeLocalStorageOperation(operation: () => T, defaultValue?: T): T | undefined { try { - promptHistory = JSON.parse(localStorage.getItem('prompts-' + lang) || '[]') + return operation() } catch (e) { console.error('error interacting with local storage', e) + return defaultValue } } + function getPromptHistory() { + const storageKey = getPromptStorageKey() + promptHistory = + safeLocalStorageOperation(() => JSON.parse(localStorage.getItem(storageKey) || '[]'), []) || + [] + } + function savePrompt() { if (promptHistory.includes(trimmedDesc)) { return @@ -221,20 +258,14 @@ while (promptHistory.length > 5) { promptHistory.pop() } - try { - localStorage.setItem('prompts-' + lang, JSON.stringify(promptHistory)) - } catch (e) { - console.error('error interacting with local storage', e) - } + const storageKey = getPromptStorageKey() + safeLocalStorageOperation(() => localStorage.setItem(storageKey, JSON.stringify(promptHistory))) } function clearPromptHistory() { promptHistory = [] - try { - localStorage.setItem('prompts-' + lang, JSON.stringify(promptHistory)) - } catch (e) { - console.error('error interacting with local storage', e) - } + const storageKey = getPromptStorageKey() + safeLocalStorageOperation(() => localStorage.setItem(storageKey, JSON.stringify(promptHistory))) } run(() => { lang && getPromptHistory() @@ -266,6 +297,12 @@ ;(dbSchema as any).publicOnly = detail === 'true' } + const aiChatScriptModeClasses = $derived( + aiChatManager.mode === AIMode.SCRIPT && aiChatManager.isOpen + ? 'dark:bg-violet-900 bg-violet-100' + : '' + ) + onDestroy(() => { abortController?.abort() }) @@ -343,22 +380,9 @@