@bolloon/bolloon-agent 0.1.40 → 0.1.41

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.
@@ -1,375 +1,279 @@
1
- /**
2
- * step-timeline.ts — 气泡内 4 状态步骤条 (2026-06-15)
3
- *
4
- * 职责 (单一):
5
- * - 给每条 AI 消息挂一个可折叠/展开的步骤条
6
- * - 状态机: idle ○ / active ● / done 🟢 / error 🔴
7
- * - 摘要条 + 详情区共用同一份 Step[] 数据
8
- * - 流式期间在 streaming 元素内, finalize 时搬到正式 message
9
- * - 折叠状态按 channel 持久化 (localStorage)
10
- *
11
- * 状态 (模块私有, 每个 message 一个实例):
12
- * - steps: Step[]
13
- * - currentIndex: number
14
- * - expanded: boolean (受 localStorage 影响)
15
- *
16
- * 输入 (从 message-renderer 调用):
17
- * - createEmptyStepTimeline(channelId) -> HTMLElement
18
- * - mountStepTimeline(messageEl, channelId) // 已有 message 上挂 timeline
19
- * - pushStepToTimeline(timelineEl, type, data) // 订阅 step_start/done/error
20
- * - migrateStepTimeline(fromEl, toEl) // finalize 时搬家
21
- *
22
- * 输出 (DOM):
23
- * - .step-timeline / .step-timeline-summary / .step-timeline-body
24
- * - .step-dot (摘要条圆点) / .step-timeline-node (详情区节点)
25
- */
26
1
  const MAX_VISIBLE_NODES = 8;
27
- const STORAGE_PREFIX = 'bolloon.stepTimeline.expanded.';
28
- const stateMap = new WeakMap();
29
- // ---------------------------------------------------------------------------
30
- // 工具: 持久化
31
- // ---------------------------------------------------------------------------
2
+ const STORAGE_PREFIX = "bolloon.stepTimeline.expanded.";
3
+ const stateMap = /* @__PURE__ */ new WeakMap();
32
4
  function readExpanded(channelId) {
33
- if (!channelId)
34
- return false;
35
- try {
36
- return localStorage.getItem(STORAGE_PREFIX + channelId) === '1';
37
- }
38
- catch {
39
- return false;
40
- }
5
+ if (!channelId) return false;
6
+ try {
7
+ return localStorage.getItem(STORAGE_PREFIX + channelId) === "1";
8
+ } catch {
9
+ return false;
10
+ }
41
11
  }
42
12
  function writeExpanded(channelId, expanded) {
43
- if (!channelId)
44
- return;
45
- try {
46
- localStorage.setItem(STORAGE_PREFIX + channelId, expanded ? '1' : '0');
47
- }
48
- catch { /* localStorage 写失败 (隐私模式), 静默 */ }
13
+ if (!channelId) return;
14
+ try {
15
+ localStorage.setItem(STORAGE_PREFIX + channelId, expanded ? "1" : "0");
16
+ } catch {
17
+ }
49
18
  }
50
- // ---------------------------------------------------------------------------
51
- // 工具: 摘要条 title
52
- // ---------------------------------------------------------------------------
53
19
  function computeTitle(steps) {
54
- if (steps.length === 0)
55
- return '执行步骤';
56
- // 优先 active, 其次最后一个 error
57
- const activeIdx = steps.findIndex(s => s.status === 'active');
58
- if (activeIdx >= 0)
59
- return `● 执行中 · ${steps[activeIdx].name}`;
60
- const lastErrorIdx = (() => {
61
- for (let i = steps.length - 1; i >= 0; i--) {
62
- if (steps[i].status === 'error')
63
- return i;
64
- }
65
- return -1;
66
- })();
67
- if (lastErrorIdx >= 0)
68
- return `✗ 失败 · ${steps[lastErrorIdx].name}`;
69
- return `✓ 已完成 · ${steps.length} 步`;
20
+ if (steps.length === 0) return "\u6267\u884C\u6B65\u9AA4";
21
+ const activeIdx = steps.findIndex((s) => s.status === "active");
22
+ if (activeIdx >= 0) return `\u25CF \u6267\u884C\u4E2D \xB7 ${steps[activeIdx].name}`;
23
+ const lastErrorIdx = (() => {
24
+ for (let i = steps.length - 1; i >= 0; i--) {
25
+ if (steps[i].status === "error") return i;
26
+ }
27
+ return -1;
28
+ })();
29
+ if (lastErrorIdx >= 0) return `\u2717 \u5931\u8D25 \xB7 ${steps[lastErrorIdx].name}`;
30
+ return `\u2713 \u5DF2\u5B8C\u6210 \xB7 ${steps.length} \u6B65`;
70
31
  }
