feat: Add workflow preview (#30234)

This commit is contained in:
yyh 2025-12-27 14:08:11 +08:00 committed by GitHub
commit ecc247bfc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 178 additions and 113 deletions

View File

@ -123,8 +123,9 @@ def make_request(method, url, max_retries=SSRF_DEFAULT_MAX_RETRIES, **kwargs):
# the request API to explicitly set headers before sending # the request API to explicitly set headers before sending
headers = {k: v for k, v in headers.items() if k.lower() != "host"} headers = {k: v for k, v in headers.items() if k.lower() != "host"}
if user_provided_host is not None: if user_provided_host is not None:
request.headers["Host"] = user_provided_host headers["Host"] = user_provided_host
request = client.build_request(method, url, headers=headers, **kwargs)
response = client.send(request, follow_redirects=follow_redirects) response = client.send(request, follow_redirects=follow_redirects)
# Check for SSRF protection by Squid proxy # Check for SSRF protection by Squid proxy

View File

@ -10,8 +10,7 @@ export const X_OFFSET = 60
export const NODE_WIDTH_X_OFFSET = NODE_WIDTH + X_OFFSET export const NODE_WIDTH_X_OFFSET = NODE_WIDTH + X_OFFSET
export const Y_OFFSET = 39 export const Y_OFFSET = 39
export const VIBE_COMMAND_EVENT = 'workflow-vibe-command' export const VIBE_COMMAND_EVENT = 'workflow-vibe-command'
export const VIBE_REGENERATE_EVENT = 'workflow-vibe-regenerate' export const VIBE_APPLY_EVENT = 'workflow-vibe-apply'
export const VIBE_ACCEPT_EVENT = 'workflow-vibe-accept'
export const START_INITIAL_POSITION = { x: 80, y: 282 } export const START_INITIAL_POSITION = { x: 80, y: 282 }
export const AUTO_LAYOUT_OFFSET = { export const AUTO_LAYOUT_OFFSET = {
x: -42, x: -42,

View File

@ -4,6 +4,7 @@ import type { ToolDefaultValue } from '../block-selector/types'
import type { Edge, Node, ToolWithProvider } from '../types' import type { Edge, Node, ToolWithProvider } from '../types'
import type { Tool } from '@/app/components/tools/types' import type { Tool } from '@/app/components/tools/types'
import type { Model } from '@/types/app' import type { Model } from '@/types/app'
import { useSessionStorageState } from 'ahooks'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useStoreApi } from 'reactflow' import { useStoreApi } from 'reactflow'
@ -25,10 +26,10 @@ import {
CUSTOM_EDGE, CUSTOM_EDGE,
NODE_WIDTH, NODE_WIDTH,
NODE_WIDTH_X_OFFSET, NODE_WIDTH_X_OFFSET,
VIBE_ACCEPT_EVENT, VIBE_APPLY_EVENT,
VIBE_COMMAND_EVENT, VIBE_COMMAND_EVENT,
VIBE_REGENERATE_EVENT,
} from '../constants' } from '../constants'
import { useHooksStore } from '../hooks-store'
import { useWorkflowStore } from '../store' import { useWorkflowStore } from '../store'
import { BlockEnum } from '../types' import { BlockEnum } from '../types'
import { import {
@ -76,6 +77,11 @@ type ParseResult = {
edges: ParsedEdge[] edges: ParsedEdge[]
} }
type FlowGraph = {
nodes: Node[]
edges: Edge[]
}
const NODE_DECLARATION = /^([A-Z][\w-]*)\s*\[(?:"([^"]+)"|([^\]]+))\]\s*$/i const NODE_DECLARATION = /^([A-Z][\w-]*)\s*\[(?:"([^"]+)"|([^\]]+))\]\s*$/i
const EDGE_DECLARATION = /^(.+?)\s*-->\s*(?:\|([^|]+)\|\s*)?(.+)$/ const EDGE_DECLARATION = /^(.+?)\s*-->\s*(?:\|([^|]+)\|\s*)?(.+)$/
@ -276,10 +282,45 @@ const buildToolParams = (parameters?: Tool['parameters']) => {
return params return params
} }
type UseVibeFlowDataParams = {
storageKey: string
}
const keyPrefix = 'vibe-flow-'
export const useVibeFlowData = ({ storageKey }: UseVibeFlowDataParams) => {
const [versions, setVersions] = useSessionStorageState<FlowGraph[]>(`${keyPrefix}${storageKey}-versions`, {
defaultValue: [],
})
const [currentVersionIndex, setCurrentVersionIndex] = useSessionStorageState<number>(`${keyPrefix}${storageKey}-version-index`, {
defaultValue: 0,
})
const current = versions?.[currentVersionIndex || 0]
const addVersion = useCallback((version: FlowGraph) => {
setCurrentVersionIndex(() => versions?.length || 0)
setVersions((prev) => {
return [...prev!, version]
})
}, [setVersions, setCurrentVersionIndex, versions?.length])
return {
versions,
addVersion,
currentVersionIndex,
setCurrentVersionIndex,
current,
}
}
export const useWorkflowVibe = () => { export const useWorkflowVibe = () => {
const { t } = useTranslation() const { t } = useTranslation()
const store = useStoreApi() const store = useStoreApi()
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const configsMap = useHooksStore(s => s.configsMap)
const language = useGetLanguage() const language = useGetLanguage()
const { nodesMap: nodesMetaDataMap } = useNodesMetaData() const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { handleSyncWorkflowDraft } = useNodesSyncDraft()
@ -296,6 +337,10 @@ export const useWorkflowVibe = () => {
const isGeneratingRef = useRef(false) const isGeneratingRef = useRef(false)
const lastInstructionRef = useRef<string>('') const lastInstructionRef = useRef<string>('')
const { addVersion, current: currentFlowGraph } = useVibeFlowData({
storageKey: configsMap?.flowId || '',
})
useEffect(() => { useEffect(() => {
const storedModel = (() => { const storedModel = (() => {
if (typeof window === 'undefined') if (typeof window === 'undefined')
@ -427,48 +472,42 @@ export const useWorkflowVibe = () => {
return map return map
}, [nodesMetaDataMap]) }, [nodesMetaDataMap])
const applyFlowchartToWorkflow = useCallback(async (mermaidCode: string) => { const flowchartToWorkflowGraph = useCallback(async (mermaidCode: string): Promise<FlowGraph> => {
const { getNodes, setNodes, edges, setEdges } = store.getState() const { getNodes } = store.getState()
const nodes = getNodes() const nodes = getNodes()
const {
setShowVibePanel,
} = workflowStore.getState()
const parseResultToUse = parseMermaidFlowchart(mermaidCode, nodeTypeLookup, toolLookup) const parseResultToUse = parseMermaidFlowchart(mermaidCode, nodeTypeLookup, toolLookup)
const emptyGraph = {
nodes: [],
edges: [],
}
if ('error' in parseResultToUse) { if ('error' in parseResultToUse) {
switch (parseResultToUse.error) { switch (parseResultToUse.error) {
case 'missingNodeType': case 'missingNodeType':
case 'missingNodeDefinition': case 'missingNodeDefinition':
Toast.notify({ type: 'error', message: t('workflow.vibe.invalidFlowchart') }) Toast.notify({ type: 'error', message: t('workflow.vibe.invalidFlowchart') })
setShowVibePanel(false) return emptyGraph
return
case 'unknownNodeId': case 'unknownNodeId':
Toast.notify({ type: 'error', message: t('workflow.vibe.unknownNodeId', { id: parseResultToUse.detail }) }) Toast.notify({ type: 'error', message: t('workflow.vibe.unknownNodeId', { id: parseResultToUse.detail }) })
setShowVibePanel(false) return emptyGraph
return
case 'unknownNodeType': case 'unknownNodeType':
Toast.notify({ type: 'error', message: t('workflow.vibe.nodeTypeUnavailable', { type: parseResultToUse.detail }) }) Toast.notify({ type: 'error', message: t('workflow.vibe.nodeTypeUnavailable', { type: parseResultToUse.detail }) })
setShowVibePanel(false) return emptyGraph
return
case 'unknownTool': case 'unknownTool':
Toast.notify({ type: 'error', message: t('workflow.vibe.toolUnavailable', { tool: parseResultToUse.detail }) }) Toast.notify({ type: 'error', message: t('workflow.vibe.toolUnavailable', { tool: parseResultToUse.detail }) })
setShowVibePanel(false) return emptyGraph
return
case 'unsupportedEdgeLabel': case 'unsupportedEdgeLabel':
Toast.notify({ type: 'error', message: t('workflow.vibe.unsupportedEdgeLabel', { label: parseResultToUse.detail }) }) Toast.notify({ type: 'error', message: t('workflow.vibe.unsupportedEdgeLabel', { label: parseResultToUse.detail }) })
setShowVibePanel(false) return emptyGraph
return
default: default:
Toast.notify({ type: 'error', message: t('workflow.vibe.invalidFlowchart') }) Toast.notify({ type: 'error', message: t('workflow.vibe.invalidFlowchart') })
setShowVibePanel(false) return emptyGraph
return
} }
} }
if (!nodesMetaDataMap) { if (!nodesMetaDataMap) {
Toast.notify({ type: 'error', message: t('workflow.vibe.nodesUnavailable') }) Toast.notify({ type: 'error', message: t('workflow.vibe.nodesUnavailable') })
setShowVibePanel(false) return emptyGraph
return
} }
const existingStartNode = nodes.find(node => node.data.type === BlockEnum.Start) const existingStartNode = nodes.find(node => node.data.type === BlockEnum.Start)
@ -513,7 +552,7 @@ export const useWorkflowVibe = () => {
if (!newNodes.length) { if (!newNodes.length) {
Toast.notify({ type: 'error', message: t('workflow.vibe.invalidFlowchart') }) Toast.notify({ type: 'error', message: t('workflow.vibe.invalidFlowchart') })
return return emptyGraph
} }
const buildEdge = ( const buildEdge = (
@ -626,10 +665,20 @@ export const useWorkflowVibe = () => {
}, },
} }
}) })
return {
nodes: updatedNodes,
edges: newEdges,
}
}, [nodeTypeLookup, toolLookup])
setNodes(updatedNodes) const applyFlowchartToWorkflow = useCallback(() => {
setEdges([...edges, ...newEdges]) const { setNodes, setEdges } = store.getState()
saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: newNodes[0].id }) const vibePanelPreviewNodes = currentFlowGraph.nodes || []
const vibePanelPreviewEdges = currentFlowGraph.edges || []
setNodes(vibePanelPreviewNodes)
setEdges(vibePanelPreviewEdges)
saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: vibePanelPreviewNodes[0].id })
handleSyncWorkflowDraft() handleSyncWorkflowDraft()
workflowStore.setState(state => ({ workflowStore.setState(state => ({
@ -744,8 +793,11 @@ export const useWorkflowVibe = () => {
isVibeGenerating: false, isVibeGenerating: false,
})) }))
const workflowGraph = await flowchartToWorkflowGraph(mermaidCode)
addVersion(workflowGraph)
if (skipPanelPreview) if (skipPanelPreview)
await applyFlowchartToWorkflow(mermaidCode) applyFlowchartToWorkflow()
} }
finally { finally {
isGeneratingRef.current = false isGeneratingRef.current = false
@ -764,47 +816,27 @@ export const useWorkflowVibe = () => {
getLatestModelConfig, getLatestModelConfig,
]) ])
const handleRegenerate = useCallback(async () => { const handleAccept = useCallback(() => {
if (!lastInstructionRef.current) { applyFlowchartToWorkflow()
Toast.notify({ type: 'error', message: t('workflow.vibe.missingInstruction') }) }, [applyFlowchartToWorkflow])
return
}
await handleVibeCommand(lastInstructionRef.current, false)
}, [handleVibeCommand, t])
const handleAccept = useCallback(async (vibePanelMermaidCode: string | undefined) => {
if (!vibePanelMermaidCode) {
Toast.notify({ type: 'error', message: t('workflow.vibe.noFlowchart') })
return
}
await applyFlowchartToWorkflow(vibePanelMermaidCode)
}, [applyFlowchartToWorkflow, t])
useEffect(() => { useEffect(() => {
const handler = (event: CustomEvent<VibeCommandDetail>) => { const handler = (event: CustomEvent<VibeCommandDetail>) => {
handleVibeCommand(event.detail?.dsl, false) handleVibeCommand(event.detail?.dsl, false)
} }
const regenerateHandler = () => { const acceptHandler = () => {
handleRegenerate() handleAccept()
}
const acceptHandler = (event: CustomEvent<VibeCommandDetail>) => {
handleAccept(event.detail?.dsl)
} }
document.addEventListener(VIBE_COMMAND_EVENT, handler as EventListener) document.addEventListener(VIBE_COMMAND_EVENT, handler as EventListener)
document.addEventListener(VIBE_REGENERATE_EVENT, regenerateHandler as EventListener) document.addEventListener(VIBE_APPLY_EVENT, acceptHandler as EventListener)
document.addEventListener(VIBE_ACCEPT_EVENT, acceptHandler as EventListener)
return () => { return () => {
document.removeEventListener(VIBE_COMMAND_EVENT, handler as EventListener) document.removeEventListener(VIBE_COMMAND_EVENT, handler as EventListener)
document.removeEventListener(VIBE_REGENERATE_EVENT, regenerateHandler as EventListener) document.removeEventListener(VIBE_APPLY_EVENT, acceptHandler as EventListener)
document.removeEventListener(VIBE_ACCEPT_EVENT, acceptHandler as EventListener)
} }
}, [handleVibeCommand, handleRegenerate]) }, [handleVibeCommand, handleAccept])
return null return null
} }

