Carson пре 5 месеци
родитељ
комит
7dbe8baeed

+ 2 - 2
app/run-agent-flow/components/ASide.tsx

@@ -19,11 +19,11 @@ const ASide = () => {
   }, [node, node?.id])
 
   // 动态注册当前节点的Atom,并将其放进一个atom集合的atom,方便后续格式化所有节点实例
-  const asideInstantAtom = asideInstantAtomFamily(node?.id)
+  const asideInstantAtom = asideInstantAtomFamily(node)
   const [, dispatchAsideInstantAtoms] = useAtom(asideInstantAtomsAtom)
   useEffect(() => {
     if (node?.id) {
-      dispatchAsideInstantAtoms({ [node.id]: asideInstantAtom })
+      dispatchAsideInstantAtoms({ payload: { [node.id]: asideInstantAtom }, type: 'update' })
     }
   }, [asideInstantAtom])
 

+ 29 - 0
app/run-agent-flow/components/ClearAfterFlowConfirmModal.tsx

@@ -0,0 +1,29 @@
+'use client'
+
+import { twMerge } from "tailwind-merge"
+import { BsFillInfoCircleFill } from "react-icons/bs";
+
+
+export default function ClearAfterFlowConfirmModal({ open, onCancel, onConfirm }: { open: boolean, onClose: () => void }) {
+
+  return (
+    <dialog className={twMerge("modal", open ? "modal-open" : "")}>
+      <div className="modal-box pb-5">
+        <h3 className="font-bold text-lg flex items-center gap-4">
+          <BsFillInfoCircleFill />检测到当前步骤已进行过,请选择
+        </h3>
+        <div className="modal-action">
+          <form method="dialog" className="flex gap-2">
+            <button className="btn btn-sm" onClick={onCancel}>取消</button>
+            <button className="btn btn-sm btn-secondary" onClick={() => onConfirm('not_clear')}>
+              保留信息并继续
+            </button>
+            <button className="btn btn-sm btn-primary" onClick={() => onConfirm('clear')}>
+              此节点后重新开始并继续
+            </button>
+          </form>
+        </div>
+      </div>
+    </dialog>
+  )
+}

+ 60 - 7
app/run-agent-flow/components/NodeRender.tsx

@@ -5,13 +5,23 @@ import Agent from './NodeType/Agent';
 import Form from './NodeType/Form';
 import Unsupport from './NodeType/Unsupport';
 import * as R from 'ramda'
-import { curNodeAtom, curStepAtom, arrowStateAtom, cardInstantAtomFamily, cardInstantAtomsAtom, instantDataAtom, stepsNodesAtom } from '../store';
-import { useAtom, useAtomValue } from 'jotai';
+import { curNodeAtom, curStepAtom, arrowStateAtom, cardInstantAtomFamily, cardInstantAtomsAtom, instantDataAtom, stepsNodesAtom, viewedStepAtom, asideInstantAtomsAtom, asideInstantAtomFamily } from '../store';
+import { useAtom, useAtomValue, useSetAtom } from 'jotai';
 import { exportFlowToDocx } from '../export';
 import PreviewModal from './PreviewModal';
