@bolloon/bolloon-agent 0.2.11 → 0.2.12
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/web/client.js +1655 -239
- package/dist/web/index.html +1 -0
- package/dist/web/style.css +5 -0
- package/package.json +1 -1
- package/scripts/build-web.ts +5 -0
package/dist/web/client.js
CHANGED
|
@@ -1,6 +1,1362 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
(() => {
|
|
3
|
-
var
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
var __esm = (fn, res) => function __init() {
|
|
14
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
15
|
+
};
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/web/ui/step-timeline.ts
|
|
31
|
+
function readExpanded(channelId) {
|
|
32
|
+
if (!channelId) return false;
|
|
33
|
+
try {
|
|
34
|
+
return localStorage.getItem(STORAGE_PREFIX + channelId) === "1";
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function writeExpanded(channelId, expanded) {
|
|
40
|
+
if (!channelId) return;
|
|
41
|
+
try {
|
|
42
|
+
localStorage.setItem(STORAGE_PREFIX + channelId, expanded ? "1" : "0");
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function computeTitle(steps) {
|
|
47
|
+
if (steps.length === 0) return "\u6267\u884C\u6B65\u9AA4";
|
|
48
|
+
const activeIdx = steps.findIndex((s) => s.status === "active");
|
|
49
|
+
if (activeIdx >= 0) return `\u25CF \u6267\u884C\u4E2D \xB7 ${steps[activeIdx].name}`;
|
|
50
|
+
const lastErrorIdx = (() => {
|
|
51
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
52
|
+
if (steps[i].status === "error") return i;
|
|
53
|
+
}
|
|
54
|
+
return -1;
|
|
55
|
+
})();
|
|
56
|
+
if (lastErrorIdx >= 0) return `\u2717 \u5931\u8D25 \xB7 ${steps[lastErrorIdx].name}`;
|
|
57
|
+
return `\u2713 \u5DF2\u5B8C\u6210 \xB7 ${steps.length} \u6B65`;
|
|
58
|
+
}
|
|
59
|
+
function render(timelineEl) {
|
|
60
|
+
const state = stateMap.get(timelineEl);
|
|
61
|
+
if (!state) return;
|
|
62
|
+
const { steps, expanded, showAll } = state;
|
|
63
|
+
const titleEl = timelineEl.querySelector("[data-current-tool]");
|
|
64
|
+
if (titleEl) titleEl.textContent = computeTitle(steps);
|
|
65
|
+
const dotsEl = timelineEl.querySelector("[data-dots]");
|
|
66
|
+
if (dotsEl) {
|
|
67
|
+
const existing = Array.from(dotsEl.children);
|
|
68
|
+
for (let i = 0; i < steps.length; i++) {
|
|
69
|
+
const step = steps[i];
|
|
70
|
+
let dot = existing[i];
|
|
71
|
+
if (!dot) {
|
|
72
|
+
dot = document.createElement("span");
|
|
73
|
+
dot.className = "step-dot";
|
|
74
|
+
dot.setAttribute("data-index", String(i));
|
|
75
|
+
dotsEl.appendChild(dot);
|
|
76
|
+
}
|
|
77
|
+
dot.setAttribute("data-status", step.status);
|
|
78
|
+
dot.setAttribute("title", `${step.name} \xB7 ${step.status}`);
|
|
79
|
+
}
|
|
80
|
+
while (dotsEl.children.length > steps.length) {
|
|
81
|
+
dotsEl.removeChild(dotsEl.lastChild);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const listEl = timelineEl.querySelector("[data-list]");
|
|
85
|
+
if (listEl) {
|
|
86
|
+
const hiddenCount = Math.max(0, steps.length - MAX_VISIBLE_NODES);
|
|
87
|
+
const visibleSteps = showAll ? steps : steps.slice(-MAX_VISIBLE_NODES);
|
|
88
|
+
const existing = Array.from(listEl.children);
|
|
89
|
+
let htmlIdx = 0;
|
|
90
|
+
if (!showAll && hiddenCount > 0) {
|
|
91
|
+
let moreBtn = existing[htmlIdx];
|
|
92
|
+
if (!moreBtn || !moreBtn.classList.contains("step-timeline-more")) {
|
|
93
|
+
moreBtn = document.createElement("li");
|
|
94
|
+
moreBtn.className = "step-timeline-more";
|
|
95
|
+
moreBtn.setAttribute("role", "button");
|
|
96
|
+
moreBtn.textContent = `+ ${hiddenCount} \u66F4\u591A`;
|
|
97
|
+
moreBtn.onclick = () => {
|
|
98
|
+
state.showAll = true;
|
|
99
|
+
render(timelineEl);
|
|
100
|
+
};
|
|
101
|
+
if (existing[htmlIdx] && existing[htmlIdx] !== moreBtn) {
|
|
102
|
+
listEl.replaceChild(moreBtn, existing[htmlIdx]);
|
|
103
|
+
} else {
|
|
104
|
+
listEl.appendChild(moreBtn);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
moreBtn.textContent = `+ ${hiddenCount} \u66F4\u591A`;
|
|
108
|
+
}
|
|
109
|
+
htmlIdx++;
|
|
110
|
+
}
|
|
111
|
+
for (let i = 0; i < visibleSteps.length; i++) {
|
|
112
|
+
const step = visibleSteps[i];
|
|
113
|
+
const realIndex = showAll ? i : steps.length - visibleSteps.length + i;
|
|
114
|
+
let node = existing[htmlIdx];
|
|
115
|
+
if (!node || !node.classList.contains("step-timeline-node")) {
|
|
116
|
+
node = document.createElement("li");
|
|
117
|
+
node.className = "step-timeline-node";
|
|
118
|
+
node.setAttribute("data-index", String(realIndex));
|
|
119
|
+
const marker = document.createElement("span");
|
|
120
|
+
marker.className = "step-timeline-marker";
|
|
121
|
+
const label2 = document.createElement("span");
|
|
122
|
+
label2.className = "step-timeline-label";
|
|
123
|
+
const args = document.createElement("span");
|
|
124
|
+
args.className = "step-timeline-args";
|
|
125
|
+
node.appendChild(marker);
|
|
126
|
+
node.appendChild(label2);
|
|
127
|
+
node.appendChild(args);
|
|
128
|
+
if (existing[htmlIdx] && existing[htmlIdx] !== node) {
|
|
129
|
+
listEl.replaceChild(node, existing[htmlIdx]);
|
|
130
|
+
} else {
|
|
131
|
+
listEl.appendChild(node);
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
node.setAttribute("data-index", String(realIndex));
|
|
135
|
+
}
|
|
136
|
+
node.setAttribute("data-status", step.status);
|
|
137
|
+
const label = node.querySelector(".step-timeline-label");
|
|
138
|
+
if (label) label.textContent = step.name;
|
|
139
|
+
const argsEl = node.querySelector(".step-timeline-args");
|
|
140
|
+
if (argsEl) {
|
|
141
|
+
const argStr = step.args && Object.keys(step.args).length > 0 ? JSON.stringify(step.args).slice(0, 60) : "";
|
|
142
|
+
argsEl.textContent = argStr;
|
|
143
|
+
argsEl.style.display = argStr ? "" : "none";
|
|
144
|
+
}
|
|
145
|
+
htmlIdx++;
|
|
146
|
+
}
|
|
147
|
+
while (listEl.children.length > htmlIdx) {
|
|
148
|
+
listEl.removeChild(listEl.lastChild);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (steps.length === 0) {
|
|
152
|
+
timelineEl.setAttribute("data-empty", "true");
|
|
153
|
+
} else {
|
|
154
|
+
timelineEl.removeAttribute("data-empty");
|
|
155
|
+
}
|
|
156
|
+
const body = timelineEl.querySelector("[data-body]");
|
|
157
|
+
if (body) {
|
|
158
|
+
if (expanded) {
|
|
159
|
+
body.style.maxHeight = body.scrollHeight + "px";
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
if (state.expanded) body.style.maxHeight = "";
|
|
162
|
+
}, 300);
|
|
163
|
+
} else {
|
|
164
|
+
body.style.maxHeight = "0";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const arrow = timelineEl.querySelector(".step-timeline-arrow");
|
|
168
|
+
if (arrow) arrow.style.transform = expanded ? "rotate(180deg)" : "rotate(0deg)";
|
|
169
|
+
if (expanded && listEl) {
|
|
170
|
+
listEl.scrollTop = listEl.scrollHeight;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function createEmptyStepTimeline(channelId = null) {
|
|
174
|
+
const root = document.createElement("div");
|
|
175
|
+
root.className = "step-timeline";
|
|
176
|
+
root.setAttribute("data-step-timeline", "");
|
|
177
|
+
root.setAttribute("data-empty", "true");
|
|
178
|
+
const summary = document.createElement("div");
|
|
179
|
+
summary.className = "step-timeline-summary";
|
|
180
|
+
summary.setAttribute("data-summary", "");
|
|
181
|
+
const titleSpan = document.createElement("span");
|
|
182
|
+
titleSpan.className = "step-timeline-title";
|
|
183
|
+
const titleContent = document.createElement("span");
|
|
184
|
+
titleContent.setAttribute("data-current-tool", "");
|
|
185
|
+
titleContent.textContent = "\u6267\u884C\u6B65\u9AA4";
|
|
186
|
+
titleSpan.appendChild(titleContent);
|
|
187
|
+
const dots = document.createElement("div");
|
|
188
|
+
dots.className = "step-timeline-dots";
|
|
189
|
+
dots.setAttribute("data-dots", "");
|
|
190
|
+
const arrow = document.createElement("span");
|
|
191
|
+
arrow.className = "step-timeline-arrow";
|
|
192
|
+
arrow.textContent = "\u25BE";
|
|
193
|
+
summary.appendChild(titleSpan);
|
|
194
|
+
summary.appendChild(dots);
|
|
195
|
+
summary.appendChild(arrow);
|
|
196
|
+
summary.onclick = () => {
|
|
197
|
+
const state = stateMap.get(root);
|
|
198
|
+
if (!state) return;
|
|
199
|
+
state.expanded = !state.expanded;
|
|
200
|
+
writeExpanded(state.channelId, state.expanded);
|
|
201
|
+
render(root);
|
|
202
|
+
};
|
|
203
|
+
const body = document.createElement("div");
|
|
204
|
+
body.className = "step-timeline-body";
|
|
205
|
+
body.setAttribute("data-body", "");
|
|
206
|
+
const list = document.createElement("ul");
|
|
207
|
+
list.className = "step-timeline-list";
|
|
208
|
+
list.setAttribute("data-list", "");
|
|
209
|
+
body.appendChild(list);
|
|
210
|
+
root.appendChild(summary);
|
|
211
|
+
root.appendChild(body);
|
|
212
|
+
const initialExpanded = readExpanded(channelId);
|
|
213
|
+
stateMap.set(root, {
|
|
214
|
+
steps: [],
|
|
215
|
+
currentIndex: -1,
|
|
216
|
+
expanded: initialExpanded,
|
|
217
|
+
channelId: channelId || null,
|
|
218
|
+
showAll: false
|
|
219
|
+
});
|
|
220
|
+
render(root);
|
|
221
|
+
return root;
|
|
222
|
+
}
|
|
223
|
+
function mountStepTimeline(messageEl, channelId) {
|
|
224
|
+
const existing = messageEl.querySelector("[data-step-timeline]");
|
|
225
|
+
if (existing) {
|
|
226
|
+
const state = stateMap.get(existing);
|
|
227
|
+
if (state && channelId && !state.channelId) state.channelId = channelId;
|
|
228
|
+
return existing;
|
|
229
|
+
}
|
|
230
|
+
const bubble = messageEl.querySelector(".bubble");
|
|
231
|
+
const actions = messageEl.querySelector(".message-actions");
|
|
232
|
+
const timeline = createEmptyStepTimeline(channelId);
|
|
233
|
+
if (actions && actions.parentNode === messageEl) {
|
|
234
|
+
messageEl.insertBefore(timeline, actions);
|
|
235
|
+
} else if (bubble && bubble.parentNode === messageEl) {
|
|
236
|
+
bubble.parentNode.insertBefore(timeline, bubble.nextSibling);
|
|
237
|
+
} else {
|
|
238
|
+
messageEl.appendChild(timeline);
|
|
239
|
+
}
|
|
240
|
+
return timeline;
|
|
241
|
+
}
|
|
242
|
+
function pushStepToTimeline(timelineEl, eventType, data) {
|
|
243
|
+
const state = stateMap.get(timelineEl);
|
|
244
|
+
if (!state) return;
|
|
245
|
+
const { steps } = state;
|
|
246
|
+
if (eventType === "step_start") {
|
|
247
|
+
state.currentIndex = steps.length;
|
|
248
|
+
steps.push({
|
|
249
|
+
name: data.tool,
|
|
250
|
+
status: "active",
|
|
251
|
+
args: data.args,
|
|
252
|
+
eventType
|
|
253
|
+
});
|
|
254
|
+
} else {
|
|
255
|
+
let targetIdx = state.currentIndex;
|
|
256
|
+
if (targetIdx < 0 || !steps[targetIdx] || steps[targetIdx].name !== data.tool) {
|
|
257
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
258
|
+
if (steps[i].name === data.tool && steps[i].status === "active") {
|
|
259
|
+
targetIdx = i;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (targetIdx < 0) {
|
|
265
|
+
targetIdx = steps.length;
|
|
266
|
+
steps.push({ name: data.tool, status: "error", eventType });
|
|
267
|
+
}
|
|
268
|
+
if (eventType === "step_done") {
|
|
269
|
+
steps[targetIdx].status = data.success === false ? "error" : "done";
|
|
270
|
+
if (data.output) steps[targetIdx].output = data.output;
|
|
271
|
+
if (data.error) steps[targetIdx].error = data.error;
|
|
272
|
+
} else {
|
|
273
|
+
steps[targetIdx].status = "error";
|
|
274
|
+
if (data.error) steps[targetIdx].error = data.error;
|
|
275
|
+
}
|
|
276
|
+
steps[targetIdx].eventType = eventType;
|
|
277
|
+
state.currentIndex = -1;
|
|
278
|
+
}
|
|
279
|
+
render(timelineEl);
|
|
280
|
+
}
|
|
281
|
+
function migrateStepTimeline(fromEl, toEl) {
|
|
282
|
+
const fromTimeline = fromEl.querySelector("[data-step-timeline]");
|
|
283
|
+
const toTimeline = toEl.querySelector("[data-step-timeline]");
|
|
284
|
+
if (!fromTimeline) return;
|
|
285
|
+
if (toTimeline && toTimeline !== fromTimeline) {
|
|
286
|
+
toTimeline.remove();
|
|
287
|
+
}
|
|
288
|
+
if (toTimeline !== fromTimeline) {
|
|
289
|
+
toEl.appendChild(fromTimeline);
|
|
290
|
+
}
|
|
291
|
+
const state = stateMap.get(fromTimeline);
|
|
292
|
+
if (state) {
|
|
293
|
+
render(fromTimeline);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function getStepTimeline(messageEl) {
|
|
297
|
+
if (!messageEl) return null;
|
|
298
|
+
return messageEl.querySelector("[data-step-timeline]");
|
|
299
|
+
}
|
|
300
|
+
var MAX_VISIBLE_NODES, STORAGE_PREFIX, stateMap;
|
|
301
|
+
var init_step_timeline = __esm({
|
|
302
|
+
"src/web/ui/step-timeline.ts"() {
|
|
303
|
+
"use strict";
|
|
304
|
+
MAX_VISIBLE_NODES = 8;
|
|
305
|
+
STORAGE_PREFIX = "bolloon.stepTimeline.expanded.";
|
|
306
|
+
stateMap = /* @__PURE__ */ new WeakMap();
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// src/agents/parse-tool-call.ts
|
|
311
|
+
function defaultResolveAlias(name, tools) {
|
|
312
|
+
if (tools.has(name)) return name;
|
|
313
|
+
const lower = name.toLowerCase();
|
|
314
|
+
const aliasMap = {
|
|
315
|
+
read: "read_file",
|
|
316
|
+
edit: "edit_file",
|
|
317
|
+
write: "write_file",
|
|
318
|
+
rm: "delete_file",
|
|
319
|
+
mv: "move_file",
|
|
320
|
+
bash: "shell_exec",
|
|
321
|
+
shell: "shell_exec",
|
|
322
|
+
sh: "shell_exec",
|
|
323
|
+
cat: "read_file",
|
|
324
|
+
test: "vitest_run",
|
|
325
|
+
vitest: "vitest_run",
|
|
326
|
+
typecheck: "tsc_check",
|
|
327
|
+
tsc: "tsc_check",
|
|
328
|
+
log: "git_log",
|
|
329
|
+
show: "git_show",
|
|
330
|
+
diff: "git_diff",
|
|
331
|
+
commit: "git_commit",
|
|
332
|
+
push: "git_push",
|
|
333
|
+
branch: "git_branch",
|
|
334
|
+
checkout: "git_branch",
|
|
335
|
+
stash: "git_stash",
|
|
336
|
+
todo_write: "create_task",
|
|
337
|
+
todowrite: "create_task",
|
|
338
|
+
task: "create_task"
|
|
339
|
+
};
|
|
340
|
+
const aliased = aliasMap[lower];
|
|
341
|
+
if (aliased && tools.has(aliased)) return aliased;
|
|
342
|
+
if (tools.has(lower)) return lower;
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
function autoSplitCommand(args) {
|
|
346
|
+
if (typeof args.command === "string" && args.command.includes(" ") && !args.args) {
|
|
347
|
+
const parts = args.command.split(/\s+/);
|
|
348
|
+
args.command = parts[0];
|
|
349
|
+
args.args = parts.slice(1).join(" ");
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function resolve(ctx, name) {
|
|
353
|
+
if (ctx.resolveAlias) return ctx.resolveAlias(name);
|
|
354
|
+
return defaultResolveAlias(name, ctx.tools);
|
|
355
|
+
}
|
|
356
|
+
function parseToolCall(content, ctx) {
|
|
357
|
+
if (!content) return null;
|
|
358
|
+
const strippedContent = content.replace(/<think[\s\S]*?<\/think/g, "");
|
|
359
|
+
const jsonPatterns = [
|
|
360
|
+
// markdown json code block + OpenAI 块, 同时匹配 arguments/input 字段
|
|
361
|
+
/(?:```(?:json|json5)?\s*\n?)?\{[\s\S]*?"name"\s*:\s*["'](\w+)["']\s*,\s*["']?(?:arguments|input)["']?\s*:\s*(\{[\s\S]*?\})\s*\}/
|
|
362
|
+
];
|
|
363
|
+
for (const p of jsonPatterns) {
|
|
364
|
+
const m = content.match(p);
|
|
365
|
+
if (m) {
|
|
366
|
+
const name = m[1];
|
|
367
|
+
let args = {};
|
|
368
|
+
try {
|
|
369
|
+
const parsed = JSON.parse(m[2]);
|
|
370
|
+
if (parsed && typeof parsed === "object") {
|
|
371
|
+
args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
|
|
372
|
+
}
|
|
373
|
+
} catch {
|
|
374
|
+
}
|
|
375
|
+
autoSplitCommand(args);
|
|
376
|
+
const resolved = resolve(ctx, name);
|
|
377
|
+
if (resolved) {
|
|
378
|
+
return { name: resolved, args };
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const toolsCallMatch = content.match(/<tools:call\s+name=["'](\w+)["']>([\s\S]*?)<\/tools:call>/);
|
|
383
|
+
if (toolsCallMatch) {
|
|
384
|
+
const name = toolsCallMatch[1];
|
|
385
|
+
const inner = toolsCallMatch[2];
|
|
386
|
+
const args = {};
|
|
387
|
+
const argTags = inner.matchAll(/<tools:call\s+name=["'](\w+)["']>([\s\S]*?)<\/tools:call>/g);
|
|
388
|
+
for (const m of argTags) {
|
|
389
|
+
args[m[1]] = m[2].trim();
|
|
390
|
+
}
|
|
391
|
+
const resolved = resolve(ctx, name);
|
|
392
|
+
if (resolved) {
|
|
393
|
+
return { name: resolved, args };
|
|
394
|
+
}
|
|
395
|
+
if (args.command) {
|
|
396
|
+
const cmdFirst = args.command.split(/\s+/)[0];
|
|
397
|
+
if (SHELL_KEYWORDS.includes(cmdFirst)) {
|
|
398
|
+
return { name: "shell_exec", args };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const patterns = [
|
|
403
|
+
// minimax/Hermes 自闭合 XML 格式 <invoke name="X">...</invoke>
|
|
404
|
+
new RegExp(`<invoke\\s+name=["']([\\w]+)["']>([\\s\\S]*?)</invoke>`),
|
|
405
|
+
// <function_calls> 包裹
|
|
406
|
+
new RegExp(`<function_calls>[\\s\\S]*?<invoke\\s+name=["']([\\w]+)["']>([\\s\\S]*?)</invoke>[\\s\\S]*?</function_calls>`),
|
|
407
|
+
// <function_calls><tool name="X"><param name="Y">value</param></tool></function_calls>
|
|
408
|
+
new RegExp(`<function_calls>[\\s\\S]*?<tool\\s+name=["']([\\w]+)["']>([\\s\\S]*?)</tool>[\\s\\S]*?</function_calls>`),
|
|
409
|
+
/调用工具[::]\s*(\w+)\s*\(([^)]*)\)/,
|
|
410
|
+
/使用工具[::]\s*(\w+)\s*\(([^)]*)\)/,
|
|
411
|
+
/tool[_\w]*[::]\s*(\w+)\s*\(([^)]*)\)/i,
|
|
412
|
+
/(\w+)\s*\(\s*([^)]*)\s*\)/,
|
|
413
|
+
// 对象字面量格式 {tool => "get_identity", args => {...}}
|
|
414
|
+
/\{\s*tool\s*=>\s*["'](\w+)["']\s*(?:,\s*args\s*=>\s*(\{[\s\S]*?\}))?\s*\}/,
|
|
415
|
+
// tool => "get_identity" (无 args 包裹)
|
|
416
|
+
/\btool\s*=>\s*["'](\w+)["']/,
|
|
417
|
+
// [TOOL_CALL] 块内 JSON 形式 {"name": "x", "args": {...}}
|
|
418
|
+
/\[TOOL_CALL\][\s\S]*?\{\s*"name"\s*:\s*"(\w+)"\s*,\s*"args"\s*:\s*(\{[\s\S]*?\})/i,
|
|
419
|
+
// "tool_name {json_args}" 形式 (单行, 无 name 字段)
|
|
420
|
+
/(?:^|\n)(\w+)\s+(\{[\s\S]*?\})(?=\n|$)/,
|
|
421
|
+
// XML 格式 <tool_name>...<arg>value</arg>...</tool_name>
|
|
422
|
+
/<(\w+)>([\s\S]*?)<\/\1>/
|
|
423
|
+
];
|
|
424
|
+
for (const pattern of patterns) {
|
|
425
|
+
const match = strippedContent.match(pattern);
|
|
426
|
+
if (!match) continue;
|
|
427
|
+
const name = match[1];
|
|
428
|
+
let args = {};
|
|
429
|
+
const rawArgs = match[2] || "";
|
|
430
|
+
if (rawArgs && rawArgs.trim().startsWith("{")) {
|
|
431
|
+
try {
|
|
432
|
+
const parsed = JSON.parse(rawArgs);
|
|
433
|
+
if (parsed && typeof parsed === "object") {
|
|
434
|
+
args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
const argPairs = rawArgs.split(",").map((s) => s.trim()).filter(Boolean);
|
|
438
|
+
for (const pair of argPairs) {
|
|
439
|
+
const [key, ...valueParts] = pair.split(":").map((s) => s.trim().replace(/['"]/g, ""));
|
|
440
|
+
if (key) args[key] = valueParts.join(":") || "";
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
} else if (rawArgs && /<[\w]/.test(rawArgs) && /<\/\w+>/.test(rawArgs)) {
|
|
444
|
+
const paramRe = /<parameter\s+name=["'](\w+)["']>([\s\S]*?)<\/parameter>/g;
|
|
445
|
+
const paramReShort = /<param\s+name=["'](\w+)["']>([\s\S]*?)<\/param>/g;
|
|
446
|
+
let pMatch;
|
|
447
|
+
while ((pMatch = paramRe.exec(rawArgs)) !== null) {
|
|
448
|
+
const argName = pMatch[1];
|
|
449
|
+
const argValue = pMatch[2].trim();
|
|
450
|
+
if (argName && argValue) {
|
|
451
|
+
args[argName] = argValue;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (Object.keys(args).length === 0) {
|
|
455
|
+
let sMatch;
|
|
456
|
+
paramReShort.lastIndex = 0;
|
|
457
|
+
while ((sMatch = paramReShort.exec(rawArgs)) !== null) {
|
|
458
|
+
const argName = sMatch[1];
|
|
459
|
+
const argValue = sMatch[2].trim().replace(/^["']|["']$/g, "");
|
|
460
|
+
if (argName && argValue) {
|
|
461
|
+
args[argName] = argValue;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (Object.keys(args).length === 0) {
|
|
466
|
+
const xmlArgPattern = /<(\w+)>([\s\S]*?)<\/\1>/g;
|
|
467
|
+
let xmlMatch;
|
|
468
|
+
while ((xmlMatch = xmlArgPattern.exec(rawArgs)) !== null) {
|
|
469
|
+
const argName = xmlMatch[1];
|
|
470
|
+
const argValue = xmlMatch[2].trim();
|
|
471
|
+
if (argName && argValue) {
|
|
472
|
+
args[argName] = argValue;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} else if (rawArgs) {
|
|
477
|
+
const argPairs = rawArgs.split(",").map((s) => s.trim()).filter(Boolean);
|
|
478
|
+
for (const pair of argPairs) {
|
|
479
|
+
const [key, ...valueParts] = pair.split(":").map((s) => s.trim().replace(/['"]/g, ""));
|
|
480
|
+
if (key) args[key] = valueParts.join(":") || "";
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const resolved = ctx.tools.has(name) ? name : resolve(ctx, name);
|
|
484
|
+
if (resolved) {
|
|
485
|
+
autoSplitCommand(args);
|
|
486
|
+
return { name: resolved, args };
|
|
487
|
+
}
|
|
488
|
+
if (rawArgs && /<\w+>[\s\S]*<\/\w+>/.test(strippedContent)) {
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const xmlTagMatch = strippedContent.match(/<(\w+)>([\s\S]*?)<\/\1>/);
|
|
493
|
+
if (xmlTagMatch) {
|
|
494
|
+
const outerTag = xmlTagMatch[1];
|
|
495
|
+
const inner = xmlTagMatch[2];
|
|
496
|
+
if (!resolve(ctx, outerTag)) {
|
|
497
|
+
const cmdMatch = inner.match(/<command>([\s\S]*?)<\/command>/);
|
|
498
|
+
if (cmdMatch) {
|
|
499
|
+
const cmd = cmdMatch[1].trim();
|
|
500
|
+
const cmdFirst = cmd.split(/\s+/)[0];
|
|
501
|
+
if (SHELL_KEYWORDS.includes(cmdFirst)) {
|
|
502
|
+
const args = {};
|
|
503
|
+
const remaining = cmd.slice(cmdFirst.length).trim();
|
|
504
|
+
if (remaining) {
|
|
505
|
+
args.command = cmdFirst;
|
|
506
|
+
args.args = remaining;
|
|
507
|
+
} else {
|
|
508
|
+
args.command = cmd;
|
|
509
|
+
}
|
|
510
|
+
const argsM = inner.match(/<args>([\s\S]*?)<\/args>/);
|
|
511
|
+
if (argsM && argsM[1].trim()) {
|
|
512
|
+
args.args = argsM[1].trim();
|
|
513
|
+
}
|
|
514
|
+
return { name: "shell_exec", args };
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
var SHELL_KEYWORDS;
|
|
522
|
+
var init_parse_tool_call = __esm({
|
|
523
|
+
"src/agents/parse-tool-call.ts"() {
|
|
524
|
+
"use strict";
|
|
525
|
+
SHELL_KEYWORDS = ["git", "npx", "npm", "tsx", "tsc", "vitest", "node", "mkdir", "touch", "ls", "echo", "cat", "head", "tail", "wc", "pwd", "date"];
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// src/agents/chat-segmenter.ts
|
|
530
|
+
function segmentChatReply(reply, ctx) {
|
|
531
|
+
if (!reply) return [];
|
|
532
|
+
const segments = [];
|
|
533
|
+
let remaining = reply;
|
|
534
|
+
if (!remaining.startsWith("<think>")) {
|
|
535
|
+
remaining = extractLeadingThinking(remaining, segments);
|
|
536
|
+
}
|
|
537
|
+
remaining = extractAndPush("think", remaining, segments, /<think>([\s\S]*?)<\/think>/g);
|
|
538
|
+
remaining = extractAndPush("env_details", remaining, segments, /<environment_details>([\s\S]*?)<\/environment_details>/g);
|
|
539
|
+
remaining = stripToolCallMarkers(remaining, segments, ctx);
|
|
540
|
+
if (remaining.trim()) {
|
|
541
|
+
remaining = filterFillerText(remaining);
|
|
542
|
+
}
|
|
543
|
+
const finalIdx = remaining.indexOf("<final gen>");
|
|
544
|
+
let beforeFinalText = remaining;
|
|
545
|
+
if (finalIdx >= 0) {
|
|
546
|
+
beforeFinalText = remaining.substring(0, finalIdx);
|
|
547
|
+
}
|
|
548
|
+
const textContent = beforeFinalText.trim();
|
|
549
|
+
if (textContent) {
|
|
550
|
+
segments.push({ type: "text", content: textContent });
|
|
551
|
+
}
|
|
552
|
+
if (finalIdx >= 0) {
|
|
553
|
+
const afterFinal = remaining.substring(finalIdx + "<final gen>".length).trim();
|
|
554
|
+
if (afterFinal) {
|
|
555
|
+
segments.push({ type: "final", content: afterFinal });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return segments;
|
|
559
|
+
}
|
|
560
|
+
function extractAndPush(type, text, out, re) {
|
|
561
|
+
const matches = [];
|
|
562
|
+
const localRe = new RegExp(re.source, re.flags);
|
|
563
|
+
let m;
|
|
564
|
+
while ((m = localRe.exec(text)) !== null) {
|
|
565
|
+
const content = m[1] ? m[1].trim() : "";
|
|
566
|
+
matches.push({
|
|
567
|
+
start: m.index,
|
|
568
|
+
end: m.index + m[0].length,
|
|
569
|
+
content
|
|
570
|
+
});
|
|
571
|
+
if (m[0].length === 0) localRe.lastIndex++;
|
|
572
|
+
}
|
|
573
|
+
if (matches.length === 0) return text;
|
|
574
|
+
for (const m2 of matches) {
|
|
575
|
+
if (m2.content) out.push({ type, content: m2.content });
|
|
576
|
+
}
|
|
577
|
+
let next = "";
|
|
578
|
+
let cursor = 0;
|
|
579
|
+
for (const m2 of matches) {
|
|
580
|
+
next += text.substring(cursor, m2.start);
|
|
581
|
+
cursor = m2.end;
|
|
582
|
+
}
|
|
583
|
+
next += text.substring(cursor);
|
|
584
|
+
return next;
|
|
585
|
+
}
|
|
586
|
+
function extractLeadingThinking(text, out) {
|
|
587
|
+
const firstLineEnd = text.indexOf("\n");
|
|
588
|
+
const firstLine = firstLineEnd === -1 ? text : text.substring(0, firstLineEnd);
|
|
589
|
+
const rest = firstLineEnd === -1 ? "" : text.substring(firstLineEnd + 1);
|
|
590
|
+
const thinkingStartRe = /^(让我|我先|我应该|先来|先|接下来|好的[,,]?\s*我|让我先|先看看|让我看看|思考|考虑)/;
|
|
591
|
+
const enThinkingStartRe = /^(Let me|I'll|I will|First,|Next,|Now,|So,|Alright[,.]\s+(?:let me|I'll|I will))/i;
|
|
592
|
+
if (firstLine.trim().length === 0) {
|
|
593
|
+
return text;
|
|
594
|
+
}
|
|
595
|
+
if (firstLine.length <= 120 && (thinkingStartRe.test(firstLine) || enThinkingStartRe.test(firstLine))) {
|
|
596
|
+
out.push({ type: "think", content: firstLine.trim() });
|
|
597
|
+
return rest;
|
|
598
|
+
}
|
|
599
|
+
if (firstLine.length <= 80 && (thinkingStartRe.test(firstLine) || enThinkingStartRe.test(firstLine))) {
|
|
600
|
+
out.push({ type: "think", content: firstLine.trim() });
|
|
601
|
+
return rest;
|
|
602
|
+
}
|
|
603
|
+
return text;
|
|
604
|
+
}
|
|
605
|
+
function filterFillerText(text) {
|
|
606
|
+
const lines = text.split("\n");
|
|
607
|
+
const filtered = [];
|
|
608
|
+
for (const line of lines) {
|
|
609
|
+
const t = line.trim();
|
|
610
|
+
if (!t) continue;
|
|
611
|
+
if (/^(好|好了|好的|完成|完成了|任务完成|可以|可以了|答完了|说完了|就这样|完了|done|ok|OK|Okay|okay|alright|fine|let me check|我来)\.?$/i.test(t)) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
filtered.push(line);
|
|
615
|
+
}
|
|
616
|
+
return filtered.join("\n").trim();
|
|
617
|
+
}
|
|
618
|
+
function stripToolCallMarkers(text, out, ctx) {
|
|
619
|
+
let result = text.replace(/\[TOOL_CALL\][\s\S]*?\[\/TOOL_CALL\]/g, (m) => {
|
|
620
|
+
const parsed = parseToolCall(m, { tools: ctx.knownToolNames });
|
|
621
|
+
if (parsed) out.push({ type: "tool_call", tool: { name: parsed.name, args: parsed.args } });
|
|
622
|
+
return "";
|
|
623
|
+
});
|
|
624
|
+
result = result.replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, (m) => {
|
|
625
|
+
const parsed = parseToolCall(m, { tools: ctx.knownToolNames });
|
|
626
|
+
if (parsed) out.push({ type: "tool_call", tool: { name: parsed.name, args: parsed.args } });
|
|
627
|
+
return "";
|
|
628
|
+
});
|
|
629
|
+
result = result.replace(/<invoke\s+name=["']([\w]+)["']>([\s\S]*?)<\/invoke>/g, (_m, name, inner) => {
|
|
630
|
+
if (ctx.knownToolNames.has(name)) {
|
|
631
|
+
const args = extractSimpleArgs(inner);
|
|
632
|
+
out.push({ type: "tool_call", tool: { name, args } });
|
|
633
|
+
}
|
|
634
|
+
return "";
|
|
635
|
+
});
|
|
636
|
+
result = result.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, (m) => {
|
|
637
|
+
const parsed = parseToolCall(m, { tools: ctx.knownToolNames });
|
|
638
|
+
if (parsed) out.push({ type: "tool_call", tool: { name: parsed.name, args: parsed.args } });
|
|
639
|
+
return "";
|
|
640
|
+
});
|
|
641
|
+
result = result.replace(
|
|
642
|
+
/\{\s*"name"\s*:\s*"([\w]+)"\s*,\s*"(?:arguments|input|args|params)"\s*:\s*(\{[\s\S]*?\})\s*\}/g,
|
|
643
|
+
(_m, name, argsJson) => {
|
|
644
|
+
if (!ctx.knownToolNames.has(name)) return "";
|
|
645
|
+
let args = {};
|
|
646
|
+
try {
|
|
647
|
+
const parsed = JSON.parse(argsJson);
|
|
648
|
+
if (parsed && typeof parsed === "object") {
|
|
649
|
+
args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
|
|
650
|
+
}
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
653
|
+
out.push({ type: "tool_call", tool: { name, args } });
|
|
654
|
+
return "";
|
|
655
|
+
}
|
|
656
|
+
);
|
|
657
|
+
result = result.replace(/\{\s*tool\s*=>\s*["']([\w]+)["']\s*(?:,\s*args\s*=>\s*(\{[\s\S]*?\}))?\s*\}/g, (_m, name, argsJson) => {
|
|
658
|
+
if (!ctx.knownToolNames.has(name)) return "";
|
|
659
|
+
let args = {};
|
|
660
|
+
if (argsJson) {
|
|
661
|
+
try {
|
|
662
|
+
const parsed = JSON.parse(argsJson);
|
|
663
|
+
if (parsed && typeof parsed === "object") {
|
|
664
|
+
args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
|
|
665
|
+
}
|
|
666
|
+
} catch {
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
out.push({ type: "tool_call", tool: { name, args } });
|
|
670
|
+
return "";
|
|
671
|
+
});
|
|
672
|
+
result = result.replace(/\[Function[^\]]*\]\s*/g, "");
|
|
673
|
+
result = stripUnclosedToolCallTags(result);
|
|
674
|
+
return result;
|
|
675
|
+
}
|
|
676
|
+
function stripUnclosedToolCallTags(text) {
|
|
677
|
+
const startTags = [
|
|
678
|
+
{ tag: "<tool_call>", closeTag: "</tool_call>", openRe: /<tool_call>/g },
|
|
679
|
+
{ tag: "<invoke ", closeTag: "</invoke>", openRe: /<invoke\s+name=["']([\w]+)["']>/g },
|
|
680
|
+
{ tag: "<function_calls>", closeTag: "</function_calls>", openRe: /<function_calls>/g },
|
|
681
|
+
{ tag: "[TOOL_CALL]", closeTag: "[/TOOL_CALL]", openRe: /\[TOOL_CALL\]/g }
|
|
682
|
+
];
|
|
683
|
+
let result = text;
|
|
684
|
+
for (const { tag, closeTag, openRe } of startTags) {
|
|
685
|
+
let m;
|
|
686
|
+
while ((m = openRe.exec(result)) !== null) {
|
|
687
|
+
const tail = result.substring(m.index);
|
|
688
|
+
const closeIdx = tail.indexOf(closeTag);
|
|
689
|
+
if (closeIdx === -1) {
|
|
690
|
+
const nextBlank = result.indexOf("\n\n", m.index);
|
|
691
|
+
const endIdx = nextBlank === -1 ? result.length : nextBlank;
|
|
692
|
+
result = result.substring(0, m.index) + result.substring(endIdx);
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return result;
|
|
698
|
+
}
|
|
699
|
+
function extractSimpleArgs(inner) {
|
|
700
|
+
const args = {};
|
|
701
|
+
const paramRe = /<parameter\s+name=["'](\w+)["']>([\s\S]*?)<\/parameter>/g;
|
|
702
|
+
let m;
|
|
703
|
+
while ((m = paramRe.exec(inner)) !== null) {
|
|
704
|
+
args[m[1]] = m[2].trim();
|
|
705
|
+
}
|
|
706
|
+
if (Object.keys(args).length === 0) {
|
|
707
|
+
const shortRe = /<param\s+name=["'](\w+)["']>([\s\S]*?)<\/param>/g;
|
|
708
|
+
while ((m = shortRe.exec(inner)) !== null) {
|
|
709
|
+
args[m[1]] = m[2].trim().replace(/^["']|["']$/g, "");
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (Object.keys(args).length === 0) {
|
|
713
|
+
const cmdM = inner.match(/<command>([\s\S]*?)<\/command>/);
|
|
714
|
+
const argsM = inner.match(/<args>([\s\S]*?)<\/args>/);
|
|
715
|
+
if (cmdM) {
|
|
716
|
+
const cmd = cmdM[1].trim();
|
|
717
|
+
if (cmd.includes(" ") && !argsM) {
|
|
718
|
+
const parts = cmd.split(/\s+/);
|
|
719
|
+
args.command = parts[0];
|
|
720
|
+
args.args = parts.slice(1).join(" ");
|
|
721
|
+
} else {
|
|
722
|
+
args.command = cmd;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (argsM) args.args = argsM[1].trim();
|
|
726
|
+
}
|
|
727
|
+
return args;
|
|
728
|
+
}
|
|
729
|
+
var init_chat_segmenter = __esm({
|
|
730
|
+
"src/agents/chat-segmenter.ts"() {
|
|
731
|
+
"use strict";
|
|
732
|
+
init_parse_tool_call();
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// src/web/ui/message-renderer.ts
|
|
737
|
+
var message_renderer_exports = {};
|
|
738
|
+
__export(message_renderer_exports, {
|
|
739
|
+
MessageRenderer: () => MessageRenderer,
|
|
740
|
+
addMessage: () => addMessage,
|
|
741
|
+
escapeHtml: () => escapeHtml,
|
|
742
|
+
finalizeTimelineAsMessage: () => finalizeTimelineAsMessage,
|
|
743
|
+
getMessagesContainerForCurrent: () => getMessagesContainerForCurrent,
|
|
744
|
+
handleStepEvent: () => handleStepEvent,
|
|
745
|
+
handleStreamTokenEvent: () => handleStreamTokenEvent,
|
|
746
|
+
hasStreamingText: () => hasStreamingText,
|
|
747
|
+
injectRecoveredText: () => injectRecoveredText,
|
|
748
|
+
replaceStreamingText: () => replaceStreamingText,
|
|
749
|
+
resetRendererState: () => resetRendererState
|
|
750
|
+
});
|
|
751
|
+
function hasStreamingText() {
|
|
752
|
+
return streamingText.length > 0;
|
|
753
|
+
}
|
|
754
|
+
function replaceStreamingText(fullContent) {
|
|
755
|
+
if (!streamingTextNode || !streamingMessageEl) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
streamingTextNode.nodeValue = String(fullContent || "");
|
|
759
|
+
streamingText = String(fullContent || "");
|
|
760
|
+
}
|
|
761
|
+
function injectRecoveredText(partialText, ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
|
|
762
|
+
if (streamingMessageEl && streamingTextNode) {
|
|
763
|
+
streamingTextNode.nodeValue = String(partialText || "");
|
|
764
|
+
streamingText = String(partialText || "");
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
handleStreamTokenEvent(
|
|
768
|
+
{
|
|
769
|
+
type: "token",
|
|
770
|
+
streamType: "token",
|
|
771
|
+
content: String(partialText || ""),
|
|
772
|
+
delta: String(partialText || "")
|
|
773
|
+
},
|
|
774
|
+
ctx
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
function scheduleScrollToBottom(container) {
|
|
778
|
+
if (!container) return;
|
|
779
|
+
if (scrollToBottomTimer) return;
|
|
780
|
+
scrollToBottomTimer = setTimeout(() => {
|
|
781
|
+
container.scrollTop = container.scrollHeight;
|
|
782
|
+
scrollToBottomTimer = null;
|
|
783
|
+
}, 60);
|
|
784
|
+
}
|
|
785
|
+
function getMessagesContainerForCurrent(currentChannelId2, messagesContainers2, messagesEl2) {
|
|
786
|
+
if (currentChannelId2 && messagesContainers2.get(currentChannelId2)) {
|
|
787
|
+
return messagesContainers2.get(currentChannelId2) || null;
|
|
788
|
+
}
|
|
789
|
+
return messagesEl2;
|
|
790
|
+
}
|
|
791
|
+
function escapeHtml(s) {
|
|
792
|
+
return String(s ?? "").replace(/[&<>"']/g, (c) => ({
|
|
793
|
+
"&": "&",
|
|
794
|
+
"<": "<",
|
|
795
|
+
">": ">",
|
|
796
|
+
'"': """,
|
|
797
|
+
"'": "'"
|
|
798
|
+
})[c]);
|
|
799
|
+
}
|
|
800
|
+
function addMessage(content, type, save = true, container, usedJudgmentIds = [], ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
|
|
801
|
+
const messagesEl2 = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
|
|
802
|
+
const messagesContainers2 = ctx.messagesContainers || /* @__PURE__ */ new Map();
|
|
803
|
+
const currentChannelId2 = ctx.currentChannelId;
|
|
804
|
+
const msgContainer = container || getMessagesContainerForCurrent(currentChannelId2, messagesContainers2, messagesEl2);
|
|
805
|
+
if (!save && msgContainer && msgContainer.children.length > 200) {
|
|
806
|
+
const toRemove = msgContainer.children.length - 200;
|
|
807
|
+
for (let i = 0; i < toRemove; i++) {
|
|
808
|
+
const first = msgContainer.firstElementChild;
|
|
809
|
+
if (first) msgContainer.removeChild(first);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (save) {
|
|
813
|
+
const lastContent = type === "user" ? lastUserCommand : lastAiContent;
|
|
814
|
+
if (lastContent && content === lastContent) {
|
|
815
|
+
console.log(`[addMessage] \u8DF3\u8FC7\u91CD\u590D\u7684 ${type} \u6D88\u606F`);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (type === "user") lastUserCommand = content;
|
|
819
|
+
else lastAiContent = content;
|
|
820
|
+
}
|
|
821
|
+
const div = document.createElement("div");
|
|
822
|
+
div.className = `message message-${type}`;
|
|
823
|
+
let cleanContent = content;
|
|
824
|
+
if (type === "ai") {
|
|
825
|
+
cleanContent = cleanContent.replace(/<think>[\s\S]*?<\/think>/g, "");
|
|
826
|
+
const finalGenIdx = cleanContent.indexOf("<final gen>");
|
|
827
|
+
if (finalGenIdx >= 0) {
|
|
828
|
+
cleanContent = cleanContent.substring(0, finalGenIdx).trim();
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
const knownToolNames = ctx && ctx.knownToolNames || /* @__PURE__ */ new Set();
|
|
832
|
+
const segments = segmentChatReply(cleanContent, { knownToolNames });
|
|
833
|
+
if (segments.length === 0) {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
let thinkContainer = null;
|
|
837
|
+
let renderedAny = false;
|
|
838
|
+
for (const seg of segments) {
|
|
839
|
+
if (seg.type === "think" && seg.content) {
|
|
840
|
+
thinkContainer = buildThinkContainer(seg.content);
|
|
841
|
+
div.appendChild(thinkContainer);
|
|
842
|
+
renderedAny = true;
|
|
843
|
+
} else if (seg.type === "env_details" && seg.content) {
|
|
844
|
+
div.appendChild(buildEnvContainer(seg.content));
|
|
845
|
+
renderedAny = true;
|
|
846
|
+
} else if (seg.type === "text" && seg.content) {
|
|
847
|
+
if (thinkContainer) div.appendChild(thinkContainer);
|
|
848
|
+
thinkContainer = null;
|
|
849
|
+
div.appendChild(buildBubble(seg.content, type));
|
|
850
|
+
renderedAny = true;
|
|
851
|
+
} else if (seg.type === "final" && seg.content) {
|
|
852
|
+
if (thinkContainer) div.appendChild(thinkContainer);
|
|
853
|
+
thinkContainer = null;
|
|
854
|
+
const finalEl = buildBubble(seg.content, type);
|
|
855
|
+
finalEl.classList.add("bubble-final");
|
|
856
|
+
div.appendChild(finalEl);
|
|
857
|
+
renderedAny = true;
|
|
858
|
+
} else if (seg.type === "tool_call" && seg.tool) {
|
|
859
|
+
if (ctx && ctx.toolCallCallback) {
|
|
860
|
+
ctx.toolCallCallback(seg.tool, div);
|
|
861
|
+
}
|
|
862
|
+
renderedAny = true;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (!renderedAny) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const rawContent = segments.filter((s) => s.type === "text" || s.type === "final").map((s) => s.content || "").join("\n");
|
|
869
|
+
const time = document.createElement("div");
|
|
870
|
+
time.className = "time";
|
|
871
|
+
time.textContent = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" });
|
|
872
|
+
if (type === "ai") {
|
|
873
|
+
div.appendChild(buildMessageActions(div, rawContent, ctx));
|
|
874
|
+
}
|
|
875
|
+
if (type === "ai" && Array.isArray(usedJudgmentIds) && usedJudgmentIds.length > 0) {
|
|
876
|
+
const link = document.createElement("a");
|
|
877
|
+
link.className = "used-judgments-link";
|
|
878
|
+
link.textContent = `\u{1F4CE} \u53C2\u8003 ${usedJudgmentIds.length} \u6761\u539F\u5219`;
|
|
879
|
+
link.onclick = (e) => {
|
|
880
|
+
e.preventDefault();
|
|
881
|
+
if (typeof ctx.openJudgmentsModalWithFilter === "function") {
|
|
882
|
+
ctx.openJudgmentsModalWithFilter(usedJudgmentIds);
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
div.appendChild(link);
|
|
886
|
+
}
|
|
887
|
+
if (type === "ai" && msgContainer) {
|
|
888
|
+
mountStepTimeline(div, currentChannelId2);
|
|
889
|
+
}
|
|
890
|
+
div.appendChild(time);
|
|
891
|
+
if (msgContainer) {
|
|
892
|
+
msgContainer.appendChild(div);
|
|
893
|
+
scheduleScrollToBottom(msgContainer);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function buildThinkContainer(thinkContent) {
|
|
897
|
+
const container = document.createElement("div");
|
|
898
|
+
container.className = "think-container";
|
|
899
|
+
const toggle = document.createElement("div");
|
|
900
|
+
toggle.className = "think-toggle";
|
|
901
|
+
toggle.innerHTML = '\u{1F4AD} \u601D\u8003\u8FC7\u7A0B <span class="think-arrow">\u25B8</span>';
|
|
902
|
+
toggle.onclick = function() {
|
|
903
|
+
const details = container.querySelector(".think-content");
|
|
904
|
+
const arrow = toggle.querySelector(".think-arrow");
|
|
905
|
+
if (!details || !arrow) return;
|
|
906
|
+
if (details.style.display === "none") {
|
|
907
|
+
details.style.display = "block";
|
|
908
|
+
arrow.textContent = "\u25BE";
|
|
909
|
+
} else {
|
|
910
|
+
details.style.display = "none";
|
|
911
|
+
arrow.textContent = "\u25B8";
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
const content = document.createElement("div");
|
|
915
|
+
content.className = "think-content";
|
|
916
|
+
content.style.display = "none";
|
|
917
|
+
content.innerHTML = `<pre>${escapeHtml(thinkContent)}</pre>`;
|
|
918
|
+
container.appendChild(toggle);
|
|
919
|
+
container.appendChild(content);
|
|
920
|
+
return container;
|
|
921
|
+
}
|
|
922
|
+
function buildEnvContainer(envDetails) {
|
|
923
|
+
const container = document.createElement("div");
|
|
924
|
+
container.className = "env-container";
|
|
925
|
+
const toggle = document.createElement("div");
|
|
926
|
+
toggle.className = "env-toggle";
|
|
927
|
+
toggle.innerHTML = '\u2699\uFE0F \u73AF\u5883\u4FE1\u606F <span class="env-arrow">\u25B8</span>';
|
|
928
|
+
toggle.onclick = function() {
|
|
929
|
+
const details = container.querySelector(".environment-details");
|
|
930
|
+
const arrow = toggle.querySelector(".env-arrow");
|
|
931
|
+
if (!details || !arrow) return;
|
|
932
|
+
if (details.style.display === "none") {
|
|
933
|
+
details.style.display = "block";
|
|
934
|
+
arrow.textContent = "\u25BE";
|
|
935
|
+
} else {
|
|
936
|
+
details.style.display = "none";
|
|
937
|
+
arrow.textContent = "\u25B8";
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
const content = document.createElement("div");
|
|
941
|
+
content.className = "environment-details";
|
|
942
|
+
content.style.display = "none";
|
|
943
|
+
content.innerHTML = `<pre>${escapeHtml(envDetails)}</pre>`;
|
|
944
|
+
container.appendChild(toggle);
|
|
945
|
+
container.appendChild(content);
|
|
946
|
+
return container;
|
|
947
|
+
}
|
|
948
|
+
function buildBubble(text, type) {
|
|
949
|
+
const bubble = document.createElement("div");
|
|
950
|
+
bubble.className = `bubble bubble-${type}`;
|
|
951
|
+
const marked2 = window.marked;
|
|
952
|
+
bubble.innerHTML = marked2 ? marked2.parse(text) : escapeHtml(text);
|
|
953
|
+
return bubble;
|
|
954
|
+
}
|
|
955
|
+
function buildMessageActions(div, rawContent, ctx) {
|
|
956
|
+
const actions = document.createElement("div");
|
|
957
|
+
actions.className = "message-actions";
|
|
958
|
+
const copyBtn = document.createElement("button");
|
|
959
|
+
copyBtn.className = "action-btn copy-btn";
|
|
960
|
+
copyBtn.innerHTML = copyIcon() + " \u590D\u5236";
|
|
961
|
+
copyBtn.title = "\u590D\u5236\u6D88\u606F";
|
|
962
|
+
copyBtn.onclick = () => {
|
|
963
|
+
navigator.clipboard.writeText(rawContent).then(() => {
|
|
964
|
+
copyBtn.innerHTML = checkIcon() + " \u5DF2\u590D\u5236";
|
|
965
|
+
setTimeout(() => {
|
|
966
|
+
copyBtn.innerHTML = copyIcon() + " \u590D\u5236";
|
|
967
|
+
}, 2e3);
|
|
968
|
+
});
|
|
969
|
+
};
|
|
970
|
+
actions.appendChild(copyBtn);
|
|
971
|
+
const saveJudgmentBtn = document.createElement("button");
|
|
972
|
+
saveJudgmentBtn.className = "action-btn save-as-judgment";
|
|
973
|
+
saveJudgmentBtn.title = "AI \u84B8\u998F\u4E3A 30-80 \u5B57\u5224\u65AD\u529B + \u81EA\u52A8\u6F14\u5316\u5BF9\u9F50";
|
|
974
|
+
saveJudgmentBtn.setAttribute("data-decision", rawContent.substring(0, 800));
|
|
975
|
+
if (ctx.currentChannelId) saveJudgmentBtn.setAttribute("data-channel-id", ctx.currentChannelId);
|
|
976
|
+
saveJudgmentBtn.innerHTML = shieldIcon() + " \u84B8\u998F\u4E3A\u5224\u65AD";
|
|
977
|
+
actions.appendChild(saveJudgmentBtn);
|
|
978
|
+
const regenBtn = document.createElement("button");
|
|
979
|
+
regenBtn.className = "action-btn regenerate-btn";
|
|
980
|
+
regenBtn.innerHTML = refreshIcon(false) + " \u91CD\u65B0\u56DE\u7B54";
|
|
981
|
+
regenBtn.title = "\u91CD\u65B0\u751F\u6210\u56DE\u590D";
|
|
982
|
+
regenBtn.onclick = () => {
|
|
983
|
+
regenBtn.innerHTML = refreshIcon(true) + " \u751F\u6210\u4E2D...";
|
|
984
|
+
regenBtn.disabled = true;
|
|
985
|
+
const messages = div.parentElement?.querySelectorAll(".message") || [];
|
|
986
|
+
let lastUserMsg = "";
|
|
987
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
988
|
+
const msg = messages[i];
|
|
989
|
+
if (msg.classList.contains("message-user")) {
|
|
990
|
+
const bubble = msg.querySelector(".bubble");
|
|
991
|
+
if (bubble) {
|
|
992
|
+
lastUserMsg = bubble.textContent || bubble.innerText || "";
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
fetch("/regenerate", {
|
|
998
|
+
method: "POST",
|
|
999
|
+
headers: { "Content-Type": "application/json" },
|
|
1000
|
+
body: JSON.stringify({ channelId: ctx.currentChannelId, userMessage: lastUserMsg })
|
|
1001
|
+
}).then((res) => {
|
|
1002
|
+
if (!res.ok) throw new Error("regenerate failed");
|
|
1003
|
+
}).catch((err) => {
|
|
1004
|
+
console.error("\u91CD\u65B0\u751F\u6210\u5931\u8D25:", err);
|
|
1005
|
+
regenBtn.innerHTML = refreshIcon(false) + " \u5931\u8D25";
|
|
1006
|
+
setTimeout(() => {
|
|
1007
|
+
regenBtn.innerHTML = refreshIcon(false) + " \u91CD\u65B0\u56DE\u7B54";
|
|
1008
|
+
regenBtn.disabled = false;
|
|
1009
|
+
}, 2e3);
|
|
1010
|
+
});
|
|
1011
|
+
};
|
|
1012
|
+
actions.appendChild(regenBtn);
|
|
1013
|
+
return actions;
|
|
1014
|
+
}
|
|
1015
|
+
function copyIcon() {
|
|
1016
|
+
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>';
|
|
1017
|
+
}
|
|
1018
|
+
function checkIcon() {
|
|
1019
|
+
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>';
|
|
1020
|
+
}
|
|
1021
|
+
function shieldIcon() {
|
|
1022
|
+
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>';
|
|
1023
|
+
}
|
|
1024
|
+
function refreshIcon(spin = false) {
|
|
1025
|
+
const cls = spin ? ' class="spin"' : "";
|
|
1026
|
+
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>`;
|
|
1027
|
+
}
|
|
1028
|
+
function handleStreamTokenEvent(data, ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
|
|
1029
|
+
const messagesEl2 = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
|
|
1030
|
+
const messagesContainers2 = ctx.messagesContainers || /* @__PURE__ */ new Map();
|
|
1031
|
+
const currentChannelId2 = ctx.currentChannelId;
|
|
1032
|
+
const container = getMessagesContainerForCurrent(currentChannelId2, messagesContainers2, messagesEl2);
|
|
1033
|
+
if (!container) return;
|
|
1034
|
+
const delta = data.content || "";
|
|
1035
|
+
if (!delta) return;
|
|
1036
|
+
if (!streamingMessageEl || !streamingMessageEl.isConnected) {
|
|
1037
|
+
streamingMessageEl = document.createElement("div");
|
|
1038
|
+
streamingMessageEl.className = "message message-ai message-streaming";
|
|
1039
|
+
streamingTextNode = document.createTextNode("");
|
|
1040
|
+
streamingMessageEl.appendChild(streamingTextNode);
|
|
1041
|
+
streamingText = "";
|
|
1042
|
+
mountStepTimeline(streamingMessageEl, currentChannelId2);
|
|
1043
|
+
container.appendChild(streamingMessageEl);
|
|
1044
|
+
if (typeof ctx.setTimelineState === "function") {
|
|
1045
|
+
ctx.setTimelineState("streaming");
|
|
1046
|
+
}
|
|
1047
|
+
scheduleScrollToBottom(container);
|
|
1048
|
+
}
|
|
1049
|
+
if (data.streamType === "token") {
|
|
1050
|
+
if (streamingTextNode) streamingTextNode.nodeValue = delta;
|
|
1051
|
+
streamingText = delta;
|
|
1052
|
+
} else {
|
|
1053
|
+
if (streamingTextNode) streamingTextNode.appendData(delta);
|
|
1054
|
+
streamingText += delta;
|
|
1055
|
+
}
|
|
1056
|
+
scheduleScrollToBottom(container);
|
|
1057
|
+
}
|
|
1058
|
+
function finalizeTimelineAsMessage(ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
|
|
1059
|
+
const messagesEl2 = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
|
|
1060
|
+
const messagesContainers2 = ctx.messagesContainers || /* @__PURE__ */ new Map();
|
|
1061
|
+
const currentChannelId2 = ctx.currentChannelId;
|
|
1062
|
+
const container = getMessagesContainerForCurrent(currentChannelId2, messagesContainers2, messagesEl2);
|
|
1063
|
+
if (streamingText.trim().length > 0) {
|
|
1064
|
+
const oldStreamingEl = streamingMessageEl;
|
|
1065
|
+
if (oldStreamingEl && oldStreamingEl.parentNode) {
|
|
1066
|
+
oldStreamingEl.parentNode.removeChild(oldStreamingEl);
|
|
1067
|
+
}
|
|
1068
|
+
addMessage(streamingText, "ai", true, container, ctx.lastUsedJudgmentIds || [], ctx);
|
|
1069
|
+
if (oldStreamingEl && container) {
|
|
1070
|
+
const newAiMsg = container.querySelector(".message-ai:last-of-type");
|
|
1071
|
+
if (newAiMsg && newAiMsg !== oldStreamingEl) {
|
|
1072
|
+
migrateStepTimeline(oldStreamingEl, newAiMsg);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
streamingMessageEl = null;
|
|
1077
|
+
streamingTextNode = null;
|
|
1078
|
+
streamingText = "";
|
|
1079
|
+
if (typeof ctx.setTimelineState === "function") {
|
|
1080
|
+
ctx.setTimelineState("done");
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
function handleStepEvent(data, ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
|
|
1084
|
+
const messagesEl2 = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
|
|
1085
|
+
const messagesContainers2 = ctx.messagesContainers || /* @__PURE__ */ new Map();
|
|
1086
|
+
const currentChannelId2 = ctx.currentChannelId;
|
|
1087
|
+
const container = getMessagesContainerForCurrent(currentChannelId2, messagesContainers2, messagesEl2);
|
|
1088
|
+
if (!container) return;
|
|
1089
|
+
if (!data || !data.type) return;
|
|
1090
|
+
let target = streamingMessageEl && streamingMessageEl.isConnected ? streamingMessageEl : null;
|
|
1091
|
+
if (!target) {
|
|
1092
|
+
const aiMsgs = container.querySelectorAll(".message-ai");
|
|
1093
|
+
if (aiMsgs.length === 0) return;
|
|
1094
|
+
target = aiMsgs[aiMsgs.length - 1];
|
|
1095
|
+
}
|
|
1096
|
+
if (!target) return;
|
|
1097
|
+
const timeline = getStepTimeline(target);
|
|
1098
|
+
if (!timeline) return;
|
|
1099
|
+
pushStepToTimeline(timeline, data.type, {
|
|
1100
|
+
tool: data.tool || "unknown",
|
|
1101
|
+
args: data.args,
|
|
1102
|
+
success: data.success,
|
|
1103
|
+
output: data.output,
|
|
1104
|
+
error: data.error
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
function resetRendererState() {
|
|
1108
|
+
streamingMessageEl = null;
|
|
1109
|
+
streamingTextNode = null;
|
|
1110
|
+
streamingText = "";
|
|
1111
|
+
lastUserCommand = "";
|
|
1112
|
+
lastAiContent = "";
|
|
1113
|
+
if (scrollToBottomTimer) {
|
|
1114
|
+
clearTimeout(scrollToBottomTimer);
|
|
1115
|
+
scrollToBottomTimer = null;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
var streamingMessageEl, streamingTextNode, streamingText, lastUserCommand, lastAiContent, scrollToBottomTimer, MessageRenderer;
|
|
1119
|
+
var init_message_renderer = __esm({
|
|
1120
|
+
"src/web/ui/message-renderer.ts"() {
|
|
1121
|
+
"use strict";
|
|
1122
|
+
init_step_timeline();
|
|
1123
|
+
init_chat_segmenter();
|
|
1124
|
+
streamingMessageEl = null;
|
|
1125
|
+
streamingTextNode = null;
|
|
1126
|
+
streamingText = "";
|
|
1127
|
+
lastUserCommand = "";
|
|
1128
|
+
lastAiContent = "";
|
|
1129
|
+
scrollToBottomTimer = null;
|
|
1130
|
+
MessageRenderer = {
|
|
1131
|
+
addMessage,
|
|
1132
|
+
handleStreamTokenEvent,
|
|
1133
|
+
finalizeTimelineAsMessage,
|
|
1134
|
+
handleStepEvent,
|
|
1135
|
+
escapeHtml,
|
|
1136
|
+
getMessagesContainerForCurrent,
|
|
1137
|
+
resetRendererState
|
|
1138
|
+
};
|
|
1139
|
+
if (typeof window !== "undefined") {
|
|
1140
|
+
window.MR = MessageRenderer;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
// src/web/client-loop-status.ts
|
|
1146
|
+
var client_loop_status_exports = {};
|
|
1147
|
+
__export(client_loop_status_exports, {
|
|
1148
|
+
applyLoopBarState: () => applyLoopBarState,
|
|
1149
|
+
hideLoopStatusBar: () => hideLoopStatusBar,
|
|
1150
|
+
inspectLoopResult: () => inspectLoopResult,
|
|
1151
|
+
markLoopBarDone: () => markLoopBarDone,
|
|
1152
|
+
openLoopInspectModal: () => openLoopInspectModal,
|
|
1153
|
+
renderLoopStatusBar: () => renderLoopStatusBar
|
|
1154
|
+
});
|
|
1155
|
+
function renderLoopStatusBar(tool, content) {
|
|
1156
|
+
if (!loopStatusBar || !loopStatusText) return;
|
|
1157
|
+
const t = String(tool || "").toLowerCase();
|
|
1158
|
+
if (!LOOP_STATUS_TOOLS.has(t)) {
|
|
1159
|
+
console.log("[SSE] status (tool=" + t + ", ignored by UI):", content?.slice(0, 80));
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
const retryMatch = String(content || "").match(/自动重试(?: loop)?\s+(\d+)\/(\d+)/);
|
|
1163
|
+
const retryFinal = /自动重试\s+\d+\s*次后仍失败/.test(String(content || ""));
|
|
1164
|
+
loopStatusBar.hidden = false;
|
|
1165
|
+
const isPivotLoopMsg = /🔄\s*循环\s*\d+\s*\/\s*\d+/.test(String(content || ""));
|
|
1166
|
+
let mainText;
|
|
1167
|
+
if (isPivotLoopMsg && !retryMatch) {
|
|
1168
|
+
mainText = "";
|
|
1169
|
+
} else {
|
|
1170
|
+
mainText = String(content || "").replace(/^[\u{1F000}-\u{1FFFF}\u{2600}-\u{27BF}]\s*/u, "").replace(/^↻\s*/, "").replace(/^⛔\s*/, "").replace(/^⚠️\s*/, "").slice(0, 200);
|
|
1171
|
+
}
|
|
1172
|
+
loopStatusText.textContent = mainText;
|
|
1173
|
+
if (retryMatch) {
|
|
1174
|
+
loopBarState = "retrying";
|
|
1175
|
+
const retryEl = document.getElementById("loop-status-retry");
|
|
1176
|
+
if (retryEl) {
|
|
1177
|
+
retryEl.hidden = false;
|
|
1178
|
+
retryEl.textContent = `\u81EA\u52A8\u91CD\u8BD5 ${retryMatch[1]}/${retryMatch[2]}`;
|
|
1179
|
+
}
|
|
1180
|
+
} else if (retryFinal) {
|
|
1181
|
+
loopBarState = "done";
|
|
1182
|
+
const retryEl = document.getElementById("loop-status-retry");
|
|
1183
|
+
if (retryEl) retryEl.hidden = true;
|
|
1184
|
+
} else {
|
|
1185
|
+
if (loopBarState !== "loading") loopBarState = "loading";
|
|
1186
|
+
const retryEl = document.getElementById("loop-status-retry");
|
|
1187
|
+
if (retryEl) retryEl.hidden = true;
|
|
1188
|
+
}
|
|
1189
|
+
applyLoopBarState();
|
|
1190
|
+
}
|
|
1191
|
+
function markLoopBarDone(summary) {
|
|
1192
|
+
loopBarState = "done";
|
|
1193
|
+
if (summary) loopBarLastSummary = summary;
|
|
1194
|
+
applyLoopBarState();
|
|
1195
|
+
}
|
|
1196
|
+
function applyLoopBarState() {
|
|
1197
|
+
if (!loopStatusBar) return;
|
|
1198
|
+
loopStatusBar.dataset.state = loopBarState;
|
|
1199
|
+
const checkBtn = document.getElementById("loop-status-check");
|
|
1200
|
+
if (checkBtn) checkBtn.hidden = loopBarState !== "done";
|
|
1201
|
+
}
|
|
1202
|
+
function hideLoopStatusBar() {
|
|
1203
|
+
if (!loopStatusBar) return;
|
|
1204
|
+
loopStatusBar.hidden = true;
|
|
1205
|
+
loopBarState = "loading";
|
|
1206
|
+
loopBarLastSummary = "";
|
|
1207
|
+
const retryEl = document.getElementById("loop-status-retry");
|
|
1208
|
+
if (retryEl) retryEl.hidden = true;
|
|
1209
|
+
applyLoopBarState();
|
|
1210
|
+
}
|
|
1211
|
+
async function inspectLoopResult() {
|
|
1212
|
+
const checkBtn = document.getElementById("loop-status-check");
|
|
1213
|
+
if (checkBtn) {
|
|
1214
|
+
checkBtn.disabled = true;
|
|
1215
|
+
checkBtn.textContent = "\u23F3 \u52A0\u8F7D...";
|
|
1216
|
+
}
|
|
1217
|
+
try {
|
|
1218
|
+
const channelId = window.currentChannelId || "";
|
|
1219
|
+
const r = await fetch(`/api/loop/inspect?channelId=${encodeURIComponent(channelId)}`);
|
|
1220
|
+
const j = await r.json().catch(() => ({}));
|
|
1221
|
+
openLoopInspectModal(j);
|
|
1222
|
+
} catch (err) {
|
|
1223
|
+
console.error("[inspect] error:", err);
|
|
1224
|
+
if (typeof window.showSimpleToast === "function") window.showSimpleToast("\u2717 \u68C0\u67E5\u5931\u8D25");
|
|
1225
|
+
} finally {
|
|
1226
|
+
if (checkBtn) {
|
|
1227
|
+
checkBtn.disabled = false;
|
|
1228
|
+
checkBtn.textContent = "\u2713 \u68C0\u67E5";
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
function openLoopInspectModal(data) {
|
|
1233
|
+
const existing = document.getElementById("loop-inspect-modal");
|
|
1234
|
+
if (existing) existing.remove();
|
|
1235
|
+
const modal = document.createElement("div");
|
|
1236
|
+
modal.id = "loop-inspect-modal";
|
|
1237
|
+
modal.className = "modal active";
|
|
1238
|
+
modal.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:1000;";
|
|
1239
|
+
const panel = document.createElement("div");
|
|
1240
|
+
panel.className = "modal-panel";
|
|
1241
|
+
panel.style.cssText = "background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:20px;max-width:720px;width:90%;max-height:80vh;overflow:auto;position:relative;";
|
|
1242
|
+
const title = document.createElement("h3");
|
|
1243
|
+
title.textContent = "\u{1F50D} \u5FAA\u73AF\u68C0\u67E5";
|
|
1244
|
+
title.style.cssText = "margin:0 0 12px;font-size:16px;";
|
|
1245
|
+
panel.appendChild(title);
|
|
1246
|
+
const close = document.createElement("button");
|
|
1247
|
+
close.textContent = "\xD7";
|
|
1248
|
+
close.style.cssText = "position:absolute;top:8px;right:12px;background:transparent;border:0;font-size:24px;cursor:pointer;color:var(--text-secondary);";
|
|
1249
|
+
close.onclick = () => modal.remove();
|
|
1250
|
+
panel.appendChild(close);
|
|
1251
|
+
if (data.error) {
|
|
1252
|
+
const e = document.createElement("div");
|
|
1253
|
+
e.style.cssText = "padding:8px 12px;background:rgba(239,68,68,0.12);color:var(--error,#ef4444);border-radius:4px;margin-bottom:12px;font-size:13px;";
|
|
1254
|
+
e.textContent = "\u26A0\uFE0F " + data.error;
|
|
1255
|
+
panel.appendChild(e);
|
|
1256
|
+
}
|
|
1257
|
+
if (data.summary) {
|
|
1258
|
+
const s = document.createElement("div");
|
|
1259
|
+
s.style.cssText = "padding:8px 12px;background:var(--bg-tertiary);border-radius:4px;margin-bottom:12px;font-size:13px;";
|
|
1260
|
+
s.textContent = data.summary;
|
|
1261
|
+
panel.appendChild(s);
|
|
1262
|
+
}
|
|
1263
|
+
if (data.tokens && (data.tokens.input || data.tokens.output)) {
|
|
1264
|
+
const t = document.createElement("div");
|
|
1265
|
+
t.style.cssText = "font-size:12px;color:var(--text-muted);margin-bottom:12px;";
|
|
1266
|
+
t.textContent = `token: input ${data.tokens.input || 0} \xB7 output ${data.tokens.output || 0}`;
|
|
1267
|
+
panel.appendChild(t);
|
|
1268
|
+
}
|
|
1269
|
+
if (Array.isArray(data.steps) && data.steps.length > 0) {
|
|
1270
|
+
const h = document.createElement("div");
|
|
1271
|
+
h.textContent = `\u6B65\u9AA4 (${data.steps.length})`;
|
|
1272
|
+
h.style.cssText = "font-weight:600;margin-bottom:8px;";
|
|
1273
|
+
panel.appendChild(h);
|
|
1274
|
+
for (const step of data.steps) {
|
|
1275
|
+
const row = document.createElement("div");
|
|
1276
|
+
row.style.cssText = "padding:6px 10px;margin-bottom:4px;background:var(--bg-secondary);border-left:3px solid var(--accent);border-radius:3px;font-size:12px;";
|
|
1277
|
+
const icon = step.status === "ok" || step.status === "completed" ? "\u2713" : step.status === "error" || step.status === "failed" ? "\u2717" : "\u25CB";
|
|
1278
|
+
const dur = step.durationMs ? ` (${(step.durationMs / 1e3).toFixed(1)}s)` : "";
|
|
1279
|
+
row.innerHTML = `<b>${icon} ${window.escapeHtml ? window.escapeHtml(step.name) : step.name}</b>${dur}`;
|
|
1280
|
+
if (step.output) {
|
|
1281
|
+
const pre = document.createElement("pre");
|
|
1282
|
+
pre.style.cssText = "margin:4px 0 0;padding:6px;background:var(--bg);border-radius:3px;font-size:11px;white-space:pre-wrap;word-break:break-word;max-height:120px;overflow:auto;";
|
|
1283
|
+
pre.textContent = String(step.output).slice(0, 800);
|
|
1284
|
+
row.appendChild(pre);
|
|
1285
|
+
}
|
|
1286
|
+
panel.appendChild(row);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
if (data.finalReply) {
|
|
1290
|
+
const h = document.createElement("div");
|
|
1291
|
+
h.textContent = "\u6700\u7EC8\u56DE\u590D";
|
|
1292
|
+
h.style.cssText = "font-weight:600;margin:12px 0 8px;";
|
|
1293
|
+
panel.appendChild(h);
|
|
1294
|
+
const r = document.createElement("div");
|
|
1295
|
+
r.style.cssText = "padding:8px 12px;background:var(--bg-secondary);border-radius:4px;font-size:13px;white-space:pre-wrap;word-break:break-word;";
|
|
1296
|
+
r.textContent = data.finalReply;
|
|
1297
|
+
panel.appendChild(r);
|
|
1298
|
+
}
|
|
1299
|
+
if (!data.error && !data.summary && (!data.steps || data.steps.length === 0) && !data.finalReply) {
|
|
1300
|
+
const empty = document.createElement("div");
|
|
1301
|
+
empty.style.cssText = "text-align:center;padding:24px;color:var(--text-muted);font-size:13px;";
|
|
1302
|
+
empty.textContent = "\u65E0\u5FAA\u73AF\u4EA7\u51FA (\u53EF\u80FD\u5DF2 abort, \u6216\u6CA1\u4EA7\u751F step)";
|
|
1303
|
+
panel.appendChild(empty);
|
|
1304
|
+
}
|
|
1305
|
+
modal.appendChild(panel);
|
|
1306
|
+
modal.onclick = (e) => {
|
|
1307
|
+
if (e.target === modal) modal.remove();
|
|
1308
|
+
};
|
|
1309
|
+
document.body.appendChild(modal);
|
|
1310
|
+
}
|
|
1311
|
+
var LOOP_STATUS_TOOLS, loopBarState, loopBarLastSummary, loopStatusBar, loopStatusText, loopStatusMeta, LoopStatusExports;
|
|
1312
|
+
var init_client_loop_status = __esm({
|
|
1313
|
+
"src/web/client-loop-status.ts"() {
|
|
1314
|
+
"use strict";
|
|
1315
|
+
LOOP_STATUS_TOOLS = /* @__PURE__ */ new Set(["loop", "compactor", "recovery", "system"]);
|
|
1316
|
+
loopBarState = "loading";
|
|
1317
|
+
loopBarLastSummary = "";
|
|
1318
|
+
loopStatusBar = document.getElementById("loop-status-bar");
|
|
1319
|
+
loopStatusText = document.getElementById("loop-status-text");
|
|
1320
|
+
loopStatusMeta = document.getElementById("loop-status-meta");
|
|
1321
|
+
LoopStatusExports = {
|
|
1322
|
+
renderLoopStatusBar,
|
|
1323
|
+
markLoopBarDone,
|
|
1324
|
+
applyLoopBarState,
|
|
1325
|
+
hideLoopStatusBar,
|
|
1326
|
+
inspectLoopResult,
|
|
1327
|
+
openLoopInspectModal,
|
|
1328
|
+
get loopBarState() {
|
|
1329
|
+
return loopBarState;
|
|
1330
|
+
},
|
|
1331
|
+
get loopBarLastSummary() {
|
|
1332
|
+
return loopBarLastSummary;
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
if (typeof window !== "undefined") {
|
|
1336
|
+
window.LoopStatus = LoopStatusExports;
|
|
1337
|
+
}
|
|
1338
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
1339
|
+
module.exports = LoopStatusExports;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
// src/web/util/safe-name.ts
|
|
1345
|
+
function safeChannelName(input2, fallback) {
|
|
1346
|
+
return safeNameInternal(input2, fallback ?? "(\u672A\u547D\u540D)", ["undefined", "null", "NaN"]);
|
|
1347
|
+
}
|
|
1348
|
+
function safeNameInternal(input2, fallback, invalidLiterals) {
|
|
1349
|
+
if (input2 === void 0 || input2 === null) return fallback;
|
|
1350
|
+
if (typeof input2 === "number" && Number.isNaN(input2)) return fallback;
|
|
1351
|
+
const s = String(input2).trim();
|
|
1352
|
+
if (!s) return fallback;
|
|
1353
|
+
for (const lit of invalidLiterals) {
|
|
1354
|
+
if (s === lit) return fallback;
|
|
1355
|
+
}
|
|
1356
|
+
return s;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// src/web/client.ts
|
|
4
1360
|
if (typeof marked === "undefined") {
|
|
5
1361
|
window.marked = { parse: (text) => String(text).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\n/g, "<br>") };
|
|
6
1362
|
}
|
|
@@ -19,9 +1375,9 @@
|
|
|
19
1375
|
return orig(...args);
|
|
20
1376
|
};
|
|
21
1377
|
})();
|
|
22
|
-
|
|
1378
|
+
var MR = {};
|
|
23
1379
|
try {
|
|
24
|
-
if (typeof
|
|
1380
|
+
if (typeof __require !== "undefined") MR = (init_message_renderer(), __toCommonJS(message_renderer_exports)) || {};
|
|
25
1381
|
} catch (e) {
|
|
26
1382
|
}
|
|
27
1383
|
function _getMR() {
|
|
@@ -29,13 +1385,13 @@
|
|
|
29
1385
|
if (typeof window !== "undefined" && window.MR) return window.MR;
|
|
30
1386
|
return {};
|
|
31
1387
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1388
|
+
var MR_addMessage = (...args) => _getMR().addMessage?.(...args);
|
|
1389
|
+
var MR_finalizeTimelineAsMessage = (...args) => _getMR().finalizeTimelineAsMessage?.(...args);
|
|
1390
|
+
var MR_handleStepEvent = (...args) => _getMR().handleStepEvent?.(...args);
|
|
1391
|
+
var MR_escapeHtml = (s) => _getMR().escapeHtml?.(s);
|
|
1392
|
+
var MR_hasStreamingText = () => _getMR().hasStreamingText?.() ?? false;
|
|
1393
|
+
var MR_replaceStreamingText = (text) => _getMR().replaceStreamingText?.(text);
|
|
1394
|
+
var MR_injectRecoveredText = (text, ctx) => _getMR().injectRecoveredText?.(text, ctx ?? getRendererCtx());
|
|
39
1395
|
function getRendererCtx() {
|
|
40
1396
|
return {
|
|
41
1397
|
messagesEl,
|
|
@@ -46,13 +1402,13 @@
|
|
|
46
1402
|
// 引用 client.js 函数, 通过参数注入避免循环 import
|
|
47
1403
|
};
|
|
48
1404
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1405
|
+
var messagesEl = document.getElementById("messages");
|
|
1406
|
+
var input = document.getElementById("input");
|
|
1407
|
+
var sendBtn = document.getElementById("send");
|
|
1408
|
+
var sidebar = document.getElementById("sidebar");
|
|
1409
|
+
var LS = {};
|
|
54
1410
|
try {
|
|
55
|
-
if (typeof
|
|
1411
|
+
if (typeof __require !== "undefined") LS = (init_client_loop_status(), __toCommonJS(client_loop_status_exports)) || {};
|
|
56
1412
|
} catch (e) {
|
|
57
1413
|
}
|
|
58
1414
|
function _getLS() {
|
|
@@ -60,28 +1416,28 @@
|
|
|
60
1416
|
if (typeof window !== "undefined" && window.LoopStatus) return window.LoopStatus;
|
|
61
1417
|
return {};
|
|
62
1418
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
1419
|
+
var renderLoopStatusBar2 = (...args) => _getLS().renderLoopStatusBar?.(...args);
|
|
1420
|
+
var hideLoopStatusBar2 = (...args) => _getLS().hideLoopStatusBar?.(...args);
|
|
1421
|
+
var sidebarToggle = document.getElementById("sidebar-toggle");
|
|
1422
|
+
var themeToggle = document.getElementById("theme-toggle");
|
|
1423
|
+
var channelList = document.getElementById("channel-list");
|
|
1424
|
+
var newChannelBtn = document.getElementById("new-channel-btn");
|
|
1425
|
+
var newChannelInput = document.getElementById("new-channel-input");
|
|
1426
|
+
var channelNameEl = document.getElementById("channel-name");
|
|
1427
|
+
var eventSources = /* @__PURE__ */ new Map();
|
|
1428
|
+
var currentChannelId = null;
|
|
1429
|
+
var currentAgentId = "";
|
|
1430
|
+
var channels = [];
|
|
1431
|
+
var remoteChannels = [];
|
|
1432
|
+
var isSidebarCollapsed = false;
|
|
1433
|
+
var reconnectAttempts = /* @__PURE__ */ new Map();
|
|
1434
|
+
var reconnectTimers = /* @__PURE__ */ new Map();
|
|
1435
|
+
var heartbeatTimers = /* @__PURE__ */ new Map();
|
|
1436
|
+
var lastKnownSeq = /* @__PURE__ */ new Map();
|
|
1437
|
+
var lastSeenMsgIds = /* @__PURE__ */ new Map();
|
|
1438
|
+
var COLLAPSED_PEERS_KEY = "bolloon.p2p.collapsedPeers";
|
|
1439
|
+
var SEEN_PEERS_KEY = "bolloon.p2p.seenPeers";
|
|
1440
|
+
var collapsedPeers = function loadCollapsed() {
|
|
85
1441
|
try {
|
|
86
1442
|
const raw = localStorage.getItem(COLLAPSED_PEERS_KEY);
|
|
87
1443
|
return new Set(raw ? JSON.parse(raw) : []);
|
|
@@ -89,7 +1445,7 @@
|
|
|
89
1445
|
return /* @__PURE__ */ new Set();
|
|
90
1446
|
}
|
|
91
1447
|
}();
|
|
92
|
-
|
|
1448
|
+
var seenPeers = function loadSeen() {
|
|
93
1449
|
try {
|
|
94
1450
|
const raw = localStorage.getItem(SEEN_PEERS_KEY);
|
|
95
1451
|
return new Set(raw ? JSON.parse(raw) : []);
|
|
@@ -139,10 +1495,10 @@
|
|
|
139
1495
|
renderRemoteChannels();
|
|
140
1496
|
if (typeof window.__syncP2PToggleAllBtn === "function") window.__syncP2PToggleAllBtn();
|
|
141
1497
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
1498
|
+
var messagesContainers = /* @__PURE__ */ new Map();
|
|
1499
|
+
var sessionMessages = /* @__PURE__ */ new Map();
|
|
1500
|
+
var currentSessionId = null;
|
|
1501
|
+
var expandedAgents = /* @__PURE__ */ new Set();
|
|
146
1502
|
function generateId() {
|
|
147
1503
|
return crypto.randomUUID();
|
|
148
1504
|
}
|
|
@@ -205,7 +1561,7 @@
|
|
|
205
1561
|
console.error("[\u52A0\u8F7D\u9891\u9053] \u5931\u8D25:", err);
|
|
206
1562
|
}
|
|
207
1563
|
}
|
|
208
|
-
|
|
1564
|
+
var v3GlobalEventSource = null;
|
|
209
1565
|
function startV3GlobalSSE() {
|
|
210
1566
|
if (v3GlobalEventSource) return;
|
|
211
1567
|
try {
|
|
@@ -229,7 +1585,7 @@
|
|
|
229
1585
|
const prefix = `\u{1F916} \u8FDC\u7AEF AI \u56DE\u590D
|
|
230
1586
|
|
|
231
1587
|
`;
|
|
232
|
-
|
|
1588
|
+
addMessage2(prefix + (msg.text || "(\u7A7A\u56DE\u590D)"), "ai", false, log);
|
|
233
1589
|
}
|
|
234
1590
|
log.scrollTop = log.scrollHeight;
|
|
235
1591
|
} else {
|
|
@@ -251,7 +1607,7 @@
|
|
|
251
1607
|
if (judgments.bound && judgments.bound.length > 0) {
|
|
252
1608
|
jh += '<div style="color:#78350f;margin-bottom:4px;"><b>\u786C\u7ED1\u5B9A</b> (\u5FC5\u987B\u9075\u5FAA):</div>';
|
|
253
1609
|
for (const j of judgments.bound) {
|
|
254
|
-
jh += `<div style="margin:2px 0;padding-left:8px;">\u2022 <b>${
|
|
1610
|
+
jh += `<div style="margin:2px 0;padding-left:8px;">\u2022 <b>${escapeHtml2((j.decision || "").slice(0, 80))}</b>${j.reasons && j.reasons.length ? '<br><span style="color:#92400e;font-size:11px;">\u7406\u7531: ' + escapeHtml2(j.reasons.join("; ").slice(0, 80)) + "</span>" : ""}</div>`;
|
|
255
1611
|
}
|
|
256
1612
|
}
|
|
257
1613
|
if (judgments.candidates && judgments.candidates.length > 0) {
|
|
@@ -278,7 +1634,7 @@
|
|
|
278
1634
|
const toast = document.createElement("div");
|
|
279
1635
|
toast.style.cssText = "margin:6px 0;padding:8px 10px;background:#fce7f3;border-left:3px solid #ec4899;border-radius:4px;font-size:12px;color:#831843;";
|
|
280
1636
|
const fromTxt = msg.source === "ai-mention-remote" ? `\u8FDC\u7AEF\u8282\u70B9 ${(msg.fromPublicKey || "").substring(0, 8)}\u2026 \u7684 ${msg.originChannelName}` : `${msg.originChannelName} (\u672C\u5730)`;
|
|
281
|
-
toast.innerHTML = `\u{1F4E1} <b>${fromTxt}</b> @-mention \u2192 \u5F53\u524D channel: <i>${
|
|
1637
|
+
toast.innerHTML = `\u{1F4E1} <b>${fromTxt}</b> @-mention \u2192 \u5F53\u524D channel: <i>${escapeHtml2((msg.text || "").slice(0, 100))}</i>${msg.text && msg.text.length > 100 ? "\u2026" : ""}`;
|
|
282
1638
|
log.appendChild(toast);
|
|
283
1639
|
log.scrollTop = log.scrollHeight;
|
|
284
1640
|
}
|
|
@@ -363,7 +1719,7 @@
|
|
|
363
1719
|
currentSessionId = null;
|
|
364
1720
|
if (currentChannelId) {
|
|
365
1721
|
const ch = channels.find((c) => c.id === currentChannelId);
|
|
366
|
-
if (channelNameEl) channelNameEl.textContent =
|
|
1722
|
+
if (channelNameEl) channelNameEl.textContent = safeChannelName(ch?.name, "Bolloon Agent");
|
|
367
1723
|
await selectChannel(currentChannelId);
|
|
368
1724
|
} else {
|
|
369
1725
|
messagesEl.innerHTML = "";
|
|
@@ -427,7 +1783,7 @@
|
|
|
427
1783
|
if (container) {
|
|
428
1784
|
container.innerHTML = "";
|
|
429
1785
|
showChannelView(currentChannelId);
|
|
430
|
-
|
|
1786
|
+
addMessage2("\u4F60\u597D\uFF01\u65B0\u4F1A\u8BDD\u5DF2\u5F00\u59CB\uFF0C\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
|
|
431
1787
|
}
|
|
432
1788
|
expandedAgents.add(currentChannelId);
|
|
433
1789
|
renderChannels();
|
|
@@ -514,8 +1870,8 @@
|
|
|
514
1870
|
console.error("Failed to delete session:", err);
|
|
515
1871
|
}
|
|
516
1872
|
}
|
|
517
|
-
|
|
518
|
-
|
|
1873
|
+
var _saveSessionMessagesDirty = false;
|
|
1874
|
+
var _saveSessionMessagesTimer = null;
|
|
519
1875
|
function saveCurrentSessionMessages() {
|
|
520
1876
|
if (!currentChannelId || !currentSessionId) return;
|
|
521
1877
|
_saveSessionMessagesDirty = true;
|
|
@@ -540,8 +1896,8 @@
|
|
|
540
1896
|
scheduleChannelsRefresh();
|
|
541
1897
|
await new Promise((r) => setTimeout(r, 600));
|
|
542
1898
|
}
|
|
543
|
-
|
|
544
|
-
|
|
1899
|
+
var channelRefreshTimer = null;
|
|
1900
|
+
var channelRefreshInFlight = null;
|
|
545
1901
|
function scheduleChannelsRefresh() {
|
|
546
1902
|
if (channelRefreshTimer) return;
|
|
547
1903
|
channelRefreshTimer = setTimeout(async () => {
|
|
@@ -637,12 +1993,12 @@
|
|
|
637
1993
|
<polyline points="9 18 15 12 9 6"></polyline>
|
|
638
1994
|
</svg>
|
|
639
1995
|
<div class="channel-icon">\u{1F4AC}</div>
|
|
640
|
-
<span class="channel-name" title="${
|
|
1996
|
+
<span class="channel-name" title="${escapeHtml2(safeChannelName(ch.name, ""))}">${escapeHtml2(safeChannelName(ch.name))}</span>
|
|
641
1997
|
<span class="agent-row-meta">
|
|
642
1998
|
${walletBadge2}
|
|
643
1999
|
${toolsBadge}
|
|
644
2000
|
${sessionCount > 1 ? `<span class="agent-session-count" title="${sessionCount} \u4E2A\u4F1A\u8BDD">${sessionCount}</span>` : ""}
|
|
645
|
-
${currentSessLabel ? `<span class="agent-current-session" title="\u5F53\u524D\u4F1A\u8BDD\uFF1A${
|
|
2001
|
+
${currentSessLabel ? `<span class="agent-current-session" title="\u5F53\u524D\u4F1A\u8BDD\uFF1A${escapeHtml2(currentSessLabel)}">\xB7 ${escapeHtml2(currentSessLabel)}</span>` : ""}
|
|
646
2002
|
<button class="agent-config-btn" title="\u914D\u7F6E\u667A\u80FD\u4F53 (\u94B1\u5305 / \u5DE5\u5177)">
|
|
647
2003
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
648
2004
|
<circle cx="12" cy="12" r="3"></circle>
|
|
@@ -704,7 +2060,7 @@
|
|
|
704
2060
|
sessLi.className = `session-item ${isActive ? "active" : ""}`;
|
|
705
2061
|
sessLi.dataset.sessionId = sess.id;
|
|
706
2062
|
sessLi.innerHTML = `
|
|
707
|
-
<span class="session-name" title="${
|
|
2063
|
+
<span class="session-name" title="${escapeHtml2(formatSessionName(sess))}">${escapeHtml2(formatSessionName(sess))}</span>
|
|
708
2064
|
<button class="session-delete" title="\u5220\u9664\u4F1A\u8BDD">\xD7</button>
|
|
709
2065
|
`;
|
|
710
2066
|
sessLi.addEventListener("click", (ev) => {
|
|
@@ -743,7 +2099,7 @@
|
|
|
743
2099
|
const id = sess.id || "";
|
|
744
2100
|
return id ? `\u4F1A\u8BDD ${id.slice(-6)}` : "\u65B0\u4F1A\u8BDD";
|
|
745
2101
|
}
|
|
746
|
-
|
|
2102
|
+
var escapeHtml2 = MR_escapeHtml || ((s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({
|
|
747
2103
|
"&": "&",
|
|
748
2104
|
"<": "<",
|
|
749
2105
|
">": ">",
|
|
@@ -779,7 +2135,7 @@
|
|
|
779
2135
|
}
|
|
780
2136
|
const channel = channels.find((c) => c.id === channelId);
|
|
781
2137
|
if (channel) {
|
|
782
|
-
if (channelNameEl) channelNameEl.textContent =
|
|
2138
|
+
if (channelNameEl) channelNameEl.textContent = safeChannelName(channel.name);
|
|
783
2139
|
currentSessionId = targetSessionId || channel.currentSessionId || "default";
|
|
784
2140
|
if (targetSessionId) {
|
|
785
2141
|
channel.currentSessionId = targetSessionId;
|
|
@@ -806,18 +2162,18 @@
|
|
|
806
2162
|
const tmpContainer = document.createElement("div");
|
|
807
2163
|
tmpContainer.style.display = "none";
|
|
808
2164
|
for (const msg of msgs) {
|
|
809
|
-
|
|
2165
|
+
addMessage2(msg.content, msg.type, false, tmpContainer, msg.metadata?.usedJudgmentIds || []);
|
|
810
2166
|
}
|
|
811
2167
|
while (tmpContainer.firstChild) {
|
|
812
2168
|
frag.appendChild(tmpContainer.firstChild);
|
|
813
2169
|
}
|
|
814
2170
|
container.appendChild(frag);
|
|
815
2171
|
} else {
|
|
816
|
-
|
|
2172
|
+
addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
|
|
817
2173
|
}
|
|
818
2174
|
} catch (err) {
|
|
819
2175
|
console.error("[selectChannel] \u52A0\u8F7D session \u5931\u8D25:", err);
|
|
820
|
-
|
|
2176
|
+
addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
|
|
821
2177
|
}
|
|
822
2178
|
}
|
|
823
2179
|
async function loadSession(channelId, sessionId = null) {
|
|
@@ -830,29 +2186,30 @@
|
|
|
830
2186
|
container.innerHTML = "";
|
|
831
2187
|
if (session.messages && session.messages.length > 0) {
|
|
832
2188
|
session.messages.forEach((msg) => {
|
|
833
|
-
|
|
2189
|
+
addMessage2(msg.content, msg.type, false, container, msg.metadata?.usedJudgmentIds || []);
|
|
834
2190
|
});
|
|
835
2191
|
} else {
|
|
836
|
-
|
|
2192
|
+
addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
|
|
837
2193
|
}
|
|
838
2194
|
} catch (err) {
|
|
839
2195
|
console.error("Failed to load session:", err);
|
|
840
2196
|
container.innerHTML = "";
|
|
841
|
-
|
|
2197
|
+
addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
|
|
842
2198
|
}
|
|
843
2199
|
}
|
|
844
|
-
function
|
|
2200
|
+
function addMessage2(content, type, save = true, container, usedJudgmentIds = []) {
|
|
845
2201
|
return MR_addMessage(content, type, save, container, usedJudgmentIds, getRendererCtx());
|
|
846
2202
|
}
|
|
847
|
-
function
|
|
2203
|
+
function finalizeTimelineAsMessage2() {
|
|
848
2204
|
return MR_finalizeTimelineAsMessage(getRendererCtx());
|
|
849
2205
|
}
|
|
850
|
-
function
|
|
2206
|
+
function handleStepEvent2(data) {
|
|
851
2207
|
return MR_handleStepEvent(data, getRendererCtx());
|
|
852
2208
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
2209
|
+
var lastUsedJudgmentIds = [];
|
|
2210
|
+
var currentPreviewBubble = null;
|
|
2211
|
+
var selfImproveCardSeq = 0;
|
|
2212
|
+
function getMessagesContainerForCurrent2() {
|
|
856
2213
|
if (currentChannelId && messagesContainers.get(currentChannelId)) {
|
|
857
2214
|
return messagesContainers.get(currentChannelId);
|
|
858
2215
|
}
|
|
@@ -885,7 +2242,7 @@
|
|
|
885
2242
|
return card;
|
|
886
2243
|
}
|
|
887
2244
|
function handleSelfImproveTriggered(data) {
|
|
888
|
-
const container =
|
|
2245
|
+
const container = getMessagesContainerForCurrent2();
|
|
889
2246
|
if (!container) return;
|
|
890
2247
|
const card = makeSelfImproveCard(data);
|
|
891
2248
|
card.querySelector(".self-improve-title").textContent = `\u{1F9E0} \u81EA\u8FED\u4EE3\u89E6\u53D1 \xB7 ${data.eventKind || "unknown"}`;
|
|
@@ -900,7 +2257,7 @@
|
|
|
900
2257
|
card.scrollIntoView({ block: "end", behavior: "smooth" });
|
|
901
2258
|
}
|
|
902
2259
|
function handleSelfImproveResult(data) {
|
|
903
|
-
const container =
|
|
2260
|
+
const container = getMessagesContainerForCurrent2();
|
|
904
2261
|
if (!container) return;
|
|
905
2262
|
const card = makeSelfImproveCard(data);
|
|
906
2263
|
const ok = !!data.success;
|
|
@@ -1019,14 +2376,14 @@
|
|
|
1019
2376
|
const container = messagesContainers.get(targetChannelId) || messagesEl;
|
|
1020
2377
|
if (msg.type === "ai") {
|
|
1021
2378
|
if (!MR_hasStreamingText()) {
|
|
1022
|
-
|
|
2379
|
+
addMessage2(msg.content, "ai", true, container, lastUsedJudgmentIds || []);
|
|
1023
2380
|
} else {
|
|
1024
2381
|
MR_replaceStreamingText?.(msg.content);
|
|
1025
2382
|
MR_finalizeTimelineAsMessage(getRendererCtx());
|
|
1026
2383
|
}
|
|
1027
2384
|
} else if (msg.type === "user") {
|
|
1028
2385
|
if (msg.source === "remote" || msg.source === "local") {
|
|
1029
|
-
|
|
2386
|
+
addMessage2(msg.content, "user", true, container);
|
|
1030
2387
|
}
|
|
1031
2388
|
}
|
|
1032
2389
|
}
|
|
@@ -1107,10 +2464,26 @@
|
|
|
1107
2464
|
const container = messagesContainers.get(msgChannelId) || messagesEl;
|
|
1108
2465
|
if (data.type === "user") {
|
|
1109
2466
|
if (data.source === "remote") {
|
|
1110
|
-
|
|
2467
|
+
addMessage2(data.content, "user", true, container);
|
|
1111
2468
|
}
|
|
1112
2469
|
} else if (data.type === "ai") {
|
|
1113
|
-
|
|
2470
|
+
addMessage2(data.content || "", "ai", true, container, lastUsedJudgmentIds || []);
|
|
2471
|
+
if (currentPreviewBubble) {
|
|
2472
|
+
currentPreviewBubble.remove();
|
|
2473
|
+
currentPreviewBubble = null;
|
|
2474
|
+
}
|
|
2475
|
+
} else if (data.type === "reply-preview") {
|
|
2476
|
+
const previewContent = data.content || "";
|
|
2477
|
+
if (!currentPreviewBubble) {
|
|
2478
|
+
currentPreviewBubble = addMessage2(previewContent, "ai", true, container, []);
|
|
2479
|
+
if (currentPreviewBubble) {
|
|
2480
|
+
currentPreviewBubble.classList.add("preview");
|
|
2481
|
+
}
|
|
2482
|
+
} else {
|
|
2483
|
+
currentPreviewBubble.replaceWith(addMessage2(previewContent, "ai", true, container, []));
|
|
2484
|
+
const msgs = container.querySelectorAll(".message-ai.preview");
|
|
2485
|
+
currentPreviewBubble = msgs.length > 0 ? msgs[msgs.length - 1] : null;
|
|
2486
|
+
}
|
|
1114
2487
|
} else if (data.type === "stream") {
|
|
1115
2488
|
if (false) handleStreamTokenEvent(data);
|
|
1116
2489
|
} else if (data.type === "regenerating") {
|
|
@@ -1121,12 +2494,12 @@
|
|
|
1121
2494
|
}
|
|
1122
2495
|
setSendMode("abort");
|
|
1123
2496
|
} else if (data.type === "status") {
|
|
1124
|
-
|
|
2497
|
+
renderLoopStatusBar2(data.tool, data.content);
|
|
1125
2498
|
} else if (data.type === "step_start" || data.type === "step_done" || data.type === "step_error") {
|
|
1126
|
-
|
|
2499
|
+
handleStepEvent2(data);
|
|
1127
2500
|
} else if (data.type === "done") {
|
|
1128
|
-
|
|
1129
|
-
|
|
2501
|
+
finalizeTimelineAsMessage2();
|
|
2502
|
+
hideLoopStatusBar2();
|
|
1130
2503
|
setSendMode("idle");
|
|
1131
2504
|
} else if (data.type === "renamed") {
|
|
1132
2505
|
const channel = channels.find((c) => c.id === data.channelId);
|
|
@@ -1134,18 +2507,18 @@
|
|
|
1134
2507
|
channel.name = data.newName;
|
|
1135
2508
|
renderChannels();
|
|
1136
2509
|
if (currentChannelId === data.channelId && channelNameEl) {
|
|
1137
|
-
channelNameEl.textContent =
|
|
2510
|
+
channelNameEl.textContent = safeChannelName(data.newName);
|
|
1138
2511
|
}
|
|
1139
2512
|
}
|
|
1140
2513
|
} else if (data.type === "error") {
|
|
1141
2514
|
const errContent = String(data.content || "\u672A\u77E5\u9519\u8BEF");
|
|
1142
|
-
|
|
2515
|
+
addMessage2(`\u26A0\uFE0F ${errContent}`, "ai", false, container);
|
|
1143
2516
|
if (typeof showSimpleToast === "function") {
|
|
1144
2517
|
showSimpleToast("\u26A0\uFE0F " + errContent.slice(0, 200));
|
|
1145
2518
|
} else {
|
|
1146
2519
|
console.error("[SSE] error:", errContent);
|
|
1147
2520
|
}
|
|
1148
|
-
|
|
2521
|
+
hideLoopStatusBar2();
|
|
1149
2522
|
setSendMode("idle");
|
|
1150
2523
|
} else if (data.type === "task_status" || data.type === "workflow_step" || data.type === "workflow_loop") {
|
|
1151
2524
|
if (data.type === "workflow_step" && (data.step === "AI \u601D\u8003" || data.step === "\u5F00\u59CB\u601D\u8003")) {
|
|
@@ -1171,11 +2544,15 @@
|
|
|
1171
2544
|
async function sendMessage() {
|
|
1172
2545
|
const text = input.value.trim();
|
|
1173
2546
|
if (!text) return;
|
|
2547
|
+
setSendMode("abort");
|
|
1174
2548
|
const container = messagesContainers.get(currentChannelId) || messagesEl;
|
|
1175
|
-
|
|
2549
|
+
addMessage2(text, "user", true, container);
|
|
1176
2550
|
if (container) container.scrollTop = container.scrollHeight;
|
|
2551
|
+
if (currentPreviewBubble) {
|
|
2552
|
+
currentPreviewBubble.remove();
|
|
2553
|
+
currentPreviewBubble = null;
|
|
2554
|
+
}
|
|
1177
2555
|
input.value = "";
|
|
1178
|
-
setSendMode("abort");
|
|
1179
2556
|
persistLastMessageToServer("user", text);
|
|
1180
2557
|
const channel = channels.find((c) => c.id === currentChannelId);
|
|
1181
2558
|
const channelDid = channel?.did || "";
|
|
@@ -1191,11 +2568,11 @@
|
|
|
1191
2568
|
})
|
|
1192
2569
|
});
|
|
1193
2570
|
if (!res.ok) {
|
|
1194
|
-
|
|
2571
|
+
addMessage2("\u53D1\u9001\u5931\u8D25", "ai");
|
|
1195
2572
|
setSendMode("idle");
|
|
1196
2573
|
}
|
|
1197
2574
|
} catch (err) {
|
|
1198
|
-
|
|
2575
|
+
addMessage2("\u8FDE\u63A5\u9519\u8BEF", "ai");
|
|
1199
2576
|
console.error("Send error", err);
|
|
1200
2577
|
setSendMode("idle");
|
|
1201
2578
|
}
|
|
@@ -1232,13 +2609,13 @@
|
|
|
1232
2609
|
abortCurrentRun();
|
|
1233
2610
|
}
|
|
1234
2611
|
});
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
2612
|
+
var mentionChannels = [];
|
|
2613
|
+
var mentionDropdownEl = null;
|
|
2614
|
+
var mentionHighlightIdx = -1;
|
|
2615
|
+
var mentionQuery = null;
|
|
2616
|
+
var mentionAnchor = -1;
|
|
2617
|
+
var mentionBlockEnd = -1;
|
|
2618
|
+
var mentionDocMousedownBound = false;
|
|
1242
2619
|
function ensureMentionDocMousedown() {
|
|
1243
2620
|
if (mentionDocMousedownBound) return;
|
|
1244
2621
|
mentionDocMousedownBound = true;
|
|
@@ -1257,7 +2634,7 @@
|
|
|
1257
2634
|
const remote = [];
|
|
1258
2635
|
for (const p of remoteData.peers || []) {
|
|
1259
2636
|
for (const c of p.channels || []) {
|
|
1260
|
-
remote.push({ id: c.id, name:
|
|
2637
|
+
remote.push({ id: c.id, name: safeChannelName(c.name, "(\u8FDC\u7AEF\u672A\u547D\u540D)"), source: "remote", ownerPublicKey: p.peerId });
|
|
1261
2638
|
}
|
|
1262
2639
|
}
|
|
1263
2640
|
mentionChannels = [
|
|
@@ -1305,9 +2682,9 @@
|
|
|
1305
2682
|
const owner = !isLocal && c.ownerPublicKey ? ` <span style="color:#9ca3af;font-size:11px;">(${c.ownerPublicKey.substring(0, 8)}\u2026)</span>` : "";
|
|
1306
2683
|
const bg = i === mentionHighlightIdx ? "#eff6ff" : "#fff";
|
|
1307
2684
|
const borderLeft = i === mentionHighlightIdx ? "3px solid #93c5fd" : "3px solid transparent";
|
|
1308
|
-
return `<div class="mention-item" data-idx="${i}" data-channel-id="${
|
|
2685
|
+
return `<div class="mention-item" data-idx="${i}" data-channel-id="${escapeHtml2(c.id)}" data-channel-name="${escapeHtml2(safeChannelName(c.name, ""))}" style="padding:8px 12px;cursor:pointer;background:${bg};border-bottom:1px solid #f3f4f6;display:flex;align-items:center;gap:8px;border-left:${borderLeft};">
|
|
1309
2686
|
<span style="font-size:10px;color:${isLocal ? "#059669" : "#2563eb"};background:${isLocal ? "#d1fae5" : "#dbeafe"};padding:1px 6px;border-radius:3px;white-space:nowrap;">${tag}</span>
|
|
1310
|
-
<span style="flex:1;">${
|
|
2687
|
+
<span style="flex:1;">${escapeHtml2(safeChannelName(c.name))}</span>${owner}
|
|
1311
2688
|
</div>`;
|
|
1312
2689
|
}).join("");
|
|
1313
2690
|
mentionDropdownEl.innerHTML = headerHtml + rows;
|
|
@@ -1342,7 +2719,7 @@
|
|
|
1342
2719
|
}
|
|
1343
2720
|
const before = input.value.slice(0, anchor);
|
|
1344
2721
|
const after = input.value.slice(blockEnd);
|
|
1345
|
-
const insert = `@${
|
|
2722
|
+
const insert = `@${safeChannelName(channel.name)} `;
|
|
1346
2723
|
input.value = before + insert + after;
|
|
1347
2724
|
const newPos = before.length + insert.length;
|
|
1348
2725
|
input.focus();
|
|
@@ -1368,7 +2745,7 @@
|
|
|
1368
2745
|
}
|
|
1369
2746
|
mentionQuery = m.query;
|
|
1370
2747
|
const q = m.query.toLowerCase();
|
|
1371
|
-
const items = mentionChannels.filter((c) =>
|
|
2748
|
+
const items = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
|
|
1372
2749
|
mentionHighlightIdx = items.length > 0 ? 0 : -1;
|
|
1373
2750
|
renderMentionDropdown(items);
|
|
1374
2751
|
}
|
|
@@ -1383,21 +2760,21 @@
|
|
|
1383
2760
|
if (items.length === 0) return;
|
|
1384
2761
|
mentionHighlightIdx = (mentionHighlightIdx + 1) % items.length;
|
|
1385
2762
|
const q = (mentionQuery || "").toLowerCase();
|
|
1386
|
-
const filtered = mentionChannels.filter((c) =>
|
|
2763
|
+
const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
|
|
1387
2764
|
renderMentionDropdown(filtered);
|
|
1388
2765
|
} else if (e.key === "ArrowUp") {
|
|
1389
2766
|
e.preventDefault();
|
|
1390
2767
|
if (items.length === 0) return;
|
|
1391
2768
|
mentionHighlightIdx = (mentionHighlightIdx - 1 + items.length) % items.length;
|
|
1392
2769
|
const q = (mentionQuery || "").toLowerCase();
|
|
1393
|
-
const filtered = mentionChannels.filter((c) =>
|
|
2770
|
+
const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
|
|
1394
2771
|
renderMentionDropdown(filtered);
|
|
1395
2772
|
} else if (e.key === "Enter" || e.key === "Tab") {
|
|
1396
2773
|
if (items.length > 0) {
|
|
1397
2774
|
e.preventDefault();
|
|
1398
2775
|
e.stopPropagation();
|
|
1399
2776
|
const q = (mentionQuery || "").toLowerCase();
|
|
1400
|
-
const filtered = mentionChannels.filter((c) =>
|
|
2777
|
+
const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
|
|
1401
2778
|
const cur = filtered[mentionHighlightIdx];
|
|
1402
2779
|
if (cur) applyMention(cur);
|
|
1403
2780
|
}
|
|
@@ -1440,7 +2817,7 @@
|
|
|
1440
2817
|
}
|
|
1441
2818
|
const before = inputEl.value.slice(0, anchor);
|
|
1442
2819
|
const after = inputEl.value.slice(blockEnd);
|
|
1443
|
-
const insert = `@${
|
|
2820
|
+
const insert = `@${safeChannelName(channel.name)} `;
|
|
1444
2821
|
inputEl.value = before + insert + after;
|
|
1445
2822
|
const newPos = before.length + insert.length;
|
|
1446
2823
|
inputEl.focus();
|
|
@@ -1466,9 +2843,9 @@
|
|
|
1466
2843
|
const owner = !isLocal && c.ownerPublicKey ? ` <span style="color:#9ca3af;font-size:11px;">(${c.ownerPublicKey.substring(0, 8)}\u2026)</span>` : "";
|
|
1467
2844
|
const bg = i === localHighlight ? "#eff6ff" : "#fff";
|
|
1468
2845
|
const borderLeft = i === localHighlight ? "3px solid #93c5fd" : "3px solid transparent";
|
|
1469
|
-
return `<div class="mention-item" data-idx="${i}" data-channel-id="${
|
|
2846
|
+
return `<div class="mention-item" data-idx="${i}" data-channel-id="${escapeHtml2(c.id)}" data-channel-name="${escapeHtml2(safeChannelName(c.name, ""))}" style="padding:8px 12px;cursor:pointer;background:${bg};border-bottom:1px solid #f3f4f6;display:flex;align-items:center;gap:8px;border-left:${borderLeft};">
|
|
1470
2847
|
<span style="font-size:10px;color:${isLocal ? "#059669" : "#2563eb"};background:${isLocal ? "#d1fae5" : "#dbeafe"};padding:1px 6px;border-radius:3px;white-space:nowrap;">${tag}</span>
|
|
1471
|
-
<span style="flex:1;">${
|
|
2848
|
+
<span style="flex:1;">${escapeHtml2(safeChannelName(c.name))}</span>${owner}
|
|
1472
2849
|
</div>`;
|
|
1473
2850
|
}).join("");
|
|
1474
2851
|
inputEl.__mentionDD.querySelectorAll(".mention-item").forEach((el) => {
|
|
@@ -1510,7 +2887,7 @@
|
|
|
1510
2887
|
}
|
|
1511
2888
|
localQuery = m.query;
|
|
1512
2889
|
const q = m.query.toLowerCase();
|
|
1513
|
-
const items = mentionChannels.filter((c) =>
|
|
2890
|
+
const items = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
|
|
1514
2891
|
localHighlight = items.length > 0 ? 0 : -1;
|
|
1515
2892
|
renderLocal(items);
|
|
1516
2893
|
}
|
|
@@ -1523,19 +2900,19 @@
|
|
|
1523
2900
|
if (items.length === 0) return;
|
|
1524
2901
|
localHighlight = (localHighlight + 1) % items.length;
|
|
1525
2902
|
const q = (localQuery || "").toLowerCase();
|
|
1526
|
-
renderLocal(mentionChannels.filter((c) =>
|
|
2903
|
+
renderLocal(mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8));
|
|
1527
2904
|
} else if (e.key === "ArrowUp") {
|
|
1528
2905
|
e.preventDefault();
|
|
1529
2906
|
if (items.length === 0) return;
|
|
1530
2907
|
localHighlight = (localHighlight - 1 + items.length) % items.length;
|
|
1531
2908
|
const q = (localQuery || "").toLowerCase();
|
|
1532
|
-
renderLocal(mentionChannels.filter((c) =>
|
|
2909
|
+
renderLocal(mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8));
|
|
1533
2910
|
} else if (e.key === "Enter" || e.key === "Tab") {
|
|
1534
2911
|
if (items.length > 0) {
|
|
1535
2912
|
e.preventDefault();
|
|
1536
2913
|
e.stopPropagation();
|
|
1537
2914
|
const q = (localQuery || "").toLowerCase();
|
|
1538
|
-
const filtered = mentionChannels.filter((c) =>
|
|
2915
|
+
const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
|
|
1539
2916
|
const cur = filtered[localHighlight];
|
|
1540
2917
|
if (cur) applyLocal(cur);
|
|
1541
2918
|
}
|
|
@@ -1545,7 +2922,7 @@
|
|
|
1545
2922
|
}
|
|
1546
2923
|
}, true);
|
|
1547
2924
|
}
|
|
1548
|
-
|
|
2925
|
+
var inputArea = document.querySelector(".input-area");
|
|
1549
2926
|
if (input && inputArea) {
|
|
1550
2927
|
const onDragOver = (e) => {
|
|
1551
2928
|
if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes("application/x-bolloon-judgment")) {
|
|
@@ -1584,14 +2961,14 @@
|
|
|
1584
2961
|
if (themeToggle) {
|
|
1585
2962
|
themeToggle.addEventListener("click", toggleTheme);
|
|
1586
2963
|
}
|
|
1587
|
-
|
|
2964
|
+
var apiConfigBtn = document.getElementById("api-config-btn");
|
|
1588
2965
|
if (apiConfigBtn) {
|
|
1589
2966
|
apiConfigBtn.addEventListener("click", () => {
|
|
1590
2967
|
window.location.href = "/api-config";
|
|
1591
2968
|
});
|
|
1592
2969
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
2970
|
+
var walletBtn = document.getElementById("wallet-btn");
|
|
2971
|
+
var walletBadge = document.getElementById("wallet-badge");
|
|
1595
2972
|
if (walletBtn) {
|
|
1596
2973
|
walletBtn.addEventListener("click", openWalletModal);
|
|
1597
2974
|
}
|
|
@@ -1663,7 +3040,7 @@
|
|
|
1663
3040
|
await createChannel("\u9ED8\u8BA4\u4F1A\u8BDD");
|
|
1664
3041
|
}
|
|
1665
3042
|
}
|
|
1666
|
-
|
|
3043
|
+
var p2pNetworkBtn = document.getElementById("p2p-network-btn");
|
|
1667
3044
|
if (p2pNetworkBtn) {
|
|
1668
3045
|
p2pNetworkBtn.addEventListener("click", () => {
|
|
1669
3046
|
if (typeof window.showP2PModal === "function") {
|
|
@@ -1671,18 +3048,18 @@
|
|
|
1671
3048
|
}
|
|
1672
3049
|
});
|
|
1673
3050
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
3051
|
+
var judgmentsModal = document.getElementById("judgments-modal");
|
|
3052
|
+
var judgmentsBtn = document.getElementById("judgments-btn");
|
|
3053
|
+
var judgmentsModalClose = document.getElementById("judgments-modal-close");
|
|
3054
|
+
var judgmentDecision = document.getElementById("judgment-decision");
|
|
3055
|
+
var judgmentReason = document.getElementById("judgment-reason");
|
|
3056
|
+
var judgmentDomain = document.getElementById("judgment-domain");
|
|
3057
|
+
var judgmentStakes = document.getElementById("judgment-stakes");
|
|
3058
|
+
var judgmentSubmitBtn = document.getElementById("judgment-submit-btn");
|
|
3059
|
+
var judgmentError = document.getElementById("judgment-error");
|
|
3060
|
+
var judgmentsList = document.getElementById("judgments-list");
|
|
3061
|
+
var judgmentsBadge = document.getElementById("judgments-badge");
|
|
3062
|
+
var judgmentsLoaded = false;
|
|
1686
3063
|
function showJudgmentsModal() {
|
|
1687
3064
|
if (judgmentsModal) judgmentsModal.classList.add("active");
|
|
1688
3065
|
if (!judgmentsLoaded) loadJudgments();
|
|
@@ -1720,9 +3097,9 @@
|
|
|
1720
3097
|
function hideJudgmentsModal() {
|
|
1721
3098
|
if (judgmentsModal) judgmentsModal.classList.remove("active");
|
|
1722
3099
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
3100
|
+
var currentJudgmentTab = "channel";
|
|
3101
|
+
var currentStatusFilter = "all";
|
|
3102
|
+
var lastJudgmentsCache = [];
|
|
1726
3103
|
function renderJudgments(items) {
|
|
1727
3104
|
if (!judgmentsList) return;
|
|
1728
3105
|
const all = items || [];
|
|
@@ -1730,7 +3107,7 @@
|
|
|
1730
3107
|
const chNameEl = document.getElementById("judgments-tab-channel-name");
|
|
1731
3108
|
const currentCh = currentChannelId ? channels.find((c) => c.id === currentChannelId) : null;
|
|
1732
3109
|
if (chNameEl) {
|
|
1733
|
-
chNameEl.textContent = currentCh ? `(${
|
|
3110
|
+
chNameEl.textContent = currentCh ? `(${safeChannelName(currentCh.name)})` : "(\u672A\u9009)";
|
|
1734
3111
|
}
|
|
1735
3112
|
if (all.length === 0) {
|
|
1736
3113
|
judgmentsList.innerHTML = '<div class="task-empty">\u8FD8\u6CA1\u6709\u5224\u65AD, \u5728\u4E0A\u9762\u8BB0\u5F55\u7B2C\u4E00\u6761\u5427</div>';
|
|
@@ -1756,7 +3133,7 @@
|
|
|
1756
3133
|
);
|
|
1757
3134
|
const bound = all.filter((j) => boundIds.has(j.id));
|
|
1758
3135
|
const unbound = all.filter((j) => !boundIds.has(j.id));
|
|
1759
|
-
if (titleEl) titleEl.textContent = `${
|
|
3136
|
+
if (titleEl) titleEl.textContent = `${safeChannelName(currentCh.name)} \u7684\u5224\u65AD\u529B (\u5DF2\u7ED1 ${bound.length} / \u5171 ${all.length})`;
|
|
1760
3137
|
let html = "";
|
|
1761
3138
|
if (bound.length > 0) {
|
|
1762
3139
|
html += `<div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.5px;padding:8px 4px 4px;">\u5DF2\u7ED1\u5B9A (${bound.length})</div>`;
|
|
@@ -1771,37 +3148,37 @@
|
|
|
1771
3148
|
function renderJudgmentItems(items, opts) {
|
|
1772
3149
|
const { showBindToggle, isBound } = opts || {};
|
|
1773
3150
|
return items.map((j) => {
|
|
1774
|
-
const reason = j.reasons && j.reasons[0] ?
|
|
1775
|
-
const domain = j.context && j.context.domain ?
|
|
1776
|
-
const stakes = j.context && j.context.stakes ?
|
|
3151
|
+
const reason = j.reasons && j.reasons[0] ? escapeHtml2(j.reasons[0]) : "";
|
|
3152
|
+
const domain = j.context && j.context.domain ? escapeHtml2(j.context.domain) : "general";
|
|
3153
|
+
const stakes = j.context && j.context.stakes ? escapeHtml2(j.context.stakes) : "medium";
|
|
1777
3154
|
const isSuperseded = j.status === "superseded";
|
|
1778
3155
|
const isRejected = j.status === "rejected";
|
|
1779
3156
|
const dimmedStyle = isSuperseded || isRejected ? "opacity:0.55;background:#f3f4f6;" : "";
|
|
1780
3157
|
const statusTag = isSuperseded ? `<span style="display:inline-block;background:#fef3c7;color:#92400e;font-size:10px;padding:1px 6px;border-radius:3px;margin-left:6px;" title="\u5DF2\u88AB\u65B0\u5224\u65AD\u529B\u6F14\u5316\u66FF\u4EE3">\u5DF2\u8FC7\u65F6</span>` : isRejected ? `<span style="display:inline-block;background:#fee2e2;color:#991b1b;font-size:10px;padding:1px 6px;border-radius:3px;margin-left:6px;">\u5DF2\u62D2\u7EDD</span>` : "";
|
|
1781
|
-
const evolveNote = isSuperseded && j.supersededBy ? `<div style="font-size:10px;color:#6b7280;margin-top:2px;">\u88AB\u65B0\u6761\u66FF\u4EE3 \xB7 ${
|
|
1782
|
-
const bindBtn = showBindToggle ? isBound ? `<button class="judgment-toggle-btn" data-id="${
|
|
3158
|
+
const evolveNote = isSuperseded && j.supersededBy ? `<div style="font-size:10px;color:#6b7280;margin-top:2px;">\u88AB\u65B0\u6761\u66FF\u4EE3 \xB7 ${escapeHtml2(j.evolutionReason || "merged")} \xB7 ${escapeHtml2(j.evolvedAt || "").substring(0, 10)}</div>` : "";
|
|
3159
|
+
const bindBtn = showBindToggle ? isBound ? `<button class="judgment-toggle-btn" data-id="${escapeHtml2(j.id)}" data-action="unbind" title="\u4ECE\u5F53\u524D channel \u79FB\u9664" style="background:none;border:1px solid #fca5a5;color:#b91c1c;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">\xD7 \u79FB\u9664</button>` : `<button class="judgment-toggle-btn" data-id="${escapeHtml2(j.id)}" data-action="bind" title="\u52A0\u8FDB\u5F53\u524D channel" style="background:none;border:1px solid #6b7280;color:#6b7280;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">+ \u52A0\u5165</button>` : "";
|
|
1783
3160
|
return `
|
|
1784
3161
|
<div class="task-item completed judgment-row"
|
|
1785
|
-
data-judgment-id="${
|
|
3162
|
+
data-judgment-id="${escapeHtml2(j.id)}"
|
|
1786
3163
|
draggable="true"
|
|
1787
3164
|
style="cursor:grab;${dimmedStyle}">
|
|
1788
3165
|
<div class="task-item-header">
|
|
1789
3166
|
<label class="judgment-checkbox" style="display:flex;align-items:center;cursor:pointer;margin-right:8px;" onclick="event.stopPropagation();">
|
|
1790
|
-
<input type="checkbox" class="judgment-select-cb" data-id="${
|
|
3167
|
+
<input type="checkbox" class="judgment-select-cb" data-id="${escapeHtml2(j.id)}" style="cursor:pointer;" onclick="event.stopPropagation();">
|
|
1791
3168
|
</label>
|
|
1792
3169
|
<div class="task-item-title">
|
|
1793
|
-
<span class="judgment-decision">${
|
|
3170
|
+
<span class="judgment-decision">${escapeHtml2(j.decision)}</span>${statusTag}
|
|
1794
3171
|
</div>
|
|
1795
3172
|
<span class="task-item-status completed">${stakes}</span>
|
|
1796
3173
|
</div>
|
|
1797
3174
|
${reason ? `<div class="task-item-desc" style="color:#555;font-size:13px;margin-top:4px;">\u7406\u7531: ${reason}</div>` : ""}
|
|
1798
3175
|
${evolveNote}
|
|
1799
3176
|
<div class="task-item-meta" style="color:#999;font-size:11px;margin-top:4px;display:flex;justify-content:space-between;align-items:center;">
|
|
1800
|
-
<span>${domain} \xB7 ${
|
|
3177
|
+
<span>${domain} \xB7 ${escapeHtml2(j.timestamp)} \xB7 ${escapeHtml2(j.id)}</span>
|
|
1801
3178
|
<span style="display:flex;gap:4px;">
|
|
1802
3179
|
${bindBtn}
|
|
1803
|
-
<button class="judgment-edit-btn" data-id="${
|
|
1804
|
-
<button class="judgment-del-btn" data-id="${
|
|
3180
|
+
<button class="judgment-edit-btn" data-id="${escapeHtml2(j.id)}" title="\u7F16\u8F91\u5224\u65AD" style="background:none;border:1px solid #d1d5db;color:#374151;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">\u7F16\u8F91</button>
|
|
3181
|
+
<button class="judgment-del-btn" data-id="${escapeHtml2(j.id)}" title="\u5220\u9664\u5224\u65AD" style="background:none;border:1px solid #fca5a5;color:#b91c1c;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">\u5220\u9664</button>
|
|
1805
3182
|
</span>
|
|
1806
3183
|
</div>
|
|
1807
3184
|
</div>
|
|
@@ -1856,7 +3233,7 @@
|
|
|
1856
3233
|
}
|
|
1857
3234
|
judgmentsLoaded = true;
|
|
1858
3235
|
} catch (e) {
|
|
1859
|
-
if (judgmentsList) judgmentsList.innerHTML = '<div class="task-empty">\u52A0\u8F7D\u5931\u8D25: ' +
|
|
3236
|
+
if (judgmentsList) judgmentsList.innerHTML = '<div class="task-empty">\u52A0\u8F7D\u5931\u8D25: ' + escapeHtml2(e.message) + "</div>";
|
|
1860
3237
|
}
|
|
1861
3238
|
}
|
|
1862
3239
|
function renderViolations(items) {
|
|
@@ -1866,18 +3243,18 @@
|
|
|
1866
3243
|
return;
|
|
1867
3244
|
}
|
|
1868
3245
|
judgmentsList.innerHTML = items.map((v) => {
|
|
1869
|
-
const ts =
|
|
1870
|
-
const userPrev =
|
|
1871
|
-
const aiPrev =
|
|
3246
|
+
const ts = escapeHtml2((v.ts || "").substring(0, 19).replace("T", " "));
|
|
3247
|
+
const userPrev = escapeHtml2(v.userInputPreview || "");
|
|
3248
|
+
const aiPrev = escapeHtml2(v.aiReplyPreview || "");
|
|
1872
3249
|
const principles = (v.result?.violatedPrinciples || []).map(
|
|
1873
3250
|
(p) => `<div style="margin-top:3px;padding:4px 8px;background:#fef2f2;border-radius:3px;">
|
|
1874
|
-
<span style="color:#dc2626;">\u26A0</span> <strong>${
|
|
1875
|
-
<span style="color:#991b1b;">\u2014 ${
|
|
3251
|
+
<span style="color:#dc2626;">\u26A0</span> <strong>${escapeHtml2(p.principle || "")}</strong>
|
|
3252
|
+
<span style="color:#991b1b;">\u2014 ${escapeHtml2(p.reason || "")}</span>
|
|
1876
3253
|
</div>`
|
|
1877
3254
|
).join("");
|
|
1878
3255
|
return `
|
|
1879
3256
|
<div class="task-item" style="border-left:3px solid #dc2626;padding:8px 12px;background:#fffbfb;">
|
|
1880
|
-
<div style="font-size:11px;color:#6b7280;margin-bottom:4px;">${ts} \xB7 confidence=${
|
|
3257
|
+
<div style="font-size:11px;color:#6b7280;margin-bottom:4px;">${ts} \xB7 confidence=${escapeHtml2(String(v.result?.confidence ?? 0))}</div>
|
|
1881
3258
|
<div style="font-size:12px;color:#1f2937;"><strong>\u7528\u6237:</strong> ${userPrev}</div>
|
|
1882
3259
|
<div style="font-size:12px;color:#1f2937;margin-top:2px;"><strong>AI:</strong> ${aiPrev}</div>
|
|
1883
3260
|
<div style="margin-top:6px;">${principles}</div>
|
|
@@ -1888,7 +3265,7 @@
|
|
|
1888
3265
|
function renderAdaptiveSuggestions(data) {
|
|
1889
3266
|
if (!judgmentsList) return;
|
|
1890
3267
|
const { judgmentsTotal, usageEntriesScanned, suggestions, scannedAt } = data;
|
|
1891
|
-
const ts =
|
|
3268
|
+
const ts = escapeHtml2((scannedAt || "").substring(0, 19).replace("T", " "));
|
|
1892
3269
|
if (!suggestions || suggestions.length === 0) {
|
|
1893
3270
|
judgmentsList.innerHTML = `
|
|
1894
3271
|
<div class="task-empty">\u{1F4CA} \u81EA\u9002\u5E94\u626B\u63CF: \u65E0\u5EFA\u8BAE
|
|
@@ -1912,21 +3289,21 @@
|
|
|
1912
3289
|
const style = KIND_STYLE[s.kind] || KIND_STYLE.unused;
|
|
1913
3290
|
const m = s.metrics || {};
|
|
1914
3291
|
return `
|
|
1915
|
-
<div class="task-item" data-suggestion-key="${
|
|
3292
|
+
<div class="task-item" data-suggestion-key="${escapeHtml2(s.key)}"
|
|
1916
3293
|
style="border-left:3px solid ${style.color};padding:8px 12px;background:${style.bg};margin-bottom:6px;">
|
|
1917
3294
|
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
|
|
1918
3295
|
<span style="color:${style.color};font-weight:600;font-size:12px;">${style.label}</span>
|
|
1919
3296
|
<span style="font-size:11px;color:#6b7280;">${s.action === "boost" ? "\u5EFA\u8BAE\u52A0\u6743" : s.action === "deprecate" ? "\u5EFA\u8BAE\u5E9F\u5F03" : "\u5EFA\u8BAE\u5BA1\u89C6"}</span>
|
|
1920
3297
|
</div>
|
|
1921
|
-
<div style="font-size:12px;color:#1f2937;margin-bottom:4px;"><strong>${
|
|
1922
|
-
<div style="font-size:11px;color:#6b7280;margin-bottom:6px;">${
|
|
3298
|
+
<div style="font-size:12px;color:#1f2937;margin-bottom:4px;"><strong>${escapeHtml2(s.decision)}</strong></div>
|
|
3299
|
+
<div style="font-size:11px;color:#6b7280;margin-bottom:6px;">${escapeHtml2(s.reason)}</div>
|
|
1923
3300
|
<div style="font-size:11px;color:#9ca3af;margin-bottom:6px;">
|
|
1924
3301
|
7\u5929 ${m.usage7d || 0} \xB7 30\u5929 ${m.usage30d || 0} \xB7 \u5171 ${m.totalUsage || 0} \xB7 \u4E0A\u6B21\u7528 ${m.daysSinceLastUse || 0} \u5929\u524D
|
|
1925
3302
|
</div>
|
|
1926
3303
|
<div style="display:flex;gap:6px;">
|
|
1927
|
-
<button class="adaptive-accept" data-key="${
|
|
3304
|
+
<button class="adaptive-accept" data-key="${escapeHtml2(s.key)}" data-id="${escapeHtml2(s.judgmentId)}" data-action-kind="${escapeHtml2(s.action)}"
|
|
1928
3305
|
style="background:#059669;color:#fff;border:none;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u2713 \u63A5\u53D7</button>
|
|
1929
|
-
<button class="adaptive-reject" data-key="${
|
|
3306
|
+
<button class="adaptive-reject" data-key="${escapeHtml2(s.key)}" data-id="${escapeHtml2(s.judgmentId)}" data-action-kind="${escapeHtml2(s.action)}"
|
|
1930
3307
|
style="background:none;border:1px solid #d1d5db;color:#6b7280;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u2717 \u62D2\u7EDD</button>
|
|
1931
3308
|
</div>
|
|
1932
3309
|
</div>
|
|
@@ -1995,18 +3372,18 @@
|
|
|
1995
3372
|
return;
|
|
1996
3373
|
}
|
|
1997
3374
|
const rows = items.map((p, idx) => `
|
|
1998
|
-
<div class="task-item" data-causal-idx="${idx}" data-judgment-a="${
|
|
3375
|
+
<div class="task-item" data-causal-idx="${idx}" data-judgment-a="${escapeHtml2(p.judgmentA)}" data-judgment-b="${escapeHtml2(p.judgmentB)}"
|
|
1999
3376
|
style="border-left:3px solid #7c3aed;padding:8px 12px;background:#faf5ff;margin-bottom:6px;">
|
|
2000
3377
|
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
|
|
2001
|
-
<span style="color:#7c3aed;font-weight:600;font-size:12px;">${
|
|
3378
|
+
<span style="color:#7c3aed;font-weight:600;font-size:12px;">${escapeHtml2(p.causalDirection)}</span>
|
|
2002
3379
|
<span style="font-size:11px;color:#6b7280;">MI=${p.mutualInfo} \xB7 co=${p.coOccurrence}</span>
|
|
2003
3380
|
</div>
|
|
2004
|
-
<div style="font-size:11px;color:#374151;margin-bottom:4px;">${
|
|
2005
|
-
<div style="font-size:10px;color:#9ca3af;">A: ${
|
|
3381
|
+
<div style="font-size:11px;color:#374151;margin-bottom:4px;">${escapeHtml2(p.explanation || "(\u65E0 LLM \u89E3\u91CA)")}</div>
|
|
3382
|
+
<div style="font-size:10px;color:#9ca3af;">A: ${escapeHtml2(p.judgmentA)} \u2194 B: ${escapeHtml2(p.judgmentB)}</div>
|
|
2006
3383
|
<div style="margin-top:6px;display:flex;gap:6px;">
|
|
2007
|
-
<button class="causal-intervention-a" data-jid="${
|
|
3384
|
+
<button class="causal-intervention-a" data-jid="${escapeHtml2(p.judgmentA)}"
|
|
2008
3385
|
style="background:#7c3aed;color:#fff;border:none;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u{1F52C} do(A)</button>
|
|
2009
|
-
<button class="causal-intervention-b" data-jid="${
|
|
3386
|
+
<button class="causal-intervention-b" data-jid="${escapeHtml2(p.judgmentB)}"
|
|
2010
3387
|
style="background:#7c3aed;color:#fff;border:none;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u{1F52C} do(B)</button>
|
|
2011
3388
|
</div>
|
|
2012
3389
|
<div class="causal-result" data-jid="" style="display:none;margin-top:6px;padding:6px;background:#f3e8ff;border-radius:3px;font-size:11px;"></div>
|
|
@@ -2050,11 +3427,11 @@
|
|
|
2050
3427
|
const color = Math.abs(effect) > 0.5 ? "#dc2626" : Math.abs(effect) > 0.2 ? "#d97706" : "#059669";
|
|
2051
3428
|
resultDiv.innerHTML = `
|
|
2052
3429
|
<div style="color:${color};font-weight:600;">do-calculus: causalEffect = ${sign}${effect} (${data.marginalContribution})</div>
|
|
2053
|
-
<div style="color:#374151;margin-top:4px;">${
|
|
3430
|
+
<div style="color:#374151;margin-top:4px;">${escapeHtml2(data.reasoning)}</div>
|
|
2054
3431
|
<div style="color:#9ca3af;margin-top:4px;">confidence=${data.confidence}</div>
|
|
2055
3432
|
`;
|
|
2056
3433
|
} catch (err) {
|
|
2057
|
-
resultDiv.innerHTML = `<div style="color:#dc2626;">\u5931\u8D25: ${
|
|
3434
|
+
resultDiv.innerHTML = `<div style="color:#dc2626;">\u5931\u8D25: ${escapeHtml2(err.message)}</div>`;
|
|
2058
3435
|
} finally {
|
|
2059
3436
|
btn.disabled = false;
|
|
2060
3437
|
}
|
|
@@ -2138,9 +3515,9 @@
|
|
|
2138
3515
|
}
|
|
2139
3516
|
});
|
|
2140
3517
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
3518
|
+
var judgmentSelectAll = document.getElementById("judgment-select-all");
|
|
3519
|
+
var judgmentSelectedCount = document.getElementById("judgment-selected-count");
|
|
3520
|
+
var judgmentBulkDeleteBtn = document.getElementById("judgment-bulk-delete-btn");
|
|
2144
3521
|
function getSelectedJudgmentIds() {
|
|
2145
3522
|
if (!judgmentsList) return [];
|
|
2146
3523
|
return Array.from(judgmentsList.querySelectorAll(".judgment-select-cb")).filter((cb) => cb.checked).map((cb) => cb.getAttribute("data-id")).filter(Boolean);
|
|
@@ -2288,8 +3665,37 @@
|
|
|
2288
3665
|
if (e.target === judgmentsModal) hideJudgmentsModal();
|
|
2289
3666
|
});
|
|
2290
3667
|
}
|
|
2291
|
-
|
|
2292
|
-
|
|
3668
|
+
var judgmentImportBtn = document.getElementById("judgment-import-btn");
|
|
3669
|
+
var judgmentImportFile = document.getElementById("judgment-import-file");
|
|
3670
|
+
var judgmentCleanupBtn = document.getElementById("judgment-cleanup-btn");
|
|
3671
|
+
async function runCleanupJudgments(dryRun) {
|
|
3672
|
+
const url = dryRun ? "/api/judgments/cleanup-dry" : "/api/judgments/cleanup";
|
|
3673
|
+
const method = dryRun ? "GET" : "POST";
|
|
3674
|
+
if (judgmentCleanupBtn) judgmentCleanupBtn.disabled = true;
|
|
3675
|
+
const origText = judgmentCleanupBtn?.textContent;
|
|
3676
|
+
if (judgmentCleanupBtn) judgmentCleanupBtn.textContent = dryRun ? "\u{1F50D} \u626B\u63CF\u2026" : "\u2699\uFE0F \u6E05\u7406\u4E2D\u2026";
|
|
3677
|
+
try {
|
|
3678
|
+
const res = await fetch(url, { method });
|
|
3679
|
+
const json = await res.json().catch(() => ({}));
|
|
3680
|
+
if (!res.ok) {
|
|
3681
|
+
showJudgmentError("\u6E05\u7406\u5931\u8D25: " + (json.error || res.status));
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3684
|
+
if (dryRun) {
|
|
3685
|
+
showJudgmentOk(`\u626B\u63CF: ${json.totalBefore} \u6761 \u2192 \u4FDD\u7559 ${json.totalAfter}, \u5C06\u88AB\u8F6F\u5220\u9664 ${json.removed} \u6761 (loadAll \u6D4B\u8BD5/\u6D4B\u8BD5\u539F\u5219\u7B49\u542F\u53D1\u5F0F)`);
|
|
3686
|
+
} else {
|
|
3687
|
+
showJudgmentOk(`\u6E05\u7406\u5B8C\u6210: ${json.totalBefore} \u2192 ${json.totalAfter} \u6761 (\u8F6F\u5220\u9664 ${json.removed} \u6761)`);
|
|
3688
|
+
if (typeof loadJudgments === "function") await loadJudgments();
|
|
3689
|
+
}
|
|
3690
|
+
} catch (err) {
|
|
3691
|
+
showJudgmentError("\u6E05\u7406\u8BF7\u6C42\u5931\u8D25: " + (err?.message || err));
|
|
3692
|
+
} finally {
|
|
3693
|
+
if (judgmentCleanupBtn) {
|
|
3694
|
+
judgmentCleanupBtn.disabled = false;
|
|
3695
|
+
judgmentCleanupBtn.textContent = origText || "\u6E05\u7406\u6D4B\u8BD5\u6570\u636E";
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
2293
3699
|
function showJudgmentError(msg) {
|
|
2294
3700
|
if (!judgmentError) return;
|
|
2295
3701
|
judgmentError.textContent = msg;
|
|
@@ -2303,12 +3709,12 @@
|
|
|
2303
3709
|
judgmentError.style.color = "#15803d";
|
|
2304
3710
|
}
|
|
2305
3711
|
function fileToBase64(file) {
|
|
2306
|
-
return new Promise((
|
|
3712
|
+
return new Promise((resolve2, reject) => {
|
|
2307
3713
|
const r = new FileReader();
|
|
2308
3714
|
r.onload = () => {
|
|
2309
3715
|
const s = String(r.result || "");
|
|
2310
3716
|
const idx = s.indexOf(",");
|
|
2311
|
-
|
|
3717
|
+
resolve2(idx >= 0 ? s.substring(idx + 1) : s);
|
|
2312
3718
|
};
|
|
2313
3719
|
r.onerror = () => reject(r.error || new Error("read failed"));
|
|
2314
3720
|
r.readAsDataURL(file);
|
|
@@ -2346,6 +3752,15 @@
|
|
|
2346
3752
|
if (f) importJudgmentFile(f);
|
|
2347
3753
|
});
|
|
2348
3754
|
}
|
|
3755
|
+
if (judgmentCleanupBtn) {
|
|
3756
|
+
judgmentCleanupBtn.addEventListener("click", async () => {
|
|
3757
|
+
if (!window.confirm("\u5C06\u4F1A\u8F6F\u5220\u9664\u6240\u6709\u300C\u6D4B\u8BD5\u704C\u6C34\u300D\u5224\u65AD\u529B (loadAll \u6D4B\u8BD5/\u6D4B\u8BD5\u539F\u5219\u7B49\u542F\u53D1\u5F0F\u5339\u914D).\n\u4E0B\u4E00\u6B65\u5C06\u5148 dry-run \u9884\u89C8, \u4E8C\u6B21\u786E\u8BA4\u518D\u771F\u6E05.")) return;
|
|
3758
|
+
const dry = await runCleanupJudgments(true);
|
|
3759
|
+
if (dry === false) return;
|
|
3760
|
+
if (!window.confirm("\u786E\u8BA4\u6E05\u7406\u5417? \u8F6F\u5220\u9664\u53EF\u8FFD\u6EAF, \u72B6\u6001\u6807\u8BB0\u4E3A rejected, \u4E0D\u5F71\u54CD\u5DF2 active \u6570\u636E.")) return;
|
|
3761
|
+
await runCleanupJudgments(false);
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
2349
3764
|
document.addEventListener("click", async (e) => {
|
|
2350
3765
|
const btn = e.target.closest && e.target.closest(".save-as-judgment");
|
|
2351
3766
|
if (!btn) return;
|
|
@@ -2445,8 +3860,8 @@
|
|
|
2445
3860
|
}
|
|
2446
3861
|
popup.innerHTML = `
|
|
2447
3862
|
<div style="font-weight:600;margin-bottom:4px;">\u5DF2\u84B8\u998F\u4E3A\u5224\u65AD\u529B</div>
|
|
2448
|
-
<div style="background:#f9fafb;padding:6px 8px;border-radius:4px;line-height:1.4;">${
|
|
2449
|
-
${evidence ? `<div style="font-size:11px;color:#6b7280;margin-top:4px;">\u8BC1\u636E: ${
|
|
3863
|
+
<div style="background:#f9fafb;padding:6px 8px;border-radius:4px;line-height:1.4;">${escapeHtml2(value)}</div>
|
|
3864
|
+
${evidence ? `<div style="font-size:11px;color:#6b7280;margin-top:4px;">\u8BC1\u636E: ${escapeHtml2(evidence)}</div>` : ""}
|
|
2450
3865
|
${evolveNote}
|
|
2451
3866
|
<div style="display:flex;gap:6px;margin-top:8px;">
|
|
2452
3867
|
<button class="dc-edit" style="background:none;border:1px solid #d1d5db;color:#374151;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u7F16\u8F91</button>
|
|
@@ -2473,7 +3888,7 @@
|
|
|
2473
3888
|
if (judgmentSubmitBtn) judgmentSubmitBtn.addEventListener("click", submitJudgment);
|
|
2474
3889
|
loadJudgments();
|
|
2475
3890
|
setInterval(loadJudgments, 1e4);
|
|
2476
|
-
|
|
3891
|
+
var knownPeers = [];
|
|
2477
3892
|
async function loadRemoteChannels() {
|
|
2478
3893
|
try {
|
|
2479
3894
|
const res = await fetch("/api/p2p-peers");
|
|
@@ -2536,22 +3951,22 @@
|
|
|
2536
3951
|
const caretChar = "\u25BE";
|
|
2537
3952
|
return `
|
|
2538
3953
|
<li class="remote-peer-group ${isCollapsed ? "collapsed" : ""}" style="margin-bottom:10px;${strangerStyle}">
|
|
2539
|
-
<div class="remote-peer-header" data-peer-name="${
|
|
3954
|
+
<div class="remote-peer-header" data-peer-name="${escapeHtml2(peer.name)}" data-peer-pk="${escapeHtml2(peer.publicKey)}"
|
|
2540
3955
|
style="display:flex;align-items:center;gap:6px;padding:6px 8px;background:var(--bg-hover);border-radius:4px;cursor:pointer;">
|
|
2541
|
-
<button class="peer-caret-btn" data-toggle-peer="${
|
|
3956
|
+
<button class="peer-caret-btn" data-toggle-peer="${escapeHtml2(peer.publicKey)}" title="\u6298\u53E0/\u5C55\u5F00"
|
|
2542
3957
|
style="background:var(--bg-active);border:1px solid var(--border);color:var(--text);cursor:pointer;width:22px;height:22px;border-radius:4px;font-size:12px;line-height:1;padding:0;display:flex;align-items:center;justify-content:center;flex:0 0 auto;">${caretChar}</button>
|
|
2543
3958
|
<span style="font-size:13px;">${strangerIcon}</span>
|
|
2544
|
-
<span style="flex:1;font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${
|
|
3959
|
+
<span style="flex:1;font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml2(peer.publicKey)}">${escapeHtml2(peer.name)}</span>
|
|
2545
3960
|
<span style="font-size:9px;color:var(--text-muted);">${peerChannels.length > 0 ? `${peerChannels.length} ch \xB7 ` : ""}${lastConn}</span>
|
|
2546
|
-
<button class="peer-share-btn" title="\u5206\u4EAB channel \u7ED9 ${
|
|
3961
|
+
<button class="peer-share-btn" title="\u5206\u4EAB channel \u7ED9 ${escapeHtml2(peer.name)}"
|
|
2547
3962
|
style="background:transparent;border:1px solid var(--border);color:var(--text);cursor:pointer;width:22px;height:22px;border-radius:4px;font-size:12px;line-height:1;padding:0;display:flex;align-items:center;justify-content:center;flex:0 0 auto;">\u{1F4E4}</button>
|
|
2548
3963
|
</div>
|
|
2549
3964
|
<div class="remote-peer-channels" style="margin-top:4px;margin-left:8px;">
|
|
2550
3965
|
${peerChannels.length === 0 ? '<div style="font-size:10px;color:var(--text-muted);padding:2px 4px;">(\u5BF9\u65B9\u8FD8\u6CA1\u5206\u4EAB channel \u7ED9\u4F60)</div>' : peerChannels.map((c) => `
|
|
2551
|
-
<div class="remote-channel-row" data-peer-id="${
|
|
3966
|
+
<div class="remote-channel-row" data-peer-id="${escapeHtml2(peer.publicKey)}" data-channel-id="${escapeHtml2(c.id)}"
|
|
2552
3967
|
style="display:flex;align-items:center;gap:6px;padding:4px 6px;cursor:pointer;border-radius:4px;font-size:12px;">
|
|
2553
3968
|
<span>\u{1F916}</span>
|
|
2554
|
-
<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${
|
|
3969
|
+
<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml2(safeChannelName(c.name, ""))}">${escapeHtml2(safeChannelName(c.name))}</span>
|
|
2555
3970
|
</div>
|
|
2556
3971
|
`).join("")}
|
|
2557
3972
|
</div>
|
|
@@ -2619,16 +4034,16 @@
|
|
|
2619
4034
|
<span style="font-size:18px;">\u270F\uFE0F</span>
|
|
2620
4035
|
<div style="flex:1;min-width:0;">
|
|
2621
4036
|
<div class="friend-req-title">\u7F16\u8F91\u597D\u53CB</div>
|
|
2622
|
-
<div class="friend-req-meta">publicKey: ${
|
|
4037
|
+
<div class="friend-req-meta">publicKey: ${escapeHtml2(peerPublicKey.substring(0, 16))}\u2026</div>
|
|
2623
4038
|
</div>
|
|
2624
4039
|
</div>
|
|
2625
4040
|
<div class="friend-req-body">
|
|
2626
4041
|
<label style="display:block;margin-bottom:6px;font-size:12px;color:var(--text-secondary);">\u663E\u793A\u540D\u5B57</label>
|
|
2627
|
-
<input id="epm-name" type="text" value="${
|
|
4042
|
+
<input id="epm-name" type="text" value="${escapeHtml2(currentName)}"
|
|
2628
4043
|
style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-main);color:var(--text);font-family:inherit;font-size:13px;box-sizing:border-box;margin-bottom:12px;">
|
|
2629
4044
|
<label style="display:block;margin-bottom:6px;font-size:12px;color:var(--text-secondary);">\u5907\u6CE8 (\u81EA\u7531\u6587\u672C, \u4F8B\u5982\u5408\u4F5C\u9886\u57DF / \u600E\u4E48\u8BA4\u8BC6\u7684)</label>
|
|
2630
4045
|
<textarea id="epm-notes" rows="4" placeholder="\u4F8B\u5982: 2026-06 \u5408\u4F5C LLM \u4EE3\u53D1\u9A8C\u8BC1"
|
|
2631
|
-
style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-main);color:var(--text);font-family:inherit;font-size:13px;box-sizing:border-box;resize:vertical;">${
|
|
4046
|
+
style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-main);color:var(--text);font-family:inherit;font-size:13px;box-sizing:border-box;resize:vertical;">${escapeHtml2(currentNotes)}</textarea>
|
|
2632
4047
|
</div>
|
|
2633
4048
|
<div class="friend-req-actions">
|
|
2634
4049
|
<button id="epm-cancel" class="friend-req-btn-deny">\u53D6\u6D88</button>
|
|
@@ -2679,11 +4094,11 @@
|
|
|
2679
4094
|
const isShared = Array.isArray(ch.shared_with_peers) && ch.shared_with_peers.includes(peerPublicKey);
|
|
2680
4095
|
return `
|
|
2681
4096
|
<label class="share-modal-row">
|
|
2682
|
-
<input type="checkbox" data-cid="${
|
|
4097
|
+
<input type="checkbox" data-cid="${escapeHtml2(ch.id)}" ${isShared ? "checked" : ""} class="share-modal-cb">
|
|
2683
4098
|
<div class="share-modal-row-info">
|
|
2684
|
-
<div class="share-modal-row-name">${
|
|
4099
|
+
<div class="share-modal-row-name">${escapeHtml2(ch.name || "(\u672A\u547D\u540D)")}</div>
|
|
2685
4100
|
<div class="share-modal-row-meta">
|
|
2686
|
-
${isShared ? "\u2713 \u5DF2\u5206\u4EAB" : "\u672A\u5206\u4EAB"} \xB7 ${
|
|
4101
|
+
${isShared ? "\u2713 \u5DF2\u5206\u4EAB" : "\u672A\u5206\u4EAB"} \xB7 ${escapeHtml2(ch.id.slice(0, 24))}\u2026
|
|
2687
4102
|
</div>
|
|
2688
4103
|
</div>
|
|
2689
4104
|
</label>
|
|
@@ -2695,8 +4110,8 @@
|
|
|
2695
4110
|
<div class="friend-req-header">
|
|
2696
4111
|
<span style="font-size:18px;">\u{1F4E4}</span>
|
|
2697
4112
|
<div style="flex:1;min-width:0;">
|
|
2698
|
-
<div class="friend-req-title">\u5206\u4EAB channel \u7ED9 ${
|
|
2699
|
-
<div class="friend-req-meta">${
|
|
4113
|
+
<div class="friend-req-title">\u5206\u4EAB channel \u7ED9 ${escapeHtml2(peerName)}</div>
|
|
4114
|
+
<div class="friend-req-meta">${escapeHtml2(peerPublicKey.substring(0, 16))}\u2026</div>
|
|
2700
4115
|
</div>
|
|
2701
4116
|
<button id="spm-close" class="friend-req-btn-close">\xD7</button>
|
|
2702
4117
|
</div>
|
|
@@ -2756,8 +4171,8 @@
|
|
|
2756
4171
|
<div class="remote-chat-shell">
|
|
2757
4172
|
<div class="remote-chat-header">
|
|
2758
4173
|
<div style="flex:1;min-width:0;">
|
|
2759
|
-
<div class="remote-chat-title">\u{1F310} \u8DDF ${
|
|
2760
|
-
<div class="remote-chat-meta">\u8FDC\u7AEF peer: ${
|
|
4174
|
+
<div class="remote-chat-title">\u{1F310} \u8DDF ${escapeHtml2(channelName)} \u804A\u5929</div>
|
|
4175
|
+
<div class="remote-chat-meta">\u8FDC\u7AEF peer: ${escapeHtml2(peerPublicKey.substring(0, 16))}\u2026 \xB7 ${escapeHtml2(channelId)}</div>
|
|
2761
4176
|
</div>
|
|
2762
4177
|
<button id="rcm-refresh-history" title="\u91CD\u65B0\u62C9\u5386\u53F2" class="remote-chat-btn-secondary">\u21BB \u5386\u53F2</button>
|
|
2763
4178
|
<button id="rcm-close" class="remote-chat-btn-close">\xD7</button>
|
|
@@ -2788,7 +4203,7 @@
|
|
|
2788
4203
|
};
|
|
2789
4204
|
document.getElementById("rcm-refresh-history").onclick = () => loadHistory(false);
|
|
2790
4205
|
const append = (text, role) => {
|
|
2791
|
-
|
|
4206
|
+
addMessage2(text, role === "user" ? "user" : "ai", false, log);
|
|
2792
4207
|
log.scrollTop = log.scrollHeight;
|
|
2793
4208
|
};
|
|
2794
4209
|
const appendSystem = (text, kind = "info") => {
|
|
@@ -2844,7 +4259,7 @@
|
|
|
2844
4259
|
jh.className = "remote-chat-judgments";
|
|
2845
4260
|
let h = `<div class="remote-chat-judgments-title">\u{1F6E1}\uFE0F \u5BF9\u65B9 channel \u7ED1\u5B9A\u7684\u5224\u65AD\u529B (${judgments.bound.length} \u6761\u786C\u7EA6\u675F)</div>`;
|
|
2846
4261
|
for (const j of judgments.bound) {
|
|
2847
|
-
h += `<div class="remote-chat-judgment-item">\u2022 <b>${
|
|
4262
|
+
h += `<div class="remote-chat-judgment-item">\u2022 <b>${escapeHtml2((j.decision || "").slice(0, 100))}</b>${j.domain ? `<span class="remote-chat-judgment-tag"> [${escapeHtml2(j.domain)}${j.stakes ? "/" + escapeHtml2(j.stakes) : ""}]</span>` : ""}${j.reasons && j.reasons.length ? '<br><span class="remote-chat-judgment-reason">\u7406\u7531: ' + escapeHtml2(j.reasons.join("; ").slice(0, 100)) + "</span>" : ""}</div>`;
|
|
2848
4263
|
}
|
|
2849
4264
|
if (judgments.candidates && judgments.candidates.length > 0) {
|
|
2850
4265
|
h += `<div class="remote-chat-judgments-foot">+ ${judgments.candidates.length} \u6761\u5019\u9009\u5224\u65AD\u529B (LLM \u53EF\u81EA\u9009\u53C2\u8003)</div>`;
|
|
@@ -2874,7 +4289,7 @@
|
|
|
2874
4289
|
|
|
2875
4290
|
`;
|
|
2876
4291
|
}
|
|
2877
|
-
|
|
4292
|
+
addMessage2(prefix + (m.content || ""), type, false, log);
|
|
2878
4293
|
}
|
|
2879
4294
|
setTimeout(() => {
|
|
2880
4295
|
log.scrollTop = log.scrollHeight;
|
|
@@ -2913,7 +4328,7 @@
|
|
|
2913
4328
|
loadHistory(false);
|
|
2914
4329
|
historyRefreshTimer = setInterval(() => loadHistory(true), 15e3);
|
|
2915
4330
|
}
|
|
2916
|
-
|
|
4331
|
+
var showMyIdBtn = document.getElementById("show-my-p2p-id-btn");
|
|
2917
4332
|
if (showMyIdBtn) {
|
|
2918
4333
|
showMyIdBtn.addEventListener("click", async (e) => {
|
|
2919
4334
|
e.stopPropagation();
|
|
@@ -2946,7 +4361,7 @@
|
|
|
2946
4361
|
body.innerHTML = `
|
|
2947
4362
|
<div style="font-size:12px;color:#6b7280;margin-bottom:8px;">\u628A\u4E0B\u9762\u8FD9\u4E32\u53D1\u7ED9\u597D\u53CB, \u597D\u53CB\u5728 P2P \u597D\u53CB\u533A\u70B9 "+ \u597D\u53CB" \u7C98\u8D34\u5373\u53EF\u52A0\u4F60:</div>
|
|
2948
4363
|
<div style="display:flex;gap:6px;align-items:center;margin-bottom:12px;">
|
|
2949
|
-
<code id="mpim-pk" style="flex:1;padding:8px 10px;background:#f3f4f6;border:1px solid #d1d5db;border-radius:4px;font-family:monospace;font-size:11px;word-break:break-all;line-height:1.4;">${
|
|
4364
|
+
<code id="mpim-pk" style="flex:1;padding:8px 10px;background:#f3f4f6;border:1px solid #d1d5db;border-radius:4px;font-family:monospace;font-size:11px;word-break:break-all;line-height:1.4;">${escapeHtml2(pk)}</code>
|
|
2950
4365
|
<button id="mpim-copy" style="padding:8px 14px;background:#2563eb;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;white-space:nowrap;">\u{1F4CB} \u590D\u5236</button>
|
|
2951
4366
|
</div>
|
|
2952
4367
|
<div id="mpim-status" style="font-size:12px;color:#059669;min-height:16px;"></div>
|
|
@@ -2977,11 +4392,11 @@
|
|
|
2977
4392
|
};
|
|
2978
4393
|
} catch (err) {
|
|
2979
4394
|
const body = document.getElementById("mpim-body");
|
|
2980
|
-
if (body) body.innerHTML = `<div style="color:#b91c1c;font-size:13px;">\u2717 \u83B7\u53D6\u5931\u8D25: ${
|
|
4395
|
+
if (body) body.innerHTML = `<div style="color:#b91c1c;font-size:13px;">\u2717 \u83B7\u53D6\u5931\u8D25: ${escapeHtml2(err.message || String(err))}</div>`;
|
|
2981
4396
|
}
|
|
2982
4397
|
});
|
|
2983
4398
|
}
|
|
2984
|
-
|
|
4399
|
+
var addPeerBtn = document.getElementById("add-p2p-peer-btn");
|
|
2985
4400
|
if (addPeerBtn) {
|
|
2986
4401
|
addPeerBtn.addEventListener("click", async (e) => {
|
|
2987
4402
|
e.stopPropagation();
|
|
@@ -3037,11 +4452,11 @@
|
|
|
3037
4452
|
<span style="font-size:20px;">\u{1F91D}</span>
|
|
3038
4453
|
<div style="flex:1;min-width:0;">
|
|
3039
4454
|
<div class="friend-req-title">\u597D\u53CB\u7533\u8BF7</div>
|
|
3040
|
-
<div class="friend-req-meta">\u6765\u81EA ${
|
|
4455
|
+
<div class="friend-req-meta">\u6765\u81EA ${escapeHtml2(req.fromName)} (${escapeHtml2(req.fromPublicKey.substring(0, 16))}\u2026)</div>
|
|
3041
4456
|
</div>
|
|
3042
4457
|
</div>
|
|
3043
4458
|
<div class="friend-req-body">
|
|
3044
|
-
<p style="margin:0 0 8px;">${
|
|
4459
|
+
<p style="margin:0 0 8px;">${escapeHtml2(req.message || "\u60F3\u52A0\u4F60\u4E3A P2P \u597D\u53CB")}</p>
|
|
3045
4460
|
<p style="margin:0;color:var(--text-muted);font-size:11px;">\u63A5\u53D7\u540E: \u53CC\u65B9\u4E92\u52A0\u597D\u53CB, \u5BF9\u65B9\u5206\u4EAB\u7684 channel \u4F1A\u81EA\u52A8\u51FA\u73B0\u5728 P2P \u597D\u53CB\u533A.</p>
|
|
3046
4461
|
</div>
|
|
3047
4462
|
<div class="friend-req-actions">
|
|
@@ -3094,7 +4509,7 @@
|
|
|
3094
4509
|
setTimeout(() => el.remove(), 320);
|
|
3095
4510
|
}, 3e3);
|
|
3096
4511
|
}
|
|
3097
|
-
|
|
4512
|
+
var p2pToggleAllBtn = document.getElementById("p2p-toggle-all-btn");
|
|
3098
4513
|
if (p2pToggleAllBtn) {
|
|
3099
4514
|
let syncToggleAllBtn = function() {
|
|
3100
4515
|
const allPks = /* @__PURE__ */ new Set([
|
|
@@ -3117,7 +4532,7 @@
|
|
|
3117
4532
|
p2pToggleAllBtn.title = "\u70B9\u51FB\u6298\u53E0\u6240\u6709 P2P \u597D\u53CB";
|
|
3118
4533
|
}
|
|
3119
4534
|
};
|
|
3120
|
-
|
|
4535
|
+
syncToggleAllBtn2 = syncToggleAllBtn;
|
|
3121
4536
|
p2pToggleAllBtn.addEventListener("click", (e) => {
|
|
3122
4537
|
e.stopPropagation();
|
|
3123
4538
|
const allPks = /* @__PURE__ */ new Set([
|
|
@@ -3138,7 +4553,8 @@
|
|
|
3138
4553
|
window.__syncP2PToggleAllBtn = syncToggleAllBtn;
|
|
3139
4554
|
syncToggleAllBtn();
|
|
3140
4555
|
}
|
|
3141
|
-
|
|
4556
|
+
var syncToggleAllBtn2;
|
|
4557
|
+
var refreshSharedBtn = document.getElementById("refresh-shared-btn");
|
|
3142
4558
|
if (refreshSharedBtn) {
|
|
3143
4559
|
refreshSharedBtn.addEventListener("click", async (e) => {
|
|
3144
4560
|
e.stopPropagation();
|
|
@@ -3163,17 +4579,17 @@
|
|
|
3163
4579
|
loadRemoteChannels();
|
|
3164
4580
|
setInterval(loadRemoteChannels, 8e3);
|
|
3165
4581
|
startV3GlobalSSE();
|
|
3166
|
-
|
|
3167
|
-
|
|
4582
|
+
var localSection = document.querySelector(".sidebar-section");
|
|
4583
|
+
var remoteSection = document.getElementById("remote-agents-section");
|
|
3168
4584
|
if (localSection) localSection.classList.add("local-flex");
|
|
3169
4585
|
if (remoteSection) remoteSection.classList.add("remote-flex");
|
|
3170
|
-
|
|
4586
|
+
var remoteHeader = document.getElementById("remote-agents-header");
|
|
3171
4587
|
if (remoteHeader && remoteSection) {
|
|
3172
4588
|
remoteHeader.addEventListener("click", (e) => {
|
|
3173
4589
|
remoteSection.classList.toggle("collapsed");
|
|
3174
4590
|
});
|
|
3175
4591
|
}
|
|
3176
|
-
|
|
4592
|
+
var splitHandle = document.getElementById("sidebar-split-handle");
|
|
3177
4593
|
if (splitHandle && localSection && remoteSection) {
|
|
3178
4594
|
const updateFlexVars = (localRatio, remoteRatio) => {
|
|
3179
4595
|
localSection.style.setProperty("--local-flex", String(localRatio));
|
|
@@ -3217,17 +4633,17 @@
|
|
|
3217
4633
|
updateFlexVars(1, 1);
|
|
3218
4634
|
});
|
|
3219
4635
|
}
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
4636
|
+
var walletModal = document.getElementById("wallet-modal");
|
|
4637
|
+
var walletModalClose = document.getElementById("wallet-modal-close");
|
|
4638
|
+
var walletBindAddress = document.getElementById("wallet-bind-address");
|
|
4639
|
+
var walletGenerateBtn = document.getElementById("wallet-generate-btn");
|
|
4640
|
+
var walletAutoTools = document.getElementById("wallet-auto-tools");
|
|
4641
|
+
var walletBindBtn = document.getElementById("wallet-bind-btn");
|
|
4642
|
+
var walletUnbindBtn = document.getElementById("wallet-unbind-btn");
|
|
4643
|
+
var walletNewInfo = document.getElementById("wallet-new-info");
|
|
4644
|
+
var walletListEl = document.getElementById("wallet-list");
|
|
4645
|
+
var walletModalPendingSecret = null;
|
|
4646
|
+
var walletModalPendingMnemonic = null;
|
|
3231
4647
|
if (walletModalClose) {
|
|
3232
4648
|
walletModalClose.addEventListener("click", closeWalletModal);
|
|
3233
4649
|
}
|
|
@@ -3287,7 +4703,7 @@
|
|
|
3287
4703
|
walletModalPendingSecret = null;
|
|
3288
4704
|
walletModalPendingMnemonic = null;
|
|
3289
4705
|
walletNewInfo.style.display = "block";
|
|
3290
|
-
walletNewInfo.innerHTML = "\u2705 \u7ED1\u5B9A\u6210\u529F<br><strong>\u5730\u5740:</strong> <code>" +
|
|
4706
|
+
walletNewInfo.innerHTML = "\u2705 \u7ED1\u5B9A\u6210\u529F<br><strong>\u5730\u5740:</strong> <code>" + escapeHtml2(updated.walletAddress) + "</code><br><strong>\u7B7E\u540D DID:</strong> <code>" + escapeHtml2(did) + '</code><br><small style="color:#9c9;">\u670D\u52A1\u7AEF\u5DF2\u7528 recoverMessage \u6821\u9A8C\u7B7E\u540D, \u8BC1\u660E\u4F60\u6301\u6709\u8BE5\u94B1\u5305\u79C1\u94A5\u3002</small>';
|
|
3291
4707
|
} catch (err) {
|
|
3292
4708
|
alert("\u7ED1\u5B9A\u5931\u8D25: " + err.message);
|
|
3293
4709
|
}
|
|
@@ -3345,24 +4761,24 @@
|
|
|
3345
4761
|
const row = document.createElement("div");
|
|
3346
4762
|
row.className = "wallet-row" + (isActive ? " is-active" : "");
|
|
3347
4763
|
row.innerHTML = `
|
|
3348
|
-
<span class="wallet-chain">${
|
|
4764
|
+
<span class="wallet-chain">${escapeHtml2(chain)}</span>
|
|
3349
4765
|
<div class="wallet-info">
|
|
3350
|
-
<span class="wallet-agent" title="${
|
|
3351
|
-
<span class="wallet-address" title="${
|
|
4766
|
+
<span class="wallet-agent" title="${escapeHtml2(ch.name || "")}">${escapeHtml2(ch.name || "(\u672A\u547D\u540D)")}</span>
|
|
4767
|
+
<span class="wallet-address" title="${escapeHtml2(ch.walletAddress)}">${escapeHtml2(ch.walletAddress)}</span>
|
|
3352
4768
|
</div>
|
|
3353
4769
|
<div class="wallet-actions">
|
|
3354
|
-
<button class="wallet-mini-btn" data-action="copy" data-addr="${
|
|
4770
|
+
<button class="wallet-mini-btn" data-action="copy" data-addr="${escapeHtml2(ch.walletAddress)}" title="\u590D\u5236\u5730\u5740">
|
|
3355
4771
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3356
4772
|
<rect x="9" y="9" width="13" height="13" rx="2"></rect>
|
|
3357
4773
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
3358
4774
|
</svg>
|
|
3359
4775
|
</button>
|
|
3360
|
-
<button class="wallet-mini-btn" data-action="goto" data-id="${
|
|
4776
|
+
<button class="wallet-mini-btn" data-action="goto" data-id="${escapeHtml2(ch.id)}" title="\u5207\u6362\u5230\u8BE5\u667A\u80FD\u4F53">
|
|
3361
4777
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3362
4778
|
<path d="M5 12h14M12 5l7 7-7 7"></path>
|
|
3363
4779
|
</svg>
|
|
3364
4780
|
</button>
|
|
3365
|
-
<button class="wallet-mini-btn" data-action="unbind" data-id="${
|
|
4781
|
+
<button class="wallet-mini-btn" data-action="unbind" data-id="${escapeHtml2(ch.id)}" title="\u89E3\u7ED1">
|
|
3366
4782
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3367
4783
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
3368
4784
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
@@ -3421,18 +4837,18 @@
|
|
|
3421
4837
|
return "?";
|
|
3422
4838
|
}
|
|
3423
4839
|
init();
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
4840
|
+
var agentAddModal = document.getElementById("agent-add-modal");
|
|
4841
|
+
var agentAddTitle = document.getElementById("agent-add-title");
|
|
4842
|
+
var agentAddModalClose = document.getElementById("agent-add-modal-close");
|
|
4843
|
+
var agentAddName = document.getElementById("agent-add-name");
|
|
4844
|
+
var agentAddWallet = document.getElementById("agent-add-wallet");
|
|
4845
|
+
var agentAddAutoTools = document.getElementById("agent-add-auto-tools");
|
|
4846
|
+
var agentAddConfirmBtn = document.getElementById("agent-add-confirm-btn");
|
|
4847
|
+
var agentAddCancelBtn = document.getElementById("agent-add-cancel-btn");
|
|
4848
|
+
var agentAddWalletInfo = document.getElementById("agent-add-wallet-info");
|
|
4849
|
+
var agentGenerateWalletBtn = document.getElementById("agent-generate-wallet-btn");
|
|
4850
|
+
var pendingWalletSecret = null;
|
|
4851
|
+
var pendingWalletMnemonic = null;
|
|
3436
4852
|
function openAgentAddModal(existingChannel) {
|
|
3437
4853
|
if (!agentAddModal) return;
|
|
3438
4854
|
if (existingChannel) {
|
|
@@ -3481,17 +4897,17 @@
|
|
|
3481
4897
|
function formatWalletInfoHtml({ address, privateKey, mnemonic }) {
|
|
3482
4898
|
const parts = [
|
|
3483
4899
|
"\u2713 \u5DF2\u751F\u6210\u771F\u5B9E EVM \u94B1\u5305 (BIP-39 + secp256k1 + EIP-55)",
|
|
3484
|
-
"<strong>\u5730\u5740:</strong> <code>" +
|
|
4900
|
+
"<strong>\u5730\u5740:</strong> <code>" + escapeHtml2(address) + "</code>"
|
|
3485
4901
|
];
|
|
3486
4902
|
if (mnemonic) {
|
|
3487
4903
|
parts.push(
|
|
3488
4904
|
"<strong>\u52A9\u8BB0\u8BCD (12 \u8BCD, \u8BF7\u6284\u5199\u4FDD\u5B58):</strong>",
|
|
3489
|
-
'<code style="color:#fc6;word-break:break-all;">' +
|
|
4905
|
+
'<code style="color:#fc6;word-break:break-all;">' + escapeHtml2(mnemonic) + "</code>"
|
|
3490
4906
|
);
|
|
3491
4907
|
}
|
|
3492
4908
|
parts.push(
|
|
3493
4909
|
"<strong>\u79C1\u94A5 (0x + 32 \u5B57\u8282):</strong>",
|
|
3494
|
-
'<code style="color:#f88;word-break:break-all;">' +
|
|
4910
|
+
'<code style="color:#f88;word-break:break-all;">' + escapeHtml2(privateKey) + "</code>",
|
|
3495
4911
|
'<small style="color:#f88;">\u26A0 \u52A9\u8BB0\u8BCD + \u79C1\u94A5\u5747\u4EC5\u5728\u672C\u6D4F\u89C8\u5668\u5185\u5B58, \u5173\u95ED\u9875\u9762\u540E\u65E0\u6CD5\u627E\u56DE\u3002</small>',
|
|
3496
4912
|
'<small style="color:#999;">\u7B7E\u540D\u7ED1\u5B9A\u5230 channel DID (EIP-191 personal_sign) \u4F1A\u53D1\u9001\u5230\u670D\u52A1\u7AEF, \u7528\u4E8E\u8BC1\u660E\u94B1\u5305\u6240\u6709\u6743\u3002</small>'
|
|
3497
4913
|
);
|
|
@@ -3508,7 +4924,7 @@
|
|
|
3508
4924
|
pendingWalletMnemonic = wallet.mnemonic;
|
|
3509
4925
|
agentAddWalletInfo.innerHTML = formatWalletInfoHtml(wallet);
|
|
3510
4926
|
} catch (err) {
|
|
3511
|
-
agentAddWalletInfo.innerHTML = "\u2717 \u751F\u6210\u94B1\u5305\u5931\u8D25: " +
|
|
4927
|
+
agentAddWalletInfo.innerHTML = "\u2717 \u751F\u6210\u94B1\u5305\u5931\u8D25: " + escapeHtml2(err.message);
|
|
3512
4928
|
}
|
|
3513
4929
|
});
|
|
3514
4930
|
}
|