| @@ -10,6 +10,7 @@ const EditingTitle = () => { | |||
| const { formatTimeFromNow } = useWorkflow() | |||
| const draftUpdatedAt = useStore(state => state.draftUpdatedAt) | |||
| const publishedAt = useStore(state => state.publishedAt) | |||
| const isSyncingWorkflowDraft = useStore(s => s.isSyncingWorkflowDraft) | |||
| return ( | |||
| <div className='flex items-center h-[18px] text-xs text-gray-500'> | |||
| @@ -26,6 +27,14 @@ const EditingTitle = () => { | |||
| ? `${t('workflow.common.published')} ${formatTimeFromNow(publishedAt)}` | |||
| : t('workflow.common.unpublished') | |||
| } | |||
| { | |||
| isSyncingWorkflowDraft && ( | |||
| <> | |||
| <span className='flex items-center mx-1'>·</span> | |||
| {t('workflow.common.syncingData')} | |||
| </> | |||
| ) | |||
| } | |||
| </div> | |||
| ) | |||
| } | |||
| @@ -57,11 +57,13 @@ export const useWorkflowUpdate = () => { | |||
| const { | |||
| appId, | |||
| setSyncWorkflowDraftHash, | |||
| setIsSyncingWorkflowDraft, | |||
| } = workflowStore.getState() | |||
| setIsSyncingWorkflowDraft(true) | |||
| fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => { | |||
| handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdator) | |||
| setSyncWorkflowDraftHash(response.hash) | |||
| }) | |||
| }).finally(() => setIsSyncingWorkflowDraft(false)) | |||
| }, [handleUpdateWorkflowCanvas, workflowStore]) | |||
| return { | |||
| @@ -55,6 +55,7 @@ import HelpLine from './help-line' | |||
| import CandidateNode from './candidate-node' | |||
| import PanelContextmenu from './panel-contextmenu' | |||
| import NodeContextmenu from './node-contextmenu' | |||
| import SyncingDataModal from './syncing-data-modal' | |||
| import { | |||
| useStore, | |||
| useWorkflowStore, | |||
| @@ -99,7 +100,10 @@ const Workflow: FC<WorkflowProps> = memo(({ | |||
| const controlMode = useStore(s => s.controlMode) | |||
| const nodeAnimation = useStore(s => s.nodeAnimation) | |||
| const showConfirm = useStore(s => s.showConfirm) | |||
| const { setShowConfirm } = workflowStore.getState() | |||
| const { | |||
| setShowConfirm, | |||
| setControlPromptEditorRerenderKey, | |||
| } = workflowStore.getState() | |||
| const { | |||
| handleSyncWorkflowDraft, | |||
| syncWorkflowDraftWhenPageClose, | |||
| @@ -113,6 +117,7 @@ const Workflow: FC<WorkflowProps> = memo(({ | |||
| if (v.type === WORKFLOW_DATA_UPDATE) { | |||
| setNodes(v.payload.nodes) | |||
| setEdges(v.payload.edges) | |||
| setTimeout(() => setControlPromptEditorRerenderKey(Date.now())) | |||
| } | |||
| }) | |||
| @@ -135,7 +140,7 @@ const Workflow: FC<WorkflowProps> = memo(({ | |||
| if (document.visibilityState === 'hidden') | |||
| syncWorkflowDraftWhenPageClose() | |||
| else if (document.visibilityState === 'visible') | |||
| handleRefreshWorkflowDraft() | |||
| setTimeout(() => handleRefreshWorkflowDraft(), 500) | |||
| }, [syncWorkflowDraftWhenPageClose, handleRefreshWorkflowDraft]) | |||
| useEffect(() => { | |||
| @@ -223,6 +228,7 @@ const Workflow: FC<WorkflowProps> = memo(({ | |||
| `} | |||
| ref={workflowContainerRef} | |||
| > | |||
| <SyncingDataModal /> | |||
| <CandidateNode /> | |||
| <Header /> | |||
| <Panel /> | |||
| @@ -27,6 +27,7 @@ import TooltipPlus from '@/app/components/base/tooltip-plus' | |||
| import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars' | |||
| import Switch from '@/app/components/base/switch' | |||
| import { Jinja } from '@/app/components/base/icons/src/vender/workflow' | |||
| import { useStore } from '@/app/components/workflow/store' | |||
| type Props = { | |||
| className?: string | |||
| @@ -82,6 +83,7 @@ const Editor: FC<Props> = ({ | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| const { eventEmitter } = useEventEmitterContextContext() | |||
| const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey) | |||
| const isShowHistory = !isChatModel && isChatApp | |||
| @@ -173,6 +175,7 @@ const Editor: FC<Props> = ({ | |||
| ? ( | |||
| <div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative px-3 min-h-[56px] overflow-y-auto')}> | |||
| <PromptEditor | |||
| key={controlPromptEditorRerenderKey} | |||
| instanceId={instanceId} | |||
| compact | |||
| className='min-h-[56px]' | |||
| @@ -125,6 +125,10 @@ type Shape = { | |||
| nodeData: VariableAssignerNodeType | |||
| } | |||
| setEnteringNodePayload: (enteringNodePayload?: Shape['enteringNodePayload']) => void | |||
| isSyncingWorkflowDraft: boolean | |||
| setIsSyncingWorkflowDraft: (isSyncingWorkflowDraft: boolean) => void | |||
| controlPromptEditorRerenderKey: number | |||
| setControlPromptEditorRerenderKey: (controlPromptEditorRerenderKey: number) => void | |||
| } | |||
| export const createWorkflowStore = () => { | |||
| @@ -209,6 +213,10 @@ export const createWorkflowStore = () => { | |||
| setConnectingNodePayload: connectingNodePayload => set(() => ({ connectingNodePayload })), | |||
| enteringNodePayload: undefined, | |||
| setEnteringNodePayload: enteringNodePayload => set(() => ({ enteringNodePayload })), | |||
| isSyncingWorkflowDraft: false, | |||
| setIsSyncingWorkflowDraft: isSyncingWorkflowDraft => set(() => ({ isSyncingWorkflowDraft })), | |||
| controlPromptEditorRerenderKey: 0, | |||
| setControlPromptEditorRerenderKey: controlPromptEditorRerenderKey => set(() => ({ controlPromptEditorRerenderKey })), | |||
| })) | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| import { useStore } from './store' | |||
| const SyncingDataModal = () => { | |||
| const isSyncingWorkflowDraft = useStore(s => s.isSyncingWorkflowDraft) | |||
| if (!isSyncingWorkflowDraft) | |||
| return null | |||
| return ( | |||
| <div className='absolute inset-0 z-[9999]'> | |||
| </div> | |||
| ) | |||
| } | |||
| export default SyncingDataModal | |||
| @@ -67,6 +67,7 @@ const translation = { | |||
| manageInTools: 'Manage in Tools', | |||
| workflowAsToolTip: 'Tool reconfiguration is required after the workflow update.', | |||
| viewDetailInTracingPanel: 'View details', | |||
| syncingData: 'Syncing data, just a few seconds.', | |||
| }, | |||
| errorMsg: { | |||
| fieldRequired: '{{field}} is required', | |||
| @@ -67,6 +67,7 @@ const translation = { | |||
| manageInTools: '访问工具页', | |||
| workflowAsToolTip: '工作流更新后需要重新配置工具参数', | |||
| viewDetailInTracingPanel: '查看详细信息', | |||
| syncingData: '同步数据中,只需几秒钟。', | |||
| }, | |||
| errorMsg: { | |||
| fieldRequired: '{{field}} 不能为空', | |||