View File

@ -3,33 +3,42 @@
import type { FC } from 'react' import type { FC } from 'react'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { CompletionParams, Model } from '@/types/app' import type { CompletionParams, Model } from '@/types/app'
import { RiCheckLine, RiRefreshLine } from '@remixicon/react' import { RiClipboardLine } from '@remixicon/react'
import copy from 'copy-to-clipboard'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ResPlaceholder from '@/app/components/app/configuration/config/automatic/res-placeholder' import ResPlaceholder from '@/app/components/app/configuration/config/automatic/res-placeholder'
import VersionSelector from '@/app/components/app/configuration/config/automatic/version-selector'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { Generator } from '@/app/components/base/icons/src/vender/other' import { Generator } from '@/app/components/base/icons/src/vender/other'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import Flowchart from '@/app/components/base/mermaid'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import Textarea from '@/app/components/base/textarea' import Textarea from '@/app/components/base/textarea'
import Toast from '@/app/components/base/toast'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import { ModelModeType } from '@/types/app' import { ModelModeType } from '@/types/app'
import { VIBE_ACCEPT_EVENT, VIBE_COMMAND_EVENT, VIBE_REGENERATE_EVENT } from '../../constants' import { VIBE_APPLY_EVENT, VIBE_COMMAND_EVENT } from '../../constants'
import { useStore } from '../../store' import { useHooksStore } from '../../hooks-store'
import { useVibeFlowData } from '../../hooks/use-workflow-vibe'
import { useStore, useWorkflowStore } from '../../store'
import WorkflowPreview from '../../workflow-preview'
const VibePanel: FC = () => { const VibePanel: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const workflowStore = useWorkflowStore()
const showVibePanel = useStore(s => s.showVibePanel) const showVibePanel = useStore(s => s.showVibePanel)
const setShowVibePanel = useStore(s => s.setShowVibePanel)
const vibePanelMermaidCode = useStore(s => s.vibePanelMermaidCode)
const setVibePanelMermaidCode = useStore(s => s.setVibePanelMermaidCode)
const isVibeGenerating = useStore(s => s.isVibeGenerating) const isVibeGenerating = useStore(s => s.isVibeGenerating)
const setIsVibeGenerating = useStore(s => s.setIsVibeGenerating)
const vibePanelInstruction = useStore(s => s.vibePanelInstruction) const vibePanelInstruction = useStore(s => s.vibePanelInstruction)
const setVibePanelInstruction = useStore(s => s.setVibePanelInstruction) const configsMap = useHooksStore(s => s.configsMap)
const { current: currentFlowGraph, versions, currentVersionIndex, setCurrentVersionIndex } = useVibeFlowData({
storageKey: configsMap?.flowId || '',
})
const vibePanelPreviewNodes = currentFlowGraph?.nodes || []
const vibePanelPreviewEdges = currentFlowGraph?.edges || []
const localModel = localStorage.getItem('auto-gen-model') const localModel = localStorage.getItem('auto-gen-model')
? JSON.parse(localStorage.getItem('auto-gen-model') as string) as Model ? JSON.parse(localStorage.getItem('auto-gen-model') as string) as Model
@ -80,11 +89,21 @@ const VibePanel: FC = () => {
localStorage.setItem('auto-gen-model', JSON.stringify(newModel)) localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
}, [model]) }, [model])
const handleInstructionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
workflowStore.setState(state => ({
...state,
vibePanelInstruction: e.target.value,
}))
}, [workflowStore])
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
setShowVibePanel(false) workflowStore.setState(state => ({
setVibePanelMermaidCode('') ...state,
setIsVibeGenerating(false) showVibePanel: false,
}, [setShowVibePanel, setVibePanelMermaidCode, setIsVibeGenerating]) vibePanelMermaidCode: '',
isVibeGenerating: false,
}))
}, [workflowStore])
const handleGenerate = useCallback(() => { const handleGenerate = useCallback(() => {
const event = new CustomEvent(VIBE_COMMAND_EVENT, { const event = new CustomEvent(VIBE_COMMAND_EVENT, {
@ -94,20 +113,16 @@ const VibePanel: FC = () => {
}, [vibePanelInstruction]) }, [vibePanelInstruction])
const handleAccept = useCallback(() => { const handleAccept = useCallback(() => {
if (vibePanelMermaidCode) { const event = new CustomEvent(VIBE_APPLY_EVENT)
const event = new CustomEvent(VIBE_ACCEPT_EVENT, {
detail: { dsl: vibePanelMermaidCode },
})
document.dispatchEvent(event)
handleClose()
}
}, [vibePanelMermaidCode, handleClose])
const handleRegenerate = useCallback(() => {
setIsVibeGenerating(true)
const event = new CustomEvent(VIBE_REGENERATE_EVENT)
document.dispatchEvent(event) document.dispatchEvent(event)
}, [setIsVibeGenerating]) handleClose()
}, [handleClose])
const handleCopyMermaid = useCallback(() => {
const { vibePanelMermaidCode } = workflowStore.getState()
copy(vibePanelMermaidCode)
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
}, [workflowStore, t])
if (!showVibePanel) if (!showVibePanel)
return null return null
@ -124,6 +139,7 @@ const VibePanel: FC = () => {
isShow={showVibePanel} isShow={showVibePanel}
onClose={handleClose} onClose={handleClose}
className="min-w-[1140px] !p-0" className="min-w-[1140px] !p-0"
highPriority
> >
<div className="flex h-[680px] flex-wrap"> <div className="flex h-[680px] flex-wrap">
<div className="h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6"> <div className="h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6">
@ -150,7 +166,7 @@ const VibePanel: FC = () => {
className="min-h-[240px] resize-none rounded-[10px] px-4 pt-3" className="min-h-[240px] resize-none rounded-[10px] px-4 pt-3"
placeholder={t('workflow.vibe.missingInstruction')} placeholder={t('workflow.vibe.missingInstruction')}
value={vibePanelInstruction} value={vibePanelInstruction}
onChange={e => setVibePanelInstruction(e.target.value)} onChange={handleInstructionChange}
/> />
</div> </div>
@ -163,48 +179,54 @@ const VibePanel: FC = () => {
disabled={isVibeGenerating} disabled={isVibeGenerating}
> >
<Generator className="h-4 w-4" /> <Generator className="h-4 w-4" />
<span className="text-xs font-semibold">{t('appDebug.generate.generate')}</span> <span className="system-xs-semibold">{t('appDebug.generate.generate')}</span>
</Button> </Button>
</div> </div>
</div> </div>
{!isVibeGenerating && vibePanelMermaidCode && ( {!isVibeGenerating && vibePanelPreviewNodes.length > 0 && (
<div className="h-full w-0 grow bg-background-default-subtle p-6 pb-0"> <div className="h-full w-0 grow bg-background-default-subtle p-6 pb-0">
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
<div className="mb-3 flex shrink-0 items-center justify-between"> <div className="mb-3 flex shrink-0 items-center justify-between">
<div className="shrink-0 text-base font-semibold leading-[160%] text-text-secondary">{t('workflow.vibe.panelTitle')}</div> <div className="flex shrink-0 flex-col">
<div className="system-xl-semibold text-text-secondary">{t('workflow.vibe.panelTitle')}</div>
<VersionSelector
versionLen={versions.length}
value={currentVersionIndex}
onChange={setCurrentVersionIndex}
/>
</div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Button <Button
variant="secondary" variant="secondary"
size="medium" size="medium"
onClick={handleRegenerate} onClick={handleCopyMermaid}
className="px-2"
> >
<RiRefreshLine className="mr-1 h-4 w-4" /> <RiClipboardLine className="h-4 w-4" />
{t('workflow.vibe.regenerate')}
</Button> </Button>
<Button <Button
variant="primary" variant="primary"
size="medium" size="medium"
onClick={handleAccept} onClick={handleAccept}
> >
<RiCheckLine className="mr-1 h-4 w-4" /> {t('workflow.vibe.apply')}
{t('workflow.vibe.accept')}
</Button> </Button>
</div> </div>
</div> </div>
<div className="flex grow flex-col overflow-y-auto pb-6"> <div className="flex grow flex-col overflow-hidden pb-6">
<div className="grow"> <WorkflowPreview
<Flowchart nodes={vibePanelPreviewNodes}
PrimitiveCode={vibePanelMermaidCode} edges={vibePanelPreviewEdges}
theme="light" viewport={{ x: 0, y: 0, zoom: 1 }}
/> className="rounded-lg border border-divider-subtle"
</div> />
</div> </div>
</div> </div>
</div> </div>
)} )}
{isVibeGenerating && renderLoading} {isVibeGenerating && renderLoading}
{!isVibeGenerating && !vibePanelMermaidCode && <ResPlaceholder />} {!isVibeGenerating && vibePanelPreviewNodes.length === 0 && <ResPlaceholder />}
</div> </div>
</Modal> </Modal>
) )

View File

@ -12,6 +12,7 @@ import type { NodeSliceShape } from './node-slice'
import type { PanelSliceShape } from './panel-slice' import type { PanelSliceShape } from './panel-slice'
import type { ToolSliceShape } from './tool-slice' import type { ToolSliceShape } from './tool-slice'
import type { VersionSliceShape } from './version-slice' import type { VersionSliceShape } from './version-slice'
import type { VibeWorkflowSliceShape } from './vibe-workflow-slice'
import type { WorkflowDraftSliceShape } from './workflow-draft-slice' import type { WorkflowDraftSliceShape } from './workflow-draft-slice'
import type { WorkflowSliceShape } from './workflow-slice' import type { WorkflowSliceShape } from './workflow-slice'
import type { RagPipelineSliceShape } from '@/app/components/rag-pipeline/store' import type { RagPipelineSliceShape } from '@/app/components/rag-pipeline/store'
@ -34,6 +35,7 @@ import { createNodeSlice } from './node-slice'
import { createPanelSlice } from './panel-slice' import { createPanelSlice } from './panel-slice'
import { createToolSlice } from './tool-slice' import { createToolSlice } from './tool-slice'
import { createVersionSlice } from './version-slice' import { createVersionSlice } from './version-slice'
import { createVibeWorkflowSlice } from './vibe-workflow-slice'
import { createWorkflowDraftSlice } from './workflow-draft-slice' import { createWorkflowDraftSlice } from './workflow-draft-slice'
import { createWorkflowSlice } from './workflow-slice' import { createWorkflowSlice } from './workflow-slice'
@ -56,6 +58,7 @@ export type Shape
& InspectVarsSliceShape & InspectVarsSliceShape
& LayoutSliceShape & LayoutSliceShape
& SliceFromInjection & SliceFromInjection
& VibeWorkflowSliceShape
export type InjectWorkflowStoreSliceFn = StateCreator<SliceFromInjection> export type InjectWorkflowStoreSliceFn = StateCreator<SliceFromInjection>
@ -80,6 +83,7 @@ export const createWorkflowStore = (params: CreateWorkflowStoreParams) => {
...createWorkflowSlice(...args), ...createWorkflowSlice(...args),
...createInspectVarsSlice(...args), ...createInspectVarsSlice(...args),
...createLayoutSlice(...args), ...createLayoutSlice(...args),
...createVibeWorkflowSlice(...args),
...(injectWorkflowStoreSliceFn?.(...args) || {} as SliceFromInjection), ...(injectWorkflowStoreSliceFn?.(...args) || {} as SliceFromInjection),
})) }))
} }

