myagent-ai 1.10.4 → 1.10.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/__pycache__/main_agent.cpython-312.pyc +0 -0
- package/agents/main_agent.py +54 -7
- package/core/__pycache__/output_parser.cpython-312.pyc +0 -0
- package/core/output_parser.py +10 -0
- package/package.json +1 -1
- package/web/__pycache__/api_server.cpython-312.pyc +0 -0
- package/web/api_server.py +7 -0
- package/web/ui/chat/chat_main.js +12 -2
- package/web/ui/chat/flow_engine.js +81 -23
|
Binary file
|
package/agents/main_agent.py
CHANGED
|
@@ -53,6 +53,8 @@ class MainAgent(BaseAgent):
|
|
|
53
53
|
<get_knowledge>下一轮执行时需要从知识库搜索获得的知识,填写检索关键词或描述。如context中已包含充足的<knowledge>内容,则为空。如需更多专业知识支撑,则填写相关搜索词。</get_knowledge>
|
|
54
54
|
<askuser>需要询问用户的内容,如无,则为空</askuser>
|
|
55
55
|
<finish>true/false,是否结束循环调用llm。如"askuser"为非空,则"finish"输出true。否则,根据"context"判断任务是否已完成,是否结束llm回调</finish>
|
|
56
|
+
<finish_reason>当 finish=true 时必填,详细说明为什么现在结束任务(如:任务已完成/需要用户补充信息/信息不足无法继续等)。finish=false 时为空。</finish_reason>
|
|
57
|
+
<next_step>当 finish=false 时必填,描述下一步计划做什么(简洁明了,1-2句话)。finish=true 时为空。</next_step>
|
|
56
58
|
|
|
57
59
|
</output>
|
|
58
60
|
|
|
@@ -70,7 +72,9 @@ class MainAgent(BaseAgent):
|
|
|
70
72
|
11. <get_knowledge>: 如果当前 <knowledge> 内容不足以完成任务,填写需要从知识库搜索的关键词;否则为空
|
|
71
73
|
12. <askuser>: 当信息不足需要用户补充时,在此填写要问的问题
|
|
72
74
|
13. <finish>: 当任务已完成或需要等待用户回应时为 true;否则为 false 继续执行
|
|
73
|
-
14.
|
|
75
|
+
14. <finish_reason>: **finish=true 时必须填写**,详细说明结束原因(任务完成/等待用户/信息不足/无法处理等)
|
|
76
|
+
15. <next_step>: **finish=false 时必须填写**,描述下一步计划做什么,要求简洁明确(1-2句话)
|
|
77
|
+
16. 使用中文输出所有内容
|
|
74
78
|
|
|
75
79
|
## 工具选择指南
|
|
76
80
|
- **搜索信息**: 用 `web_search`(返回标题+URL+摘要),不要用 browser_open
|
|
@@ -420,7 +424,7 @@ class MainAgent(BaseAgent):
|
|
|
420
424
|
if all_tool_outputs:
|
|
421
425
|
messages.append(Message(
|
|
422
426
|
role="user",
|
|
423
|
-
content=f"[上一轮工具执行结果汇总]\n{truncate_str(all_tool_outputs,
|
|
427
|
+
content=f"[上一轮工具执行结果汇总]\n{truncate_str(all_tool_outputs, 30000)}"
|
|
424
428
|
))
|
|
425
429
|
all_tool_outputs = ""
|
|
426
430
|
|
|
@@ -464,6 +468,8 @@ class MainAgent(BaseAgent):
|
|
|
464
468
|
"remember": truncate_str(parsed.remember, 200),
|
|
465
469
|
"ask_user": truncate_str(parsed.ask_user, 200),
|
|
466
470
|
"finish": parsed.finish,
|
|
471
|
+
"finish_reason": truncate_str(parsed.finish_reason, 200),
|
|
472
|
+
"next_step": truncate_str(parsed.next_step, 200),
|
|
467
473
|
"parse_success": parsed.parse_success,
|
|
468
474
|
}},
|
|
469
475
|
stream_callback,
|
|
@@ -700,20 +706,59 @@ class MainAgent(BaseAgent):
|
|
|
700
706
|
|
|
701
707
|
# 发送工具结果事件
|
|
702
708
|
# 提取实际输出:SkillResult 有 output/message/data,ExecResult 有 stdout/stderr
|
|
709
|
+
def _format_data_for_llm(data):
|
|
710
|
+
"""将结构化 data 格式化为 LLM 可读的文本"""
|
|
711
|
+
if data is None:
|
|
712
|
+
return ""
|
|
713
|
+
if isinstance(data, str):
|
|
714
|
+
return data
|
|
715
|
+
if isinstance(data, dict):
|
|
716
|
+
# 搜索结果列表格式 (web_search)
|
|
717
|
+
results = data.get("results")
|
|
718
|
+
if isinstance(results, list):
|
|
719
|
+
lines = []
|
|
720
|
+
for i, r in enumerate(results, 1):
|
|
721
|
+
title = r.get("title", "")
|
|
722
|
+
url = r.get("url", "")
|
|
723
|
+
snippet = r.get("snippet", "")
|
|
724
|
+
lines.append(f"{i}. {title}\n URL: {url}\n {snippet}")
|
|
725
|
+
return "\n".join(lines)
|
|
726
|
+
# 网页内容格式 (web_read)
|
|
727
|
+
if "url" in data and "content" in data:
|
|
728
|
+
title = data.get("title", "")
|
|
729
|
+
content = data.get("content", "")
|
|
730
|
+
lines = [f"标题: {title}", f"URL: {data['url']}", f"内容:\n{content}"]
|
|
731
|
+
return "\n".join(lines)
|
|
732
|
+
# 通用 dict: key-value 格式
|
|
733
|
+
parts = []
|
|
734
|
+
for k, v in data.items():
|
|
735
|
+
if k == "results":
|
|
736
|
+
continue # 已在上面处理
|
|
737
|
+
parts.append(f"{k}: {v}")
|
|
738
|
+
return "\n".join(parts) if parts else str(data)
|
|
739
|
+
if isinstance(data, list):
|
|
740
|
+
return "\n".join(str(item) for item in data)
|
|
741
|
+
return str(data)
|
|
742
|
+
|
|
703
743
|
def _extract_tool_output(tr):
|
|
704
744
|
"""从工具结果中提取实际输出文本"""
|
|
745
|
+
# 优先使用 output 字段 (技能明确设置的输出)
|
|
705
746
|
out = tr.get("output", "")
|
|
706
747
|
if out:
|
|
707
748
|
return out
|
|
749
|
+
# 如果 output 为空,尝试智能格式化 data 字段
|
|
750
|
+
data = tr.get("data")
|
|
751
|
+
if data is not None:
|
|
752
|
+
formatted = _format_data_for_llm(data)
|
|
753
|
+
if formatted:
|
|
754
|
+
return formatted
|
|
755
|
+
# 降级到 message / stdout / error
|
|
708
756
|
out = tr.get("message", "")
|
|
709
757
|
if out:
|
|
710
758
|
return out
|
|
711
759
|
out = tr.get("stdout", "")
|
|
712
760
|
if out:
|
|
713
761
|
return out
|
|
714
|
-
data = tr.get("data")
|
|
715
|
-
if data is not None:
|
|
716
|
-
return str(data) if not isinstance(data, str) else data
|
|
717
762
|
return tr.get("error", "")
|
|
718
763
|
|
|
719
764
|
tool_output_text = _extract_tool_output(tool_result)
|
|
@@ -745,16 +790,18 @@ class MainAgent(BaseAgent):
|
|
|
745
790
|
need_callback = True
|
|
746
791
|
|
|
747
792
|
output_str = tool_output_text
|
|
793
|
+
# 搜索和网页读取类工具允许更长的输出
|
|
794
|
+
_max_output = 6000 if tool_name in ("web_search", "web_read", "url_read") else 3000
|
|
748
795
|
tool_outputs_parts.append(
|
|
749
796
|
f"### {before_call}\n"
|
|
750
797
|
f"**工具**: {tool_name}\n"
|
|
751
798
|
f"**结果**: {'成功' if tool_result.get('success') else '失败'}\n"
|
|
752
|
-
f"{truncate_str(output_str,
|
|
799
|
+
f"{truncate_str(output_str, _max_output)}\n"
|
|
753
800
|
)
|
|
754
801
|
|
|
755
802
|
conversation_history.append(Message(
|
|
756
803
|
role="assistant",
|
|
757
|
-
content=f"执行工具 {tool_name}:\n{truncate_str(output_str,
|
|
804
|
+
content=f"执行工具 {tool_name}:\n{truncate_str(output_str, _max_output)}",
|
|
758
805
|
))
|
|
759
806
|
conversation_history.append(Message(
|
|
760
807
|
role="user",
|
|
Binary file
|
package/core/output_parser.py
CHANGED
|
@@ -86,6 +86,8 @@ class ParsedOutput:
|
|
|
86
86
|
get_knowledge: Knowledge search keywords for the next loop iteration.
|
|
87
87
|
The ContextBuilder will use this to perform RAG retrieval.
|
|
88
88
|
finish: When ``True`` the execution loop should terminate.
|
|
89
|
+
finish_reason: When finish=True, explains why the task is ending.
|
|
90
|
+
next_step: When finish=False, describes what to do next.
|
|
89
91
|
raw_text: The verbatim raw text returned by the LLM.
|
|
90
92
|
parse_success: Whether the XML was parsed successfully (``True``)
|
|
91
93
|
or the regex fallback was used (``False``).
|
|
@@ -99,6 +101,8 @@ class ParsedOutput:
|
|
|
99
101
|
ask_user: str = ""
|
|
100
102
|
get_knowledge: str = ""
|
|
101
103
|
finish: bool = False
|
|
104
|
+
finish_reason: str = ""
|
|
105
|
+
next_step: str = ""
|
|
102
106
|
response: str = "" # 模型对用户的直接回复(友好自然的话语)
|
|
103
107
|
raw_text: str = ""
|
|
104
108
|
parse_success: bool = False
|
|
@@ -363,6 +367,8 @@ def _parse_xml_content(xml_content: str) -> ParsedOutput:
|
|
|
363
367
|
parsed.ask_user = _safe_strip(root.findtext("askuser"))
|
|
364
368
|
parsed.get_knowledge = _safe_strip(root.findtext("get_knowledge"))
|
|
365
369
|
parsed.finish = _parse_bool(root.findtext("finish"), False)
|
|
370
|
+
parsed.finish_reason = _safe_strip(root.findtext("finish_reason"))
|
|
371
|
+
parsed.next_step = _safe_strip(root.findtext("next_step"))
|
|
366
372
|
parsed.response = _safe_strip(root.findtext("response"))
|
|
367
373
|
|
|
368
374
|
return parsed
|
|
@@ -393,6 +399,8 @@ def _fallback_regex_parse(raw_text: str) -> ParsedOutput:
|
|
|
393
399
|
parsed.ask_user = _safe_strip(tag_map.get("askuser"))
|
|
394
400
|
parsed.get_knowledge = _safe_strip(tag_map.get("get_knowledge"))
|
|
395
401
|
parsed.finish = _parse_bool(tag_map.get("finish"), False)
|
|
402
|
+
parsed.finish_reason = _safe_strip(tag_map.get("finish_reason"))
|
|
403
|
+
parsed.next_step = _safe_strip(tag_map.get("next_step"))
|
|
396
404
|
parsed.response = _safe_strip(tag_map.get("response"))
|
|
397
405
|
|
|
398
406
|
# For toolstocal we attempt to find individual <tool> blocks.
|
|
@@ -482,6 +490,8 @@ def parse_output(raw_text: str) -> ParsedOutput:
|
|
|
482
490
|
parsed.ask_user = _safe_strip(root.findtext("askuser"))
|
|
483
491
|
parsed.get_knowledge = _safe_strip(root.findtext("get_knowledge"))
|
|
484
492
|
parsed.finish = _parse_bool(root.findtext("finish"), False)
|
|
493
|
+
parsed.finish_reason = _safe_strip(root.findtext("finish_reason"))
|
|
494
|
+
parsed.next_step = _safe_strip(root.findtext("next_step"))
|
|
485
495
|
parsed.response = _safe_strip(root.findtext("response"))
|
|
486
496
|
return parsed
|
|
487
497
|
except ET.ParseError:
|
package/package.json
CHANGED
|
Binary file
|
package/web/api_server.py
CHANGED
|
@@ -2357,6 +2357,9 @@ class ApiServer:
|
|
|
2357
2357
|
pass
|
|
2358
2358
|
return web.json_response({**agent_info, "sessions": sessions})
|
|
2359
2359
|
|
|
2360
|
+
# Internal keys that should not appear in chat history UI
|
|
2361
|
+
_HIDDEN_KEYS = {"llm_output", "tool_call", "tool_result"}
|
|
2362
|
+
|
|
2360
2363
|
async def handle_get_messages(self, request):
|
|
2361
2364
|
sid = request.match_info["sid"]
|
|
2362
2365
|
if not self.core.memory: return web.json_response([])
|
|
@@ -2364,6 +2367,8 @@ class ApiServer:
|
|
|
2364
2367
|
offset = int(request.query.get("offset", 0))
|
|
2365
2368
|
entries = self.core.memory.get_conversation(sid, limit=limit + offset)
|
|
2366
2369
|
entries = entries[offset:]
|
|
2370
|
+
# Filter out internal entries (LLM raw output, tool calls/results)
|
|
2371
|
+
entries = [e for e in entries if (e.key or "") not in self._HIDDEN_KEYS]
|
|
2367
2372
|
return web.json_response([{"role": e.role, "content": e.content, "time": e.created_at, "key": e.key or ""} for e in entries])
|
|
2368
2373
|
|
|
2369
2374
|
async def handle_get_messages_query(self, request):
|
|
@@ -2376,6 +2381,8 @@ class ApiServer:
|
|
|
2376
2381
|
offset = int(request.query.get("offset", 0))
|
|
2377
2382
|
entries = self.core.memory.get_conversation(sid, limit=limit + offset)
|
|
2378
2383
|
entries = entries[offset:]
|
|
2384
|
+
# Filter out internal entries (LLM raw output, tool calls/results)
|
|
2385
|
+
entries = [e for e in entries if (e.key or "") not in self._HIDDEN_KEYS]
|
|
2379
2386
|
return web.json_response([{"role": e.role, "content": e.content, "time": e.created_at, "key": e.key or ""} for e in entries])
|
|
2380
2387
|
|
|
2381
2388
|
async def handle_delete_session(self, request):
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -1765,9 +1765,14 @@ async function selectSession(id) {
|
|
|
1765
1765
|
const loaded = (Array.isArray(data) ? data : []).filter(function(m) {
|
|
1766
1766
|
return m && (m.role === 'user' || m.role === 'assistant' || m.role === 'tool');
|
|
1767
1767
|
}).map(function(m) {
|
|
1768
|
+
var content = (m.content != null) ? String(m.content) : '';
|
|
1769
|
+
// Strip XML tags from assistant messages (backend may store raw LLM XML output)
|
|
1770
|
+
if (m.role === 'assistant' && content && content.trim().startsWith('<')) {
|
|
1771
|
+
content = (typeof _stripXmlTags === 'function') ? _stripXmlTags(content) : content;
|
|
1772
|
+
}
|
|
1768
1773
|
return {
|
|
1769
1774
|
role: m.role || 'assistant',
|
|
1770
|
-
content:
|
|
1775
|
+
content: content,
|
|
1771
1776
|
time: m.time || m.created_at || '',
|
|
1772
1777
|
key: m.key || '',
|
|
1773
1778
|
};
|
|
@@ -1824,9 +1829,14 @@ async function loadMoreMessages() {
|
|
|
1824
1829
|
const loaded = data.filter(function(m) {
|
|
1825
1830
|
return m && (m.role === 'user' || m.role === 'assistant' || m.role === 'tool');
|
|
1826
1831
|
}).map(function(m) {
|
|
1832
|
+
var content = (m.content != null) ? String(m.content) : '';
|
|
1833
|
+
// Strip XML tags from assistant messages (backend may store raw LLM XML output)
|
|
1834
|
+
if (m.role === 'assistant' && content && content.trim().startsWith('<')) {
|
|
1835
|
+
content = (typeof _stripXmlTags === 'function') ? _stripXmlTags(content) : content;
|
|
1836
|
+
}
|
|
1827
1837
|
return {
|
|
1828
1838
|
role: m.role || 'assistant',
|
|
1829
|
-
content:
|
|
1839
|
+
content: content,
|
|
1830
1840
|
time: m.time || m.created_at || '',
|
|
1831
1841
|
};
|
|
1832
1842
|
});
|
|
@@ -305,22 +305,25 @@ function updateStreamingMessage(msgIdx) {
|
|
|
305
305
|
const container = document.getElementById('messagesInner');
|
|
306
306
|
if (!container) return;
|
|
307
307
|
|
|
308
|
-
// Find
|
|
309
|
-
|
|
308
|
+
// Find the streaming message row by counting ALL message rows (not just assistant)
|
|
309
|
+
// msgIdx is the global index in state.messages, so we count all rows to match
|
|
310
|
+
const allRows = container.querySelectorAll('.message-row');
|
|
310
311
|
let targetRow = null;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
assistantCount++;
|
|
315
|
-
if (assistantCount === msgIdx + 1) {
|
|
312
|
+
let rowCount = 0;
|
|
313
|
+
for (const row of allRows) {
|
|
314
|
+
if (rowCount === msgIdx) {
|
|
316
315
|
targetRow = row;
|
|
317
316
|
break;
|
|
318
317
|
}
|
|
318
|
+
rowCount++;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
// Fallback: if we can't find by count, use last assistant row
|
|
322
|
-
if (!targetRow
|
|
323
|
-
|
|
322
|
+
if (!targetRow) {
|
|
323
|
+
const assistantRows = container.querySelectorAll('.message-row.assistant');
|
|
324
|
+
if (assistantRows.length > 0) {
|
|
325
|
+
targetRow = assistantRows[assistantRows.length - 1];
|
|
326
|
+
}
|
|
324
327
|
}
|
|
325
328
|
|
|
326
329
|
if (!targetRow) {
|
|
@@ -343,12 +346,32 @@ function updateStreamingMessage(msgIdx) {
|
|
|
343
346
|
}
|
|
344
347
|
}
|
|
345
348
|
if (msg.reasoning) {
|
|
346
|
-
// Count words/chars during streaming for live counter
|
|
347
349
|
const reasoningLen = msg.reasoning.length;
|
|
348
350
|
const reasoningWordCount = msg.streaming
|
|
349
351
|
? '<span class="thought-word-count">' + reasoningLen + ' 字</span>'
|
|
350
352
|
: '';
|
|
351
|
-
|
|
353
|
+
if (reasoningDetails) {
|
|
354
|
+
// Incremental update: only update word count and badge, append new text to content
|
|
355
|
+
const label = reasoningDetails.querySelector('.thought-label');
|
|
356
|
+
if (label) label.innerHTML = '模型推理过程' + reasoningWordCount;
|
|
357
|
+
const badge = reasoningDetails.querySelector('.thought-badge');
|
|
358
|
+
if (badge) badge.textContent = msg.streaming ? '推理中...' : '已完成';
|
|
359
|
+
// Incremental text append for streaming (avoid full markdown rebuild)
|
|
360
|
+
const thoughtContent = reasoningDetails.querySelector('.thought-content');
|
|
361
|
+
if (thoughtContent && msg.streaming) {
|
|
362
|
+
const prevLen = reasoningDetails._lastReasoningLen || 0;
|
|
363
|
+
if (msg.reasoning.length > prevLen) {
|
|
364
|
+
const newText = msg.reasoning.substring(prevLen);
|
|
365
|
+
thoughtContent.insertAdjacentHTML('beforeend', renderMarkdown(newText));
|
|
366
|
+
reasoningDetails._lastReasoningLen = msg.reasoning.length;
|
|
367
|
+
}
|
|
368
|
+
} else if (thoughtContent && !msg.streaming) {
|
|
369
|
+
// Final render once streaming stops
|
|
370
|
+
thoughtContent.innerHTML = renderMarkdown(msg.reasoning);
|
|
371
|
+
reasoningDetails._lastReasoningLen = msg.reasoning.length;
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
const reasoningHtml = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
|
|
352
375
|
<summary>
|
|
353
376
|
<span class="thought-icon">💡</span>
|
|
354
377
|
<span class="thought-label">模型推理过程${reasoningWordCount}</span>
|
|
@@ -356,10 +379,10 @@ function updateStreamingMessage(msgIdx) {
|
|
|
356
379
|
</summary>
|
|
357
380
|
<div class="thought-content">${renderMarkdown(msg.reasoning)}</div>
|
|
358
381
|
</details>`;
|
|
359
|
-
if (reasoningDetails) {
|
|
360
|
-
reasoningDetails.outerHTML = reasoningHtml;
|
|
361
|
-
} else {
|
|
362
382
|
contentArea.insertAdjacentHTML('afterbegin', reasoningHtml);
|
|
383
|
+
// Set initial length tracking
|
|
384
|
+
const newBlock = contentArea.querySelector(':scope > .thought-block');
|
|
385
|
+
if (newBlock) newBlock._lastReasoningLen = msg.reasoning.length;
|
|
363
386
|
}
|
|
364
387
|
}
|
|
365
388
|
|
|
@@ -378,7 +401,26 @@ function updateStreamingMessage(msgIdx) {
|
|
|
378
401
|
const thoughtWordCount = msg.streaming
|
|
379
402
|
? '<span class="thought-word-count">' + thoughtLen + ' 字</span>'
|
|
380
403
|
: '';
|
|
381
|
-
|
|
404
|
+
if (thoughtBlock) {
|
|
405
|
+
// Incremental update for thought block too
|
|
406
|
+
const label = thoughtBlock.querySelector('.thought-label');
|
|
407
|
+
if (label) label.innerHTML = 'Agent 思考过程' + thoughtWordCount;
|
|
408
|
+
const badge = thoughtBlock.querySelector('.thought-badge');
|
|
409
|
+
if (badge) badge.textContent = msg.streaming ? '思考中...' : '已完成';
|
|
410
|
+
const thoughtContent = thoughtBlock.querySelector('.thought-content');
|
|
411
|
+
if (thoughtContent && msg.streaming) {
|
|
412
|
+
const prevLen = thoughtBlock._lastThoughtLen || 0;
|
|
413
|
+
if (msg.thought.length > prevLen) {
|
|
414
|
+
const newText = msg.thought.substring(prevLen);
|
|
415
|
+
thoughtContent.insertAdjacentHTML('beforeend', renderMarkdown(newText));
|
|
416
|
+
thoughtBlock._lastThoughtLen = msg.thought.length;
|
|
417
|
+
}
|
|
418
|
+
} else if (thoughtContent && !msg.streaming) {
|
|
419
|
+
thoughtContent.innerHTML = renderMarkdown(msg.thought);
|
|
420
|
+
thoughtBlock._lastThoughtLen = msg.thought.length;
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
const thoughtHtml = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
|
|
382
424
|
<summary>
|
|
383
425
|
<span class="thought-icon">💭</span>
|
|
384
426
|
<span class="thought-label">Agent 思考过程${thoughtWordCount}</span>
|
|
@@ -386,16 +428,14 @@ function updateStreamingMessage(msgIdx) {
|
|
|
386
428
|
</summary>
|
|
387
429
|
<div class="thought-content">${renderMarkdown(msg.thought)}</div>
|
|
388
430
|
</details>`;
|
|
389
|
-
if (thoughtBlock) {
|
|
390
|
-
thoughtBlock.outerHTML = thoughtHtml;
|
|
391
|
-
} else {
|
|
392
|
-
// Insert after reasoning block if exists, otherwise at beginning
|
|
393
431
|
const existingReasoning = contentArea.querySelectorAll('.thought-block');
|
|
394
432
|
if (existingReasoning.length > 0) {
|
|
395
433
|
existingReasoning[existingReasoning.length - 1].insertAdjacentHTML('afterend', thoughtHtml);
|
|
396
434
|
} else {
|
|
397
435
|
contentArea.insertAdjacentHTML('afterbegin', thoughtHtml);
|
|
398
436
|
}
|
|
437
|
+
const newBlock = contentArea.querySelectorAll('.thought-block');
|
|
438
|
+
if (newBlock.length > 0) newBlock[newBlock.length - 1]._lastThoughtLen = msg.thought.length;
|
|
399
439
|
}
|
|
400
440
|
}
|
|
401
441
|
|
|
@@ -417,7 +457,26 @@ function updateStreamingMessage(msgIdx) {
|
|
|
417
457
|
const v2WordCount = msg.streaming
|
|
418
458
|
? '<span class="thought-word-count">' + v2Len + ' 字</span>'
|
|
419
459
|
: '';
|
|
420
|
-
|
|
460
|
+
if (v2ReasoningBlock) {
|
|
461
|
+
// Incremental update for V2 reasoning block
|
|
462
|
+
const label = v2ReasoningBlock.querySelector('.thought-label');
|
|
463
|
+
if (label) label.innerHTML = 'V2 推理过程' + v2WordCount;
|
|
464
|
+
const badge = v2ReasoningBlock.querySelector('.thought-badge');
|
|
465
|
+
if (badge) badge.textContent = msg.streaming ? '推理中...' : '已完成';
|
|
466
|
+
const thoughtContent = v2ReasoningBlock.querySelector('.thought-content');
|
|
467
|
+
if (thoughtContent && msg.streaming) {
|
|
468
|
+
const prevLen = v2ReasoningBlock._lastV2Len || 0;
|
|
469
|
+
if (msg._v2Reasoning.length > prevLen) {
|
|
470
|
+
const newText = msg._v2Reasoning.substring(prevLen);
|
|
471
|
+
thoughtContent.insertAdjacentHTML('beforeend', renderMarkdown(newText));
|
|
472
|
+
v2ReasoningBlock._lastV2Len = msg._v2Reasoning.length;
|
|
473
|
+
}
|
|
474
|
+
} else if (thoughtContent && !msg.streaming) {
|
|
475
|
+
thoughtContent.innerHTML = renderMarkdown(msg._v2Reasoning);
|
|
476
|
+
v2ReasoningBlock._lastV2Len = msg._v2Reasoning.length;
|
|
477
|
+
}
|
|
478
|
+
} else if (!msg.thought) {
|
|
479
|
+
const v2Html = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
|
|
421
480
|
<summary>
|
|
422
481
|
<span class="thought-icon">🧠</span>
|
|
423
482
|
<span class="thought-label">V2 推理过程${v2WordCount}</span>
|
|
@@ -425,10 +484,9 @@ function updateStreamingMessage(msgIdx) {
|
|
|
425
484
|
</summary>
|
|
426
485
|
<div class="thought-content">${renderMarkdown(msg._v2Reasoning)}</div>
|
|
427
486
|
</details>`;
|
|
428
|
-
if (v2ReasoningBlock) {
|
|
429
|
-
v2ReasoningBlock.outerHTML = v2Html;
|
|
430
|
-
} else if (!msg.thought) {
|
|
431
487
|
contentArea.insertAdjacentHTML('afterbegin', v2Html);
|
|
488
|
+
const newBlock = contentArea.querySelector(':scope > .thought-block');
|
|
489
|
+
if (newBlock) newBlock._lastV2Len = msg._v2Reasoning.length;
|
|
432
490
|
}
|
|
433
491
|
}
|
|
434
492
|
}
|