+import { useReducerAtom } from 'jotai/utils';
+import ClearAfterFlowConfirmModal from './ClearAfterFlowConfirmModal';
 
 const NodeRender = () => {
   const [curStep, setCurStep] = useAtom(curStepAtom)
+  const [viewedStep, dispatchViewedStep] = useReducerAtom(viewedStepAtom, (prev: number, step: number) => {
+    return R.max(prev, step)
+  })
+  const forceSetViewedStep = useSetAtom(viewedStepAtom)
+  useEffect(() => {
+    dispatchViewedStep(curStep)
+  }, [curStep])
+  const [confirmClearAfterFlowModalOpen, setConfirmClearAfterFlowModalOpen] = useState(false)
   const node = useAtomValue(curNodeAtom)
   const arrowState = useAtomValue(arrowStateAtom)
   const Comp = useMemo(() => {
@@ -31,15 +41,53 @@ const NodeRender = () => {
 
   // 动态注册当前节点的Atom,并将其放进一个atom集合的atom,方便后续格式化所有节点实例
   const cardInstantAtom = cardInstantAtomFamily(node)
-  const [, dispatchCardInstantAtoms] = useAtom(cardInstantAtomsAtom)
+  const [cardInstantAtoms, dispatchCardInstantAtoms] = useAtom(cardInstantAtomsAtom)
   useEffect(() => {
     if (node?.id) {
-      dispatchCardInstantAtoms({ [node.id]: cardInstantAtom })
+      dispatchCardInstantAtoms({ payload: { [node.id]: cardInstantAtom }, type: 'update' })
     }
   }, [cardInstantAtom])
 
-  const onNextStep = () => {
+  const stepsNodes = useAtomValue(stepsNodesAtom);
+  const [asideInstantAtoms, dispatchAsideInstantAtoms] = useAtom(asideInstantAtomsAtom)
+  const onNextStep = (confirmed?: 'clear' | 'not_clear') => {
+    if (!confirmed) {
+      if (curStep < viewedStep) {
+        setConfirmClearAfterFlowModalOpen(true)
+      } else {
+        setCurStep(prev => prev + 1)
+      }
+      return
+    }
+    switch (confirmed) {
+      case 'clear':
+        // Clear node instant data after current node
+        const nodesToClear = stepsNodes.slice(curStep + 1);
+        const updatedCardInstantAtoms = R.pickBy(
+          (_, key) => !nodesToClear.some(node => node.id === key),
+          cardInstantAtoms
+        );
+        dispatchCardInstantAtoms({ payload: updatedCardInstantAtoms, type: 'replace' });
+
+        const updatedAsideInstantAtoms = R.pickBy(
+          (_, key) => !nodesToClear.some(node => node.id === key),
+          asideInstantAtoms
+        )
+        dispatchAsideInstantAtoms({ payload: updatedAsideInstantAtoms, type: 'replace' })
+        nodesToClear.forEach((node) => {
+          console.log('removed: ', node.id)
+          cardInstantAtomFamily.remove(node)
+          asideInstantAtomFamily.remove(node)
+        })
+        forceSetViewedStep(curStep);
+        break;
+      case 'not_clear':
+        break;
+      default:
+        break;
+    }
     setCurStep(prev => prev + 1)
+    setConfirmClearAfterFlowModalOpen(false)
   }
 
   const onPrevStep = () => {
@@ -60,10 +108,10 @@ const NodeRender = () => {
             {curStep + 1}: {nodeName}
           </h2>
           {arrowState.prev &&
-            <button className='btn btn-sm' onClick={onPrevStep}>上一步</button>
+            <button className='btn btn-sm' onClick={() => onPrevStep()}>上一步</button>
           }
           {arrowState.next
-            ? <button className='btn btn-sm' onClick={onNextStep}>下一步</button>
+            ? <button className='btn btn-sm' onClick={() => onNextStep()}>下一步</button>
             : <button className='btn btn-sm btn-neutral' onClick={onPreview}>预览</button>
           }
         </div>
@@ -71,6 +119,11 @@ const NodeRender = () => {
           <Comp key={node?.id} node={node} cardInstantAtom={cardInstantAtom}></Comp>
         </div>
       </div>
+      <ClearAfterFlowConfirmModal
+        open={confirmClearAfterFlowModalOpen}
+        onCancel={() => setConfirmClearAfterFlowModalOpen(false)}
+        onConfirm={onNextStep}
+      />
       <PreviewModal open={isPreviewModalOpen} onClose={() => setPreviewModalOpen(false)} />
     </div>
   )

+ 30 - 7
app/run-agent-flow/store.tsx

@@ -114,9 +114,19 @@ export const cardInstantAtomFamily = atomFamily(
   (a, b) => a?.id === b?.id
 )
 
-export const cardInstantAtomsAtom = atomWithReducer({}, (prev, payload: { id: string; } & { [K in any]?: any }) => {
-  return { ...prev, ...payload }
-})
+export const cardInstantAtomsAtom = atomWithReducer(
+  {},
+  (prev, action: { payload: { [K in any]?: any }, type: 'replace' | 'update' } = { payload: {}, type: 'update' }) => {
+    switch (action.type) {
+      case 'replace':
+        return action.payload
+      case 'update':
+        return { ...prev, ...action.payload }
+      default:
+        return prev
+    }
+  }
+)
 cardInstantAtomsAtom.debugLabel = 'cardInstantAtomsAtom'
 
 // Readonly
@@ -129,9 +139,22 @@ instantDataAtom.debugLabel = 'instantDataAtom'
 
 
 // aside下相关的状态
-export const asideInstantAtomFamily = atomFamily((id) => atom({ id }))
+export const asideInstantAtomFamily = atomFamily(
+  (node) => atom({ id: node?.id }),
+  (a, b) => a?.id === b?.id
+)
 
-export const asideInstantAtomsAtom = atomWithReducer({}, (prev, payload: { id: string; } & { [K in any]?: any }) => {
-  return { ...prev, ...payload }
-})
+export const asideInstantAtomsAtom = atomWithReducer(
+  {},
+  (prev, action: { payload: { [K in any]?: any }, type: 'replace' | 'update' } = { payload: {}, type: 'update' }) => {
+    switch (action.type) {
+      case 'replace':
+        return action.payload
+      case 'update':
+        return { ...prev, ...action.payload }
+      default:
+        return prev
+    }
+  }
+)
 asideInstantAtomsAtom.debugLabel = 'asideInstantAtomsAtom'