@bolloon/bolloon-agent 0.3.22 → 0.3.24
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/dist/agents/deny-pipeline.js +116 -0
- package/dist/agents/parse-tool-call.js +26 -4
- package/dist/agents/pi-sdk.js +247 -50
- package/dist/agents/session-store.js +87 -2
- package/dist/bootstrap/snip-collapse.js +135 -0
- package/dist/cli/ink-app.js +116 -0
- package/dist/cli/loading-tui.js +64 -10
- package/dist/electron/config.js +14 -9
- package/dist/electron/dialogs.js +53 -16
- package/dist/electron/first-run.js +65 -24
- package/dist/electron/ipc.js +14 -10
- package/dist/electron/logger.js +44 -7
- package/dist/electron/main.js +45 -42
- package/dist/electron/menu.js +18 -13
- package/dist/electron/paths.js +54 -12
- package/dist/electron/server.js +57 -18
- package/dist/electron/tray.js +53 -15
- package/dist/electron/window.js +61 -22
- package/dist/electron-preload.js +19 -16
- package/dist/electron.js +4 -1
- package/dist/external-engines/delegate.js +19 -0
- package/dist/hooks/hooks-engine.js +329 -0
- package/dist/index.js +130 -133
- package/dist/llm/pi-ai.js +5 -17
- package/dist/security/tool-gate.js +8 -1
- package/dist/social/dunbar-tier.js +409 -0
- package/dist/utils/auto-update.js +51 -12
- package/dist/web/client.js +4833 -4328
- package/dist/web/components/p2p/index.js +234 -276
- package/dist/web/server.js +17 -3
- package/dist/web/style.css +2 -2
- package/dist/web/ui/message-renderer.js +396 -535
- package/dist/web/ui/step-timeline.js +273 -372
- package/package.json +27 -25
- package/dist/web/components/p2p/P2PModal.js +0 -188
- package/dist/web/components/p2p/p2p-modal.js +0 -664
- package/dist/web/components/p2p/p2p-tools.js +0 -248
|
@@ -1,572 +1,433 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* - 折叠 think / environment_details 块
|
|
9
|
-
* - 复制 / 重新回答 / 蒸馏为判断 按钮
|
|
10
|
-
* - 挂载 step-timeline (2026-06-15) 步骤状态条到每条 AI 消息内
|
|
11
|
-
*
|
|
12
|
-
* 状态 (本模块私有):
|
|
13
|
-
* - streamingMessageEl / streamingTextNode / streamingText
|
|
14
|
-
* - lastUserCommand / lastAiContent (去重)
|
|
15
|
-
*
|
|
16
|
-
* 依赖 (import):
|
|
17
|
-
* - 浏览器 API (document, marked, fetch, navigator)
|
|
18
|
-
* - ./step-timeline (4 状态步骤条模块)
|
|
19
|
-
*
|
|
20
|
-
* 不依赖 (零业务 import, 防循环):
|
|
21
|
-
* - 不 import client.js
|
|
22
|
-
* - 不 import 任何业务模块
|
|
23
|
-
*
|
|
24
|
-
* 输入 (从 client.js 调用):
|
|
25
|
-
* - addMessage(content, type, save, container, usedIds, ctx)
|
|
26
|
-
* - handleStreamTokenEvent({ streamType, content }, ctx)
|
|
27
|
-
* - finalizeTimelineAsMessage(ctx)
|
|
28
|
-
* - handleStepEvent({ type: 'step_start'|'step_done'|'step_error', ... }, ctx)
|
|
29
|
-
* - escapeHtml(s)
|
|
30
|
-
* - getMessagesContainerForCurrent(currentChannelId, messagesContainers, messagesEl)
|
|
31
|
-
*
|
|
32
|
-
* 输出 (DOM):
|
|
33
|
-
* - .message / .bubble / .think-container / .env-container
|
|
34
|
-
* - .message-streaming (流式期间, finalize 时移除)
|
|
35
|
-
* - .message-actions (复制/重新回答/蒸馏按钮)
|
|
36
|
-
* - .used-judgments-link (P0.5 反向引用)
|
|
37
|
-
* - .step-timeline (气泡内步骤状态条, 由 ./step-timeline 渲染)
|
|
38
|
-
*/
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// 类型定义
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
import { mountStepTimeline, pushStepToTimeline, migrateStepTimeline, getStepTimeline, } from './step-timeline.js';
|
|
43
|
-
// 2026-07-01 (v0.2.6): 共享后端切 LLM 输出. 消除 <invoke>/<function_calls>/<tool_call>
|
|
44
|
-
// 等各种 LLM 格式在前端气泡里出现的 bug.
|
|
45
|
-
import { segmentChatReply } from '../../agents/chat-segmenter.js';
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// 模块私有状态
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
1
|
+
import {
|
|
2
|
+
mountStepTimeline,
|
|
3
|
+
pushStepToTimeline,
|
|
4
|
+
migrateStepTimeline,
|
|
5
|
+
getStepTimeline
|
|
6
|
+
} from "./step-timeline.js";
|
|
7
|
+
import { segmentChatReply } from "../../agents/chat-segmenter.js";
|
|
49
8
|
let streamingMessageEl = null;
|
|
50
9
|
let streamingTextNode = null;
|
|
51
|
-
let streamingText =
|
|
52
|
-
let lastUserCommand =
|
|
53
|
-
let lastAiContent =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
* 2026-06-17: 流式状态查询 — 客户端用它在收到 server `ai` 事件时判断是否需要跳过 addMessage,
|
|
58
|
-
* 避免和后续 `done` → finalizeTimelineAsMessage 产生双气泡.
|
|
59
|
-
* 流式过程中 (streamingText > 0) server 推 `ai(content=fullResponse)` 时跳过,
|
|
60
|
-
* 真正渲染走 `done` 触发的 finalizeTimelineAsMessage.
|
|
61
|
-
*/
|
|
62
|
-
export function hasStreamingText() {
|
|
63
|
-
return streamingText.length > 0;
|
|
10
|
+
let streamingText = "";
|
|
11
|
+
let lastUserCommand = "";
|
|
12
|
+
let lastAiContent = "";
|
|
13
|
+
const stepEventBuffer = /* @__PURE__ */ new Map();
|
|
14
|
+
function hasStreamingText() {
|
|
15
|
+
return streamingText.length > 0;
|
|
64
16
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
streamingTextNode.nodeValue = String(fullContent || '');
|
|
73
|
-
streamingText = String(fullContent || '');
|
|
17
|
+
function replaceStreamingText(fullContent) {
|
|
18
|
+
if (!streamingTextNode || !streamingMessageEl) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
streamingTextNode.nodeValue = String(fullContent || "");
|
|
22
|
+
streamingText = String(fullContent || "");
|
|
74
23
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
24
|
+
function injectRecoveredText(partialText, ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
|
|
25
|
+
if (streamingMessageEl && streamingTextNode) {
|
|
26
|
+
streamingTextNode.nodeValue = String(partialText || "");
|
|
27
|
+
streamingText = String(partialText || "");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
handleStreamTokenEvent(
|
|
31
|
+
{
|
|
32
|
+
type: "token",
|
|
33
|
+
streamType: "token",
|
|
34
|
+
content: String(partialText || ""),
|
|
35
|
+
delta: String(partialText || "")
|
|
36
|
+
},
|
|
37
|
+
ctx
|
|
38
|
+
);
|
|
90
39
|
}
|
|
91
|
-
// 滚动限频 (60ms 16fps, 减 reflow)
|
|
92
40
|
let scrollToBottomTimer = null;
|
|
93
41
|
function scheduleScrollToBottom(container) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
scrollToBottomTimer =
|
|
99
|
-
|
|
100
|
-
scrollToBottomTimer = null;
|
|
101
|
-
}, 60);
|
|
42
|
+
if (!container) return;
|
|
43
|
+
if (scrollToBottomTimer) return;
|
|
44
|
+
scrollToBottomTimer = setTimeout(() => {
|
|
45
|
+
container.scrollTop = container.scrollHeight;
|
|
46
|
+
scrollToBottomTimer = null;
|
|
47
|
+
}, 60);
|
|
102
48
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return messagesContainers.get(currentChannelId) || null;
|
|
109
|
-
}
|
|
110
|
-
return messagesEl;
|
|
49
|
+
function getMessagesContainerForCurrent(currentChannelId, messagesContainers, messagesEl) {
|
|
50
|
+
if (currentChannelId && messagesContainers.get(currentChannelId)) {
|
|
51
|
+
return messagesContainers.get(currentChannelId) || null;
|
|
52
|
+
}
|
|
53
|
+
return messagesEl;
|
|
111
54
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
55
|
+
function escapeHtml(s) {
|
|
56
|
+
return String(s ?? "").replace(/[&<>"']/g, (c) => ({
|
|
57
|
+
"&": "&",
|
|
58
|
+
"<": "<",
|
|
59
|
+
">": ">",
|
|
60
|
+
'"': """,
|
|
61
|
+
"'": "'"
|
|
62
|
+
})[c]);
|
|
119
63
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// 内存保护: 单个 channel 容器超过 200 条, 旧会话加载时淘汰最旧
|
|
131
|
-
if (!save && msgContainer && msgContainer.children.length > 200) {
|
|
132
|
-
const toRemove = msgContainer.children.length - 200;
|
|
133
|
-
for (let i = 0; i < toRemove; i++) {
|
|
134
|
-
const first = msgContainer.firstElementChild;
|
|
135
|
-
if (first)
|
|
136
|
-
msgContainer.removeChild(first);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// 去重 (save=true 时)
|
|
140
|
-
if (save) {
|
|
141
|
-
const lastContent = type === 'user' ? lastUserCommand : lastAiContent;
|
|
142
|
-
if (lastContent && content === lastContent) {
|
|
143
|
-
console.log(`[addMessage] 跳过重复的 ${type} 消息`);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (type === 'user')
|
|
147
|
-
lastUserCommand = content;
|
|
148
|
-
else
|
|
149
|
-
lastAiContent = content;
|
|
64
|
+
function addMessage(content, type, save = true, container, usedJudgmentIds = [], ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }, timestamp) {
|
|
65
|
+
const messagesEl = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
|
|
66
|
+
const messagesContainers = ctx.messagesContainers || /* @__PURE__ */ new Map();
|
|
67
|
+
const currentChannelId = ctx.currentChannelId;
|
|
68
|
+
const msgContainer = container || getMessagesContainerForCurrent(currentChannelId, messagesContainers, messagesEl);
|
|
69
|
+
if (!save && msgContainer && msgContainer.children.length > 200) {
|
|
70
|
+
const toRemove = msgContainer.children.length - 200;
|
|
71
|
+
for (let i = 0; i < toRemove; i++) {
|
|
72
|
+
const first = msgContainer.firstElementChild;
|
|
73
|
+
if (first) msgContainer.removeChild(first);
|
|
150
74
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
cleanContent = cleanContent.replace(/<think>[\s\S]*?<\/think>/g, '');
|
|
158
|
-
const finalGenIdx = cleanContent.indexOf('<final gen>');
|
|
159
|
-
if (finalGenIdx >= 0) {
|
|
160
|
-
cleanContent = cleanContent.substring(0, finalGenIdx).trim();
|
|
161
|
-
}
|
|
75
|
+
}
|
|
76
|
+
if (save) {
|
|
77
|
+
const lastContent = type === "user" ? lastUserCommand : lastAiContent;
|
|
78
|
+
if (lastContent && content === lastContent) {
|
|
79
|
+
console.log(`[addMessage] \u8DF3\u8FC7\u91CD\u590D\u7684 ${type} \u6D88\u606F`);
|
|
80
|
+
return;
|
|
162
81
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
82
|
+
if (type === "user") lastUserCommand = content;
|
|
83
|
+
else lastAiContent = content;
|
|
84
|
+
}
|
|
85
|
+
const div = document.createElement("div");
|
|
86
|
+
div.className = `message message-${type}`;
|
|
87
|
+
let cleanContent = content;
|
|
88
|
+
if (type === "ai") {
|
|
89
|
+
cleanContent = cleanContent.replace(/<think>[\s\S]*?<\/think>/g, "");
|
|
90
|
+
const finalGenIdx = cleanContent.indexOf("<final gen>");
|
|
91
|
+
if (finalGenIdx >= 0) {
|
|
92
|
+
cleanContent = cleanContent.substring(0, finalGenIdx).trim();
|
|
172
93
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (ctx && ctx.toolCallCallback) {
|
|
207
|
-
ctx.toolCallCallback(seg.tool, div);
|
|
208
|
-
}
|
|
209
|
-
renderedAny = true; // 即使没文字, tool_call 也算"有意义"
|
|
210
|
-
}
|
|
94
|
+
}
|
|
95
|
+
const knownToolNames = ctx && ctx.knownToolNames || /* @__PURE__ */ new Set();
|
|
96
|
+
const segments = segmentChatReply(cleanContent, { knownToolNames });
|
|
97
|
+
if (segments.length === 0) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
let thinkContainer = null;
|
|
101
|
+
let renderedAny = false;
|
|
102
|
+
for (const seg of segments) {
|
|
103
|
+
if (seg.type === "think" && seg.content) {
|
|
104
|
+
thinkContainer = buildThinkContainer(seg.content);
|
|
105
|
+
div.appendChild(thinkContainer);
|
|
106
|
+
renderedAny = true;
|
|
107
|
+
} else if (seg.type === "env_details" && seg.content) {
|
|
108
|
+
div.appendChild(buildEnvContainer(seg.content));
|
|
109
|
+
renderedAny = true;
|
|
110
|
+
} else if (seg.type === "text" && seg.content) {
|
|
111
|
+
if (thinkContainer) div.appendChild(thinkContainer);
|
|
112
|
+
thinkContainer = null;
|
|
113
|
+
div.appendChild(buildBubble(seg.content, type));
|
|
114
|
+
renderedAny = true;
|
|
115
|
+
} else if (seg.type === "final" && seg.content) {
|
|
116
|
+
if (thinkContainer) div.appendChild(thinkContainer);
|
|
117
|
+
thinkContainer = null;
|
|
118
|
+
const finalEl = buildBubble(seg.content, type);
|
|
119
|
+
finalEl.classList.add("bubble-final");
|
|
120
|
+
div.appendChild(finalEl);
|
|
121
|
+
renderedAny = true;
|
|
122
|
+
} else if (seg.type === "tool_call" && seg.tool) {
|
|
123
|
+
if (ctx && ctx.toolCallCallback) {
|
|
124
|
+
ctx.toolCallCallback(seg.tool, div);
|
|
125
|
+
}
|
|
126
|
+
renderedAny = true;
|
|
211
127
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const d = timestamp instanceof Date ? timestamp : new Date(timestamp);
|
|
225
|
-
if (!isNaN(d.getTime())) {
|
|
226
|
-
timeLabel = d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
catch { /* fallback below */ }
|
|
231
|
-
if (!timeLabel)
|
|
232
|
-
timeLabel = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
|
|
233
|
-
const time = document.createElement('div');
|
|
234
|
-
time.className = 'time';
|
|
235
|
-
time.textContent = timeLabel;
|
|
236
|
-
// AI 消息操作按钮
|
|
237
|
-
if (type === 'ai') {
|
|
238
|
-
div.appendChild(buildMessageActions(div, rawContent, ctx));
|
|
239
|
-
}
|
|
240
|
-
// P0.5 反向引用
|
|
241
|
-
if (type === 'ai' && Array.isArray(usedJudgmentIds) && usedJudgmentIds.length > 0) {
|
|
242
|
-
const link = document.createElement('a');
|
|
243
|
-
link.className = 'used-judgments-link';
|
|
244
|
-
link.textContent = `📎 参考 ${usedJudgmentIds.length} 条原则`;
|
|
245
|
-
link.onclick = (e) => {
|
|
246
|
-
e.preventDefault();
|
|
247
|
-
if (typeof ctx.openJudgmentsModalWithFilter === 'function') {
|
|
248
|
-
ctx.openJudgmentsModalWithFilter(usedJudgmentIds);
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
div.appendChild(link);
|
|
252
|
-
}
|
|
253
|
-
// 2026-06-15: 每条 AI 消息挂一个空 step-timeline 占位 (用户决策)
|
|
254
|
-
// 后续 step_start/step_done 事件会通过 handleStepEvent 推入
|
|
255
|
-
if (type === 'ai' && msgContainer) {
|
|
256
|
-
mountStepTimeline(div, currentChannelId);
|
|
257
|
-
// 2026-07-20 Bug 1: AI 消息创建后回放此前缓冲的 step 事件
|
|
258
|
-
flushStepEventBuffer(currentChannelId, ctx);
|
|
259
|
-
}
|
|
260
|
-
div.appendChild(time);
|
|
261
|
-
if (msgContainer) {
|
|
262
|
-
msgContainer.appendChild(div);
|
|
263
|
-
scheduleScrollToBottom(msgContainer);
|
|
128
|
+
}
|
|
129
|
+
if (!renderedAny) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const rawContent = segments.filter((s) => s.type === "text" || s.type === "final").map((s) => s.content || "").join("\n");
|
|
133
|
+
let timeLabel = "";
|
|
134
|
+
try {
|
|
135
|
+
if (timestamp !== void 0 && timestamp !== null) {
|
|
136
|
+
const d = timestamp instanceof Date ? timestamp : new Date(timestamp);
|
|
137
|
+
if (!isNaN(d.getTime())) {
|
|
138
|
+
timeLabel = d.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" });
|
|
139
|
+
}
|
|
264
140
|
}
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
if (!timeLabel) timeLabel = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" });
|
|
144
|
+
const time = document.createElement("div");
|
|
145
|
+
time.className = "time";
|
|
146
|
+
time.textContent = timeLabel;
|
|
147
|
+
if (type === "ai") {
|
|
148
|
+
div.appendChild(buildMessageActions(div, rawContent, ctx));
|
|
149
|
+
}
|
|
150
|
+
if (type === "ai" && Array.isArray(usedJudgmentIds) && usedJudgmentIds.length > 0) {
|
|
151
|
+
const link = document.createElement("a");
|
|
152
|
+
link.className = "used-judgments-link";
|
|
153
|
+
link.textContent = `\u{1F4CE} \u53C2\u8003 ${usedJudgmentIds.length} \u6761\u539F\u5219`;
|
|
154
|
+
link.onclick = (e) => {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
if (typeof ctx.openJudgmentsModalWithFilter === "function") {
|
|
157
|
+
ctx.openJudgmentsModalWithFilter(usedJudgmentIds);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
div.appendChild(link);
|
|
161
|
+
}
|
|
162
|
+
if (type === "ai" && msgContainer) {
|
|
163
|
+
mountStepTimeline(div, currentChannelId);
|
|
164
|
+
flushStepEventBuffer(currentChannelId, ctx);
|
|
165
|
+
}
|
|
166
|
+
div.appendChild(time);
|
|
167
|
+
if (msgContainer) {
|
|
168
|
+
msgContainer.appendChild(div);
|
|
169
|
+
scheduleScrollToBottom(msgContainer);
|
|
170
|
+
}
|
|
265
171
|
}
|
|
266
|
-
// ---------------------------------------------------------------------------
|
|
267
|
-
// 私有: think 折叠块
|
|
268
|
-
// ---------------------------------------------------------------------------
|
|
269
172
|
function buildThinkContainer(thinkContent) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
container.appendChild(content);
|
|
295
|
-
return container;
|
|
173
|
+
const container = document.createElement("div");
|
|
174
|
+
container.className = "think-container";
|
|
175
|
+
const toggle = document.createElement("div");
|
|
176
|
+
toggle.className = "think-toggle";
|
|
177
|
+
toggle.innerHTML = '\u{1F4AD} \u601D\u8003\u8FC7\u7A0B <span class="think-arrow">\u25B8</span>';
|
|
178
|
+
toggle.onclick = function() {
|
|
179
|
+
const details = container.querySelector(".think-content");
|
|
180
|
+
const arrow = toggle.querySelector(".think-arrow");
|
|
181
|
+
if (!details || !arrow) return;
|
|
182
|
+
if (details.style.display === "none") {
|
|
183
|
+
details.style.display = "block";
|
|
184
|
+
arrow.textContent = "\u25BE";
|
|
185
|
+
} else {
|
|
186
|
+
details.style.display = "none";
|
|
187
|
+
arrow.textContent = "\u25B8";
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const content = document.createElement("div");
|
|
191
|
+
content.className = "think-content";
|
|
192
|
+
content.style.display = "none";
|
|
193
|
+
content.innerHTML = `<pre>${escapeHtml(thinkContent)}</pre>`;
|
|
194
|
+
container.appendChild(toggle);
|
|
195
|
+
container.appendChild(content);
|
|
196
|
+
return container;
|
|
296
197
|
}
|
|
297
|
-
// ---------------------------------------------------------------------------
|
|
298
|
-
// 私有: environment_details 折叠块
|
|
299
|
-
// ---------------------------------------------------------------------------
|
|
300
198
|
function buildEnvContainer(envDetails) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
container.appendChild(content);
|
|
326
|
-
return container;
|
|
199
|
+
const container = document.createElement("div");
|
|
200
|
+
container.className = "env-container";
|
|
201
|
+
const toggle = document.createElement("div");
|
|
202
|
+
toggle.className = "env-toggle";
|
|
203
|
+
toggle.innerHTML = '\u2699\uFE0F \u73AF\u5883\u4FE1\u606F <span class="env-arrow">\u25B8</span>';
|
|
204
|
+
toggle.onclick = function() {
|
|
205
|
+
const details = container.querySelector(".environment-details");
|
|
206
|
+
const arrow = toggle.querySelector(".env-arrow");
|
|
207
|
+
if (!details || !arrow) return;
|
|
208
|
+
if (details.style.display === "none") {
|
|
209
|
+
details.style.display = "block";
|
|
210
|
+
arrow.textContent = "\u25BE";
|
|
211
|
+
} else {
|
|
212
|
+
details.style.display = "none";
|
|
213
|
+
arrow.textContent = "\u25B8";
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const content = document.createElement("div");
|
|
217
|
+
content.className = "environment-details";
|
|
218
|
+
content.style.display = "none";
|
|
219
|
+
content.innerHTML = `<pre>${escapeHtml(envDetails)}</pre>`;
|
|
220
|
+
container.appendChild(toggle);
|
|
221
|
+
container.appendChild(content);
|
|
222
|
+
return container;
|
|
327
223
|
}
|
|
328
|
-
// ---------------------------------------------------------------------------
|
|
329
|
-
// 私有: 气泡 (marked.parse 渲染)
|
|
330
|
-
// ---------------------------------------------------------------------------
|
|
331
224
|
function buildBubble(text, type) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
bubble.innerHTML = marked ? marked.parse(text) : escapeHtml(text);
|
|
338
|
-
return bubble;
|
|
225
|
+
const bubble = document.createElement("div");
|
|
226
|
+
bubble.className = `bubble bubble-${type}`;
|
|
227
|
+
const marked = window.marked;
|
|
228
|
+
bubble.innerHTML = marked ? marked.parse(text) : escapeHtml(text);
|
|
229
|
+
return bubble;
|
|
339
230
|
}
|
|
340
|
-
// ---------------------------------------------------------------------------
|
|
341
|
-
// 私有: 消息操作按钮 (复制 / 重新回答 / 蒸馏为判断)
|
|
342
|
-
// ---------------------------------------------------------------------------
|
|
343
231
|
function buildMessageActions(div, rawContent, ctx) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
regenBtn.
|
|
372
|
-
regenBtn.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
lastUserMsg = bubble.textContent || bubble.innerText || '';
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
fetch('/regenerate', {
|
|
388
|
-
method: 'POST',
|
|
389
|
-
headers: { 'Content-Type': 'application/json' },
|
|
390
|
-
body: JSON.stringify({ channelId: ctx.currentChannelId, userMessage: lastUserMsg })
|
|
391
|
-
}).then(res => {
|
|
392
|
-
if (!res.ok)
|
|
393
|
-
throw new Error('regenerate failed');
|
|
394
|
-
}).catch(err => {
|
|
395
|
-
console.error('重新生成失败:', err);
|
|
396
|
-
regenBtn.innerHTML = refreshIcon(false) + ' 失败';
|
|
397
|
-
setTimeout(() => {
|
|
398
|
-
regenBtn.innerHTML = refreshIcon(false) + ' 重新回答';
|
|
399
|
-
regenBtn.disabled = false;
|
|
400
|
-
}, 2000);
|
|
401
|
-
});
|
|
402
|
-
};
|
|
403
|
-
actions.appendChild(regenBtn);
|
|
404
|
-
return actions;
|
|
405
|
-
}
|
|
406
|
-
function copyIcon() { return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>'; }
|
|
407
|
-
function checkIcon() { return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>'; }
|
|
408
|
-
function shieldIcon() { return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L4 6v6c0 5 3.5 9.5 8 10 4.5-.5 8-5 8-10V6l-8-4z"></path><path d="M9 12l2 2 4-4"></path></svg>'; }
|
|
409
|
-
function refreshIcon(spin = false) { const cls = spin ? ' class="spin"' : ''; return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"${cls}><path d="M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16"></path></svg>`; }
|
|
410
|
-
// ---------------------------------------------------------------------------
|
|
411
|
-
// 流式 token 处理 (textNode 增量, 不重排)
|
|
412
|
-
// ---------------------------------------------------------------------------
|
|
413
|
-
export function handleStreamTokenEvent(data, ctx = { messagesEl: null, messagesContainers: new Map(), currentChannelId: null }) {
|
|
414
|
-
const messagesEl = ctx.messagesEl || (typeof document !== 'undefined' ? document.getElementById('messages') : null);
|
|
415
|
-
const messagesContainers = ctx.messagesContainers || new Map();
|
|
416
|
-
const currentChannelId = ctx.currentChannelId;
|
|
417
|
-
const container = getMessagesContainerForCurrent(currentChannelId, messagesContainers, messagesEl);
|
|
418
|
-
if (!container)
|
|
419
|
-
return;
|
|
420
|
-
const delta = data.content || '';
|
|
421
|
-
if (!delta)
|
|
422
|
-
return;
|
|
423
|
-
if (!streamingMessageEl || !streamingMessageEl.isConnected) {
|
|
424
|
-
// 新建流式消息
|
|
425
|
-
streamingMessageEl = document.createElement('div');
|
|
426
|
-
streamingMessageEl.className = 'message message-ai message-streaming';
|
|
427
|
-
streamingTextNode = document.createTextNode('');
|
|
428
|
-
streamingMessageEl.appendChild(streamingTextNode);
|
|
429
|
-
streamingText = '';
|
|
430
|
-
// 2026-06-15: 流式期间也挂一个空 step-timeline 占位 — step_start/done/error 事件
|
|
431
|
-
// 走 handleStepEvent, getStepTimeline 找到这个流式元素内的 timeline 推入
|
|
432
|
-
mountStepTimeline(streamingMessageEl, currentChannelId);
|
|
433
|
-
container.appendChild(streamingMessageEl);
|
|
434
|
-
// 2026-07-21: 流式元素创建后立即回放缓冲的 step 事件, 让用户尽早看到 tool call 状态
|
|
435
|
-
// 必须先 appendChild 让元素 isConnected=true, 否则 flushStepEventBuffer → handleStepEvent
|
|
436
|
-
// 会因 streamingMessageEl.isConnected===false 而丢弃缓冲中的 step.
|
|
437
|
-
flushStepEventBuffer(currentChannelId, ctx);
|
|
438
|
-
// B-3: 首个 token 来时切状态到 streaming (蓝徽), 提示用户 panel 在工作
|
|
439
|
-
if (typeof ctx.setTimelineState === 'function') {
|
|
440
|
-
ctx.setTimelineState('streaming');
|
|
232
|
+
const actions = document.createElement("div");
|
|
233
|
+
actions.className = "message-actions";
|
|
234
|
+
const copyBtn = document.createElement("button");
|
|
235
|
+
copyBtn.className = "action-btn copy-btn";
|
|
236
|
+
copyBtn.innerHTML = copyIcon() + " \u590D\u5236";
|
|
237
|
+
copyBtn.title = "\u590D\u5236\u6D88\u606F";
|
|
238
|
+
copyBtn.onclick = () => {
|
|
239
|
+
navigator.clipboard.writeText(rawContent).then(() => {
|
|
240
|
+
copyBtn.innerHTML = checkIcon() + " \u5DF2\u590D\u5236";
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
copyBtn.innerHTML = copyIcon() + " \u590D\u5236";
|
|
243
|
+
}, 2e3);
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
actions.appendChild(copyBtn);
|
|
247
|
+
const saveJudgmentBtn = document.createElement("button");
|
|
248
|
+
saveJudgmentBtn.className = "action-btn save-as-judgment";
|
|
249
|
+
saveJudgmentBtn.title = "AI \u84B8\u998F\u4E3A 30-80 \u5B57\u5224\u65AD\u529B + \u81EA\u52A8\u6F14\u5316\u5BF9\u9F50";
|
|
250
|
+
saveJudgmentBtn.setAttribute("data-decision", rawContent.substring(0, 800));
|
|
251
|
+
if (ctx.currentChannelId) saveJudgmentBtn.setAttribute("data-channel-id", ctx.currentChannelId);
|
|
252
|
+
saveJudgmentBtn.innerHTML = shieldIcon() + " \u84B8\u998F\u4E3A\u5224\u65AD";
|
|
253
|
+
actions.appendChild(saveJudgmentBtn);
|
|
254
|
+
const regenBtn = document.createElement("button");
|
|
255
|
+
regenBtn.className = "action-btn regenerate-btn";
|
|
256
|
+
regenBtn.innerHTML = refreshIcon(false) + " \u91CD\u65B0\u56DE\u7B54";
|
|
257
|
+
regenBtn.title = "\u91CD\u65B0\u751F\u6210\u56DE\u590D";
|
|
258
|
+
regenBtn.onclick = () => {
|
|
259
|
+
regenBtn.innerHTML = refreshIcon(true) + " \u751F\u6210\u4E2D...";
|
|
260
|
+
regenBtn.disabled = true;
|
|
261
|
+
const messages = div.parentElement?.querySelectorAll(".message") || [];
|
|
262
|
+
let lastUserMsg = "";
|
|
263
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
264
|
+
const msg = messages[i];
|
|
265
|
+
if (msg.classList.contains("message-user")) {
|
|
266
|
+
const bubble = msg.querySelector(".bubble");
|
|
267
|
+
if (bubble) {
|
|
268
|
+
lastUserMsg = bubble.textContent || bubble.innerText || "";
|
|
269
|
+
break;
|
|
441
270
|
}
|
|
442
|
-
|
|
271
|
+
}
|
|
443
272
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
273
|
+
fetch("/regenerate", {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: { "Content-Type": "application/json" },
|
|
276
|
+
body: JSON.stringify({ channelId: ctx.currentChannelId, userMessage: lastUserMsg })
|
|
277
|
+
}).then((res) => {
|
|
278
|
+
if (!res.ok) throw new Error("regenerate failed");
|
|
279
|
+
}).catch((err) => {
|
|
280
|
+
console.error("\u91CD\u65B0\u751F\u6210\u5931\u8D25:", err);
|
|
281
|
+
regenBtn.innerHTML = refreshIcon(false) + " \u5931\u8D25";
|
|
282
|
+
setTimeout(() => {
|
|
283
|
+
regenBtn.innerHTML = refreshIcon(false) + " \u91CD\u65B0\u56DE\u7B54";
|
|
284
|
+
regenBtn.disabled = false;
|
|
285
|
+
}, 2e3);
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
actions.appendChild(regenBtn);
|
|
289
|
+
return actions;
|
|
290
|
+
}
|
|
291
|
+
function copyIcon() {
|
|
292
|
+
return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
|
|
293
|
+
}
|
|
294
|
+
function checkIcon() {
|
|
295
|
+
return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>';
|
|
296
|
+
}
|
|
297
|
+
function shieldIcon() {
|
|
298
|
+
return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L4 6v6c0 5 3.5 9.5 8 10 4.5-.5 8-5 8-10V6l-8-4z"></path><path d="M9 12l2 2 4-4"></path></svg>';
|
|
299
|
+
}
|
|
300
|
+
function refreshIcon(spin = false) {
|
|
301
|
+
const cls = spin ? ' class="spin"' : "";
|
|
302
|
+
return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"${cls}><path d="M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16"></path></svg>`;
|
|
303
|
+
}
|
|
304
|
+
function handleStreamTokenEvent(data, ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
|
|
305
|
+
const messagesEl = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
|
|
306
|
+
const messagesContainers = ctx.messagesContainers || /* @__PURE__ */ new Map();
|
|
307
|
+
const currentChannelId = ctx.currentChannelId;
|
|
308
|
+
const container = getMessagesContainerForCurrent(currentChannelId, messagesContainers, messagesEl);
|
|
309
|
+
if (!container) return;
|
|
310
|
+
const delta = data.content || "";
|
|
311
|
+
if (!delta) return;
|
|
312
|
+
if (!streamingMessageEl || !streamingMessageEl.isConnected) {
|
|
313
|
+
streamingMessageEl = document.createElement("div");
|
|
314
|
+
streamingMessageEl.className = "message message-ai message-streaming";
|
|
315
|
+
streamingTextNode = document.createTextNode("");
|
|
316
|
+
streamingMessageEl.appendChild(streamingTextNode);
|
|
317
|
+
streamingText = "";
|
|
318
|
+
mountStepTimeline(streamingMessageEl, currentChannelId);
|
|
319
|
+
container.appendChild(streamingMessageEl);
|
|
320
|
+
flushStepEventBuffer(currentChannelId, ctx);
|
|
321
|
+
if (typeof ctx.setTimelineState === "function") {
|
|
322
|
+
ctx.setTimelineState("streaming");
|
|
461
323
|
}
|
|
462
324
|
scheduleScrollToBottom(container);
|
|
325
|
+
}
|
|
326
|
+
if (data.streamType === "token") {
|
|
327
|
+
if (streamingTextNode) streamingTextNode.nodeValue = delta;
|
|
328
|
+
streamingText = delta;
|
|
329
|
+
} else {
|
|
330
|
+
if (streamingTextNode) streamingTextNode.appendData(delta);
|
|
331
|
+
streamingText += delta;
|
|
332
|
+
}
|
|
333
|
+
scheduleScrollToBottom(container);
|
|
463
334
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
// 2026-06-15: finalize 时把 streaming 内的 step-timeline 整体搬到新建的正式 AI message 内
|
|
474
|
-
// addMessage 会建一个新 timeline 占位, 先记下 streaming 的引用, addMessage 后再迁移
|
|
475
|
-
// 避免节点从 0 重渲 (10+ 步的任务, 重渲闪烁会很厉害)
|
|
476
|
-
const oldStreamingEl = streamingMessageEl;
|
|
477
|
-
if (oldStreamingEl && oldStreamingEl.parentNode) {
|
|
478
|
-
oldStreamingEl.parentNode.removeChild(oldStreamingEl);
|
|
479
|
-
}
|
|
480
|
-
addMessage(streamingText, 'ai', true, container, ctx.lastUsedJudgmentIds || [], ctx);
|
|
481
|
-
if (oldStreamingEl && container) {
|
|
482
|
-
// 找刚 addMessage 创建的最后一条 ai message
|
|
483
|
-
const newAiMsg = container.querySelector('.message-ai:last-of-type');
|
|
484
|
-
if (newAiMsg && newAiMsg !== oldStreamingEl) {
|
|
485
|
-
migrateStepTimeline(oldStreamingEl, newAiMsg);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
335
|
+
function finalizeTimelineAsMessage(ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
|
|
336
|
+
const messagesEl = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
|
|
337
|
+
const messagesContainers = ctx.messagesContainers || /* @__PURE__ */ new Map();
|
|
338
|
+
const currentChannelId = ctx.currentChannelId;
|
|
339
|
+
const container = getMessagesContainerForCurrent(currentChannelId, messagesContainers, messagesEl);
|
|
340
|
+
if (streamingText.trim().length > 0) {
|
|
341
|
+
const oldStreamingEl = streamingMessageEl;
|
|
342
|
+
if (oldStreamingEl && oldStreamingEl.parentNode) {
|
|
343
|
+
oldStreamingEl.parentNode.removeChild(oldStreamingEl);
|
|
488
344
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
ctx.setTimelineState('done');
|
|
345
|
+
addMessage(streamingText, "ai", true, container, ctx.lastUsedJudgmentIds || [], ctx);
|
|
346
|
+
if (oldStreamingEl && container) {
|
|
347
|
+
const newAiMsg = container.querySelector(".message-ai:last-of-type");
|
|
348
|
+
if (newAiMsg && newAiMsg !== oldStreamingEl) {
|
|
349
|
+
migrateStepTimeline(oldStreamingEl, newAiMsg);
|
|
350
|
+
}
|
|
496
351
|
}
|
|
352
|
+
}
|
|
353
|
+
streamingMessageEl = null;
|
|
354
|
+
streamingTextNode = null;
|
|
355
|
+
streamingText = "";
|
|
356
|
+
if (typeof ctx.setTimelineState === "function") {
|
|
357
|
+
ctx.setTimelineState("done");
|
|
358
|
+
}
|
|
497
359
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
// 2. 无流式元素 → 缓冲等待 stream token 或 addMessage 后回放
|
|
512
|
-
// (2026-07-21: 不再回退到 welcome 等旧 AI 消息, 避免 step 贴错 message)
|
|
513
|
-
if (!target) {
|
|
514
|
-
if (currentChannelId) {
|
|
515
|
-
const buf = stepEventBuffer.get(currentChannelId) || [];
|
|
516
|
-
buf.push(data);
|
|
517
|
-
stepEventBuffer.set(currentChannelId, buf);
|
|
518
|
-
}
|
|
519
|
-
return;
|
|
360
|
+
function handleStepEvent(data, ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
|
|
361
|
+
const messagesEl = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
|
|
362
|
+
const messagesContainers = ctx.messagesContainers || /* @__PURE__ */ new Map();
|
|
363
|
+
const currentChannelId = ctx.currentChannelId;
|
|
364
|
+
const container = getMessagesContainerForCurrent(currentChannelId, messagesContainers, messagesEl);
|
|
365
|
+
if (!container) return;
|
|
366
|
+
if (!data || !data.type) return;
|
|
367
|
+
let target = streamingMessageEl && streamingMessageEl.isConnected ? streamingMessageEl : null;
|
|
368
|
+
if (!target) {
|
|
369
|
+
if (currentChannelId) {
|
|
370
|
+
const buf = stepEventBuffer.get(currentChannelId) || [];
|
|
371
|
+
buf.push(data);
|
|
372
|
+
stepEventBuffer.set(currentChannelId, buf);
|
|
520
373
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (!target) return;
|
|
377
|
+
const timeline = getStepTimeline(target);
|
|
378
|
+
if (!timeline) return;
|
|
379
|
+
pushStepToTimeline(timeline, data.type, {
|
|
380
|
+
tool: data.tool || "unknown",
|
|
381
|
+
args: data.args,
|
|
382
|
+
success: data.success,
|
|
383
|
+
output: data.output,
|
|
384
|
+
error: data.error
|
|
385
|
+
});
|
|
533
386
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
for (const evt of buf) {
|
|
543
|
-
handleStepEvent(evt, ctx);
|
|
544
|
-
}
|
|
387
|
+
function flushStepEventBuffer(channelId, ctx) {
|
|
388
|
+
if (!channelId) return;
|
|
389
|
+
const buf = stepEventBuffer.get(channelId);
|
|
390
|
+
if (!buf || buf.length === 0) return;
|
|
391
|
+
stepEventBuffer.delete(channelId);
|
|
392
|
+
for (const evt of buf) {
|
|
393
|
+
handleStepEvent(evt, ctx);
|
|
394
|
+
}
|
|
545
395
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
clearTimeout(scrollToBottomTimer);
|
|
557
|
-
scrollToBottomTimer = null;
|
|
558
|
-
}
|
|
396
|
+
function resetRendererState() {
|
|
397
|
+
streamingMessageEl = null;
|
|
398
|
+
streamingTextNode = null;
|
|
399
|
+
streamingText = "";
|
|
400
|
+
lastUserCommand = "";
|
|
401
|
+
lastAiContent = "";
|
|
402
|
+
if (scrollToBottomTimer) {
|
|
403
|
+
clearTimeout(scrollToBottomTimer);
|
|
404
|
+
scrollToBottomTimer = null;
|
|
405
|
+
}
|
|
559
406
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
407
|
+
const MessageRenderer = {
|
|
408
|
+
addMessage,
|
|
409
|
+
handleStreamTokenEvent,
|
|
410
|
+
finalizeTimelineAsMessage,
|
|
411
|
+
handleStepEvent,
|
|
412
|
+
flushStepEventBuffer,
|
|
413
|
+
escapeHtml,
|
|
414
|
+
getMessagesContainerForCurrent,
|
|
415
|
+
resetRendererState
|
|
569
416
|
};
|
|
570
|
-
if (typeof window !==
|
|
571
|
-
|
|
417
|
+
if (typeof window !== "undefined") {
|
|
418
|
+
window.MR = MessageRenderer;
|
|
572
419
|
}
|
|
420
|
+
export {
|
|
421
|
+
MessageRenderer,
|
|
422
|
+
addMessage,
|
|
423
|
+
escapeHtml,
|
|
424
|
+
finalizeTimelineAsMessage,
|
|
425
|
+
flushStepEventBuffer,
|
|
426
|
+
getMessagesContainerForCurrent,
|
|
427
|
+
handleStepEvent,
|
|
428
|
+
handleStreamTokenEvent,
|
|
429
|
+
hasStreamingText,
|
|
430
|
+
injectRecoveredText,
|
|
431
|
+
replaceStreamingText,
|
|
432
|
+
resetRendererState
|
|
433
|
+
};
|