71
- // ---------------------------------------------------------------------------
72
- // 核心: 渲染 (摘要条 + 详情区, 共用 steps 状态)
73
- // ---------------------------------------------------------------------------
74
32
  function render(timelineEl) {
75
- const state = stateMap.get(timelineEl);
76
- if (!state)
77
- return;
78
- const { steps, expanded, showAll } = state;
79
- const titleEl = timelineEl.querySelector('[data-current-tool]');
80
- if (titleEl)
81
- titleEl.textContent = computeTitle(steps);
82
- // 摘要条 dots
83
- const dotsEl = timelineEl.querySelector('[data-dots]');
84
- if (dotsEl) {
85
- // diff: 复用现有 dot, 多余 remove, 缺少 append
86
- const existing = Array.from(dotsEl.children);
87
- for (let i = 0; i < steps.length; i++) {
88
- const step = steps[i];
89
- let dot = existing[i];
90
- if (!dot) {
91
- dot = document.createElement('span');
92
- dot.className = 'step-dot';
93
- dot.setAttribute('data-index', String(i));
94
- dotsEl.appendChild(dot);
95
- }
96
- dot.setAttribute('data-status', step.status);
97
- dot.setAttribute('title', `${step.name} · ${step.status}`);
98
- }
99
- while (dotsEl.children.length > steps.length) {
100
- dotsEl.removeChild(dotsEl.lastChild);
101
- }
33
+ const state = stateMap.get(timelineEl);
34
+ if (!state) return;
35
+ const { steps, expanded, showAll } = state;
36
+ const titleEl = timelineEl.querySelector("[data-current-tool]");
37
+ if (titleEl) titleEl.textContent = computeTitle(steps);
38
+ const dotsEl = timelineEl.querySelector("[data-dots]");
39
+ if (dotsEl) {
40
+ const existing = Array.from(dotsEl.children);
41
+ for (let i = 0; i < steps.length; i++) {
42
+ const step = steps[i];
43
+ let dot = existing[i];
44
+ if (!dot) {
45
+ dot = document.createElement("span");
46
+ dot.className = "step-dot";
47
+ dot.setAttribute("data-index", String(i));
48
+ dotsEl.appendChild(dot);
49
+ }
50
+ dot.setAttribute("data-status", step.status);
51
+ dot.setAttribute("title", `${step.name} \xB7 ${step.status}`);
102
52
  }
103
- // 详情区 list
104
- const listEl = timelineEl.querySelector('[data-list]');
105
- if (listEl) {
106
- const hiddenCount = Math.max(0, steps.length - MAX_VISIBLE_NODES);
107
- const visibleSteps = showAll ? steps : steps.slice(-MAX_VISIBLE_NODES);
108
- const existing = Array.from(listEl.children);
109
- let htmlIdx = 0;
110
- // "+N 更多" 折叠行 (仅在 showAll=false 且有 hidden 时)
111
- if (!showAll && hiddenCount > 0) {
112
- let moreBtn = existing[htmlIdx];
113
- if (!moreBtn || !moreBtn.classList.contains('step-timeline-more')) {
114
- moreBtn = document.createElement('li');
115
- moreBtn.className = 'step-timeline-more';
116
- moreBtn.setAttribute('role', 'button');
117
- moreBtn.textContent = `+ ${hiddenCount} 更多`;
118
- moreBtn.onclick = () => {
119
- state.showAll = true;
120
- render(timelineEl);
121
- };
122
- if (existing[htmlIdx] && existing[htmlIdx] !== moreBtn) {
123
- listEl.replaceChild(moreBtn, existing[htmlIdx]);
124
- }
125
- else {
126
- listEl.appendChild(moreBtn);
127
- }
128
- }
129
- else {
130
- moreBtn.textContent = `+ ${hiddenCount} 更多`;
131
- }
132
- htmlIdx++;
133
- }
134
- for (let i = 0; i < visibleSteps.length; i++) {
135
- const step = visibleSteps[i];
136
- const realIndex = showAll ? i : (steps.length - visibleSteps.length + i);
137
- let node = existing[htmlIdx];
138
- if (!node || !node.classList.contains('step-timeline-node')) {
139
- node = document.createElement('li');
140
- node.className = 'step-timeline-node';
141
- node.setAttribute('data-index', String(realIndex));
142
- const marker = document.createElement('span');
143
- marker.className = 'step-timeline-marker';
144
- const label = document.createElement('span');
145
- label.className = 'step-timeline-label';
146
- const args = document.createElement('span');
147
- args.className = 'step-timeline-args';
148
- node.appendChild(marker);
149
- node.appendChild(label);
150
- node.appendChild(args);
151
- if (existing[htmlIdx] && existing[htmlIdx] !== node) {
152
- listEl.replaceChild(node, existing[htmlIdx]);
153
- }
154
- else {
155
- listEl.appendChild(node);
156
- }
157
- }
158
- else {
159
- node.setAttribute('data-index', String(realIndex));
160
- }
161
- node.setAttribute('data-status', step.status);
162
- const label = node.querySelector('.step-timeline-label');
163
- if (label)
164
- label.textContent = step.name;
165
- const argsEl = node.querySelector('.step-timeline-args');
166
- if (argsEl) {
167
- const argStr = step.args && Object.keys(step.args).length > 0
168
- ? JSON.stringify(step.args).slice(0, 60)
169
- : '';
170
- argsEl.textContent = argStr;
171
- argsEl.style.display = argStr ? '' : 'none';
172
- }
173
- htmlIdx++;
174
- }
175
- // 清理多余 li
176
- while (listEl.children.length > htmlIdx) {
177
- listEl.removeChild(listEl.lastChild);
178
- }
179
- }
180
- // 折叠态: 空状态显示占位, 有节点就保持
181
- if (steps.length === 0) {
182
- timelineEl.setAttribute('data-empty', 'true');
53
+ while (dotsEl.children.length > steps.length) {
54
+ dotsEl.removeChild(dotsEl.lastChild);
183
55
  }
184
- else {
185
- timelineEl.removeAttribute('data-empty');
186
- }
187
- // 折叠 class
188
- const body = timelineEl.querySelector('[data-body]');
189
- if (body) {
190
- if (expanded) {
191
- body.style.maxHeight = body.scrollHeight + 'px';
192
- setTimeout(() => {
193
- // 动画结束后清掉, 让内容自适应 (step 进来时能撑开)
194
- if (state.expanded)
195
- body.style.maxHeight = '';
196
- }, 300);
56
+ }
57
+ const listEl = timelineEl.querySelector("[data-list]");
58
+ if (listEl) {
59
+ const hiddenCount = Math.max(0, steps.length - MAX_VISIBLE_NODES);
60
+ const visibleSteps = showAll ? steps : steps.slice(-MAX_VISIBLE_NODES);
61
+ const existing = Array.from(listEl.children);
62
+ let htmlIdx = 0;
63
+ if (!showAll && hiddenCount > 0) {
64
+ let moreBtn = existing[htmlIdx];
65
+ if (!moreBtn || !moreBtn.classList.contains("step-timeline-more")) {
66
+ moreBtn = document.createElement("li");
67
+ moreBtn.className = "step-timeline-more";
68
+ moreBtn.setAttribute("role", "button");
69
+ moreBtn.textContent = `+ ${hiddenCount} \u66F4\u591A`;
70
+ moreBtn.onclick = () => {
71
+ state.showAll = true;
72
+ render(timelineEl);
73
+ };
74
+ if (existing[htmlIdx] && existing[htmlIdx] !== moreBtn) {
75
+ listEl.replaceChild(moreBtn, existing[htmlIdx]);
76
+ } else {
77
+ listEl.appendChild(moreBtn);
197
78
  }
198
- else {
199
- body.style.maxHeight = '0';
79
+ } else {
80
+ moreBtn.textContent = `+ ${hiddenCount} \u66F4\u591A`;
81
+ }
82
+ htmlIdx++;
83
+ }
84
+ for (let i = 0; i < visibleSteps.length; i++) {
85
+ const step = visibleSteps[i];
86
+ const realIndex = showAll ? i : steps.length - visibleSteps.length + i;
87
+ let node = existing[htmlIdx];
88
+ if (!node || !node.classList.contains("step-timeline-node")) {
89
+ node = document.createElement("li");
90
+ node.className = "step-timeline-node";
91
+ node.setAttribute("data-index", String(realIndex));
92
+ const marker = document.createElement("span");
93
+ marker.className = "step-timeline-marker";
94
+ const label2 = document.createElement("span");
95
+ label2.className = "step-timeline-label";
96
+ const args = document.createElement("span");
97
+ args.className = "step-timeline-args";
98
+ node.appendChild(marker);
99
+ node.appendChild(label2);
100
+ node.appendChild(args);
101
+ if (existing[htmlIdx] && existing[htmlIdx] !== node) {
102
+ listEl.replaceChild(node, existing[htmlIdx]);
103
+ } else {
104
+ listEl.appendChild(node);
200
105
  }
106
+ } else {
107
+ node.setAttribute("data-index", String(realIndex));
108
+ }
109
+ node.setAttribute("data-status", step.status);
110
+ const label = node.querySelector(".step-timeline-label");
111
+ if (label) label.textContent = step.name;
112
+ const argsEl = node.querySelector(".step-timeline-args");
113
+ if (argsEl) {
114
+ const argStr = step.args && Object.keys(step.args).length > 0 ? JSON.stringify(step.args).slice(0, 60) : "";
115
+ argsEl.textContent = argStr;
116
+ argsEl.style.display = argStr ? "" : "none";
117
+ }
118
+ htmlIdx++;
119
+ }
120
+ while (listEl.children.length > htmlIdx) {
121
+ listEl.removeChild(listEl.lastChild);
201
122
  }
202
- const arrow = timelineEl.querySelector('.step-timeline-arrow');
203
- if (arrow)
204
- arrow.style.transform = expanded ? 'rotate(180deg)' : 'rotate(0deg)';
205
- // 自动滚到底 (新节点进来时, 详情区跟到底)
206
- if (expanded && listEl) {
207
- listEl.scrollTop = listEl.scrollHeight;
123
+ }
124
+ if (steps.length === 0) {
125
+ timelineEl.setAttribute("data-empty", "true");
126
+ } else {
127
+ timelineEl.removeAttribute("data-empty");
128
+ }
129
+ const body = timelineEl.querySelector("[data-body]");
130
+ if (body) {
131
+ if (expanded) {
132
+ body.style.maxHeight = body.scrollHeight + "px";
133
+ setTimeout(() => {
134
+ if (state.expanded) body.style.maxHeight = "";
135
+ }, 300);
136
+ } else {
137
+ body.style.maxHeight = "0";
208
138
  }
139
+ }
140
+ const arrow = timelineEl.querySelector(".step-timeline-arrow");
141
+ if (arrow) arrow.style.transform = expanded ? "rotate(180deg)" : "rotate(0deg)";
142
+ if (expanded && listEl) {
143
+ listEl.scrollTop = listEl.scrollHeight;
144
+ }
209
145
  }
210
- // ---------------------------------------------------------------------------
211
- // 公共: 创建一个空 timeline (AI 消息创建时挂的占位)
212
- // ---------------------------------------------------------------------------
213
- export function createEmptyStepTimeline(channelId = null) {
214
- const root = document.createElement('div');
215
- root.className = 'step-timeline';
216
- root.setAttribute('data-step-timeline', '');
217
- root.setAttribute('data-empty', 'true');
218
- // 摘要条
219
- const summary = document.createElement('div');
220
- summary.className = 'step-timeline-summary';
221
- summary.setAttribute('data-summary', '');
222
- const titleSpan = document.createElement('span');
223
- titleSpan.className = 'step-timeline-title';
224
- const titleContent = document.createElement('span');
225
- titleContent.setAttribute('data-current-tool', '');
226
- titleContent.textContent = '执行步骤';
227
- titleSpan.appendChild(titleContent);
228
- const dots = document.createElement('div');
229
- dots.className = 'step-timeline-dots';
230
- dots.setAttribute('data-dots', '');
231
- const arrow = document.createElement('span');
232
- arrow.className = 'step-timeline-arrow';
233
- arrow.textContent = '▾';
234
- summary.appendChild(titleSpan);
235
- summary.appendChild(dots);
236
- summary.appendChild(arrow);
237
- summary.onclick = () => {
238
- const state = stateMap.get(root);
239
- if (!state)
240
- return;
241
- state.expanded = !state.expanded;
242
- writeExpanded(state.channelId, state.expanded);
243
- render(root);
244
- };
245
- // 详情区
246
- const body = document.createElement('div');
247
- body.className = 'step-timeline-body';
248
- body.setAttribute('data-body', '');
249
- const list = document.createElement('ul');
250
- list.className = 'step-timeline-list';
251
- list.setAttribute('data-list', '');
252
- body.appendChild(list);
253
- root.appendChild(summary);
254
- root.appendChild(body);
255
- // 初始折叠态
256
- const initialExpanded = readExpanded(channelId);
257
- stateMap.set(root, {
258
- steps: [],
259
- currentIndex: -1,
260
- expanded: initialExpanded,
261
- channelId: channelId || null,
262
- showAll: false,
263
- });
146
+ function createEmptyStepTimeline(channelId = null) {
147
+ const root = document.createElement("div");
148
+ root.className = "step-timeline";
149
+ root.setAttribute("data-step-timeline", "");
150
+ root.setAttribute("data-empty", "true");
151
+ const summary = document.createElement("div");
152
+ summary.className = "step-timeline-summary";
153
+ summary.setAttribute("data-summary", "");
154
+ const titleSpan = document.createElement("span");
155
+ titleSpan.className = "step-timeline-title";
156
+ const titleContent = document.createElement("span");
157
+ titleContent.setAttribute("data-current-tool", "");
158
+ titleContent.textContent = "\u6267\u884C\u6B65\u9AA4";
159
+ titleSpan.appendChild(titleContent);
160
+ const dots = document.createElement("div");
161
+ dots.className = "step-timeline-dots";
162
+ dots.setAttribute("data-dots", "");
163
+ const arrow = document.createElement("span");
164
+ arrow.className = "step-timeline-arrow";
165
+ arrow.textContent = "\u25BE";
166
+ summary.appendChild(titleSpan);
167
+ summary.appendChild(dots);
168
+ summary.appendChild(arrow);
169
+ summary.onclick = () => {
170
+ const state = stateMap.get(root);
171
+ if (!state) return;
172
+ state.expanded = !state.expanded;
173
+ writeExpanded(state.channelId, state.expanded);
264
174
  render(root);
265
- return root;
175
+ };
176
+ const body = document.createElement("div");
177
+ body.className = "step-timeline-body";
178
+ body.setAttribute("data-body", "");
179
+ const list = document.createElement("ul");
180
+ list.className = "step-timeline-list";
181
+ list.setAttribute("data-list", "");
182
+ body.appendChild(list);
183
+ root.appendChild(summary);
184
+ root.appendChild(body);
185
+ const initialExpanded = readExpanded(channelId);
186
+ stateMap.set(root, {
187
+ steps: [],
188
+ currentIndex: -1,
189
+ expanded: initialExpanded,
190
+ channelId: channelId || null,
191
+ showAll: false
192
+ });
193
+ render(root);
194
+ return root;
266
195
  }
267
- // ---------------------------------------------------------------------------
268
- // 公共: timeline 挂到 message 上 (初始化或补挂)
269
- // ---------------------------------------------------------------------------
270
- export function mountStepTimeline(messageEl, channelId) {
271
- // 已经挂过 复用
272
- const existing = messageEl.querySelector('[data-step-timeline]');
273
- if (existing) {
274
- const state = stateMap.get(existing);
275
- if (state && channelId && !state.channelId)
276
- state.channelId = channelId;
277
- return existing;
278
- }
279
- // 找插入点: .bubble 之后, .message-actions 之前
280
- const bubble = messageEl.querySelector('.bubble');
281
- const actions = messageEl.querySelector('.message-actions');
282
- const timeline = createEmptyStepTimeline(channelId);
283
- if (actions && actions.parentNode === messageEl) {
284
- messageEl.insertBefore(timeline, actions);
285
- }
286
- else if (bubble && bubble.parentNode === messageEl) {
287
- bubble.parentNode.insertBefore(timeline, bubble.nextSibling);
288
- }
289
- else {
290
- messageEl.appendChild(timeline);
291
- }
292
- return timeline;
196
+ function mountStepTimeline(messageEl, channelId) {
197
+ const existing = messageEl.querySelector("[data-step-timeline]");
198
+ if (existing) {
199
+ const state = stateMap.get(existing);
200
+ if (state && channelId && !state.channelId) state.channelId = channelId;
201
+ return existing;
202
+ }
203
+ const bubble = messageEl.querySelector(".bubble");
204
+ const actions = messageEl.querySelector(".message-actions");
205
+ const timeline = createEmptyStepTimeline(channelId);
206
+ if (actions && actions.parentNode === messageEl) {
207
+ messageEl.insertBefore(timeline, actions);
208
+ } else if (bubble && bubble.parentNode === messageEl) {
209
+ bubble.parentNode.insertBefore(timeline, bubble.nextSibling);
210
+ } else {
211
+ messageEl.appendChild(timeline);
212
+ }
213
+ return timeline;
293
214
  }
294
- // ---------------------------------------------------------------------------
295
- // 公共: 推一个 step 事件 (start/done/error)
296
- // ---------------------------------------------------------------------------
297
- export function pushStepToTimeline(timelineEl, eventType, data) {
298
- const state = stateMap.get(timelineEl);
299
- if (!state)
300
- return;
301
- const { steps } = state;
302
- if (eventType === 'step_start') {
303
- state.currentIndex = steps.length;
304
- steps.push({
305
- name: data.tool,
306
- status: 'active',
307
- args: data.args,
308
- eventType,
309
- });
310
- }
311
- else {
312
- // step_done / step_error — 找当前 active 节点关闭
313
- // 优先匹配 currentIndex, 其次回退到最后一个 active
314
- let targetIdx = state.currentIndex;
315
- if (targetIdx < 0 || !steps[targetIdx] || steps[targetIdx].name !== data.tool) {
316
- // 兜底: 找最后一个同名 active
317
- for (let i = steps.length - 1; i >= 0; i--) {
318
- if (steps[i].name === data.tool && steps[i].status === 'active') {
319
- targetIdx = i;
320
- break;
321
- }
322
- }
323
- }
324
- if (targetIdx < 0) {
325
- // 没有 active 节点匹配, 当作新 error 节点
326
- targetIdx = steps.length;
327
- steps.push({ name: data.tool, status: 'error', eventType });
328
- }
329
- if (eventType === 'step_done') {
330
- steps[targetIdx].status = data.success === false ? 'error' : 'done';
331
- if (data.output)
332
- steps[targetIdx].output = data.output;
333
- if (data.error)
334
- steps[targetIdx].error = data.error;
335
- }
336
- else {
337
- steps[targetIdx].status = 'error';
338
- if (data.error)
339
- steps[targetIdx].error = data.error;
215
+ function pushStepToTimeline(timelineEl, eventType, data) {
216
+ const state = stateMap.get(timelineEl);
217
+ if (!state) return;
218
+ const { steps } = state;
219
+ if (eventType === "step_start") {
220
+ state.currentIndex = steps.length;
221
+ steps.push({
222
+ name: data.tool,
223
+ status: "active",
224
+ args: data.args,
225
+ eventType
226
+ });
227
+ } else {
228
+ let targetIdx = state.currentIndex;
229
+ if (targetIdx < 0 || !steps[targetIdx] || steps[targetIdx].name !== data.tool) {
230
+ for (let i = steps.length - 1; i >= 0; i--) {
231
+ if (steps[i].name === data.tool && steps[i].status === "active") {
232
+ targetIdx = i;
233
+ break;
340
234
  }
341
- steps[targetIdx].eventType = eventType;
342
- state.currentIndex = -1;
235
+ }
343
236
  }
344
- render(timelineEl);
345
- }
346
- // ---------------------------------------------------------------------------
347
- // 公共: finalize 时把 timeline 元素从 from 搬到 to (避免重渲)
348
- // 接受 timelineEl 本身或 fromEl 内部的 timeline
349
- // ---------------------------------------------------------------------------
350
- export function migrateStepTimeline(fromEl, toEl) {
351
- const fromTimeline = fromEl.querySelector('[data-step-timeline]');
352
- const toTimeline = toEl.querySelector('[data-step-timeline]');
353
- if (!fromTimeline)
354
- return;
355
- // 移除 toEl 的占位, 搬 fromEl 的过来
356
- if (toTimeline && toTimeline !== fromTimeline) {
357
- toTimeline.remove();
358
- }
359
- if (toTimeline !== fromTimeline) {
360
- toEl.appendChild(fromTimeline);
237
+ if (targetIdx < 0) {
238
+ targetIdx = steps.length;
239
+ steps.push({ name: data.tool, status: "error", eventType });
361
240
  }
362
- // 重渲一次 (channelId 注入)
363
- const state = stateMap.get(fromTimeline);
364
- if (state) {
365
- render(fromTimeline);
241
+ if (eventType === "step_done") {
242
+ steps[targetIdx].status = data.success === false ? "error" : "done";
243
+ if (data.output) steps[targetIdx].output = data.output;
244
+ if (data.error) steps[targetIdx].error = data.error;
245
+ } else {
246
+ steps[targetIdx].status = "error";
247
+ if (data.error) steps[targetIdx].error = data.error;
366
248
  }
249
+ steps[targetIdx].eventType = eventType;
250
+ state.currentIndex = -1;
251
+ }
252
+ render(timelineEl);
253
+ }
254
+ function migrateStepTimeline(fromEl, toEl) {
255
+ const fromTimeline = fromEl.querySelector("[data-step-timeline]");
256
+ const toTimeline = toEl.querySelector("[data-step-timeline]");
257
+ if (!fromTimeline) return;
258
+ if (toTimeline && toTimeline !== fromTimeline) {
259
+ toTimeline.remove();
260
+ }
261
+ if (toTimeline !== fromTimeline) {
262
+ toEl.appendChild(fromTimeline);
263
+ }
264
+ const state = stateMap.get(fromTimeline);
265
+ if (state) {
266
+ render(fromTimeline);
267
+ }
367
268
  }
368
- // ---------------------------------------------------------------------------
369
- // 公共: 拿到一个 message 内的 timeline (无则返回 null)
370
- // ---------------------------------------------------------------------------
371
- export function getStepTimeline(messageEl) {
372
- if (!messageEl)
373
- return null;
374
- return messageEl.querySelector('[data-step-timeline]');
269
+ function getStepTimeline(messageEl) {
270
+ if (!messageEl) return null;
271
+ return messageEl.querySelector("[data-step-timeline]");
375
272
  }
273
+ export {
274
+ createEmptyStepTimeline,
275
+ getStepTimeline,
276
+ migrateStepTimeline,
277
+ mountStepTimeline,
278
+ pushStepToTimeline
279
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolloon/bolloon-agent",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "type": "module",
5
5
  "description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
6
6
  "main": "dist/cli.js",