瀏覽代碼

fix: random

Carson 10 月之前
父節點
當前提交
34e83f398e

+ 27 - 3
app/run-agent-flow/components/ASideType/Agent.tsx

@@ -5,7 +5,7 @@ import * as R from 'ramda'
 import markdownit from 'markdown-it'
 import { v4 as uuid4 } from 'uuid'
 import TextareaAutosize from 'react-textarea-autosize';
-import { BsFillSendFill } from "react-icons/bs";
+import { BsCapslockFill, BsFillStopFill } from "react-icons/bs";
 
 
 import Chater from './Chater'
@@ -21,6 +21,7 @@ const Agent = ({ node, asideInstantAtom }) => {
   const [asideInstant, dispatchAsideInstant] = useReducerAtom(asideInstantAtom, (prev, payload) => ({ ...prev, ...payload }))
   const [messages, setMessages] = useState(asideInstant?.messages ?? [])
   // const messageItem = useRef<unknown>(null)
+  const ctrlRef = useRef<AbortController>(null)
   const [sessionName, setSessionName] = useState(asideInstant?.sessionName ?? uuid4())
 
   const stepsNodes = useAtomValue(stepsNodesAtom)
@@ -52,6 +53,7 @@ const Agent = ({ node, asideInstantAtom }) => {
       // FIXME
       userId: '1c9dc4b-d95f-11ea-af4c-52540005ab01'
     })
