mirror of
https://github.com/langgenius/dify.git
synced 2026-08-28 22:36:52 +08:00
feat: Add workflow preview (#30234)
This commit is contained in:
commit
ecc247bfc7
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 })),
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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 })),
|
||||||
|
})
|
||||||
@ -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: {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user