2 Revīzijas 805164aa62 ... 7dbe8baeed

Autors SHA1 Ziņojums Datums
  Carson 7dbe8baeed feat: rerun logic 1 gadu atpakaļ
  Carson 281f74c89c feat: 重新生成按钮逻辑 1 gadu atpakaļ

+ 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])
 

+ 17 - 10
app/run-agent-flow/components/ASideType/Agent.tsx

@@ -52,6 +52,7 @@ const Agent = ({ node, asideInstantAtom }) => {
         // userId: '1c9dc4b-d95f-11ea-af4c-52540005ab01'
       })
       ctrlRef.current = ctrl
+      setIsSending(true)
       for await (const chunk of chunks) {
         message.content += chunk;
         setMessages((prev) => [...prev])
@@ -60,9 +61,9 @@ const Agent = ({ node, asideInstantAtom }) => {
       message.isError = true
       message.error = e.message
     } finally {
-      setMessages((prev) => [...prev])
       message.isLoading = false
       setIsSending(false)
+      setMessages((prev) => [...prev])
       return message
     }
   }
@@ -90,7 +91,6 @@ const Agent = ({ node, asideInstantAtom }) => {
     ${formContext}
     ${nodeContext}
     `
-    setIsSending(true)
     const message = await onSend({ text, ignoreQuestionMessage: true })
     dispatchCardInstantData({ content: markdownit().render(message.content) })
   }
@@ -104,7 +104,6 @@ const Agent = ({ node, asideInstantAtom }) => {
   const onCommit = async () => {
     const text = input
     setInput('')
-    setIsSending(true)
     await onSend({ text })
   }
 
@@ -127,17 +126,25 @@ const Agent = ({ node, asideInstantAtom }) => {
 
   const effectButtons = []
   if (!isSending) {
-    effectButtons.push(
-      <button key="rerun" className='btn btn-xs' onClick={() => {
-        setMessages([])
-        onFirstSend()
-      }}>重新运行</button>,
-    )
+    if (messages?.length) {
+      effectButtons.push(
+        <button key="clear" className='btn btn-xs' onClick={() => {
+          setMessages([])
+          setSessionName(uuid4())
+        }}>清屏</button>,
+      )
+    } else {
+      effectButtons.push(
+        <button key="rerun" className='btn btn-primary btn-xs' onClick={() => {
+          onFirstSend()
+        }}>重新运行</button>,
+      )
+    }
   }
 
   return (
     <div className="w-full h-full flex relative">
-      <Chater messages={messages} node={node} onAccept={onAccept}></Chater>
+      <Chater messages={messages} node={node} onAccept={onAccept} onSend={onSend}></Chater>
 
       <Sender input={input} onInput={setInput} isSending={isSending} onStop={onStop} onCommit={onCommit} effectButtons={effectButtons} />
     </div>

+ 5 - 2
app/run-agent-flow/components/ASideType/Chater.tsx

@@ -18,7 +18,7 @@ type IMessage = {
 }
 type IMessages = IMessage[]
 
-const Chater = ({ messages, node, onAccept }: { messages: IMessages }) => {
+const Chater = ({ messages, node, onAccept, onSend }: { messages: IMessages }) => {
   const reversedMessages = useMemo(() => {
     return R.reverse(messages)
   }, [messages])
@@ -68,7 +68,10 @@ const Chater = ({ messages, node, onAccept }: { messages: IMessages }) => {
                         ? (
                           <>
                             <div className="divider my-0"></div>
-                            <button className="btn btn-xs btn-neutral" onClick={() => onAccept?.(message)}>采纳</button>
+                            <div className="flex gap-1 flex-wrap">
+                              <button className="btn btn-xs btn-neutral" onClick={() => onAccept?.(message)}>采纳</button>
+                              <button className="btn btn-xs btn-neutral" onClick={() => onSend?.({ text: '重新生成' })}>重新生成</button>
+                            </div>
                           </>
                         )
                         : null

+ 27 - 22
app/run-agent-flow/components/ASideType/Form.tsx

@@ -26,39 +26,44 @@ const Form = ({ node, asideInstantAtom }) => {
   }, [messages, sessionName])
 
   const onSend = async ({ text, ignoreQuestionMessage = false }: { text: string, ignoreQuestionMessage?: boolean }) => {
-    // FIXME fixed assi id
-    const assistantId = "8dcff108-64e8-11ef-826e-12e77c4cb76b"
-    const newMessages = messages
+    const assistantId = R.path(['properties', 'item', 'assistant_id'], node)
+    const newMessages = []
     if (!ignoreQuestionMessage) {
       newMessages.push({ type: 'md', role: 'user', content: text })
     }
     const message = { type: 'md', role: 'assistant', content: '', isLoading: true }
     newMessages.push(message)
-    setMessages(() => [...newMessages])
+    setMessages((prev) => [...prev, ...newMessages])
     // messageItem.current = message
-    const [chunks, ctrl] = getChatResponse({
-      text,
-      assistantId,
-      sessionName,
-      // userId: session?.user?.id,
-      // FIXME
-      userId: '1c9dc4b-d95f-11ea-af4c-52540005ab01'
-    })
-    ctrlRef.current = ctrl
-    for await (const chunk of chunks) {
-      message.content += chunk;
-      setMessages(() => [...newMessages])
+    try {
+      const [chunks, ctrl] = getChatResponse({
+        text,
+        assistantId,
+        sessionName,
+        userId: session?.user?.id,
+        // FIXME
+        // userId: '1c9dc4b-d95f-11ea-af4c-52540005ab01'
+      })
+      ctrlRef.current = ctrl
+      setIsSending(true)
+      for await (const chunk of chunks) {
+        message.content += chunk;
+        setMessages((prev) => [...prev])
+      }
+    } catch (e) {
+      message.isError = true
+      message.error = e.message
+    } finally {
+      message.isLoading = false
+      setIsSending(false)
+      setMessages((prev) => [...prev])
+      return message
     }
-    message.isLoading = false
-    setMessages(() => [...newMessages])
-    setIsSending(false)
-    return message
   }
 
   const onCommit = async () => {
     const text = input
     setInput('')
-    setIsSending(true)
     await onSend({ text })
   }
 
@@ -69,7 +74,7 @@ const Form = ({ node, asideInstantAtom }) => {
 
   return (
     <div className="w-full h-full flex relative">
-      <Chater messages={messages} node={node}></Chater>
+      <Chater messages={messages} node={node} onSend={onSend}></Chater>
 
       <Sender input={input} onInput={setInput} isSending={isSending} onStop={onStop} onCommit={onCommit} />
 

+ 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'