+    ctrlRef.current = ctrl
     for await (const chunk of chunks) {
       message.content += chunk;
       setMessages(() => [...newMessages])
@@ -118,12 +120,34 @@ const Agent = ({ node, asideInstantAtom }) => {
     dispatchCardInstantData({ content })
   }
 
+  const onStop = async () => {
+    ctrlRef.current?.abort()
+  }
+  const onKeyDown = (e) => {
+    if (e.key === 'Enter' && R.none(R.equals(true), R.props(['altKey', 'shiftKey', 'ctrlKey', 'metaKey'], e))) {
+      onCommit()
+      e.preventDefault()
+    }
+  }
+
   return (
     <div className="w-full h-full flex relative">
       <Chater messages={messages} node={node} onAccept={onAccept}></Chater>
       <div className="absolute flex inset-x-2.5 bottom-2.5 w-auto">
-        <TextareaAutosize className="textarea textarea-bordered pr-12 w-full resize-none" value={input} onChange={ev => setInput(ev.target.value)} maxRows={4} />
-        <button className="btn btn-active btn-primary btn-sm absolute right-[8px] bottom-[8px] w-[2rem] px-0" disabled={isSending} onClick={onCommit}><BsFillSendFill size={18} /></button>
+        <TextareaAutosize className="textarea textarea-bordered pr-12 w-full resize-none" value={input} onChange={ev => setInput(ev.target.value)} maxRows={4} onKeyDown={onKeyDown} />
+        {
+          isSending
+            ? (
+              <button className="btn btn-secondary btn-sm absolute right-[8px] bottom-[8px] w-[2rem] px-0" onClick={onStop}>
+                <BsFillStopFill size={18} />
+              </button>
+            )
+            : (
+              <button className="btn btn-primary btn-sm absolute right-[8px] bottom-[8px] w-[2rem] px-0" disabled={!input} onClick={onCommit}>
+                <BsCapslockFill size={18} />
+              </button>
+            )
+        }
       </div>
     </div>
   )

+ 19 - 13
app/run-agent-flow/components/ASideType/Chater.tsx

@@ -28,29 +28,35 @@ const Chater = ({ messages, node, onAccept }: { messages: IMessages }) => {
         reversedMessages.map((message, i) => (
           <div key={i} className={twMerge("chat ", message.role === 'assistant' ? "chat-start" : 'chat-end')}>
             <div className="chat-image avatar placeholder">
-              <div className="w-10 rounded-full bg-neutral text-neutral-content">
-                {message.role === 'assistant'
-                  ? (
+              {message.role === 'assistant'
+                ? (
+                  <div className="w-10 rounded-full">
                     <img
                       alt="avatar"
-                      src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
-                  )
-                  : <span className="text-xl">我</span>}
-              </div>
+                      src="/images/assistant-avatar.png" />
+                  </div>
+                )
+                : (
+                  <div className="w-10 rounded-full bg-neutral text-neutral-content">
+                    <span className="text-xl">我</span>
+                  </div>
+                )}
             </div>
             <div className={twMerge("chat-bubble prose prose-sm", message.role === 'assistant' ? "chat-bubble-primary" : "chat-bubble-secondary")}>
               <Markdown>
                 {message?.content}
               </Markdown>
               {
-                (node.type === 'UserTask' && message.role === 'assistant')
+                message.role === 'assistant'
                   ? message?.isLoading
                     ? <span className="loading loading-dots loading-sm"></span>
-                    : (
-                      <>
-                        <button className="btn btn-xs btn-neutral" onClick={() => onAccept?.(message)}>采纳</button>
-                      </>
-                    )
+                    : node.type === 'UserTask'
+                      ? (
+                        <>
+                          <button className="btn btn-xs btn-neutral" onClick={() => onAccept?.(message)}>采纳</button>
+                        </>
+                      )
+                      : null
                   : null
               }
             </div>

+ 31 - 5
app/run-agent-flow/components/ASideType/Form.tsx

@@ -1,20 +1,23 @@
 'use client'
 
-import React, { useEffect, useState } from "react"
+import React, { useEffect, useRef, useState } from "react"
 import { v4 as uuid4 } from 'uuid'
 import { useReducerAtom } from "jotai/utils";
 import { useSession } from "next-auth/react";
 import TextareaAutosize from 'react-textarea-autosize';
 import Chater from "./Chater";
 import { getChatResponse } from "@/lib/utils";
-import { BsFillSendFill } from "react-icons/bs";
+import { BsCapslockFill } from "react-icons/bs";
 import * as R from 'ramda'
+import { BsFillStopFill } from "react-icons/bs";
+
 
 const Form = ({ node, asideInstantAtom }) => {
   const { data: session } = useSession()
   const [asideInstant, dispatchAsideInstant] = useReducerAtom(asideInstantAtom, (prev, payload) => ({ ...prev, ...payload }))
-  const [messages, setMessages] = useState(asideInstant?.messages ?? [])
+  const [messages, setMessages] = useState(asideInstant?.messages ?? [{ type: 'md', role: 'assistant', content: 'Hi~' }])
   // const messageItem = useRef<unknown>(null)
+  const ctrlRef = useRef<AbortController>(null)
   const [sessionName, setSessionName] = useState(asideInstant?.sessionName ?? uuid4())
 
   const [input, setInput] = useState('')
@@ -43,6 +46,7 @@ const Form = ({ node, asideInstantAtom }) => {
       // FIXME
       userId: '1c9dc4b-d95f-11ea-af4c-52540005ab01'
     })
+    ctrlRef.current = ctrl
     for await (const chunk of chunks) {
       message.content += chunk;
       setMessages(() => [...newMessages])
@@ -60,12 +64,34 @@ const Form = ({ node, asideInstantAtom }) => {
     await onSend({ text })
   }
 
+  const onStop = async () => {
+    ctrlRef.current?.abort()
+  }
+  const onKeyDown = (e) => {
+    if (e.key === 'Enter' && R.none(R.equals(true), R.props(['altKey', 'shiftKey', 'ctrlKey', 'metaKey'], e))) {
+      onCommit()
+      e.preventDefault()
+    }
+  }
+
   return (
     <div className="w-full h-full flex relative">
       <Chater messages={messages} node={node}></Chater>
       <div className="absolute flex inset-x-2.5 bottom-2.5 w-auto">
-        <TextareaAutosize className="textarea textarea-bordered pr-12 w-full resize-none" value={input} onChange={ev => setInput(ev.target.value)} maxRows={4} />
-        <button className="btn btn-active btn-primary btn-sm absolute right-[8px] bottom-[8px] w-[2rem] px-0" disabled={isSending} onClick={onCommit}><BsFillSendFill size={18} /></button>
+        <TextareaAutosize className="textarea textarea-bordered pr-12 w-full resize-none" value={input} onChange={ev => setInput(ev.target.value)} maxRows={4} onKeyDown={onKeyDown} />
+        {
+          isSending
+            ? (
+              <button className="btn btn-secondary btn-sm absolute right-[8px] bottom-[8px] w-[2rem] px-0" onClick={onStop}>
+                <BsFillStopFill size={18} />
+              </button>
+            )
+            : (
+              <button className="btn btn-primary btn-sm absolute right-[8px] bottom-[8px] w-[2rem] px-0" disabled={!input} onClick={onCommit}>
+                <BsCapslockFill size={18} />
+              </button>
+            )
+        }
       </div>
     </div>
   )

+ 0 - 1
app/run-agent-flow/components/Header.tsx

@@ -75,7 +75,6 @@ export default function Header() {
       }
       setIsLogInFail(authToken); // 如果存在 authToken,则用户已登录
     };
-    console.log("start")
     const intervalId = setInterval(async () => {
       await checkLoginStatus(intervalId);
     }, 5000);

+ 2 - 1
app/run-agent-flow/components/NodeType/Agent.tsx

@@ -164,6 +164,7 @@ const Agent = ({ node, cardInstantAtom }) => {
   useEffect(() => {
     if (!editor?.isFocused) {
       editor?.commands.setContent(cardInstantData?.content)
+      editor?.view.dom.focus()
     }
   }, [cardInstantData, cardInstantData?.content])
 
@@ -171,7 +172,7 @@ const Agent = ({ node, cardInstantAtom }) => {
     return null;
   }
   return (
-    <div>
+    <div className="hover:bg-gray-200 has-[:focus]:bg-gray-200 rounded-lg">
       {/* {editor && <BubbleMenu editor={editor} tippyOptions={{ duration: 100 }}>
         <MenuBar editor={editor} />
       </BubbleMenu>} */}

二進制
public/images/assistant-avatar.png