Procházet zdrojové kódy

feat:增加思考过程展示样式

韩洋 před 1 měsícem
rodič
revize
1a23dbfe8e

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 8 - 0
dist/assets/index--ho6O6Kg.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 8 - 0
dist/assets/index-BE9g0XkS.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 8 - 0
dist/assets/index-C35Q_VFj.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 8 - 0
dist/assets/index-CvS9RMHQ.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
dist/assets/index-DPekV8Of.css


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
dist/assets/index-KT_Kbj1G.css


+ 2 - 2
dist/index.html

@@ -5,8 +5,8 @@
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>智能体使用平台</title>
-    <script type="module" crossorigin src="/assets/index-BRbaVW_b.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-CCUDalut.css">
+    <script type="module" crossorigin src="/assets/index-BE9g0XkS.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-DPekV8Of.css">
   </head>
   <body>
     <div id="root"></div>

+ 80 - 0
src/App.css

@@ -3445,6 +3445,86 @@
   color: #64748b;
 }
 
+.execute-msg-think {
+  margin-bottom: 14px;
+  padding: 12px 14px;
+  border: 1px solid rgba(148, 163, 184, 0.26);
+  border-radius: 14px;
+  background: linear-gradient(180deg, rgba(248, 250, 252, 0.92), rgba(241, 245, 249, 0.78));
+  color: #64748b;
+}
+
+.execute-msg-think.is-thinking {
+  border-color: rgba(22, 93, 255, 0.22);
+  background: linear-gradient(180deg, rgba(239, 246, 255, 0.92), rgba(248, 250, 252, 0.86));
+}
+
+.execute-msg-think-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  color: #475569;
+  font-size: 12px;
+  font-weight: 700;
+  cursor: pointer;
+  list-style: none;
+  user-select: none;
+}
+
+.execute-msg-think-title::-webkit-details-marker {
+  display: none;
+}
+
+.execute-msg-think-title::before {
+  content: '';
+  width: 7px;
+  height: 7px;
+  border-radius: 999px;
+  background: #94a3b8;
+}
+
+.execute-msg-think-title::after {
+  content: '';
+  width: 7px;
+  height: 7px;
+  margin-left: auto;
+  border-right: 1.5px solid #94a3b8;
+  border-bottom: 1.5px solid #94a3b8;
+  transform: rotate(45deg) translateY(-2px);
+  transition: transform 0.18s ease;
+}
+
+.execute-msg-think[open] .execute-msg-think-title::after {
+  transform: rotate(-135deg) translateY(-1px);
+}
+
+.execute-msg-think.is-thinking .execute-msg-think-title::before {
+  background: #165dff;
+  box-shadow: 0 0 0 5px rgba(22, 93, 255, 0.12);
+}
+
+.execute-msg-think-body {
+  margin-top: 8px;
+  color: #64748b;
+  font-size: 13px;
+  line-height: 1.68;
+}
+
+.execute-msg-think-toggle {
+  margin-left: auto;
+  color: #94a3b8;
+  font-size: 12px;
+  font-weight: 500;
+}
+
+.execute-msg-think[open] .execute-msg-think-toggle {
+  color: #64748b;
+}
+
+.execute-msg-answer {
+  color: inherit;
+}
+
 .markdown-body {
   color: inherit;
   line-height: 1.7;

+ 1 - 1
src/config/index.js

@@ -1,6 +1,6 @@
 // 浏览器直接连后端 5001。Vite proxy 路径已停用以减少中间层开销。
 // CORS 走后端配置(CONSOLE_CORS_ALLOW_ORIGINS / WEB_API_CORS_ALLOW_ORIGINS)放行。
-export const API_BASE_URL = 'http://192.168.100.26:5001'
+export const API_BASE_URL = 'http://192.168.100.25:5001'
 // export const API_BASE_URL = 'http://localhost:5001'
 export const API_PREFIX = `${API_BASE_URL}/console/api`
 export const PUBLIC_API_PREFIX = `${API_BASE_URL}/api`

+ 90 - 3
src/pages/AgentExecute.jsx

@@ -64,6 +64,91 @@ const renderMarkdown = (text) => {
   }
 }
 