View File

@ -26,12 +26,6 @@ export type PanelSliceShape = {
setInitShowLastRunTab: (initShowLastRunTab: boolean) => void setInitShowLastRunTab: (initShowLastRunTab: boolean) => void
showVibePanel: boolean showVibePanel: boolean
setShowVibePanel: (showVibePanel: boolean) => void setShowVibePanel: (showVibePanel: boolean) => void
vibePanelMermaidCode: string
setVibePanelMermaidCode: (vibePanelMermaidCode: string) => void
isVibeGenerating: boolean
setIsVibeGenerating: (isVibeGenerating: boolean) => void
vibePanelInstruction: string
setVibePanelInstruction: (vibePanelInstruction: string) => void
} }
export const createPanelSlice: StateCreator<PanelSliceShape> = set => ({ export const createPanelSlice: StateCreator<PanelSliceShape> = set => ({
@ -54,10 +48,4 @@ export const createPanelSlice: StateCreator<PanelSliceShape> = set => ({
setInitShowLastRunTab: initShowLastRunTab => set(() => ({ initShowLastRunTab })), setInitShowLastRunTab: initShowLastRunTab => set(() => ({ initShowLastRunTab })),
showVibePanel: false, showVibePanel: false,
setShowVibePanel: showVibePanel => set(() => ({ showVibePanel })), setShowVibePanel: showVibePanel => set(() => ({ showVibePanel })),
vibePanelMermaidCode: '',
setVibePanelMermaidCode: vibePanelMermaidCode => set(() => ({ vibePanelMermaidCode })),
isVibeGenerating: false,
setIsVibeGenerating: isVibeGenerating => set(() => ({ isVibeGenerating })),
vibePanelInstruction: '',
setVibePanelInstruction: vibePanelInstruction => set(() => ({ vibePanelInstruction })),
}) })

View File

@ -0,0 +1,19 @@
import type { StateCreator } from 'zustand'
export type VibeWorkflowSliceShape = {
vibePanelMermaidCode: string
setVibePanelMermaidCode: (vibePanelMermaidCode: string) => void
isVibeGenerating: boolean
setIsVibeGenerating: (isVibeGenerating: boolean) => void
vibePanelInstruction: string
setVibePanelInstruction: (vibePanelInstruction: string) => void
}
export const createVibeWorkflowSlice: StateCreator<VibeWorkflowSliceShape> = set => ({
vibePanelMermaidCode: '',
setVibePanelMermaidCode: vibePanelMermaidCode => set(() => ({ vibePanelMermaidCode })),
isVibeGenerating: false,
setIsVibeGenerating: isVibeGenerating => set(() => ({ isVibeGenerating })),
vibePanelInstruction: '',
setVibePanelInstruction: vibePanelInstruction => set(() => ({ vibePanelInstruction })),
})

View File

@ -138,7 +138,7 @@ const translation = {
generatingFlowchart: 'Generating flowchart preview...', generatingFlowchart: 'Generating flowchart preview...',
noFlowchartYet: 'No flowchart preview available', noFlowchartYet: 'No flowchart preview available',
regenerate: 'Regenerate', regenerate: 'Regenerate',
accept: 'Accept', apply: 'Apply',
noFlowchart: 'No flowchart provided', noFlowchart: 'No flowchart provided',
}, },
publishLimit: { publishLimit: {