From 28cb1792cf33b2621623e2409f99c47db13c9e6b Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Mon, 29 Dec 2025 12:59:20 +0800 Subject: [PATCH] fix: fix generated workflow preview and version control (#30300) --- .../config/automatic/version-selector.tsx | 9 +- .../workflow/hooks/use-workflow-vibe.tsx | 156 ++++++++++-------- .../workflow/panel/vibe-panel/index.tsx | 19 ++- .../store/workflow/vibe-workflow-slice.ts | 61 ++++++- 4 files changed, 167 insertions(+), 78 deletions(-) diff --git a/web/app/components/app/configuration/config/automatic/version-selector.tsx b/web/app/components/app/configuration/config/automatic/version-selector.tsx index 5449f518a5..9d551c012e 100644 --- a/web/app/components/app/configuration/config/automatic/version-selector.tsx +++ b/web/app/components/app/configuration/config/automatic/version-selector.tsx @@ -10,9 +10,15 @@ type VersionSelectorProps = { versionLen: number value: number onChange: (index: number) => void + contentClassName?: string } -const VersionSelector: React.FC = ({ versionLen, value, onChange }) => { +const VersionSelector: React.FC = ({ + versionLen, + value, + onChange, + contentClassName, +}) => { const { t } = useTranslation() const [isOpen, { setFalse: handleOpenFalse, @@ -64,6 +70,7 @@ const VersionSelector: React.FC = ({ versionLen, value, on
\s*(?:\|([^|]+)\|\s*)?(.+)$/ @@ -282,67 +277,92 @@ const buildToolParams = (parameters?: Tool['parameters']) => { return params } -type UseVibeFlowDataParams = { - storageKey: string +// Sync vibe flow data to sessionStorage +const STORAGE_KEY_PREFIX = 'vibe-flow-' + +const loadFromSessionStorage = (flowId: string): { versions: FlowGraph[], currentIndex: number } | null => { + if (typeof window === 'undefined') + return null + + try { + const versionsKey = `${STORAGE_KEY_PREFIX}${flowId}-versions` + const indexKey = `${STORAGE_KEY_PREFIX}${flowId}-version-index` + + const versionsRaw = sessionStorage.getItem(versionsKey) + const indexRaw = sessionStorage.getItem(indexKey) + + if (!versionsRaw) + return null + + const versions = JSON.parse(versionsRaw) as FlowGraph[] + const currentIndex = indexRaw ? Number.parseInt(indexRaw, 10) : 0 + + return { versions, currentIndex } + } + catch { + return null + } } -const keyPrefix = 'vibe-flow-' +const saveToSessionStorage = (flowId: string, versions: FlowGraph[], currentIndex: number) => { + if (typeof window === 'undefined') + return -export const useVibeFlowData = ({ storageKey }: UseVibeFlowDataParams) => { - const [versions, setVersions] = useSessionStorageState(`${keyPrefix}${storageKey}-versions`, { - defaultValue: [], - }) + try { + const versionsKey = `${STORAGE_KEY_PREFIX}${flowId}-versions` + const indexKey = `${STORAGE_KEY_PREFIX}${flowId}-version-index` - const [currentVersionIndex, setCurrentVersionIndex] = useSessionStorageState(`${keyPrefix}${storageKey}-version-index`, { - defaultValue: 0, - }) - - useEffect(() => { - if (!versions || versions.length === 0) { - if (currentVersionIndex !== 0 && currentVersionIndex !== -1) - setCurrentVersionIndex(0) - return - } - - if (currentVersionIndex === -1) - return - - const normalizedIndex = Math.min(Math.max(currentVersionIndex ?? 0, 0), versions.length - 1) - if (normalizedIndex !== currentVersionIndex) - setCurrentVersionIndex(normalizedIndex) - }, [versions, currentVersionIndex, setCurrentVersionIndex]) - - const current = useMemo(() => { - if (!versions || versions.length === 0) - return undefined - const index = currentVersionIndex ?? 0 - if (index < 0) - return undefined - return versions[index] || versions[versions.length - 1] - }, [versions, currentVersionIndex]) - - const addVersion = useCallback((version: FlowGraph) => { - // Prevent adding empty graphs - if (!version || !version.nodes || version.nodes.length === 0) { - setCurrentVersionIndex(-1) - return - } - - setVersions((prev) => { - const newVersions = [...(prev || []), version] - // Set index in setVersions callback to ensure using the latest length - setCurrentVersionIndex(newVersions.length - 1) - return newVersions - }) - }, [setVersions, setCurrentVersionIndex]) - - return { - versions, - addVersion, - currentVersionIndex, - setCurrentVersionIndex, - current, + sessionStorage.setItem(versionsKey, JSON.stringify(versions)) + sessionStorage.setItem(indexKey, String(currentIndex)) } + catch (error) { + console.error('Failed to save vibe flow to sessionStorage:', error) + } +} + +export const useVibeFlowSessionStorage = (flowId: string) => { + const workflowStore = useWorkflowStore() + const versions = useStore(s => s.vibeFlowVersions) + const currentIndex = useStore(s => s.vibeFlowCurrentIndex) + const loadedFlowIdRef = useRef(null) + const isLoadingRef = useRef(false) + + // Load from sessionStorage when flowId changes + useEffect(() => { + if (!flowId || loadedFlowIdRef.current === flowId) + return + + isLoadingRef.current = true + const stored = loadFromSessionStorage(flowId) + + if (stored) { + workflowStore.setState({ + vibeFlowVersions: stored.versions, + vibeFlowCurrentIndex: stored.currentIndex, + }) + } + else { + workflowStore.setState({ + vibeFlowVersions: [], + vibeFlowCurrentIndex: 0, + currentVibeFlow: undefined, + }) + } + + loadedFlowIdRef.current = flowId + // Delay to prevent immediate save + setTimeout(() => { + isLoadingRef.current = false + }, 100) + }, [flowId, workflowStore]) + + // Save to sessionStorage when versions or index change + useEffect(() => { + if (!flowId || loadedFlowIdRef.current !== flowId || isLoadingRef.current) + return + + saveToSessionStorage(flowId, versions, currentIndex) + }, [flowId, versions, currentIndex]) } export const useWorkflowVibe = () => { @@ -367,9 +387,7 @@ export const useWorkflowVibe = () => { const isGeneratingRef = useRef(false) const lastInstructionRef = useRef('') - const { addVersion, current: currentFlowGraph } = useVibeFlowData({ - storageKey: configsMap?.flowId || '', - }) + useVibeFlowSessionStorage(configsMap?.flowId || '') useEffect(() => { const storedModel = (() => { @@ -702,6 +720,8 @@ export const useWorkflowVibe = () => { }, [nodeTypeLookup, toolLookup]) const applyFlowchartToWorkflow = useCallback(() => { + const currentFlowGraph = workflowStore.getState().currentVibeFlow + if (!currentFlowGraph || !currentFlowGraph.nodes || currentFlowGraph.nodes.length === 0) { Toast.notify({ type: 'error', message: t('workflow.vibe.invalidFlowchart') }) return @@ -722,7 +742,6 @@ export const useWorkflowVibe = () => { vibePanelMermaidCode: '', })) }, [ - currentFlowGraph, handleSyncWorkflowDraft, nodeTypeLookup, nodesMetaDataMap, @@ -730,6 +749,7 @@ export const useWorkflowVibe = () => { store, t, toolLookup, + workflowStore, ]) const handleVibeCommand = useCallback(async (dsl?: string, skipPanelPreview = false) => { @@ -830,7 +850,7 @@ export const useWorkflowVibe = () => { })) const workflowGraph = await flowchartToWorkflowGraph(mermaidCode) - addVersion(workflowGraph) + workflowStore.getState().addVibeFlowVersion(workflowGraph) if (skipPanelPreview) applyFlowchartToWorkflow() diff --git a/web/app/components/workflow/panel/vibe-panel/index.tsx b/web/app/components/workflow/panel/vibe-panel/index.tsx index 966172518c..b7c4748594 100644 --- a/web/app/components/workflow/panel/vibe-panel/index.tsx +++ b/web/app/components/workflow/panel/vibe-panel/index.tsx @@ -20,8 +20,6 @@ import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/com import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import { ModelModeType } from '@/types/app' import { VIBE_APPLY_EVENT, VIBE_COMMAND_EVENT } from '../../constants' -import { useHooksStore } from '../../hooks-store' -import { useVibeFlowData } from '../../hooks/use-workflow-vibe' import { useStore, useWorkflowStore } from '../../store' import WorkflowPreview from '../../workflow-preview' @@ -31,11 +29,9 @@ const VibePanel: FC = () => { const showVibePanel = useStore(s => s.showVibePanel) const isVibeGenerating = useStore(s => s.isVibeGenerating) const vibePanelInstruction = useStore(s => s.vibePanelInstruction) - const configsMap = useHooksStore(s => s.configsMap) - - const { current: currentFlowGraph, versions, currentVersionIndex, setCurrentVersionIndex } = useVibeFlowData({ - storageKey: configsMap?.flowId || '', - }) + const currentFlowGraph = useStore(s => s.currentVibeFlow) + const versions = useStore(s => s.vibeFlowVersions) + const currentVersionIndex = useStore(s => s.vibeFlowCurrentIndex) const vibePanelPreviewNodes = currentFlowGraph?.nodes || [] const vibePanelPreviewEdges = currentFlowGraph?.edges || [] @@ -124,6 +120,11 @@ const VibePanel: FC = () => { Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) }, [workflowStore, t]) + const handleVersionChange = useCallback((index: number) => { + const { setVibeFlowCurrentIndex } = workflowStore.getState() + setVibeFlowCurrentIndex(index) + }, [workflowStore]) + if (!showVibePanel) return null @@ -193,7 +194,8 @@ const VibePanel: FC = () => {
@@ -216,6 +218,7 @@ const VibePanel: FC = () => {
void vibePanelInstruction: string setVibePanelInstruction: (vibePanelInstruction: string) => void + vibeFlowVersions: FlowGraph[] + setVibeFlowVersions: (versions: FlowGraph[]) => void + vibeFlowCurrentIndex: number + setVibeFlowCurrentIndex: (index: number) => void + addVibeFlowVersion: (version: FlowGraph) => void + currentVibeFlow: FlowGraph | undefined } -export const createVibeWorkflowSlice: StateCreator = set => ({ +const getCurrentVibeFlow = (versions: FlowGraph[], currentIndex: number): FlowGraph | undefined => { + if (!versions || versions.length === 0) + return undefined + const index = currentIndex ?? 0 + if (index < 0) + return undefined + return versions[index] || versions[versions.length - 1] +} + +export const createVibeWorkflowSlice: StateCreator = (set, get) => ({ vibePanelMermaidCode: '', setVibePanelMermaidCode: vibePanelMermaidCode => set(() => ({ vibePanelMermaidCode })), isVibeGenerating: false, setIsVibeGenerating: isVibeGenerating => set(() => ({ isVibeGenerating })), vibePanelInstruction: '', setVibePanelInstruction: vibePanelInstruction => set(() => ({ vibePanelInstruction })), + vibeFlowVersions: [], + setVibeFlowVersions: versions => set((state) => { + const currentVibeFlow = getCurrentVibeFlow(versions, state.vibeFlowCurrentIndex) + return { vibeFlowVersions: versions, currentVibeFlow } + }), + vibeFlowCurrentIndex: 0, + setVibeFlowCurrentIndex: (index) => { + const state = get() + const versions = state.vibeFlowVersions || [] + + if (!versions || versions.length === 0) { + set({ vibeFlowCurrentIndex: 0, currentVibeFlow: undefined }) + return + } + + const normalizedIndex = Math.min(Math.max(index, 0), versions.length - 1) + const currentVibeFlow = getCurrentVibeFlow(versions, normalizedIndex) + set({ vibeFlowCurrentIndex: normalizedIndex, currentVibeFlow }) + }, + addVibeFlowVersion: (version) => { + // Prevent adding empty graphs + if (!version || !version.nodes || version.nodes.length === 0) { + set({ vibeFlowCurrentIndex: -1, currentVibeFlow: undefined }) + return + } + + set((state) => { + const newVersions = [...(state.vibeFlowVersions || []), version] + const newIndex = newVersions.length - 1 + const currentVibeFlow = getCurrentVibeFlow(newVersions, newIndex) + return { + vibeFlowVersions: newVersions, + vibeFlowCurrentIndex: newIndex, + currentVibeFlow, + } + }) + }, + currentVibeFlow: undefined, })