+const splitThinkContent = (text) => {
+  const source = String(text || '')
+  const lower = source.toLowerCase()
+  const thoughts = []
+  const answers = []
+  let cursor = 0
+  let thinking = false
+  while (cursor < source.length) {
+    if (thinking) {
+      const end = lower.indexOf('</think>', cursor)
+      if (end === -1) {
+        thoughts.push(source.slice(cursor))
+        cursor = source.length
+      } else {
+        thoughts.push(source.slice(cursor, end))
+        cursor = end + 8
+        thinking = false
+      }
+    } else {
+      const start = lower.indexOf('<think>', cursor)
+      if (start === -1) {
+        answers.push(source.slice(cursor))
+        cursor = source.length
+      } else {
+        answers.push(source.slice(cursor, start))
+        cursor = start + 7
+        thinking = true
+      }
+    }
+  }
+  return {
+    thought: thoughts.join('').trim(),
+    answer: answers.join('').trim(),
+    thinking,
+  }
+}
+
+const renderAssistantMessage = (message) => {
+  return <AssistantMessageContent message={message} />
+}
+
+function AssistantMessageContent({ message }) {
+  const parsed = splitThinkContent(message?.content || '')
+  const answer = parsed.answer || (!parsed.thought ? message?.content : '')
+  const thinking = message?.thinking || parsed.thinking
+  const prevThinkingRef = useRef(thinking)
+  const [thinkOpen, setThinkOpen] = useState(thinking)
+
+  useEffect(() => {
+    if (thinking) {
+      setThinkOpen(true)
+    } else if (prevThinkingRef.current) {
+      setThinkOpen(false)
+    }
+    prevThinkingRef.current = thinking
+  }, [thinking])
+
+  return (
+    <>
+      {parsed.thought && (
+        <details
+          className={`execute-msg-think ${thinking ? 'is-thinking' : ''}`}
+          open={thinkOpen}
+          onToggle={e => setThinkOpen(e.currentTarget.open)}
+        >
+          <summary className="execute-msg-think-title">
+            <span>{thinking ? '思考中' : '思考过程'}</span>
+            {/* <span className="execute-msg-think-toggle">展开/收起</span> */}
+          </summary>
+          <div
+            className="markdown-body execute-msg-think-body"
+            dangerouslySetInnerHTML={{ __html: renderMarkdown(parsed.thought) }}
+          />
+        </details>
+      )}
+      {answer && (
+        <div
+          className="markdown-body execute-msg-answer"
+          dangerouslySetInnerHTML={{ __html: renderMarkdown(answer) }}
+        />
+      )}
+    </>
+  )
+}
+
 const STREAM_WAITING_TEXT = '内容生成中'
 const AUTO_SCROLL_THRESHOLD = 24
 
@@ -840,7 +925,7 @@ function AgentExecute() {
       if (streamId) {
         const updateMessages = prev => prev.map(m =>
           m.id === streamId
-            ? { ...m, content: m.pending ? answer : (m.content || '') + answer, pending: false }
+            ? { ...m, content: m.pending ? answer : (m.content || '') + answer, pending: false, thinking: splitThinkContent(m.pending ? answer : (m.content || '') + answer).thinking }
             : m
         )
         if (viewingHistoryRef.current && activeChatSnapshotRef.current) {
@@ -867,7 +952,7 @@ function AgentExecute() {
       if (streamId) {
         const updateMessages = prev => prev.map(m =>
           m.id === streamId
-            ? { ...m, streaming: false, pending: false, metadata: evtMetadata, messageId: evtMessageId }
+            ? { ...m, streaming: false, pending: false, thinking: false, metadata: evtMetadata, messageId: evtMessageId }
             : m
         )
         if (viewingHistoryRef.current && activeChatSnapshotRef.current) {
@@ -918,7 +1003,7 @@ function AgentExecute() {
       if (streamId) {
         const updateMessages = prev => prev.map(m =>
           m.id === streamId
-            ? { ...m, content: data?.answer || '' }
+            ? { ...m, content: data?.answer || '', thinking: splitThinkContent(data?.answer || '').thinking }
             : m
         )
         if (viewingHistoryRef.current && activeChatSnapshotRef.current) {
@@ -1560,6 +1645,8 @@ function AgentExecute() {
                   <div className="execute-msg-bubble">
                     {m.pending ? (
                       <span className="execute-msg-generating">{m.content}</span>
+                    ) : m.role === 'assistant' ? (
+                      renderAssistantMessage(m)
                     ) : (
                       <div
                         className="markdown-body"

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů