@brainpilot/web 0.0.10 → 0.0.11
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/assets/index-DkoqxJfs.css +1 -0
- package/dist/assets/index-DtLW483q.js +451 -0
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/src/__tests__/messageGroups.test.ts +150 -0
- package/src/__tests__/newUiEvents.test.ts +32 -0
- package/src/components/chat/MessageStream.tsx +102 -32
- package/src/components/chat/PromptComposer.tsx +1 -0
- package/src/components/demo/DemoView.tsx +1 -1
- package/src/components/session/AgentTraceViews.tsx +5 -9
- package/src/components/settings/KnowledgeBasePanel.tsx +307 -143
- package/src/components/settings/SettingsDialog.tsx +115 -57
- package/src/components/shell/SandboxStatus.tsx +128 -84
- package/src/contexts/messageGroups.ts +110 -4
- package/src/contexts/messageReducer.ts +11 -1
- package/src/i18n/messages/chat.ts +10 -0
- package/src/i18n/messages/sandbox.ts +3 -0
- package/src/i18n/messages/settings.ts +40 -4
- package/src/i18n/messages/trace.ts +0 -2
- package/src/styles/global.css +821 -69
- package/src/utils/api.ts +63 -0
- package/dist/assets/index-D63mUJxx.js +0 -450
- package/dist/assets/index-D8J9Cnup.css +0 -1
package/dist/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="color-scheme" content="light dark" />
|
|
7
7
|
<title>BrainPilot</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DtLW483q.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DkoqxJfs.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainpilot/web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=22"
|
|
6
6
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@brainpilot/protocol": "^0.0.
|
|
34
|
+
"@brainpilot/protocol": "^0.0.11",
|
|
35
35
|
"@fontsource-variable/geist": "^5.2.9",
|
|
36
36
|
"@fontsource-variable/geist-mono": "^5.2.8",
|
|
37
37
|
"@types/react": "^18.3.12",
|
|
@@ -78,3 +78,153 @@ describe("buildRenderItems — activity block run-active awareness", () => {
|
|
|
78
78
|
expect(activities[1]).toMatchObject({ streaming: true });
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
|
+
|
|
82
|
+
/* -------------------------------------------------------------------------- *
|
|
83
|
+
* #219 — expert-agent activity grouping (groupExpert=true, 3rd arg).
|
|
84
|
+
* -------------------------------------------------------------------------- */
|
|
85
|
+
|
|
86
|
+
// A standalone assistant text row for an agent.
|
|
87
|
+
function text(over: Partial<ChatMessage> = {}): ChatMessage {
|
|
88
|
+
return {
|
|
89
|
+
id: over.id ?? `t-${Math.random().toString(36).slice(2)}`,
|
|
90
|
+
role: "assistant",
|
|
91
|
+
content: over.content ?? "hello",
|
|
92
|
+
createdAt: new Date().toISOString(),
|
|
93
|
+
agent: over.agent ?? "principal",
|
|
94
|
+
streaming: false,
|
|
95
|
+
kind: "text",
|
|
96
|
+
...over,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
describe("buildRenderItems — #219 expert grouping", () => {
|
|
101
|
+
it("legacy: default (groupExpert off) never emits an expertGroup", () => {
|
|
102
|
+
const items = buildRenderItems(
|
|
103
|
+
[text({ agent: "analyst" }), text({ agent: "analyst", id: "a2" })],
|
|
104
|
+
undefined,
|
|
105
|
+
);
|
|
106
|
+
expect(items.some((i) => i.type === "expertGroup")).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("folds a consecutive run of specialist text into one expertGroup", () => {
|
|
110
|
+
const items = buildRenderItems(
|
|
111
|
+
[
|
|
112
|
+
text({ id: "p1", agent: "principal", content: "PI intro" }),
|
|
113
|
+
text({ id: "a1", agent: "analyst" }),
|
|
114
|
+
text({ id: "a2", agent: "analyst" }),
|
|
115
|
+
text({ id: "p2", agent: "principal", content: "PI wrap" }),
|
|
116
|
+
],
|
|
117
|
+
undefined,
|
|
118
|
+
true,
|
|
119
|
+
);
|
|
120
|
+
// principal / group / principal
|
|
121
|
+
expect(items.map((i) => i.type)).toEqual(["single", "expertGroup", "single"]);
|
|
122
|
+
const group = items.find((i) => i.type === "expertGroup")!;
|
|
123
|
+
expect(group).toMatchObject({ type: "expertGroup", agents: ["analyst"] });
|
|
124
|
+
expect((group as { items: unknown[] }).items).toHaveLength(2);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("PI item breaks the specialist run into separate groups", () => {
|
|
128
|
+
const items = buildRenderItems(
|
|
129
|
+
[
|
|
130
|
+
text({ id: "a1", agent: "analyst" }),
|
|
131
|
+
text({ id: "a2", agent: "analyst" }),
|
|
132
|
+
text({ id: "p1", agent: "principal" }),
|
|
133
|
+
text({ id: "w1", agent: "writer" }),
|
|
134
|
+
text({ id: "w2", agent: "writer" }),
|
|
135
|
+
],
|
|
136
|
+
undefined,
|
|
137
|
+
true,
|
|
138
|
+
);
|
|
139
|
+
expect(items.filter((i) => i.type === "expertGroup")).toHaveLength(2);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("important events from a specialist escape the group and stay standalone", () => {
|
|
143
|
+
const err: ChatMessage = text({ id: "e1", agent: "analyst", kind: "error", content: "boom" });
|
|
144
|
+
const ask: ChatMessage = {
|
|
145
|
+
...text({ id: "q1", agent: "analyst" }),
|
|
146
|
+
kind: "ask_user",
|
|
147
|
+
askUser: { requestId: "r1", agent: "analyst", question: "?" } as never,
|
|
148
|
+
};
|
|
149
|
+
const items = buildRenderItems(
|
|
150
|
+
[text({ id: "a1", agent: "analyst" }), err, ask, text({ id: "a2", agent: "analyst" })],
|
|
151
|
+
undefined,
|
|
152
|
+
true,
|
|
153
|
+
);
|
|
154
|
+
// error + ask_user MUST render standalone (never buried in a collapsed group).
|
|
155
|
+
const singleIds = items
|
|
156
|
+
.filter((i) => i.type === "single")
|
|
157
|
+
.map((i) => (i as { message: ChatMessage }).message.id);
|
|
158
|
+
expect(singleIds).toContain("e1");
|
|
159
|
+
expect(singleIds).toContain("q1");
|
|
160
|
+
// the escapes are NOT swallowed into any group
|
|
161
|
+
const groupedIds = items
|
|
162
|
+
.filter((i) => i.type === "expertGroup")
|
|
163
|
+
.flatMap((i) => (i as { items: { type: string; message?: ChatMessage; id: string }[] }).items)
|
|
164
|
+
.map((it) => (it.type === "single" ? it.message!.id : it.id));
|
|
165
|
+
expect(groupedIds).not.toContain("e1");
|
|
166
|
+
expect(groupedIds).not.toContain("q1");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("warning+ system_message from a specialist escapes; info-level folds in", () => {
|
|
170
|
+
const warn: ChatMessage = {
|
|
171
|
+
...text({ id: "sw", agent: "analyst" }),
|
|
172
|
+
kind: "system_message",
|
|
173
|
+
systemMessage: { level: "warning", message: "heads up", recoverable: true } as never,
|
|
174
|
+
};
|
|
175
|
+
const info: ChatMessage = {
|
|
176
|
+
...text({ id: "si", agent: "analyst" }),
|
|
177
|
+
kind: "system_message",
|
|
178
|
+
systemMessage: { level: "info", message: "fyi", recoverable: true } as never,
|
|
179
|
+
};
|
|
180
|
+
const items = buildRenderItems(
|
|
181
|
+
[info, text({ id: "a1", agent: "analyst" }), warn],
|
|
182
|
+
undefined,
|
|
183
|
+
true,
|
|
184
|
+
);
|
|
185
|
+
// info + a1 fold together; warning stays standalone.
|
|
186
|
+
const group = items.find((i) => i.type === "expertGroup") as { items: RenderItemLike[] } | undefined;
|
|
187
|
+
expect(group).toBeTruthy();
|
|
188
|
+
expect(items.some((i) => i.type === "single" && (i as { message: ChatMessage }).message.id === "sw")).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("principal items never fold into a group", () => {
|
|
192
|
+
const items = buildRenderItems(
|
|
193
|
+
[text({ id: "p1", agent: "principal" }), text({ id: "p2", agent: "principal" })],
|
|
194
|
+
undefined,
|
|
195
|
+
true,
|
|
196
|
+
);
|
|
197
|
+
expect(items.every((i) => i.type === "single")).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("a lone specialist activity is left as-is (no double wrapper)", () => {
|
|
201
|
+
const items = buildRenderItems(
|
|
202
|
+
[step({ id: "a1", agent: "analyst", kind: "tool" })],
|
|
203
|
+
undefined,
|
|
204
|
+
true,
|
|
205
|
+
);
|
|
206
|
+
expect(items).toHaveLength(1);
|
|
207
|
+
expect(items[0].type).toBe("activity");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("multi-agent run dedups agent names and sets streaming from running set", () => {
|
|
211
|
+
const items = buildRenderItems(
|
|
212
|
+
[
|
|
213
|
+
text({ id: "a1", agent: "analyst" }),
|
|
214
|
+
text({ id: "w1", agent: "writer" }),
|
|
215
|
+
text({ id: "a2", agent: "analyst" }),
|
|
216
|
+
],
|
|
217
|
+
new Set(["writer"]),
|
|
218
|
+
true,
|
|
219
|
+
);
|
|
220
|
+
const group = items.find((i) => i.type === "expertGroup") as
|
|
221
|
+
| { agents: string[]; streaming: boolean }
|
|
222
|
+
| undefined;
|
|
223
|
+
expect(group).toBeTruthy();
|
|
224
|
+
expect(group!.agents.sort()).toEqual(["analyst", "writer"]);
|
|
225
|
+
expect(group!.streaming).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Minimal structural alias for readability in the escape test above.
|
|
230
|
+
type RenderItemLike = { type: string };
|
|
@@ -56,6 +56,38 @@ describe("system_message mapping", () => {
|
|
|
56
56
|
expect(out[0].systemMessage?.level).toBe("warning");
|
|
57
57
|
expect(out[0].content).toBe("watch out");
|
|
58
58
|
});
|
|
59
|
+
|
|
60
|
+
it("#167: coalesces repeated system_messages sharing a stable id (retry ticks)", () => {
|
|
61
|
+
const mk = (attempt: number) =>
|
|
62
|
+
({
|
|
63
|
+
type: "system_message",
|
|
64
|
+
id: "retry-librarian-run_1",
|
|
65
|
+
level: "warning",
|
|
66
|
+
message: `retrying (${attempt}/3)`,
|
|
67
|
+
agent: "librarian",
|
|
68
|
+
}) as WebSocketEvent;
|
|
69
|
+
let msgs = reduceMessagesForEvent([], mk(1));
|
|
70
|
+
msgs = reduceMessagesForEvent(msgs, mk(2));
|
|
71
|
+
msgs = reduceMessagesForEvent(msgs, mk(3));
|
|
72
|
+
// One bubble, updated in place to the latest attempt.
|
|
73
|
+
expect(msgs).toHaveLength(1);
|
|
74
|
+
expect(msgs[0].id).toBe("retry-librarian-run_1");
|
|
75
|
+
expect(msgs[0].content).toBe("retrying (3/3)");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("#167: system_messages without a stable id still append", () => {
|
|
79
|
+
let msgs = reduceMessagesForEvent([], {
|
|
80
|
+
type: "system_message",
|
|
81
|
+
level: "warning",
|
|
82
|
+
message: "one",
|
|
83
|
+
} as WebSocketEvent);
|
|
84
|
+
msgs = reduceMessagesForEvent(msgs, {
|
|
85
|
+
type: "system_message",
|
|
86
|
+
level: "warning",
|
|
87
|
+
message: "two",
|
|
88
|
+
} as WebSocketEvent);
|
|
89
|
+
expect(msgs).toHaveLength(2);
|
|
90
|
+
});
|
|
59
91
|
});
|
|
60
92
|
|
|
61
93
|
describe("ask_user mapping + submit + reducer round-trip", () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Check, ChevronDown, Copy } from "lucide-react";
|
|
1
|
+
import { Check, ChevronDown, Copy, Users } from "lucide-react";
|
|
2
2
|
import { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import type { ChatMessage } from "../../contracts/backend";
|
|
4
4
|
import { buildRenderItems } from "../../contexts/messageGroups";
|
|
@@ -46,6 +46,12 @@ interface MessageStreamProps {
|
|
|
46
46
|
* (demo replay), where messages are already terminal.
|
|
47
47
|
*/
|
|
48
48
|
runningAgents?: ReadonlySet<string>;
|
|
49
|
+
/**
|
|
50
|
+
* #219 — fold non-PI (specialist) agent activity into collapsible per-run
|
|
51
|
+
* groups so the Principal narrative reads cleanly by default. Off by default
|
|
52
|
+
* so demo replay keeps its flat, curated presentation.
|
|
53
|
+
*/
|
|
54
|
+
groupExpertActivity?: boolean;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
// Whether this message participates in same-agent avatar merging. User
|
|
@@ -80,15 +86,24 @@ function MessageStreamImpl({
|
|
|
80
86
|
onAskUserSubmit,
|
|
81
87
|
onRetryCancel,
|
|
82
88
|
runningAgents,
|
|
89
|
+
groupExpertActivity = false,
|
|
83
90
|
}: MessageStreamProps) {
|
|
84
91
|
const t = useT();
|
|
85
92
|
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
93
|
+
// #219 — audit mode: force every specialist group open (reasoning/tool folds
|
|
94
|
+
// inside stay independent, per issue).
|
|
95
|
+
const [expandAll, setExpandAll] = useState(false);
|
|
86
96
|
const stackRef = useRef<HTMLDivElement | null>(null);
|
|
87
97
|
const isPinnedRef = useRef(true);
|
|
88
98
|
|
|
89
99
|
const renderItems = useMemo(
|
|
90
|
-
() => buildRenderItems(messages, runningAgents),
|
|
91
|
-
[messages, runningAgents],
|
|
100
|
+
() => buildRenderItems(messages, runningAgents, groupExpertActivity),
|
|
101
|
+
[messages, runningAgents, groupExpertActivity],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const hasExpertGroup = useMemo(
|
|
105
|
+
() => renderItems.some((item) => item.type === "expertGroup"),
|
|
106
|
+
[renderItems],
|
|
92
107
|
);
|
|
93
108
|
|
|
94
109
|
// Avatar merging: a mergeable assistant/system row whose immediately
|
|
@@ -98,17 +113,26 @@ function MessageStreamImpl({
|
|
|
98
113
|
const continuationIds = useMemo(() => {
|
|
99
114
|
const set = new Set<string>();
|
|
100
115
|
let prevName: string | null = null;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
116
|
+
const walk = (items: typeof renderItems) => {
|
|
117
|
+
for (const item of items) {
|
|
118
|
+
if (item.type === "single" && isMergeable(item.message)) {
|
|
119
|
+
const name = mergeName(item.message);
|
|
120
|
+
if (prevName === name) {
|
|
121
|
+
set.add(item.message.id);
|
|
122
|
+
}
|
|
123
|
+
prevName = name;
|
|
124
|
+
} else if (item.type === "expertGroup") {
|
|
125
|
+
// A group is its own merge scope: the first row inside always shows
|
|
126
|
+
// its avatar, and the group boundary breaks the outer run.
|
|
127
|
+
prevName = null;
|
|
128
|
+
walk(item.items);
|
|
129
|
+
prevName = null;
|
|
130
|
+
} else {
|
|
131
|
+
prevName = null;
|
|
106
132
|
}
|
|
107
|
-
prevName = name;
|
|
108
|
-
} else {
|
|
109
|
-
prevName = null;
|
|
110
133
|
}
|
|
111
|
-
}
|
|
134
|
+
};
|
|
135
|
+
walk(renderItems);
|
|
112
136
|
return set;
|
|
113
137
|
}, [renderItems]);
|
|
114
138
|
|
|
@@ -452,6 +476,58 @@ function MessageStreamImpl({
|
|
|
452
476
|
return t("chat.thinking");
|
|
453
477
|
};
|
|
454
478
|
|
|
479
|
+
// A folded reasoning/tool activity block. Extracted so it renders identically
|
|
480
|
+
// at the top level and nested inside an expert group (#219).
|
|
481
|
+
const renderActivityBlock = (id: string, steps: ChatMessage[], streaming: boolean) => (
|
|
482
|
+
<div className="activity-block" key={id}>
|
|
483
|
+
<details>
|
|
484
|
+
<summary className="activity-summary" aria-label={t("chat.aria.expandThinking")}>
|
|
485
|
+
{streaming ? <span className="activity-summary__dot" /> : null}
|
|
486
|
+
<ChevronDown size={14} className="activity-summary__chevron" aria-hidden="true" />
|
|
487
|
+
<span className="activity-summary__subtitle">{activitySubtitle(steps, streaming)}</span>
|
|
488
|
+
</summary>
|
|
489
|
+
<div className="activity-steps">{steps.map(renderActivityStep)}</div>
|
|
490
|
+
</details>
|
|
491
|
+
</div>
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
// Render one top-level or nested render item (single row or activity block).
|
|
495
|
+
// expertGroup is handled by renderExpertGroup, not here.
|
|
496
|
+
const renderItem = (item: (typeof renderItems)[number]) => {
|
|
497
|
+
if (item.type === "single") {
|
|
498
|
+
return renderSingle(item.message, continuationIds.has(item.message.id));
|
|
499
|
+
}
|
|
500
|
+
if (item.type === "activity") {
|
|
501
|
+
return renderActivityBlock(item.id, item.steps, item.streaming);
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// #219 — a collapsed run of specialist-agent activity. Summary names the
|
|
507
|
+
// agent(s) and item count; the body reuses the normal single/activity
|
|
508
|
+
// renderers so reasoning/tool folds inside are preserved. `expandAll` (audit
|
|
509
|
+
// mode) forces every group open.
|
|
510
|
+
const renderExpertGroup = (item: Extract<(typeof renderItems)[number], { type: "expertGroup" }>) => {
|
|
511
|
+
const count = item.items.length;
|
|
512
|
+
const summary =
|
|
513
|
+
item.agents.length === 1
|
|
514
|
+
? t("chat.expertGroup.summary", { agent: item.agents[0], count })
|
|
515
|
+
: t("chat.expertGroup.summaryMulti", { n: item.agents.length, count });
|
|
516
|
+
return (
|
|
517
|
+
<div className="expert-group" key={item.id}>
|
|
518
|
+
<details open={expandAll || undefined}>
|
|
519
|
+
<summary className="expert-group__summary" aria-label={t("chat.aria.expandExpert")}>
|
|
520
|
+
{item.streaming ? <span className="activity-summary__dot" /> : null}
|
|
521
|
+
<ChevronDown size={14} className="activity-summary__chevron" aria-hidden="true" />
|
|
522
|
+
<Users size={13} className="expert-group__icon" aria-hidden="true" />
|
|
523
|
+
<span className="activity-summary__subtitle">{summary}</span>
|
|
524
|
+
</summary>
|
|
525
|
+
<div className="expert-group__body">{item.items.map(renderItem)}</div>
|
|
526
|
+
</details>
|
|
527
|
+
</div>
|
|
528
|
+
);
|
|
529
|
+
};
|
|
530
|
+
|
|
455
531
|
return (
|
|
456
532
|
<div
|
|
457
533
|
className={`message-stack ${className ?? ""}`}
|
|
@@ -459,30 +535,24 @@ function MessageStreamImpl({
|
|
|
459
535
|
onScroll={handleScroll}
|
|
460
536
|
ref={stackRef}
|
|
461
537
|
>
|
|
462
|
-
{showToolbarCount ? (
|
|
538
|
+
{showToolbarCount || hasExpertGroup ? (
|
|
463
539
|
<div className="message-stack__toolbar">
|
|
464
|
-
<span>{t("chat.messageCount", { count: messages.length })}</span>
|
|
540
|
+
{showToolbarCount ? <span>{t("chat.messageCount", { count: messages.length })}</span> : <span />}
|
|
541
|
+
{hasExpertGroup ? (
|
|
542
|
+
<button
|
|
543
|
+
className={`message-stack__audit-toggle ${expandAll ? "is-active" : ""}`}
|
|
544
|
+
onClick={() => setExpandAll((v) => !v)}
|
|
545
|
+
type="button"
|
|
546
|
+
aria-pressed={expandAll}
|
|
547
|
+
>
|
|
548
|
+
<Users size={12} aria-hidden="true" />
|
|
549
|
+
{expandAll ? t("chat.expertGroup.collapseAll") : t("chat.expertGroup.expandAll")}
|
|
550
|
+
</button>
|
|
551
|
+
) : null}
|
|
465
552
|
</div>
|
|
466
553
|
) : null}
|
|
467
554
|
{renderItems.map((item) =>
|
|
468
|
-
item.type === "
|
|
469
|
-
renderSingle(item.message, continuationIds.has(item.message.id))
|
|
470
|
-
) : (
|
|
471
|
-
<div className="activity-block" key={item.id}>
|
|
472
|
-
<details>
|
|
473
|
-
<summary className="activity-summary" aria-label={t("chat.aria.expandThinking")}>
|
|
474
|
-
{item.streaming ? <span className="activity-summary__dot" /> : null}
|
|
475
|
-
<ChevronDown size={14} className="activity-summary__chevron" aria-hidden="true" />
|
|
476
|
-
<span className="activity-summary__subtitle">
|
|
477
|
-
{activitySubtitle(item.steps, item.streaming)}
|
|
478
|
-
</span>
|
|
479
|
-
</summary>
|
|
480
|
-
<div className="activity-steps">
|
|
481
|
-
{item.steps.map(renderActivityStep)}
|
|
482
|
-
</div>
|
|
483
|
-
</details>
|
|
484
|
-
</div>
|
|
485
|
-
),
|
|
555
|
+
item.type === "expertGroup" ? renderExpertGroup(item) : renderItem(item),
|
|
486
556
|
)}
|
|
487
557
|
{showTiming && turnTiming && turnTiming.elapsedMs !== null ? (
|
|
488
558
|
<div className="message-stack__total" role="status">
|
|
@@ -308,6 +308,7 @@ export function PromptComposer() {
|
|
|
308
308
|
showTiming
|
|
309
309
|
turnTiming={turnTiming}
|
|
310
310
|
runningAgents={runningAgents}
|
|
311
|
+
groupExpertActivity
|
|
311
312
|
onAskUserSubmit={(requestId, answer) => void respondToInput(requestId, answer)}
|
|
312
313
|
onRetryCancel={() => void interruptCurrent()}
|
|
313
314
|
/>
|
|
@@ -614,7 +614,7 @@ export function DemoView({ resetSignal }: DemoViewProps = {}) {
|
|
|
614
614
|
{condensedMessages.length === 0 ? (
|
|
615
615
|
<p className="demo-panel__empty">{t("demo.conversation.empty")}</p>
|
|
616
616
|
) : (
|
|
617
|
-
<MessageStream messages={condensedMessages} showToolbarCount={false} className="demo-message-stream" />
|
|
617
|
+
<MessageStream messages={condensedMessages} showToolbarCount={false} groupExpertActivity className="demo-message-stream" />
|
|
618
618
|
)}
|
|
619
619
|
</section>
|
|
620
620
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Network, Pause, Play, RefreshCw, Search,
|
|
2
|
+
import { Network, Pause, Play, RefreshCw, Search, X } from "lucide-react";
|
|
3
3
|
import { TraceNode } from "../../contracts/backend";
|
|
4
4
|
import { useSessions } from "../../contexts/SessionContext";
|
|
5
5
|
import { useT } from "../../i18n/useT";
|
|
@@ -24,14 +24,10 @@ export function AgentsPanel() {
|
|
|
24
24
|
<section className="workspace-panel" aria-labelledby="agents-panel-heading">
|
|
25
25
|
<div className="workspace-panel__inner workspace-panel__inner--trace">
|
|
26
26
|
<header className="workspace-panel__header">
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
</span>
|
|
32
|
-
<h2 id="agents-panel-heading">{t("trace.agents.title")}</h2>
|
|
33
|
-
</div>
|
|
34
|
-
<UserRoundCog size={18} />
|
|
27
|
+
<h2 id="agents-panel-heading" className="workspace-panel__title-icon">
|
|
28
|
+
<Network size={18} />
|
|
29
|
+
{t("trace.agents.eyebrow")}
|
|
30
|
+
</h2>
|
|
35
31
|
</header>
|
|
36
32
|
|
|
37
33
|
{!currentSession ? (
|