@springbrand/message-panel 0.1.0 → 0.1.2
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/demo/camel-chat-showcase.d.ts +1 -0
- package/dist/demo/camel-chat-showcase.js +1007 -0
- package/dist/demo/fixtures.d.ts +32 -0
- package/dist/demo/fixtures.js +250 -0
- package/dist/demo/index.js +4 -0
- package/dist/demo/message-panel-chat-showcase.d.ts +1 -0
- package/dist/demo/message-panel-chat-showcase.js +114 -0
- package/dist/demo/message-panel-gallery.d.ts +1 -0
- package/dist/demo/message-panel-gallery.js +130 -0
- package/dist/src/adapters/ai-sdk.d.ts +4 -0
- package/dist/src/adapters/ai-sdk.js +25 -0
- package/dist/src/camel/camel-chat-messages.d.ts +44 -0
- package/dist/src/camel/camel-chat-messages.js +421 -0
- package/dist/src/camel/camel-model-select.d.ts +13 -0
- package/dist/src/camel/camel-model-select.js +7 -0
- package/dist/src/camel/camel-plan-bar.d.ts +5 -0
- package/dist/src/camel/camel-plan-bar.js +24 -0
- package/dist/src/camel/camel-prompt-input.d.ts +32 -0
- package/dist/src/camel/camel-prompt-input.js +182 -0
- package/dist/src/camel/camel-tool-presentation.d.ts +21 -0
- package/dist/src/camel/camel-tool-presentation.js +784 -0
- package/dist/src/camel/camel-turn.d.ts +57 -0
- package/dist/src/camel/camel-turn.js +203 -0
- package/dist/src/camel/index.js +7 -0
- package/dist/src/camel/turn-summary-bar.d.ts +8 -0
- package/dist/src/camel/turn-summary-bar.js +12 -0
- package/dist/src/chat-summary-panel.d.ts +22 -0
- package/dist/src/chat-summary-panel.js +26 -0
- package/dist/src/composer/chat-composer.d.ts +4 -0
- package/dist/src/composer/chat-composer.js +90 -0
- package/dist/src/composer/composer-attachments.d.ts +13 -0
- package/dist/src/composer/composer-attachments.js +47 -0
- package/dist/src/composer/composer-trigger-popover.d.ts +31 -0
- package/dist/src/composer/composer-trigger-popover.js +29 -0
- package/dist/src/composer/composer.d.ts +15 -0
- package/dist/src/composer/composer.js +180 -0
- package/dist/src/composer/drop-controller.d.ts +8 -0
- package/dist/src/composer/drop-controller.js +69 -0
- package/dist/src/composer/index.js +6 -0
- package/dist/src/composer/types.d.ts +62 -0
- package/dist/src/composer/types.js +1 -0
- package/dist/src/contracts.d.ts +94 -0
- package/dist/src/contracts.js +1 -0
- package/dist/src/conversation.d.ts +13 -0
- package/dist/src/conversation.js +18 -0
- package/dist/src/index.js +12 -0
- package/dist/src/internal/cn.d.ts +1 -0
- package/dist/src/internal/cn.js +3 -0
- package/dist/src/message-panel.d.ts +4 -0
- package/dist/src/message-panel.js +144 -0
- package/dist/src/message.d.ts +36 -0
- package/dist/src/message.js +83 -0
- package/dist/src/parts/index.d.ts +187 -0
- package/dist/src/parts/index.js +332 -0
- package/dist/src/parts/kind.d.ts +3 -0
- package/dist/src/parts/kind.js +69 -0
- package/dist/src/parts/plan.d.ts +10 -0
- package/dist/src/parts/plan.js +40 -0
- package/dist/src/parts/primitives.d.ts +70 -0
- package/dist/src/parts/primitives.js +153 -0
- package/dist/src/registry.d.ts +4 -0
- package/dist/src/registry.js +31 -0
- package/dist/src/status.d.ts +26 -0
- package/dist/src/status.js +74 -0
- package/package.json +17 -8
- package/demo/camel-chat-showcase.tsx +0 -1265
- package/demo/fixtures.ts +0 -292
- package/demo/message-panel-chat-showcase.tsx +0 -250
- package/demo/message-panel-gallery.tsx +0 -389
- package/src/adapters/ai-sdk.ts +0 -32
- package/src/camel/camel-chat-messages.tsx +0 -1420
- package/src/camel/camel-model-select.tsx +0 -88
- package/src/camel/camel-plan-bar.tsx +0 -115
- package/src/camel/camel-prompt-input.tsx +0 -540
- package/src/camel/camel-tool-presentation.tsx +0 -1270
- package/src/camel/camel-turn.ts +0 -315
- package/src/camel/turn-summary-bar.tsx +0 -81
- package/src/chat-summary-panel.tsx +0 -229
- package/src/composer/chat-composer.tsx +0 -216
- package/src/composer/composer-attachments.tsx +0 -138
- package/src/composer/composer-trigger-popover.tsx +0 -236
- package/src/composer/composer.tsx +0 -425
- package/src/composer/drop-controller.ts +0 -80
- package/src/composer/types.ts +0 -79
- package/src/contracts.ts +0 -139
- package/src/conversation.tsx +0 -76
- package/src/internal/cn.ts +0 -3
- package/src/message-panel.tsx +0 -368
- package/src/message.tsx +0 -254
- package/src/parts/index.tsx +0 -1149
- package/src/parts/kind.ts +0 -67
- package/src/parts/plan.ts +0 -63
- package/src/parts/primitives.tsx +0 -439
- package/src/registry.ts +0 -44
- package/src/status.tsx +0 -132
- /package/{demo/index.ts → dist/demo/index.d.ts} +0 -0
- /package/{src/camel/index.ts → dist/src/camel/index.d.ts} +0 -0
- /package/{src/composer/index.ts → dist/src/composer/index.d.ts} +0 -0
- /package/{src/index.ts → dist/src/index.d.ts} +0 -0
package/src/camel/camel-turn.ts
DELETED
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
import type { UIMessage } from "ai";
|
|
2
|
-
import { USER_STOP_REASON } from "@springbrand/agent-runtime/contracts";
|
|
3
|
-
import { resolvePartKind, toolNameOfPart } from "../parts/kind";
|
|
4
|
-
|
|
5
|
-
type UnknownRecord = Record<string, unknown>;
|
|
6
|
-
|
|
7
|
-
export type CamelPart = UIMessage["parts"][number];
|
|
8
|
-
|
|
9
|
-
export interface CamelMessageMetadata extends UnknownRecord {
|
|
10
|
-
authorDisplayName?: string;
|
|
11
|
-
completedAt?: number;
|
|
12
|
-
createdAt?: number;
|
|
13
|
-
error?: string;
|
|
14
|
-
interruptedByUser?: boolean;
|
|
15
|
-
messageSource?: string;
|
|
16
|
-
turnDurationMs?: number;
|
|
17
|
-
turnStartedAt?: number;
|
|
18
|
-
turnStatus?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface CamelPartRef {
|
|
22
|
-
messageId: string;
|
|
23
|
-
partIndex: number;
|
|
24
|
-
part: CamelPart;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface CamelAssistantTurn {
|
|
28
|
-
kind: "assistant";
|
|
29
|
-
key: string;
|
|
30
|
-
message: UIMessage;
|
|
31
|
-
messages: readonly [UIMessage];
|
|
32
|
-
actionMessageId: string;
|
|
33
|
-
parts: readonly CamelPartRef[];
|
|
34
|
-
traceParts: readonly CamelPartRef[];
|
|
35
|
-
finalParts: readonly CamelPartRef[];
|
|
36
|
-
terminalState?: {
|
|
37
|
-
kind: "interrupted" | "error";
|
|
38
|
-
title: string;
|
|
39
|
-
body?: string;
|
|
40
|
-
};
|
|
41
|
-
stepCount: number;
|
|
42
|
-
copyText: string;
|
|
43
|
-
replyText?: string;
|
|
44
|
-
durationMs?: number;
|
|
45
|
-
completedAt?: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type CamelStandaloneKind =
|
|
49
|
-
| "user"
|
|
50
|
-
| "notice"
|
|
51
|
-
| "slash-command"
|
|
52
|
-
| "local-command-output"
|
|
53
|
-
| "compact-summary";
|
|
54
|
-
|
|
55
|
-
export interface CamelStandaloneMessage {
|
|
56
|
-
kind: "message";
|
|
57
|
-
key: string;
|
|
58
|
-
message: UIMessage;
|
|
59
|
-
displayKind: CamelStandaloneKind;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export type CamelMessageItem = CamelAssistantTurn | CamelStandaloneMessage;
|
|
63
|
-
|
|
64
|
-
const TRACE_PART_KINDS = new Set(["tool", "parallel", "subagents"]);
|
|
65
|
-
|
|
66
|
-
function recordOf(value: unknown): UnknownRecord {
|
|
67
|
-
return value != null && typeof value === "object"
|
|
68
|
-
? (value as UnknownRecord)
|
|
69
|
-
: {};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function nonNegativeNumber(value: unknown): number | undefined {
|
|
73
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
74
|
-
? value
|
|
75
|
-
: undefined;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function camelMetadata(message: UIMessage): CamelMessageMetadata {
|
|
79
|
-
return recordOf(message.metadata) as CamelMessageMetadata;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export const camelMessageMetadata = camelMetadata;
|
|
83
|
-
|
|
84
|
-
function isVisibleText(
|
|
85
|
-
part: CamelPart,
|
|
86
|
-
): part is CamelPart & { type: "text"; text: string } {
|
|
87
|
-
return part.type === "text" && part.text.trim().length > 0;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isVisibleReasoning(part: CamelPart): boolean {
|
|
91
|
-
return part.type === "reasoning" && part.text.trim().length > 0;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function standalonePartKind(part: CamelPart): CamelStandaloneKind | null {
|
|
95
|
-
switch (part.type) {
|
|
96
|
-
case "data-notice":
|
|
97
|
-
return "notice";
|
|
98
|
-
case "data-slash-command":
|
|
99
|
-
return "slash-command";
|
|
100
|
-
case "data-local-command-output":
|
|
101
|
-
return "local-command-output";
|
|
102
|
-
case "data-compact-summary":
|
|
103
|
-
return "compact-summary";
|
|
104
|
-
default:
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function standaloneKind(message: UIMessage): CamelStandaloneKind | null {
|
|
110
|
-
for (const part of message.parts) {
|
|
111
|
-
const kind = standalonePartKind(part);
|
|
112
|
-
if (kind !== null) return kind;
|
|
113
|
-
}
|
|
114
|
-
return message.role === "user" ? "user" : null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function isDirectUserMessage(message: UIMessage): boolean {
|
|
118
|
-
return message.role === "user" && standaloneKind(message) === "user";
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function camelMessageText(message: UIMessage): string {
|
|
122
|
-
return message.parts
|
|
123
|
-
.filter(isVisibleText)
|
|
124
|
-
.map((part) => part.text)
|
|
125
|
-
.join("\n");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function camelCreatedAt(message: UIMessage): number | undefined {
|
|
129
|
-
return nonNegativeNumber(camelMetadata(message).createdAt);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function camelCompletedAt(message: UIMessage): number | undefined {
|
|
133
|
-
return (
|
|
134
|
-
nonNegativeNumber(camelMetadata(message).completedAt) ??
|
|
135
|
-
camelCreatedAt(message)
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function formatCamelMessageTime(timestamp: number): string {
|
|
140
|
-
const date = new Date(timestamp);
|
|
141
|
-
const now = new Date();
|
|
142
|
-
const isToday =
|
|
143
|
-
date.getFullYear() === now.getFullYear() &&
|
|
144
|
-
date.getMonth() === now.getMonth() &&
|
|
145
|
-
date.getDate() === now.getDate();
|
|
146
|
-
return date.toLocaleString([], {
|
|
147
|
-
...(isToday ? {} : { month: "short", day: "numeric" }),
|
|
148
|
-
hour: "numeric",
|
|
149
|
-
minute: "2-digit",
|
|
150
|
-
hour12: true,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function formatCamelTurnDuration(milliseconds: number): string {
|
|
155
|
-
const totalSeconds = Math.max(0, Math.floor(milliseconds / 1000));
|
|
156
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
157
|
-
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
158
|
-
const seconds = totalSeconds % 60;
|
|
159
|
-
return hours > 0
|
|
160
|
-
? `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`
|
|
161
|
-
: `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function isTraceWork(
|
|
165
|
-
part: CamelPart,
|
|
166
|
-
finalToolNames: readonly string[],
|
|
167
|
-
): boolean {
|
|
168
|
-
if (isVisibleReasoning(part)) return true;
|
|
169
|
-
const kind = resolvePartKind(part);
|
|
170
|
-
if (kind === "plan") return false;
|
|
171
|
-
const toolName = toolNameOfPart(part);
|
|
172
|
-
if (toolName !== null && finalToolNames.includes(toolName)) return false;
|
|
173
|
-
return TRACE_PART_KINDS.has(kind ?? "");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function isTraceBoundary(
|
|
177
|
-
part: CamelPart,
|
|
178
|
-
finalToolNames: readonly string[],
|
|
179
|
-
): boolean {
|
|
180
|
-
return part.type === "step-start" || isTraceWork(part, finalToolNames);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function terminalState(
|
|
184
|
-
message: UIMessage,
|
|
185
|
-
): CamelAssistantTurn["terminalState"] {
|
|
186
|
-
const metadata = camelMetadata(message);
|
|
187
|
-
if (metadata.interruptedByUser === true) {
|
|
188
|
-
return {
|
|
189
|
-
kind: "interrupted",
|
|
190
|
-
title: USER_STOP_REASON,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
if (
|
|
194
|
-
metadata.turnStatus === "aborted" &&
|
|
195
|
-
metadata.error === USER_STOP_REASON
|
|
196
|
-
) return undefined;
|
|
197
|
-
if (metadata.turnStatus === "error" || metadata.error) {
|
|
198
|
-
return {
|
|
199
|
-
kind: "error",
|
|
200
|
-
title: "Error",
|
|
201
|
-
body: metadata.error ?? "The response failed.",
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
return undefined;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function turnDuration(message: UIMessage): number | undefined {
|
|
208
|
-
const metadata = camelMetadata(message);
|
|
209
|
-
const explicit = nonNegativeNumber(metadata.turnDurationMs);
|
|
210
|
-
if (explicit !== undefined) return explicit;
|
|
211
|
-
const startedAt = nonNegativeNumber(metadata.turnStartedAt);
|
|
212
|
-
const completedAt = nonNegativeNumber(metadata.completedAt);
|
|
213
|
-
return startedAt !== undefined && completedAt !== undefined
|
|
214
|
-
? Math.max(0, completedAt - startedAt)
|
|
215
|
-
: undefined;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export function projectCamelAssistantTurn(
|
|
219
|
-
message: UIMessage,
|
|
220
|
-
finalToolNames: readonly string[] = [],
|
|
221
|
-
replyText?: string,
|
|
222
|
-
): CamelAssistantTurn {
|
|
223
|
-
const parts = message.parts.map((part, partIndex) => ({
|
|
224
|
-
messageId: message.id,
|
|
225
|
-
partIndex,
|
|
226
|
-
part,
|
|
227
|
-
}));
|
|
228
|
-
let lastTraceBoundary = -1;
|
|
229
|
-
for (let index = parts.length - 1; index >= 0; index -= 1) {
|
|
230
|
-
if (isTraceBoundary(parts[index].part, finalToolNames)) {
|
|
231
|
-
lastTraceBoundary = index;
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const traceParts: CamelPartRef[] = [];
|
|
237
|
-
const finalParts: CamelPartRef[] = [];
|
|
238
|
-
for (const partRef of parts) {
|
|
239
|
-
const { part, partIndex } = partRef;
|
|
240
|
-
if (part.type === "step-start" || resolvePartKind(part) === "plan") {
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (
|
|
244
|
-
isTraceWork(part, finalToolNames) ||
|
|
245
|
-
(isVisibleText(part) && partIndex < lastTraceBoundary)
|
|
246
|
-
) {
|
|
247
|
-
traceParts.push(partRef);
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
const kind = resolvePartKind(part);
|
|
251
|
-
if (isVisibleText(part) || (kind !== null && kind !== "reasoning")) {
|
|
252
|
-
finalParts.push(partRef);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const hasFinalNarrative = finalParts.some(
|
|
257
|
-
({ part }) => isVisibleText(part) || resolvePartKind(part) === "error",
|
|
258
|
-
);
|
|
259
|
-
const terminal = terminalState(message);
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
kind: "assistant",
|
|
263
|
-
key: `assistant-${message.id}`,
|
|
264
|
-
message,
|
|
265
|
-
messages: [message],
|
|
266
|
-
actionMessageId: message.id,
|
|
267
|
-
parts,
|
|
268
|
-
traceParts,
|
|
269
|
-
finalParts,
|
|
270
|
-
terminalState: terminal?.kind === "interrupted" || !hasFinalNarrative
|
|
271
|
-
? terminal
|
|
272
|
-
: undefined,
|
|
273
|
-
stepCount: Math.max(
|
|
274
|
-
1,
|
|
275
|
-
parts.filter(({ part }) => part.type === "step-start").length,
|
|
276
|
-
),
|
|
277
|
-
copyText: camelMessageText(message),
|
|
278
|
-
replyText,
|
|
279
|
-
durationMs: turnDuration(message),
|
|
280
|
-
completedAt: camelCompletedAt(message),
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export function projectCamelMessages(
|
|
285
|
-
messages: readonly UIMessage[],
|
|
286
|
-
finalToolNames: readonly string[] = [],
|
|
287
|
-
): CamelMessageItem[] {
|
|
288
|
-
const items: CamelMessageItem[] = [];
|
|
289
|
-
messages.forEach((message, index) => {
|
|
290
|
-
if (message.role === "system") return;
|
|
291
|
-
const displayKind = standaloneKind(message);
|
|
292
|
-
if (message.role === "assistant" && displayKind === null) {
|
|
293
|
-
const reply = messages
|
|
294
|
-
.slice(index + 1)
|
|
295
|
-
.find(isDirectUserMessage);
|
|
296
|
-
items.push(
|
|
297
|
-
projectCamelAssistantTurn(
|
|
298
|
-
message,
|
|
299
|
-
finalToolNames,
|
|
300
|
-
reply ? camelMessageText(reply).trim() || undefined : undefined,
|
|
301
|
-
),
|
|
302
|
-
);
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
if (displayKind !== null) {
|
|
306
|
-
items.push({
|
|
307
|
-
kind: "message",
|
|
308
|
-
key: `message-${message.id}`,
|
|
309
|
-
message,
|
|
310
|
-
displayKind,
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
return items;
|
|
315
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { ChevronRightIcon } from "lucide-react";
|
|
2
|
-
import { useState, type ReactNode } from "react";
|
|
3
|
-
import { cn } from "../internal/cn";
|
|
4
|
-
import { formatCamelTurnDuration } from "./camel-turn";
|
|
5
|
-
|
|
6
|
-
export interface TurnSummaryBarProps {
|
|
7
|
-
durationMs?: number;
|
|
8
|
-
stepCount: number;
|
|
9
|
-
children: ReactNode;
|
|
10
|
-
showSeparator?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function TurnSummaryBar({
|
|
14
|
-
durationMs,
|
|
15
|
-
stepCount,
|
|
16
|
-
children,
|
|
17
|
-
showSeparator = true,
|
|
18
|
-
}: TurnSummaryBarProps) {
|
|
19
|
-
const [expanded, setExpanded] = useState(false);
|
|
20
|
-
const stepLabel = `${stepCount} step${stepCount === 1 ? "" : "s"}`;
|
|
21
|
-
const showDuration = durationMs !== undefined && durationMs >= 1000;
|
|
22
|
-
const toggleLabel = expanded ? "hide work" : "show work";
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<div data-camel-turn-summary="" data-state={expanded ? "open" : "closed"}>
|
|
26
|
-
<button
|
|
27
|
-
type="button"
|
|
28
|
-
aria-expanded={expanded}
|
|
29
|
-
aria-label={`${expanded ? "Hide" : "Show"} work, ${stepLabel}${
|
|
30
|
-
showDuration ? `, ${formatCamelTurnDuration(durationMs)}` : ""
|
|
31
|
-
}`}
|
|
32
|
-
className={cn(
|
|
33
|
-
"group/turn-summary flex w-full cursor-pointer items-center gap-1.5 rounded py-1 text-left",
|
|
34
|
-
"font-mono text-xs text-muted-foreground/60 transition-colors duration-150",
|
|
35
|
-
"hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring/50",
|
|
36
|
-
)}
|
|
37
|
-
onClick={() => setExpanded((current) => !current)}
|
|
38
|
-
>
|
|
39
|
-
{showDuration && (
|
|
40
|
-
<>
|
|
41
|
-
<span>worked for</span>
|
|
42
|
-
<span className="text-muted-foreground/80">
|
|
43
|
-
{formatCamelTurnDuration(durationMs)}
|
|
44
|
-
</span>
|
|
45
|
-
<span className="text-muted-foreground/30">·</span>
|
|
46
|
-
</>
|
|
47
|
-
)}
|
|
48
|
-
<span className="text-muted-foreground/80">{stepLabel}</span>
|
|
49
|
-
<span className="text-muted-foreground/30">·</span>
|
|
50
|
-
<span>{toggleLabel}</span>
|
|
51
|
-
<ChevronRightIcon
|
|
52
|
-
className={cn(
|
|
53
|
-
"size-3.5 text-muted-foreground/40 transition-transform duration-250",
|
|
54
|
-
expanded && "rotate-90",
|
|
55
|
-
)}
|
|
56
|
-
aria-hidden="true"
|
|
57
|
-
/>
|
|
58
|
-
</button>
|
|
59
|
-
<div
|
|
60
|
-
data-camel-turn-summary-content=""
|
|
61
|
-
data-camel-collapsible-content=""
|
|
62
|
-
data-state={expanded ? "open" : "closed"}
|
|
63
|
-
aria-hidden={!expanded}
|
|
64
|
-
inert={!expanded ? true : undefined}
|
|
65
|
-
>
|
|
66
|
-
{expanded && (
|
|
67
|
-
<div data-camel-collapsible-inner="">
|
|
68
|
-
<div
|
|
69
|
-
className="space-y-1 py-2"
|
|
70
|
-
data-camel-turn-trace=""
|
|
71
|
-
data-state="open"
|
|
72
|
-
>
|
|
73
|
-
{children}
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
)}
|
|
77
|
-
</div>
|
|
78
|
-
{showSeparator && <hr className="my-2 border-t border-border/40" />}
|
|
79
|
-
</div>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { useState, type ComponentType } from "react";
|
|
2
|
-
import {
|
|
3
|
-
ActivityIcon,
|
|
4
|
-
BotIcon,
|
|
5
|
-
ChevronUpIcon,
|
|
6
|
-
FileIcon,
|
|
7
|
-
LinkIcon,
|
|
8
|
-
ListIcon,
|
|
9
|
-
MessageSquareIcon,
|
|
10
|
-
WrenchIcon,
|
|
11
|
-
} from "lucide-react";
|
|
12
|
-
import { cn } from "./internal/cn";
|
|
13
|
-
|
|
14
|
-
export interface ChatSummarySource {
|
|
15
|
-
kind: "file" | "link";
|
|
16
|
-
label: string;
|
|
17
|
-
href?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ChatSummaryPanelProps {
|
|
21
|
-
sessionTitle: string;
|
|
22
|
-
messageCount: number;
|
|
23
|
-
currentGoal?: string;
|
|
24
|
-
agentTitle: string;
|
|
25
|
-
agentValue?: string;
|
|
26
|
-
stateLabel: string;
|
|
27
|
-
stateTone: "danger" | "active" | "success";
|
|
28
|
-
activityCount: number;
|
|
29
|
-
taskCount: number;
|
|
30
|
-
runningTaskCount: number;
|
|
31
|
-
completedTaskCount: number;
|
|
32
|
-
sources: readonly ChatSummarySource[];
|
|
33
|
-
defaultExpanded?: boolean;
|
|
34
|
-
className?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function SummaryRow({
|
|
38
|
-
icon: Icon,
|
|
39
|
-
label,
|
|
40
|
-
value,
|
|
41
|
-
}: {
|
|
42
|
-
icon: ComponentType<{ className?: string }>;
|
|
43
|
-
label: string;
|
|
44
|
-
value?: string;
|
|
45
|
-
}) {
|
|
46
|
-
return (
|
|
47
|
-
<div className="flex w-full min-w-0 items-center gap-2 rounded-md py-1.5 text-left text-sm text-foreground">
|
|
48
|
-
<Icon className="size-4 shrink-0 text-muted-foreground" />
|
|
49
|
-
<span className="min-w-0 flex-1 truncate">{label}</span>
|
|
50
|
-
{value && (
|
|
51
|
-
<span className="shrink-0 text-xs text-muted-foreground">{value}</span>
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function ChatSummaryPanel({
|
|
58
|
-
sessionTitle,
|
|
59
|
-
messageCount,
|
|
60
|
-
currentGoal,
|
|
61
|
-
agentTitle,
|
|
62
|
-
agentValue,
|
|
63
|
-
stateLabel,
|
|
64
|
-
stateTone,
|
|
65
|
-
activityCount,
|
|
66
|
-
taskCount,
|
|
67
|
-
runningTaskCount,
|
|
68
|
-
completedTaskCount,
|
|
69
|
-
sources,
|
|
70
|
-
defaultExpanded = false,
|
|
71
|
-
className,
|
|
72
|
-
}: ChatSummaryPanelProps) {
|
|
73
|
-
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
74
|
-
const stateColor = {
|
|
75
|
-
danger: "bg-destructive",
|
|
76
|
-
active: "bg-primary",
|
|
77
|
-
success: "bg-success",
|
|
78
|
-
}[stateTone];
|
|
79
|
-
|
|
80
|
-
if (!expanded) {
|
|
81
|
-
return (
|
|
82
|
-
<button
|
|
83
|
-
type="button"
|
|
84
|
-
className={cn(
|
|
85
|
-
"inline-flex h-8 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-border bg-background/90 px-2.5 text-xs font-medium text-muted-foreground shadow-sm backdrop-blur transition-all outline-none",
|
|
86
|
-
"hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
87
|
-
"animate-in fade-in-0 zoom-in-95 slide-in-from-top-1 duration-150 motion-reduce:animate-none",
|
|
88
|
-
className,
|
|
89
|
-
)}
|
|
90
|
-
onClick={() => setExpanded(true)}
|
|
91
|
-
aria-expanded={false}
|
|
92
|
-
aria-label="展开摘要"
|
|
93
|
-
title="展开摘要"
|
|
94
|
-
data-message-panel-summary=""
|
|
95
|
-
data-state="collapsed"
|
|
96
|
-
>
|
|
97
|
-
<ListIcon className="size-3.5" />
|
|
98
|
-
摘要
|
|
99
|
-
</button>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return (
|
|
104
|
-
<aside
|
|
105
|
-
className={cn(
|
|
106
|
-
"w-[19rem] min-w-0 max-w-full overflow-hidden rounded-2xl border border-border bg-popover shadow-card",
|
|
107
|
-
"animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200 motion-reduce:animate-none",
|
|
108
|
-
className,
|
|
109
|
-
)}
|
|
110
|
-
data-message-panel-summary=""
|
|
111
|
-
data-state="expanded"
|
|
112
|
-
>
|
|
113
|
-
<header className="flex h-10 items-center border-b border-border px-4">
|
|
114
|
-
<span className="text-sm font-medium text-muted-foreground">摘要</span>
|
|
115
|
-
<button
|
|
116
|
-
type="button"
|
|
117
|
-
className="group/collapse ml-auto inline-flex size-8 shrink-0 items-center justify-center rounded-md outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4"
|
|
118
|
-
onClick={() => setExpanded(false)}
|
|
119
|
-
aria-expanded={true}
|
|
120
|
-
aria-label="收起摘要"
|
|
121
|
-
title="收起摘要"
|
|
122
|
-
>
|
|
123
|
-
<ChevronUpIcon className="transition-transform duration-200 group-hover/collapse:-translate-y-0.5 motion-reduce:transition-none" />
|
|
124
|
-
</button>
|
|
125
|
-
</header>
|
|
126
|
-
<div className="max-h-[min(31rem,calc(100dvh-8rem))] overflow-y-auto">
|
|
127
|
-
<div className="px-4 py-3">
|
|
128
|
-
<section>
|
|
129
|
-
<p className="mb-2 text-xs font-medium text-muted-foreground">
|
|
130
|
-
会话
|
|
131
|
-
</p>
|
|
132
|
-
<SummaryRow
|
|
133
|
-
icon={MessageSquareIcon}
|
|
134
|
-
label={sessionTitle}
|
|
135
|
-
value={`${messageCount} 条消息`}
|
|
136
|
-
/>
|
|
137
|
-
{currentGoal && (
|
|
138
|
-
<p className="mb-1 ml-6 line-clamp-2 text-xs leading-5 text-muted-foreground">
|
|
139
|
-
{currentGoal}
|
|
140
|
-
</p>
|
|
141
|
-
)}
|
|
142
|
-
<SummaryRow
|
|
143
|
-
icon={BotIcon}
|
|
144
|
-
label={agentTitle}
|
|
145
|
-
value={agentValue}
|
|
146
|
-
/>
|
|
147
|
-
<div className="flex items-center gap-2 py-1.5 text-sm">
|
|
148
|
-
<ActivityIcon className="size-4 shrink-0 text-muted-foreground" />
|
|
149
|
-
<span className="min-w-0 flex-1">状态</span>
|
|
150
|
-
<span
|
|
151
|
-
className={cn(
|
|
152
|
-
"size-1.5 rounded-full transition-colors duration-200",
|
|
153
|
-
stateTone === "active" &&
|
|
154
|
-
"animate-pulse motion-reduce:animate-none",
|
|
155
|
-
stateColor,
|
|
156
|
-
)}
|
|
157
|
-
/>
|
|
158
|
-
<span className="text-xs text-muted-foreground">{stateLabel}</span>
|
|
159
|
-
</div>
|
|
160
|
-
<SummaryRow
|
|
161
|
-
icon={WrenchIcon}
|
|
162
|
-
label={`${activityCount} 次工具活动`}
|
|
163
|
-
/>
|
|
164
|
-
</section>
|
|
165
|
-
|
|
166
|
-
<section className="mt-3 border-t border-border pt-3">
|
|
167
|
-
<p className="mb-2 text-xs font-medium text-muted-foreground">
|
|
168
|
-
子任务
|
|
169
|
-
</p>
|
|
170
|
-
<SummaryRow
|
|
171
|
-
icon={BotIcon}
|
|
172
|
-
label={
|
|
173
|
-
taskCount === 0 ? "暂无子任务" : `${runningTaskCount} 个运行中`
|
|
174
|
-
}
|
|
175
|
-
value={
|
|
176
|
-
completedTaskCount > 0
|
|
177
|
-
? `${completedTaskCount} 已完成`
|
|
178
|
-
: undefined
|
|
179
|
-
}
|
|
180
|
-
/>
|
|
181
|
-
</section>
|
|
182
|
-
|
|
183
|
-
<section className="mt-3 border-t border-border pt-3">
|
|
184
|
-
<p className="mb-2 text-xs font-medium text-muted-foreground">
|
|
185
|
-
来源
|
|
186
|
-
</p>
|
|
187
|
-
{sources.length === 0 ? (
|
|
188
|
-
<p className="py-1.5 text-xs text-muted-foreground">
|
|
189
|
-
当前会话还没有附件或引用来源
|
|
190
|
-
</p>
|
|
191
|
-
) : (
|
|
192
|
-
<div className="space-y-0.5">
|
|
193
|
-
{sources.map((source) => {
|
|
194
|
-
const Icon = source.kind === "file" ? FileIcon : LinkIcon;
|
|
195
|
-
const content = (
|
|
196
|
-
<>
|
|
197
|
-
<Icon className="size-4 shrink-0 text-muted-foreground" />
|
|
198
|
-
<span className="min-w-0 flex-1 truncate">
|
|
199
|
-
{source.label}
|
|
200
|
-
</span>
|
|
201
|
-
</>
|
|
202
|
-
);
|
|
203
|
-
return source.href ? (
|
|
204
|
-
<a
|
|
205
|
-
key={`${source.kind}:${source.href}`}
|
|
206
|
-
href={source.href}
|
|
207
|
-
target="_blank"
|
|
208
|
-
rel="noreferrer"
|
|
209
|
-
className="flex items-center gap-2 rounded-md py-1.5 text-sm text-foreground hover:bg-accent"
|
|
210
|
-
>
|
|
211
|
-
{content}
|
|
212
|
-
</a>
|
|
213
|
-
) : (
|
|
214
|
-
<div
|
|
215
|
-
key={`${source.kind}:${source.label}`}
|
|
216
|
-
className="flex items-center gap-2 py-1.5 text-sm text-foreground"
|
|
217
|
-
>
|
|
218
|
-
{content}
|
|
219
|
-
</div>
|
|
220
|
-
);
|
|
221
|
-
})}
|
|
222
|
-
</div>
|
|
223
|
-
)}
|
|
224
|
-
</section>
|
|
225
|
-
</div>
|
|
226
|
-
</div>
|
|
227
|
-
</aside>
|
|
228
|
-
);
|
|
229
|
-
}
|