mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 08:02:38 +00:00
fix: ai button in inline script editor to open AI chat in flow builder (#5989)
* 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 <rubenfiszel@users.noreply.github.com> * 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 <HugoCasa@users.noreply.github.com> * 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 <HugoCasa@users.noreply.github.com> * nits * better color --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> Co-authored-by: HugoCasa <HugoCasa@users.noreply.github.com> Co-authored-by: HugoCasa <hugo@casademont.ch>
This commit is contained in:
committed by
GitHub
co-authored by
HugoCasa
Ruben Fiszel
HugoCasa
parent
35b7cb9c5e
commit
a3f2f8867a
@@ -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}
|
||||
<ScriptGen {editor} {diffEditor} {lang} {iconOnly} {args} />
|
||||
<ScriptGen {editor} {diffEditor} {lang} {iconOnly} {args} {openAiChat} />
|
||||
{/if}
|
||||
|
||||
<EditorSettings {customUi} />
|
||||
|
||||
@@ -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<string, any>
|
||||
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<T>(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 @@
|
||||
<Button
|
||||
size="xs"
|
||||
color={genLoading ? 'red' : 'light'}
|
||||
btnClasses={genLoading ? '!px-3 z-[5000]' : '!px-2'}
|
||||
propagateEvent={!genLoading}
|
||||
on:click={genLoading
|
||||
? () => abortController?.abort()
|
||||
: () => {
|
||||
if (editor) {
|
||||
if (isInitialCode(editor.getCode())) {
|
||||
mode = 'gen'
|
||||
} else {
|
||||
mode = 'edit'
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
autoResize()
|
||||
}, 0)
|
||||
}}
|
||||
btnClasses={twMerge(genLoading ? '!px-3 z-[5000]' : '!px-2', aiChatScriptModeClasses)}
|
||||
propagateEvent={!genLoading && !openAiChat}
|
||||
on:click={genLoading ? () => abortController?.abort() : handleAiButtonClick}
|
||||
bind:element={button}
|
||||
iconOnly
|
||||
title="Generate code from prompt"
|
||||
@@ -371,27 +395,15 @@
|
||||
title="Generate code from prompt"
|
||||
btnClasses={twMerge(
|
||||
'!font-medium',
|
||||
genLoading ? 'z-[5000]' : 'text-violet-800 dark:text-violet-400'
|
||||
genLoading ? 'z-[5000]' : 'text-violet-800 dark:text-violet-400',
|
||||
aiChatScriptModeClasses
|
||||
)}
|
||||
size="xs"
|
||||
color={genLoading ? 'red' : 'light'}
|
||||
spacingSize="md"
|
||||
startIcon={genLoading ? { icon: Ban } : { icon: Wand2 }}
|
||||
propagateEvent={!genLoading}
|
||||
on:click={genLoading
|
||||
? () => abortController?.abort()
|
||||
: () => {
|
||||
if (editor) {
|
||||
if (isInitialCode(editor.getCode())) {
|
||||
mode = 'gen'
|
||||
} else {
|
||||
mode = 'edit'
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
autoResize()
|
||||
}, 0)
|
||||
}}
|
||||
propagateEvent={!genLoading && !openAiChat}
|
||||
on:click={genLoading ? () => abortController?.abort() : handleAiButtonClick}
|
||||
bind:element={button}
|
||||
{iconOnly}
|
||||
>
|
||||
|
||||
@@ -58,6 +58,7 @@ class AIChatManager {
|
||||
|
||||
mode = $state<AIMode>(AIMode.NAVIGATOR)
|
||||
size = $state<number>(localStorage.getItem('ai-chat-open') === 'true' ? this.DEFAULT_SIZE : 0)
|
||||
readonly isOpen = $derived(this.size > 0)
|
||||
savedSize = $state<number>(0)
|
||||
instructions = $state<string>('')
|
||||
pendingPrompt = $state<string>('')
|
||||
|
||||
@@ -369,6 +369,7 @@
|
||||
on:hideDiffMode={hideDiffMode}
|
||||
{lastDeployedCode}
|
||||
{diffMode}
|
||||
openAiChat
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user