@couplet/agent-ui 1.0.0
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/chunk-LQB4Y7J4.js +135 -0
- package/dist/chunk-MJFUPOSB.js +432 -0
- package/dist/chunk-PVUMYYWU.js +194 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +15 -0
- package/dist/core.d.ts +51 -0
- package/dist/core.js +58 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.js +1659 -0
- package/dist/types-rklAgjcr.d.ts +219 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1659 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadInitialChatSession,
|
|
3
|
+
replaceSession,
|
|
4
|
+
runAgentStream,
|
|
5
|
+
sortSessions,
|
|
6
|
+
upsertSession
|
|
7
|
+
} from "./chunk-PVUMYYWU.js";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_AGENT_ENDPOINTS,
|
|
10
|
+
createAgentClient,
|
|
11
|
+
getContextUsageEndpoint,
|
|
12
|
+
getSessionDetailEndpoint,
|
|
13
|
+
resolveAgentEndpoints
|
|
14
|
+
} from "./chunk-LQB4Y7J4.js";
|
|
15
|
+
import {
|
|
16
|
+
appendMessageChainNode,
|
|
17
|
+
appendMessageToolCalls,
|
|
18
|
+
appendSessionMessages,
|
|
19
|
+
appendUserReplyChainNode,
|
|
20
|
+
consumeAgentSseStream,
|
|
21
|
+
createAgentMessage,
|
|
22
|
+
createAgentSession,
|
|
23
|
+
createClarifyMessage,
|
|
24
|
+
extractClarifyFromChain,
|
|
25
|
+
findLastClarifyCall,
|
|
26
|
+
getPendingClarify,
|
|
27
|
+
getSessionTitle,
|
|
28
|
+
isClarifyCallAnswered,
|
|
29
|
+
parseSseEnvelope,
|
|
30
|
+
parseStreamEvent,
|
|
31
|
+
removeSessionMessage,
|
|
32
|
+
streamEventToChainNode,
|
|
33
|
+
updateClarifyMessage,
|
|
34
|
+
updateMessageContent,
|
|
35
|
+
updateMessageReasoning,
|
|
36
|
+
updateMessageType
|
|
37
|
+
} from "./chunk-MJFUPOSB.js";
|
|
38
|
+
|
|
39
|
+
// src/hooks/use-agent-session.ts
|
|
40
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
41
|
+
function assertCanSwitchSession(status) {
|
|
42
|
+
if (status === "streaming" || status === "retrying") {
|
|
43
|
+
throw new Error("Cannot switch Agent session while streaming");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function useAgentSession(options) {
|
|
47
|
+
const { client, labels, autoInit = false, initialSessionMode = "latest", onNavigate } = options;
|
|
48
|
+
const [sessions, setSessions] = useState(() => [createAgentSession()]);
|
|
49
|
+
const [currentSessionId, setCurrentSessionId] = useState(() => sessions[0].id);
|
|
50
|
+
const [status, setStatus] = useState("idle");
|
|
51
|
+
const [contextUsage, setContextUsage] = useState(null);
|
|
52
|
+
const initializedRef = useRef(false);
|
|
53
|
+
const abortControllerRef = useRef(null);
|
|
54
|
+
const currentSession = useMemo(() => {
|
|
55
|
+
return sessions.find((session) => session.id === currentSessionId) ?? sessions[0];
|
|
56
|
+
}, [currentSessionId, sessions]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!autoInit || initializedRef.current) return;
|
|
59
|
+
let cancelled = false;
|
|
60
|
+
initializedRef.current = true;
|
|
61
|
+
loadInitialChatSession(client, initialSessionMode).then(({ sessionId, messages }) => {
|
|
62
|
+
if (cancelled) return;
|
|
63
|
+
const nextSession = {
|
|
64
|
+
...createAgentSession(),
|
|
65
|
+
id: sessionId,
|
|
66
|
+
messages
|
|
67
|
+
};
|
|
68
|
+
setSessions([nextSession]);
|
|
69
|
+
setCurrentSessionId(sessionId);
|
|
70
|
+
}).catch((err) => {
|
|
71
|
+
console.error("[Agent] init chat session failed", err);
|
|
72
|
+
});
|
|
73
|
+
return () => {
|
|
74
|
+
cancelled = true;
|
|
75
|
+
};
|
|
76
|
+
}, [autoInit, client, initialSessionMode]);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!currentSession?.id) return;
|
|
79
|
+
if (currentSession.id.startsWith("session-")) return;
|
|
80
|
+
void client.fetchContextUsage(currentSession.id).then(setContextUsage).catch((err) => console.error("[Agent] context usage failed", err));
|
|
81
|
+
}, [client, currentSession.id]);
|
|
82
|
+
const createSession = async () => {
|
|
83
|
+
assertCanSwitchSession(status);
|
|
84
|
+
const nextSession = createAgentSession(await client.createSession());
|
|
85
|
+
setSessions((prev) => [nextSession, ...prev]);
|
|
86
|
+
setCurrentSessionId(nextSession.id);
|
|
87
|
+
setStatus("idle");
|
|
88
|
+
setContextUsage(null);
|
|
89
|
+
};
|
|
90
|
+
const selectSession = async (sessionId) => {
|
|
91
|
+
assertCanSwitchSession(status);
|
|
92
|
+
const nextSession = await client.getSessionDetail(sessionId);
|
|
93
|
+
setSessions((prev) => upsertSession(prev, nextSession));
|
|
94
|
+
setCurrentSessionId(nextSession.id);
|
|
95
|
+
setStatus("idle");
|
|
96
|
+
setContextUsage(null);
|
|
97
|
+
};
|
|
98
|
+
const stopStreaming = () => {
|
|
99
|
+
abortControllerRef.current?.abort();
|
|
100
|
+
abortControllerRef.current = null;
|
|
101
|
+
setStatus("idle");
|
|
102
|
+
};
|
|
103
|
+
const sendMessage = async (content) => {
|
|
104
|
+
if (status === "streaming" || status === "retrying") return;
|
|
105
|
+
await runAgentStream({
|
|
106
|
+
client,
|
|
107
|
+
session: currentSession,
|
|
108
|
+
content,
|
|
109
|
+
setSessions,
|
|
110
|
+
setStatus,
|
|
111
|
+
setContextUsage,
|
|
112
|
+
abortControllerRef,
|
|
113
|
+
onNavigate,
|
|
114
|
+
getStreamErrorMessage: () => labels.streamError
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
const sendClarifyOption = async (option, chainReplyTo) => {
|
|
118
|
+
if (status === "streaming" || status === "retrying") return;
|
|
119
|
+
await runAgentStream({
|
|
120
|
+
client,
|
|
121
|
+
session: currentSession,
|
|
122
|
+
content: option,
|
|
123
|
+
chainReplyTo,
|
|
124
|
+
setSessions,
|
|
125
|
+
setStatus,
|
|
126
|
+
setContextUsage,
|
|
127
|
+
abortControllerRef,
|
|
128
|
+
onNavigate,
|
|
129
|
+
getStreamErrorMessage: () => labels.streamError
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
currentSession,
|
|
134
|
+
status,
|
|
135
|
+
contextUsage,
|
|
136
|
+
createSession,
|
|
137
|
+
selectSession,
|
|
138
|
+
sendMessage,
|
|
139
|
+
sendClarifyOption,
|
|
140
|
+
stopStreaming
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/components/composer/index.tsx
|
|
145
|
+
import { useState as useState5 } from "react";
|
|
146
|
+
|
|
147
|
+
// src/components/ui/cn.ts
|
|
148
|
+
import { clsx } from "clsx";
|
|
149
|
+
import { twMerge } from "tailwind-merge";
|
|
150
|
+
function cn(...inputs) {
|
|
151
|
+
return twMerge(clsx(inputs));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/components/ui/button.tsx
|
|
155
|
+
import { forwardRef } from "react";
|
|
156
|
+
import { jsx } from "react/jsx-runtime";
|
|
157
|
+
var Button = forwardRef(function Button2({ className, variant = "default", size = "default", ...props }, ref) {
|
|
158
|
+
return /* @__PURE__ */ jsx(
|
|
159
|
+
"button",
|
|
160
|
+
{
|
|
161
|
+
ref,
|
|
162
|
+
className: cn(
|
|
163
|
+
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
|
164
|
+
variant === "default" && "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
165
|
+
variant === "ghost" && "hover:bg-accent hover:text-accent-foreground",
|
|
166
|
+
variant === "outline" && "border border-input bg-background hover:bg-accent",
|
|
167
|
+
size === "default" && "h-9 px-4 py-2",
|
|
168
|
+
size === "sm" && "h-8 rounded-md px-3 text-xs",
|
|
169
|
+
size === "icon" && "size-9",
|
|
170
|
+
className
|
|
171
|
+
),
|
|
172
|
+
...props
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// src/components/ui/scroll-area.tsx
|
|
178
|
+
import { forwardRef as forwardRef2 } from "react";
|
|
179
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
180
|
+
var ScrollArea = forwardRef2(
|
|
181
|
+
function ScrollArea2({ className, ...props }, ref) {
|
|
182
|
+
return /* @__PURE__ */ jsx2(
|
|
183
|
+
"div",
|
|
184
|
+
{
|
|
185
|
+
ref,
|
|
186
|
+
className: cn("overflow-auto [scrollbar-gutter:stable]", className),
|
|
187
|
+
...props
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// src/components/ui/skeleton.tsx
|
|
194
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
195
|
+
function Skeleton({ className, style }) {
|
|
196
|
+
return /* @__PURE__ */ jsx3(
|
|
197
|
+
"div",
|
|
198
|
+
{
|
|
199
|
+
className: cn("animate-pulse rounded-md bg-muted", className),
|
|
200
|
+
style,
|
|
201
|
+
"aria-hidden": "true"
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/components/ui/copy-button.tsx
|
|
207
|
+
import { useState as useState2 } from "react";
|
|
208
|
+
import { Check, Copy } from "lucide-react";
|
|
209
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
210
|
+
function CopyButton({ value, label, copiedLabel, className }) {
|
|
211
|
+
const [copied, setCopied] = useState2(false);
|
|
212
|
+
const handleCopy = async () => {
|
|
213
|
+
try {
|
|
214
|
+
await navigator.clipboard.writeText(value);
|
|
215
|
+
setCopied(true);
|
|
216
|
+
window.setTimeout(() => setCopied(false), 1500);
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
return /* @__PURE__ */ jsx4(
|
|
221
|
+
"button",
|
|
222
|
+
{
|
|
223
|
+
type: "button",
|
|
224
|
+
"aria-label": copied ? copiedLabel : label,
|
|
225
|
+
className: cn("inline-flex items-center justify-center rounded-sm", className),
|
|
226
|
+
onClick: () => void handleCopy(),
|
|
227
|
+
children: copied ? /* @__PURE__ */ jsx4(Check, { className: "size-4" }) : /* @__PURE__ */ jsx4(Copy, { className: "size-4" })
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/components/ui/markdown-viewer.tsx
|
|
233
|
+
import ReactMarkdown from "react-markdown";
|
|
234
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
235
|
+
function MarkdownViewer({ children, className }) {
|
|
236
|
+
return /* @__PURE__ */ jsx5("div", { className: cn("prose prose-sm max-w-none dark:prose-invert", className), children: /* @__PURE__ */ jsx5(ReactMarkdown, { children }) });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/components/ui/ai-conversation.tsx
|
|
240
|
+
import {
|
|
241
|
+
forwardRef as forwardRef3,
|
|
242
|
+
useCallback,
|
|
243
|
+
useImperativeHandle,
|
|
244
|
+
useRef as useRef2
|
|
245
|
+
} from "react";
|
|
246
|
+
import { ArrowDown } from "lucide-react";
|
|
247
|
+
import { jsx as jsx6, jsxs } from "react/jsx-runtime";
|
|
248
|
+
var AIConversation = forwardRef3(
|
|
249
|
+
function AIConversation2({ className, children, stickToBottom = false, ...props }, ref) {
|
|
250
|
+
const scrollRef = useRef2(null);
|
|
251
|
+
const scrollToBottom = useCallback(() => {
|
|
252
|
+
const node = scrollRef.current;
|
|
253
|
+
if (!node) return;
|
|
254
|
+
node.scrollTop = node.scrollHeight;
|
|
255
|
+
}, []);
|
|
256
|
+
useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
|
|
257
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative flex min-h-0 flex-col", className), ...props, children: [
|
|
258
|
+
/* @__PURE__ */ jsx6(ScrollArea, { ref: scrollRef, className: "min-h-0 flex-1", children }),
|
|
259
|
+
stickToBottom ? /* @__PURE__ */ jsx6(AIConversationScrollButton, { onClick: scrollToBottom, className: "absolute bottom-4 left-1/2 -translate-x-1/2" }) : null
|
|
260
|
+
] });
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
function AIConversationContent({
|
|
264
|
+
className,
|
|
265
|
+
...props
|
|
266
|
+
}) {
|
|
267
|
+
return /* @__PURE__ */ jsx6("div", { className: cn("flex flex-col", className), ...props });
|
|
268
|
+
}
|
|
269
|
+
function AIConversationScrollButton({
|
|
270
|
+
className,
|
|
271
|
+
onClick
|
|
272
|
+
}) {
|
|
273
|
+
return /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", className, onClick, children: [
|
|
274
|
+
/* @__PURE__ */ jsx6(ArrowDown, { className: "mr-1 size-4" }),
|
|
275
|
+
"Scroll"
|
|
276
|
+
] });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/components/ui/ai-prompt-input.tsx
|
|
280
|
+
import { Loader2, Send, Square } from "lucide-react";
|
|
281
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
282
|
+
function AIPromptInput({ className, children, onSubmit }) {
|
|
283
|
+
return /* @__PURE__ */ jsx7("form", { className: cn("flex flex-col", className), onSubmit, children });
|
|
284
|
+
}
|
|
285
|
+
function AIPromptInputTextarea({
|
|
286
|
+
value,
|
|
287
|
+
placeholder,
|
|
288
|
+
disabled,
|
|
289
|
+
className,
|
|
290
|
+
submitOnEnter,
|
|
291
|
+
onChange,
|
|
292
|
+
onSubmitShortcut
|
|
293
|
+
}) {
|
|
294
|
+
return /* @__PURE__ */ jsx7(
|
|
295
|
+
"textarea",
|
|
296
|
+
{
|
|
297
|
+
value,
|
|
298
|
+
placeholder,
|
|
299
|
+
disabled,
|
|
300
|
+
rows: 3,
|
|
301
|
+
className: cn(
|
|
302
|
+
"w-full resize-none rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
303
|
+
className
|
|
304
|
+
),
|
|
305
|
+
onChange,
|
|
306
|
+
onKeyDown: (event) => {
|
|
307
|
+
if (submitOnEnter && event.key === "Enter" && !event.shiftKey) {
|
|
308
|
+
event.preventDefault();
|
|
309
|
+
onSubmitShortcut?.();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
function AIPromptInputToolbar({
|
|
316
|
+
className,
|
|
317
|
+
children
|
|
318
|
+
}) {
|
|
319
|
+
return /* @__PURE__ */ jsx7("div", { className: cn("flex items-center gap-2", className), children });
|
|
320
|
+
}
|
|
321
|
+
function AIPromptInputSubmit({
|
|
322
|
+
status,
|
|
323
|
+
sendLabel,
|
|
324
|
+
stopLabel,
|
|
325
|
+
disabled,
|
|
326
|
+
"aria-label": ariaLabel
|
|
327
|
+
}) {
|
|
328
|
+
const isStreaming = status === "streaming";
|
|
329
|
+
return /* @__PURE__ */ jsx7(
|
|
330
|
+
Button,
|
|
331
|
+
{
|
|
332
|
+
type: "submit",
|
|
333
|
+
size: "icon",
|
|
334
|
+
"aria-label": ariaLabel ?? (isStreaming ? stopLabel : sendLabel),
|
|
335
|
+
disabled,
|
|
336
|
+
children: isStreaming ? /* @__PURE__ */ jsx7(Square, { className: "size-4" }) : status === "error" ? /* @__PURE__ */ jsx7(Loader2, { className: "size-4" }) : /* @__PURE__ */ jsx7(Send, { className: "size-4" })
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/components/ui/command-palette.tsx
|
|
342
|
+
import { useEffect as useEffect2, useMemo as useMemo2, useState as useState3 } from "react";
|
|
343
|
+
import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
344
|
+
function CommandPalette({
|
|
345
|
+
open,
|
|
346
|
+
title,
|
|
347
|
+
description,
|
|
348
|
+
placeholder = "Search\u2026",
|
|
349
|
+
emptyText = "No results",
|
|
350
|
+
contentClassName,
|
|
351
|
+
listClassName,
|
|
352
|
+
groups,
|
|
353
|
+
onOpenChange,
|
|
354
|
+
onItemSelect
|
|
355
|
+
}) {
|
|
356
|
+
const [query, setQuery] = useState3("");
|
|
357
|
+
useEffect2(() => {
|
|
358
|
+
if (!open) setQuery("");
|
|
359
|
+
}, [open]);
|
|
360
|
+
useEffect2(() => {
|
|
361
|
+
if (!open) return;
|
|
362
|
+
const onKeyDown = (event) => {
|
|
363
|
+
if (event.key === "Escape") onOpenChange(false);
|
|
364
|
+
};
|
|
365
|
+
window.addEventListener("keydown", onKeyDown);
|
|
366
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
367
|
+
}, [open, onOpenChange]);
|
|
368
|
+
const filteredGroups = useMemo2(() => {
|
|
369
|
+
const normalized = query.trim().toLowerCase();
|
|
370
|
+
if (!normalized) return groups;
|
|
371
|
+
return groups.map((group) => ({
|
|
372
|
+
...group,
|
|
373
|
+
items: group.items.filter((item) => {
|
|
374
|
+
const haystack = [
|
|
375
|
+
typeof item.label === "string" ? item.label : "",
|
|
376
|
+
...item.keywords ?? []
|
|
377
|
+
].join(" ").toLowerCase();
|
|
378
|
+
return haystack.includes(normalized);
|
|
379
|
+
})
|
|
380
|
+
})).filter((group) => group.items.length > 0);
|
|
381
|
+
}, [groups, query]);
|
|
382
|
+
if (!open) return null;
|
|
383
|
+
return /* @__PURE__ */ jsx8("div", { className: "fixed inset-0 z-50 flex items-start justify-center bg-black/40 p-4 pt-[15vh]", children: /* @__PURE__ */ jsxs2(
|
|
384
|
+
"div",
|
|
385
|
+
{
|
|
386
|
+
role: "dialog",
|
|
387
|
+
"aria-modal": "true",
|
|
388
|
+
"aria-label": title,
|
|
389
|
+
className: cn(
|
|
390
|
+
"w-full max-w-xl overflow-hidden rounded-xl border bg-background shadow-xl",
|
|
391
|
+
contentClassName
|
|
392
|
+
),
|
|
393
|
+
children: [
|
|
394
|
+
/* @__PURE__ */ jsxs2("div", { className: "border-b px-4 py-3", children: [
|
|
395
|
+
/* @__PURE__ */ jsx8("div", { className: "text-sm font-semibold", children: title }),
|
|
396
|
+
description ? /* @__PURE__ */ jsx8("div", { className: "text-xs text-muted-foreground", children: description }) : null,
|
|
397
|
+
/* @__PURE__ */ jsx8(
|
|
398
|
+
"input",
|
|
399
|
+
{
|
|
400
|
+
autoFocus: true,
|
|
401
|
+
value: query,
|
|
402
|
+
placeholder,
|
|
403
|
+
className: "mt-3 w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
404
|
+
onChange: (event) => setQuery(event.target.value)
|
|
405
|
+
}
|
|
406
|
+
)
|
|
407
|
+
] }),
|
|
408
|
+
/* @__PURE__ */ jsx8("div", { className: cn("max-h-[420px] overflow-y-auto p-2", listClassName), children: filteredGroups.length === 0 ? /* @__PURE__ */ jsx8("div", { className: "px-3 py-6 text-center text-sm text-muted-foreground", children: emptyText }) : filteredGroups.map((group) => /* @__PURE__ */ jsxs2("div", { className: "mb-2", children: [
|
|
409
|
+
group.heading ? /* @__PURE__ */ jsx8("div", { className: "px-2 py-1 text-xs font-medium text-muted-foreground", children: group.heading }) : null,
|
|
410
|
+
group.items.map((item) => /* @__PURE__ */ jsxs2(
|
|
411
|
+
"button",
|
|
412
|
+
{
|
|
413
|
+
type: "button",
|
|
414
|
+
disabled: item.disabled,
|
|
415
|
+
className: "flex w-full flex-col items-start rounded-md px-2 py-2 text-left text-sm hover:bg-accent disabled:opacity-50",
|
|
416
|
+
onClick: () => onItemSelect(item),
|
|
417
|
+
children: [
|
|
418
|
+
/* @__PURE__ */ jsx8("span", { children: item.label }),
|
|
419
|
+
item.description ? /* @__PURE__ */ jsx8("span", { className: "text-xs text-muted-foreground", children: item.description }) : null
|
|
420
|
+
]
|
|
421
|
+
},
|
|
422
|
+
item.key
|
|
423
|
+
))
|
|
424
|
+
] }, group.key)) })
|
|
425
|
+
]
|
|
426
|
+
}
|
|
427
|
+
) });
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/components/context-usage/indicator.tsx
|
|
431
|
+
import { useState as useState4 } from "react";
|
|
432
|
+
|
|
433
|
+
// src/components/context-usage/colors.ts
|
|
434
|
+
var CATEGORY_COLORS = {
|
|
435
|
+
system_prompt: "#6366F1",
|
|
436
|
+
rules: "#8B5CF6",
|
|
437
|
+
output_format: "#A855F7",
|
|
438
|
+
skills: "#EC4899",
|
|
439
|
+
tool_definitions: "#F59E0B",
|
|
440
|
+
compact_summary: "#14B8A6",
|
|
441
|
+
conversation: "#3B82F6"
|
|
442
|
+
};
|
|
443
|
+
var FALLBACK_COLORS = ["#6366F1", "#8B5CF6", "#EC4899", "#F59E0B", "#14B8A6", "#3B82F6", "#10B981"];
|
|
444
|
+
function getCategoryColor(id, index) {
|
|
445
|
+
return CATEGORY_COLORS[id] ?? FALLBACK_COLORS[index % FALLBACK_COLORS.length];
|
|
446
|
+
}
|
|
447
|
+
function getRingColor(percent) {
|
|
448
|
+
if (percent >= 90) return "#EF4444";
|
|
449
|
+
if (percent >= 70) return "#F59E0B";
|
|
450
|
+
return "#22C55E";
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/components/context-usage/format.ts
|
|
454
|
+
function formatTokenCount(tokens) {
|
|
455
|
+
if (tokens >= 1e6) {
|
|
456
|
+
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
457
|
+
}
|
|
458
|
+
if (tokens >= 1e4) {
|
|
459
|
+
return `${Math.round(tokens / 1e3)}K`;
|
|
460
|
+
}
|
|
461
|
+
if (tokens >= 1e3) {
|
|
462
|
+
return `${(tokens / 1e3).toFixed(1)}K`;
|
|
463
|
+
}
|
|
464
|
+
return String(tokens);
|
|
465
|
+
}
|
|
466
|
+
function formatUsageSummary(used, budget) {
|
|
467
|
+
return `${formatTokenCount(used)} / ${formatTokenCount(budget)}`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/components/context-usage/detail.tsx
|
|
471
|
+
import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
472
|
+
function ContextUsageDetail({ usage, labels, className }) {
|
|
473
|
+
const totalCategoryTokens = usage.categories.reduce((sum, item) => sum + item.tokens, 0);
|
|
474
|
+
return /* @__PURE__ */ jsxs3(
|
|
475
|
+
"div",
|
|
476
|
+
{
|
|
477
|
+
className: cn(
|
|
478
|
+
"w-[280px] rounded-xl border border-[#D5DCE8] bg-white p-3 shadow-lg dark:border-white/10 dark:bg-[#111C2E]",
|
|
479
|
+
className
|
|
480
|
+
),
|
|
481
|
+
children: [
|
|
482
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between gap-2", children: [
|
|
483
|
+
/* @__PURE__ */ jsx9("span", { className: "text-[12px] font-semibold text-[#111C30] dark:text-foreground", children: labels.title }),
|
|
484
|
+
/* @__PURE__ */ jsx9("span", { className: "text-[11px] tabular-nums text-[#7A828F] dark:text-[#AAB6CB]", children: formatUsageSummary(usage.used_tokens, usage.budget_tokens) })
|
|
485
|
+
] }),
|
|
486
|
+
/* @__PURE__ */ jsx9("div", { className: "mt-2 flex h-2 overflow-hidden rounded-full bg-[#EEF2F8] dark:bg-white/10", children: usage.categories.map((category, index) => {
|
|
487
|
+
const width = totalCategoryTokens > 0 ? category.tokens / totalCategoryTokens * 100 : 0;
|
|
488
|
+
return /* @__PURE__ */ jsx9(
|
|
489
|
+
"div",
|
|
490
|
+
{
|
|
491
|
+
title: category.label,
|
|
492
|
+
style: {
|
|
493
|
+
width: `${width}%`,
|
|
494
|
+
backgroundColor: getCategoryColor(category.id, index)
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
category.id
|
|
498
|
+
);
|
|
499
|
+
}) }),
|
|
500
|
+
/* @__PURE__ */ jsx9("ul", { className: "mt-3 max-h-48 space-y-1.5 overflow-y-auto", children: usage.categories.map((category, index) => /* @__PURE__ */ jsxs3("li", { className: "flex items-center justify-between gap-2 text-[11px]", children: [
|
|
501
|
+
/* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 items-center gap-1.5 text-[#4B5565] dark:text-[#C5D0E0]", children: [
|
|
502
|
+
/* @__PURE__ */ jsx9(
|
|
503
|
+
"span",
|
|
504
|
+
{
|
|
505
|
+
className: "size-2 shrink-0 rounded-full",
|
|
506
|
+
style: { backgroundColor: getCategoryColor(category.id, index) }
|
|
507
|
+
}
|
|
508
|
+
),
|
|
509
|
+
/* @__PURE__ */ jsx9("span", { className: "truncate", children: labels.categoryLabels[category.id] ?? category.label })
|
|
510
|
+
] }),
|
|
511
|
+
/* @__PURE__ */ jsxs3("span", { className: "shrink-0 tabular-nums text-[#7A828F] dark:text-[#AAB6CB]", children: [
|
|
512
|
+
formatTokenCount(category.tokens),
|
|
513
|
+
" ",
|
|
514
|
+
labels.tokens
|
|
515
|
+
] })
|
|
516
|
+
] }, category.id)) })
|
|
517
|
+
]
|
|
518
|
+
}
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// src/components/context-usage/ring.tsx
|
|
523
|
+
import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
524
|
+
function ContextUsageRing({
|
|
525
|
+
percent,
|
|
526
|
+
size = 18,
|
|
527
|
+
strokeWidth = 2.5
|
|
528
|
+
}) {
|
|
529
|
+
const radius = (size - strokeWidth) / 2;
|
|
530
|
+
const circumference = 2 * Math.PI * radius;
|
|
531
|
+
const clamped = Math.min(100, Math.max(0, percent));
|
|
532
|
+
const offset = circumference - clamped / 100 * circumference;
|
|
533
|
+
const color = getRingColor(clamped);
|
|
534
|
+
return /* @__PURE__ */ jsxs4(
|
|
535
|
+
"svg",
|
|
536
|
+
{
|
|
537
|
+
width: size,
|
|
538
|
+
height: size,
|
|
539
|
+
viewBox: `0 0 ${size} ${size}`,
|
|
540
|
+
className: "shrink-0 -rotate-90",
|
|
541
|
+
"aria-hidden": true,
|
|
542
|
+
children: [
|
|
543
|
+
/* @__PURE__ */ jsx10(
|
|
544
|
+
"circle",
|
|
545
|
+
{
|
|
546
|
+
cx: size / 2,
|
|
547
|
+
cy: size / 2,
|
|
548
|
+
r: radius,
|
|
549
|
+
fill: "none",
|
|
550
|
+
stroke: "currentColor",
|
|
551
|
+
strokeWidth,
|
|
552
|
+
className: "text-[#D5DCE8] dark:text-white/15"
|
|
553
|
+
}
|
|
554
|
+
),
|
|
555
|
+
/* @__PURE__ */ jsx10(
|
|
556
|
+
"circle",
|
|
557
|
+
{
|
|
558
|
+
cx: size / 2,
|
|
559
|
+
cy: size / 2,
|
|
560
|
+
r: radius,
|
|
561
|
+
fill: "none",
|
|
562
|
+
stroke: color,
|
|
563
|
+
strokeWidth,
|
|
564
|
+
strokeLinecap: "round",
|
|
565
|
+
strokeDasharray: circumference,
|
|
566
|
+
strokeDashoffset: offset,
|
|
567
|
+
className: "transition-[stroke-dashoffset,stroke] duration-300"
|
|
568
|
+
}
|
|
569
|
+
)
|
|
570
|
+
]
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/components/context-usage/visibility.ts
|
|
576
|
+
var CONTEXT_USAGE_AUTO_THRESHOLD_PERCENT = 50;
|
|
577
|
+
function shouldShowContextUsage({
|
|
578
|
+
usage,
|
|
579
|
+
hasMessages,
|
|
580
|
+
isStreaming,
|
|
581
|
+
mode = "auto",
|
|
582
|
+
autoThresholdPercent = CONTEXT_USAGE_AUTO_THRESHOLD_PERCENT
|
|
583
|
+
}) {
|
|
584
|
+
if (!usage || mode === "never") return false;
|
|
585
|
+
if (!hasMessages && !isStreaming) return false;
|
|
586
|
+
if (isStreaming) return true;
|
|
587
|
+
if (mode === "always") return true;
|
|
588
|
+
return usage.used_percent >= autoThresholdPercent;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/components/context-usage/indicator.tsx
|
|
592
|
+
import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
593
|
+
function ContextUsageIndicator({
|
|
594
|
+
usage,
|
|
595
|
+
hasMessages,
|
|
596
|
+
isStreaming,
|
|
597
|
+
labels,
|
|
598
|
+
className
|
|
599
|
+
}) {
|
|
600
|
+
const [open, setOpen] = useState4(false);
|
|
601
|
+
if (!usage || !shouldShowContextUsage({ usage, hasMessages, isStreaming })) return null;
|
|
602
|
+
const percentLabel = labels.percent.replace("{percent}", String(usage.used_percent));
|
|
603
|
+
return /* @__PURE__ */ jsxs5(
|
|
604
|
+
"div",
|
|
605
|
+
{
|
|
606
|
+
className: cn("relative flex items-center", className),
|
|
607
|
+
onMouseEnter: () => setOpen(true),
|
|
608
|
+
onMouseLeave: () => setOpen(false),
|
|
609
|
+
onFocus: () => setOpen(true),
|
|
610
|
+
onBlur: () => setOpen(false),
|
|
611
|
+
children: [
|
|
612
|
+
/* @__PURE__ */ jsxs5(
|
|
613
|
+
"div",
|
|
614
|
+
{
|
|
615
|
+
className: "flex items-center gap-1.5 rounded-md px-1 py-0.5 text-[11px] tabular-nums text-[#7A828F] outline-none dark:text-[#AAB6CB]",
|
|
616
|
+
tabIndex: 0,
|
|
617
|
+
role: "status",
|
|
618
|
+
"aria-label": percentLabel,
|
|
619
|
+
children: [
|
|
620
|
+
/* @__PURE__ */ jsx11(ContextUsageRing, { percent: usage.used_percent }),
|
|
621
|
+
/* @__PURE__ */ jsxs5("span", { className: "hidden sm:inline", children: [
|
|
622
|
+
usage.used_percent,
|
|
623
|
+
"%"
|
|
624
|
+
] }),
|
|
625
|
+
/* @__PURE__ */ jsxs5("span", { className: "hidden md:inline", children: [
|
|
626
|
+
"\xB7 ",
|
|
627
|
+
formatTokenCount(usage.used_tokens)
|
|
628
|
+
] })
|
|
629
|
+
]
|
|
630
|
+
}
|
|
631
|
+
),
|
|
632
|
+
open ? /* @__PURE__ */ jsx11("div", { className: "absolute bottom-full left-0 z-40 mb-2", children: /* @__PURE__ */ jsx11(ContextUsageDetail, { usage, labels }) }) : null
|
|
633
|
+
]
|
|
634
|
+
}
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/components/composer/index.tsx
|
|
639
|
+
import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
640
|
+
var COMPOSER_FRAME_CLASS_NAME = "mx-auto w-full max-w-[900px] px-6";
|
|
641
|
+
function getPromptStatus(status) {
|
|
642
|
+
if (status === "streaming" || status === "retrying") return "streaming";
|
|
643
|
+
if (status === "error") return "error";
|
|
644
|
+
return "ready";
|
|
645
|
+
}
|
|
646
|
+
function ClarifyChoiceOverlay({
|
|
647
|
+
clarify,
|
|
648
|
+
title,
|
|
649
|
+
onOption
|
|
650
|
+
}) {
|
|
651
|
+
return /* @__PURE__ */ jsxs6("div", { className: "absolute right-6 bottom-full left-6 z-30 mb-3 rounded-[20px] border border-[#D5DCE8] bg-white px-5 py-4 shadow-[0_12px_32px_rgba(17,28,48,0.10)] dark:border-white/10 dark:bg-[#111C2E] dark:shadow-[0_12px_32px_rgba(0,0,0,0.28)]", children: [
|
|
652
|
+
/* @__PURE__ */ jsx12("div", { className: "text-[13px] leading-5 font-semibold text-[#7A828F] dark:text-[#AAB6CB]", children: title }),
|
|
653
|
+
/* @__PURE__ */ jsx12("div", { className: "mt-2 text-[15px] leading-6 font-semibold text-[#111C30] dark:text-foreground", children: clarify.question }),
|
|
654
|
+
/* @__PURE__ */ jsx12("div", { className: "mt-3 flex flex-wrap gap-2", children: clarify.options.map((option) => /* @__PURE__ */ jsx12(
|
|
655
|
+
"button",
|
|
656
|
+
{
|
|
657
|
+
type: "button",
|
|
658
|
+
onClick: () => void onOption(option, clarify.chainReplyTo),
|
|
659
|
+
className: "rounded-full border border-[#C5CEDD] bg-[#F7F9FC] px-3 py-1.5 text-[13px] font-medium text-[#111C30] transition-colors hover:border-[#AAB6CB] hover:bg-[#EEF3FA] dark:border-white/10 dark:bg-[#17243A] dark:text-foreground dark:hover:border-white/20 dark:hover:bg-[#1D2D49]",
|
|
660
|
+
children: option
|
|
661
|
+
},
|
|
662
|
+
option
|
|
663
|
+
)) })
|
|
664
|
+
] });
|
|
665
|
+
}
|
|
666
|
+
function PromptInputForm({
|
|
667
|
+
input,
|
|
668
|
+
inputDisabled,
|
|
669
|
+
promptStatus,
|
|
670
|
+
contextUsage,
|
|
671
|
+
hasMessages,
|
|
672
|
+
contextUsageLabels,
|
|
673
|
+
labels,
|
|
674
|
+
onInputChange,
|
|
675
|
+
onSubmit
|
|
676
|
+
}) {
|
|
677
|
+
const isStreaming = promptStatus === "streaming";
|
|
678
|
+
return /* @__PURE__ */ jsxs6(
|
|
679
|
+
AIPromptInput,
|
|
680
|
+
{
|
|
681
|
+
className: cn(
|
|
682
|
+
"rounded-2xl border border-[#D5DCE8] bg-white p-3 shadow-none transition-colors dark:border-white/10 dark:bg-[#111C2E]"
|
|
683
|
+
),
|
|
684
|
+
onSubmit: (event) => {
|
|
685
|
+
event.preventDefault();
|
|
686
|
+
onSubmit();
|
|
687
|
+
},
|
|
688
|
+
children: [
|
|
689
|
+
/* @__PURE__ */ jsx12(
|
|
690
|
+
AIPromptInputTextarea,
|
|
691
|
+
{
|
|
692
|
+
autoResize: true,
|
|
693
|
+
submitOnEnter: true,
|
|
694
|
+
value: input,
|
|
695
|
+
placeholder: labels.placeholder,
|
|
696
|
+
disabled: inputDisabled,
|
|
697
|
+
onChange: (event) => onInputChange(event.target.value),
|
|
698
|
+
onSubmitShortcut: onSubmit,
|
|
699
|
+
className: "min-h-14 [scrollbar-gutter:stable] overflow-y-auto border-0 bg-transparent p-0 text-[15px] text-[#111C30] shadow-none placeholder:text-[#7B8798] focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-transparent dark:text-foreground dark:placeholder:text-[#6F7B90]"
|
|
700
|
+
}
|
|
701
|
+
),
|
|
702
|
+
/* @__PURE__ */ jsxs6(AIPromptInputToolbar, { className: "justify-between pt-2", children: [
|
|
703
|
+
/* @__PURE__ */ jsx12("div", { className: "flex min-h-6 items-center", children: /* @__PURE__ */ jsx12(
|
|
704
|
+
ContextUsageIndicator,
|
|
705
|
+
{
|
|
706
|
+
usage: contextUsage ?? null,
|
|
707
|
+
hasMessages,
|
|
708
|
+
isStreaming,
|
|
709
|
+
labels: contextUsageLabels
|
|
710
|
+
}
|
|
711
|
+
) }),
|
|
712
|
+
/* @__PURE__ */ jsx12(
|
|
713
|
+
AIPromptInputSubmit,
|
|
714
|
+
{
|
|
715
|
+
status: promptStatus,
|
|
716
|
+
sendLabel: labels.send,
|
|
717
|
+
stopLabel: labels.stop,
|
|
718
|
+
"aria-label": isStreaming ? labels.stop : labels.send,
|
|
719
|
+
disabled: inputDisabled || !input.trim() && !isStreaming
|
|
720
|
+
}
|
|
721
|
+
)
|
|
722
|
+
] })
|
|
723
|
+
]
|
|
724
|
+
}
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
function InitialQuestionSuggestions({ suggestions, onSelect }) {
|
|
728
|
+
return /* @__PURE__ */ jsx12("div", { className: "mt-3 flex flex-col items-start gap-2", children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsx12(
|
|
729
|
+
"button",
|
|
730
|
+
{
|
|
731
|
+
type: "button",
|
|
732
|
+
onClick: () => onSelect(suggestion),
|
|
733
|
+
className: "rounded-full border border-transparent bg-[#F3F5F8] px-3.5 py-2 text-[13px] text-[#4E5E79] transition-colors hover:bg-[#EAEFF5] dark:bg-[#1D2D49] dark:text-[#AAB6CB] dark:hover:bg-[#25375A]",
|
|
734
|
+
children: suggestion
|
|
735
|
+
},
|
|
736
|
+
index
|
|
737
|
+
)) });
|
|
738
|
+
}
|
|
739
|
+
function AgentComposerFrame({
|
|
740
|
+
className,
|
|
741
|
+
isNew,
|
|
742
|
+
showInitialQuestions,
|
|
743
|
+
initialQuestions,
|
|
744
|
+
clarify,
|
|
745
|
+
input,
|
|
746
|
+
inputDisabled,
|
|
747
|
+
promptStatus,
|
|
748
|
+
contextUsage,
|
|
749
|
+
hasMessages,
|
|
750
|
+
contextUsageLabels,
|
|
751
|
+
labels,
|
|
752
|
+
onInputChange,
|
|
753
|
+
onSubmit,
|
|
754
|
+
onInitialQuestion,
|
|
755
|
+
onClarifyOption
|
|
756
|
+
}) {
|
|
757
|
+
return /* @__PURE__ */ jsxs6(
|
|
758
|
+
"div",
|
|
759
|
+
{
|
|
760
|
+
className: cn("relative z-20", className ?? COMPOSER_FRAME_CLASS_NAME, !isNew && "mt-4 mb-6"),
|
|
761
|
+
children: [
|
|
762
|
+
clarify ? /* @__PURE__ */ jsx12(
|
|
763
|
+
ClarifyChoiceOverlay,
|
|
764
|
+
{
|
|
765
|
+
clarify,
|
|
766
|
+
title: labels.clarifyTitle,
|
|
767
|
+
onOption: onClarifyOption
|
|
768
|
+
}
|
|
769
|
+
) : null,
|
|
770
|
+
/* @__PURE__ */ jsx12(
|
|
771
|
+
PromptInputForm,
|
|
772
|
+
{
|
|
773
|
+
input,
|
|
774
|
+
inputDisabled,
|
|
775
|
+
promptStatus,
|
|
776
|
+
contextUsage,
|
|
777
|
+
hasMessages,
|
|
778
|
+
contextUsageLabels,
|
|
779
|
+
labels,
|
|
780
|
+
onInputChange,
|
|
781
|
+
onSubmit
|
|
782
|
+
}
|
|
783
|
+
),
|
|
784
|
+
showInitialQuestions ? /* @__PURE__ */ jsx12(InitialQuestionSuggestions, { suggestions: initialQuestions, onSelect: onInitialQuestion }) : null
|
|
785
|
+
]
|
|
786
|
+
}
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
function AgentComposer({
|
|
790
|
+
status,
|
|
791
|
+
contextUsage,
|
|
792
|
+
pendingClarify,
|
|
793
|
+
labels,
|
|
794
|
+
isNew,
|
|
795
|
+
initialQuestions = [],
|
|
796
|
+
className,
|
|
797
|
+
onSubmit,
|
|
798
|
+
onClarifyOption,
|
|
799
|
+
onStop
|
|
800
|
+
}) {
|
|
801
|
+
const [input, setInput] = useState5("");
|
|
802
|
+
const promptStatus = getPromptStatus(status);
|
|
803
|
+
const activeClarify = status === "streaming" || status === "retrying" ? void 0 : pendingClarify;
|
|
804
|
+
const inputDisabled = Boolean(activeClarify);
|
|
805
|
+
const showInitialQuestions = initialQuestions.length > 0 && isNew && !activeClarify && status !== "streaming";
|
|
806
|
+
const handleSubmit = async () => {
|
|
807
|
+
if (status === "streaming" || status === "retrying") {
|
|
808
|
+
onStop();
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (inputDisabled) return;
|
|
812
|
+
const content = input.trim();
|
|
813
|
+
if (!content) return;
|
|
814
|
+
setInput("");
|
|
815
|
+
await onSubmit(content);
|
|
816
|
+
};
|
|
817
|
+
const handleInitialQuestion = async (question) => {
|
|
818
|
+
if (!showInitialQuestions) return;
|
|
819
|
+
await onSubmit(question);
|
|
820
|
+
};
|
|
821
|
+
return /* @__PURE__ */ jsx12(
|
|
822
|
+
AgentComposerFrame,
|
|
823
|
+
{
|
|
824
|
+
className,
|
|
825
|
+
isNew,
|
|
826
|
+
showInitialQuestions,
|
|
827
|
+
initialQuestions,
|
|
828
|
+
clarify: activeClarify,
|
|
829
|
+
input,
|
|
830
|
+
inputDisabled,
|
|
831
|
+
promptStatus,
|
|
832
|
+
contextUsage,
|
|
833
|
+
hasMessages: !isNew,
|
|
834
|
+
contextUsageLabels: labels.contextUsage,
|
|
835
|
+
labels,
|
|
836
|
+
onInputChange: setInput,
|
|
837
|
+
onSubmit: () => void handleSubmit(),
|
|
838
|
+
onInitialQuestion: (question) => void handleInitialQuestion(question),
|
|
839
|
+
onClarifyOption
|
|
840
|
+
}
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/components/conversation-view/helpers.ts
|
|
845
|
+
function formatMessageTime(timestamp) {
|
|
846
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
847
|
+
hour: "2-digit",
|
|
848
|
+
minute: "2-digit",
|
|
849
|
+
hour12: false
|
|
850
|
+
}).format(timestamp);
|
|
851
|
+
}
|
|
852
|
+
function prepareMessages(messages) {
|
|
853
|
+
const merged = [];
|
|
854
|
+
let buffer = [];
|
|
855
|
+
const flush = () => {
|
|
856
|
+
if (buffer.length === 0) return;
|
|
857
|
+
if (buffer.length === 1) {
|
|
858
|
+
merged.push(buffer[0]);
|
|
859
|
+
} else {
|
|
860
|
+
const last2 = buffer[buffer.length - 1];
|
|
861
|
+
const chain = buffer.flatMap((message) => message.chain ?? []);
|
|
862
|
+
const lastWithContent = [...buffer].reverse().find((message) => message.content.trim());
|
|
863
|
+
const content = lastWithContent?.content ?? "";
|
|
864
|
+
const lastClarifyCall = findLastClarifyCall(chain);
|
|
865
|
+
const isPendingClarify = lastClarifyCall && !isClarifyCallAnswered(chain, lastClarifyCall);
|
|
866
|
+
merged.push({
|
|
867
|
+
...last2,
|
|
868
|
+
content,
|
|
869
|
+
chain,
|
|
870
|
+
type: isPendingClarify ? "clarify" : last2.type,
|
|
871
|
+
clarify: isPendingClarify ? last2.clarify : void 0
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
buffer = [];
|
|
875
|
+
};
|
|
876
|
+
for (const message of messages) {
|
|
877
|
+
if (message.role === "assistant") {
|
|
878
|
+
if (buffer.length > 0 && buffer[buffer.length - 1].chain?.some((node) => node.type === "user_reply")) {
|
|
879
|
+
flush();
|
|
880
|
+
}
|
|
881
|
+
buffer.push(message);
|
|
882
|
+
} else {
|
|
883
|
+
flush();
|
|
884
|
+
merged.push(message);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
flush();
|
|
888
|
+
const last = merged[merged.length - 1];
|
|
889
|
+
if (last && last.role === "assistant" && !last.content.trim() && !last.chain?.length && !last.reasoning?.trim() && !last.toolCalls?.length) {
|
|
890
|
+
merged.pop();
|
|
891
|
+
}
|
|
892
|
+
return merged;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/components/conversation-view/process-block.tsx
|
|
896
|
+
import { useState as useState6 } from "react";
|
|
897
|
+
import { ChevronRight } from "lucide-react";
|
|
898
|
+
|
|
899
|
+
// src/components/conversation-view/timeline.ts
|
|
900
|
+
function readNodeTimestamp(node, fallback) {
|
|
901
|
+
if (!node.created_at) return fallback;
|
|
902
|
+
const timestamp = new Date(node.created_at).getTime();
|
|
903
|
+
return Number.isNaN(timestamp) ? fallback : timestamp;
|
|
904
|
+
}
|
|
905
|
+
function stringifyPayload(payload) {
|
|
906
|
+
if (!payload) return "";
|
|
907
|
+
return JSON.stringify(payload, null, 2);
|
|
908
|
+
}
|
|
909
|
+
function readToolPayloadSummary(payload) {
|
|
910
|
+
if (!payload) return "";
|
|
911
|
+
const candidates = [payload.summary, payload.message, payload.content, payload.result];
|
|
912
|
+
const value = candidates.find((item) => typeof item === "string");
|
|
913
|
+
return typeof value === "string" ? value : "";
|
|
914
|
+
}
|
|
915
|
+
function createToolStep(node, chain) {
|
|
916
|
+
const nodeIndex = chain.findIndex((item) => item.id === node.id);
|
|
917
|
+
const result = chain.slice(nodeIndex + 1).find(
|
|
918
|
+
(item) => item.type === "tool_result" && item.tool_name === node.tool_name && (!item.parent_id || item.parent_id === node.id)
|
|
919
|
+
);
|
|
920
|
+
return {
|
|
921
|
+
kind: "step",
|
|
922
|
+
id: node.id,
|
|
923
|
+
title: node.title || node.tool_name || "Tool call",
|
|
924
|
+
toolName: node.tool_name,
|
|
925
|
+
input: node.tool_input,
|
|
926
|
+
output: result?.tool_output
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function createToolResultStep(node, chain) {
|
|
930
|
+
const nodeIndex = chain.findIndex((item) => item.id === node.id);
|
|
931
|
+
const pairedCall = chain.slice(0, nodeIndex).some(
|
|
932
|
+
(item) => item.type === "tool_call" && item.tool_name === node.tool_name && (!node.parent_id || node.parent_id === item.id)
|
|
933
|
+
);
|
|
934
|
+
if (pairedCall) return null;
|
|
935
|
+
return {
|
|
936
|
+
kind: "step",
|
|
937
|
+
id: node.id,
|
|
938
|
+
title: node.title || `${node.tool_name || "Tool"} result`,
|
|
939
|
+
toolName: node.tool_name,
|
|
940
|
+
output: node.tool_output
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function createClarifyBlock(node, chain) {
|
|
944
|
+
const clarify = extractClarifyFromChain([node]);
|
|
945
|
+
if (!clarify?.question) return null;
|
|
946
|
+
const reply = chain.find((item) => item.type === "user_reply" && item.parent_id === node.id);
|
|
947
|
+
return {
|
|
948
|
+
kind: "clarify",
|
|
949
|
+
id: node.id,
|
|
950
|
+
question: clarify.question,
|
|
951
|
+
answered: Boolean(reply) || isClarifyCallAnswered(chain, node),
|
|
952
|
+
answer: reply?.content
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
function createToolCallBlock(node, chain) {
|
|
956
|
+
if (node.tool_name === "clarify") return createClarifyBlock(node, chain);
|
|
957
|
+
return createToolStep(node, chain);
|
|
958
|
+
}
|
|
959
|
+
function createNodeBlock(node, chain, messageCreatedAt) {
|
|
960
|
+
if ((node.type === "thinking" || node.type === "task_plan" || node.type === "task_update" || node.type === "goal") && node.content) {
|
|
961
|
+
return {
|
|
962
|
+
kind: "text",
|
|
963
|
+
id: node.id,
|
|
964
|
+
content: node.content,
|
|
965
|
+
createdAt: readNodeTimestamp(node, messageCreatedAt),
|
|
966
|
+
tone: "process"
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
if (node.type === "tool_call") return createToolCallBlock(node, chain);
|
|
970
|
+
if (node.type === "user_reply" && node.content) {
|
|
971
|
+
const parent = chain.find((item) => item.id === node.parent_id);
|
|
972
|
+
if (parent?.type === "tool_call" && parent.tool_name === "clarify") return null;
|
|
973
|
+
return { kind: "userReply", id: node.id, content: node.content };
|
|
974
|
+
}
|
|
975
|
+
if (node.type === "final" && node.content) {
|
|
976
|
+
return {
|
|
977
|
+
kind: "text",
|
|
978
|
+
id: node.id,
|
|
979
|
+
content: node.content,
|
|
980
|
+
createdAt: readNodeTimestamp(node, messageCreatedAt),
|
|
981
|
+
tone: "final"
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
if (node.type === "tool_result") return createToolResultStep(node, chain);
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
function createToolCallDeltas(toolCalls) {
|
|
988
|
+
return (toolCalls ?? []).map((tool) => ({
|
|
989
|
+
kind: "step",
|
|
990
|
+
id: tool.id || `tool-${tool.index}`,
|
|
991
|
+
title: tool.function.name,
|
|
992
|
+
toolName: tool.function.name,
|
|
993
|
+
input: tool.function.arguments ? { arguments: tool.function.arguments } : void 0
|
|
994
|
+
}));
|
|
995
|
+
}
|
|
996
|
+
function createAssistantBlocks(message) {
|
|
997
|
+
if (message.error) {
|
|
998
|
+
return [
|
|
999
|
+
{
|
|
1000
|
+
kind: "error",
|
|
1001
|
+
id: `${message.id}:error`,
|
|
1002
|
+
content: message.content,
|
|
1003
|
+
createdAt: message.createdAt
|
|
1004
|
+
}
|
|
1005
|
+
];
|
|
1006
|
+
}
|
|
1007
|
+
const blocks = [];
|
|
1008
|
+
const chain = message.chain ?? [];
|
|
1009
|
+
for (const node of chain) {
|
|
1010
|
+
const block = createNodeBlock(node, chain, message.createdAt);
|
|
1011
|
+
if (block) blocks.push(block);
|
|
1012
|
+
}
|
|
1013
|
+
if (!chain.length && message.reasoning?.trim()) {
|
|
1014
|
+
blocks.push({
|
|
1015
|
+
kind: "text",
|
|
1016
|
+
id: `${message.id}:reasoning`,
|
|
1017
|
+
content: message.reasoning,
|
|
1018
|
+
createdAt: message.createdAt,
|
|
1019
|
+
tone: "process"
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
const hasStructuredToolNode = chain.some(
|
|
1023
|
+
(node) => node.type === "tool_call" || node.type === "tool_result"
|
|
1024
|
+
);
|
|
1025
|
+
if (!hasStructuredToolNode) {
|
|
1026
|
+
blocks.push(...createToolCallDeltas(message.toolCalls));
|
|
1027
|
+
}
|
|
1028
|
+
const hasFinalNode = chain.some((node) => node.type === "final" && node.content?.trim());
|
|
1029
|
+
if (message.content.trim() && !hasFinalNode) {
|
|
1030
|
+
blocks.push({
|
|
1031
|
+
kind: "text",
|
|
1032
|
+
id: `${message.id}:content`,
|
|
1033
|
+
content: message.content,
|
|
1034
|
+
createdAt: message.createdAt
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
return blocks;
|
|
1038
|
+
}
|
|
1039
|
+
function mergeProcessBlocks(blocks) {
|
|
1040
|
+
const merged = [];
|
|
1041
|
+
let processIndex = -1;
|
|
1042
|
+
for (const block of blocks) {
|
|
1043
|
+
if (block.kind === "prompt") {
|
|
1044
|
+
processIndex = -1;
|
|
1045
|
+
merged.push(block);
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
if (block.kind === "step") {
|
|
1049
|
+
if (processIndex < 0) {
|
|
1050
|
+
processIndex = merged.length;
|
|
1051
|
+
merged.push({
|
|
1052
|
+
kind: "text",
|
|
1053
|
+
id: `process:${block.id}`,
|
|
1054
|
+
content: "",
|
|
1055
|
+
createdAt: 0,
|
|
1056
|
+
tone: "process",
|
|
1057
|
+
steps: [block]
|
|
1058
|
+
});
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
const previous2 = merged[processIndex];
|
|
1062
|
+
if (previous2.kind !== "text") continue;
|
|
1063
|
+
merged[processIndex] = {
|
|
1064
|
+
...previous2,
|
|
1065
|
+
steps: [...previous2.steps ?? [], block]
|
|
1066
|
+
};
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
if (block.kind !== "text" || block.tone !== "process") {
|
|
1070
|
+
merged.push(block);
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
if (processIndex < 0) {
|
|
1074
|
+
processIndex = merged.length;
|
|
1075
|
+
merged.push(block);
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
const previous = merged[processIndex];
|
|
1079
|
+
if (previous.kind !== "text") continue;
|
|
1080
|
+
merged[processIndex] = {
|
|
1081
|
+
...previous,
|
|
1082
|
+
content: previous.content ? `${previous.content}
|
|
1083
|
+
|
|
1084
|
+
${block.content}` : block.content,
|
|
1085
|
+
createdAt: block.createdAt,
|
|
1086
|
+
steps: previous.steps
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
return merged;
|
|
1090
|
+
}
|
|
1091
|
+
function buildTimelineBlocks(messages) {
|
|
1092
|
+
const blocks = [];
|
|
1093
|
+
for (const message of prepareMessages(messages)) {
|
|
1094
|
+
if (message.role === "user") {
|
|
1095
|
+
blocks.push({
|
|
1096
|
+
kind: "prompt",
|
|
1097
|
+
id: message.id,
|
|
1098
|
+
content: message.content,
|
|
1099
|
+
createdAt: message.createdAt
|
|
1100
|
+
});
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
blocks.push(...createAssistantBlocks(message));
|
|
1104
|
+
}
|
|
1105
|
+
return mergeProcessBlocks(blocks);
|
|
1106
|
+
}
|
|
1107
|
+
function formatBlockPayload(payload) {
|
|
1108
|
+
return stringifyPayload(payload);
|
|
1109
|
+
}
|
|
1110
|
+
function getStepSummary(block) {
|
|
1111
|
+
return readToolPayloadSummary(block.output);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/components/conversation-view/process-block.tsx
|
|
1115
|
+
import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1116
|
+
var DETAIL_CLASS_NAME = "mt-2 rounded-lg border border-[#D5DCE8] bg-white p-3 text-[11px] leading-4 text-[#4E5E79] dark:border-white/10 dark:bg-[#0E1726] dark:text-[#AAB6CB]";
|
|
1117
|
+
function useProcessOpen(autoOpen) {
|
|
1118
|
+
const [manualOpen, setManualOpen] = useState6(null);
|
|
1119
|
+
const [prevAutoOpen, setPrevAutoOpen] = useState6(autoOpen);
|
|
1120
|
+
if (autoOpen !== prevAutoOpen) {
|
|
1121
|
+
setPrevAutoOpen(autoOpen);
|
|
1122
|
+
if (!autoOpen) {
|
|
1123
|
+
setManualOpen(null);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
const open = manualOpen ?? autoOpen;
|
|
1127
|
+
const toggle = () => {
|
|
1128
|
+
setManualOpen(!open);
|
|
1129
|
+
};
|
|
1130
|
+
return { open, toggle };
|
|
1131
|
+
}
|
|
1132
|
+
function DetailDisclosure({
|
|
1133
|
+
label,
|
|
1134
|
+
payload
|
|
1135
|
+
}) {
|
|
1136
|
+
const value = formatBlockPayload(payload);
|
|
1137
|
+
if (!value) return null;
|
|
1138
|
+
return /* @__PURE__ */ jsxs7("details", { className: "group", children: [
|
|
1139
|
+
/* @__PURE__ */ jsxs7("summary", { className: "mt-2 flex cursor-pointer list-none items-center gap-1 text-[12px] font-medium text-[#7A828F] dark:text-[#AAB6CB]", children: [
|
|
1140
|
+
/* @__PURE__ */ jsx13("span", { children: label }),
|
|
1141
|
+
/* @__PURE__ */ jsx13(ChevronRight, { className: "size-3.5 shrink-0 transition-transform group-open:rotate-90" })
|
|
1142
|
+
] }),
|
|
1143
|
+
/* @__PURE__ */ jsx13("pre", { className: DETAIL_CLASS_NAME, children: value })
|
|
1144
|
+
] });
|
|
1145
|
+
}
|
|
1146
|
+
function ToolStepBlock({
|
|
1147
|
+
block,
|
|
1148
|
+
labels
|
|
1149
|
+
}) {
|
|
1150
|
+
const summary = getStepSummary(block);
|
|
1151
|
+
const hasDetails = Boolean(summary || block.input || block.output);
|
|
1152
|
+
const showToolName = Boolean(block.toolName && block.title !== block.toolName);
|
|
1153
|
+
return /* @__PURE__ */ jsxs7("details", { className: "group", children: [
|
|
1154
|
+
/* @__PURE__ */ jsxs7("summary", { className: "flex cursor-pointer list-none items-center gap-4", children: [
|
|
1155
|
+
/* @__PURE__ */ jsx13("span", { className: "size-2 shrink-0 rounded-full bg-[#7A828F] dark:bg-[#6F7B90]" }),
|
|
1156
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-wrap items-center gap-2 text-[15px] leading-6 font-semibold text-[#111C30] dark:text-[#D8D8D8]", children: [
|
|
1157
|
+
/* @__PURE__ */ jsx13("span", { children: block.title }),
|
|
1158
|
+
showToolName ? /* @__PURE__ */ jsx13("span", { className: "text-[13px] font-medium text-[#7A828F] dark:text-[#8B95A7]", children: block.toolName }) : null,
|
|
1159
|
+
/* @__PURE__ */ jsx13(ChevronRight, { className: "size-4 shrink-0 text-[#7A828F] transition-transform group-open:rotate-90 dark:text-[#8B95A7]" })
|
|
1160
|
+
] })
|
|
1161
|
+
] }),
|
|
1162
|
+
hasDetails ? /* @__PURE__ */ jsxs7("div", { className: "mt-2 ml-6", children: [
|
|
1163
|
+
summary ? /* @__PURE__ */ jsx13("div", { className: "text-[14px] leading-6 font-medium text-[#4E5E79] dark:text-[#AAB6CB]", children: summary }) : null,
|
|
1164
|
+
/* @__PURE__ */ jsx13(DetailDisclosure, { label: labels.toolInput, payload: block.input }),
|
|
1165
|
+
/* @__PURE__ */ jsx13(DetailDisclosure, { label: labels.toolOutput, payload: block.output })
|
|
1166
|
+
] }) : null
|
|
1167
|
+
] });
|
|
1168
|
+
}
|
|
1169
|
+
function ProcessBlock({ block, autoOpen, labels }) {
|
|
1170
|
+
const { content, steps } = block;
|
|
1171
|
+
const { open, toggle } = useProcessOpen(autoOpen);
|
|
1172
|
+
return /* @__PURE__ */ jsxs7("div", { children: [
|
|
1173
|
+
/* @__PURE__ */ jsxs7(
|
|
1174
|
+
"button",
|
|
1175
|
+
{
|
|
1176
|
+
type: "button",
|
|
1177
|
+
className: "flex items-center gap-1.5 text-[14px] leading-6 font-semibold text-[#7A828F] dark:text-[#AAB6CB]",
|
|
1178
|
+
"aria-expanded": open,
|
|
1179
|
+
onClick: toggle,
|
|
1180
|
+
children: [
|
|
1181
|
+
/* @__PURE__ */ jsx13("span", { children: labels.thoughtProcess }),
|
|
1182
|
+
/* @__PURE__ */ jsx13(ChevronRight, { className: cn("size-4 shrink-0 transition-transform", open && "rotate-90") })
|
|
1183
|
+
]
|
|
1184
|
+
}
|
|
1185
|
+
),
|
|
1186
|
+
/* @__PURE__ */ jsx13(
|
|
1187
|
+
"div",
|
|
1188
|
+
{
|
|
1189
|
+
className: cn(
|
|
1190
|
+
"grid",
|
|
1191
|
+
autoOpen ? "transition-none" : "transition-[grid-template-rows,opacity] duration-200 ease-out",
|
|
1192
|
+
open ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"
|
|
1193
|
+
),
|
|
1194
|
+
children: /* @__PURE__ */ jsx13("div", { className: "min-h-0 overflow-hidden", children: /* @__PURE__ */ jsxs7("div", { className: "mt-3 space-y-4", children: [
|
|
1195
|
+
content ? /* @__PURE__ */ jsx13("div", { className: "text-[14px] leading-6 font-medium text-[#4E5E79] dark:text-[#AAB6CB]", children: /* @__PURE__ */ jsx13(
|
|
1196
|
+
MarkdownViewer,
|
|
1197
|
+
{
|
|
1198
|
+
className: "text-inherit [&>:first-child]:mt-0 [&>:last-child]:mb-0",
|
|
1199
|
+
copyLabel: labels.copy,
|
|
1200
|
+
copiedLabel: labels.copied,
|
|
1201
|
+
children: content
|
|
1202
|
+
}
|
|
1203
|
+
) }) : null,
|
|
1204
|
+
steps?.map((step) => /* @__PURE__ */ jsx13(ToolStepBlock, { block: step, labels }, step.id))
|
|
1205
|
+
] }) })
|
|
1206
|
+
}
|
|
1207
|
+
)
|
|
1208
|
+
] });
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// src/components/conversation-view/sections.tsx
|
|
1212
|
+
import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1213
|
+
function MessageActionOverlay({
|
|
1214
|
+
align,
|
|
1215
|
+
timestamp,
|
|
1216
|
+
labels,
|
|
1217
|
+
value
|
|
1218
|
+
}) {
|
|
1219
|
+
return /* @__PURE__ */ jsxs8(
|
|
1220
|
+
"div",
|
|
1221
|
+
{
|
|
1222
|
+
className: cn(
|
|
1223
|
+
"pointer-events-none absolute top-full right-0 left-0 z-10 flex min-h-6 w-full items-center gap-2 pt-1 text-[13px] leading-5 font-medium text-[#9AA1AD] opacity-0 transition-opacity group-hover/message:pointer-events-auto group-hover/message:opacity-100 dark:text-[#8B95A7]",
|
|
1224
|
+
align === "right" ? "justify-end" : "justify-start"
|
|
1225
|
+
),
|
|
1226
|
+
children: [
|
|
1227
|
+
/* @__PURE__ */ jsx14("span", { children: formatMessageTime(timestamp) }),
|
|
1228
|
+
/* @__PURE__ */ jsx14(
|
|
1229
|
+
CopyButton,
|
|
1230
|
+
{
|
|
1231
|
+
value,
|
|
1232
|
+
label: labels.copy,
|
|
1233
|
+
copiedLabel: labels.copied,
|
|
1234
|
+
className: "size-4 h-auto shrink-0 p-0 text-[#9AA1AD] hover:bg-transparent hover:text-[#111C30] dark:text-[#8B95A7] dark:hover:bg-transparent dark:hover:text-foreground"
|
|
1235
|
+
}
|
|
1236
|
+
)
|
|
1237
|
+
]
|
|
1238
|
+
}
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
function PromptBlock({
|
|
1242
|
+
block,
|
|
1243
|
+
labels
|
|
1244
|
+
}) {
|
|
1245
|
+
return /* @__PURE__ */ jsxs8("div", { className: "group/message relative", children: [
|
|
1246
|
+
/* @__PURE__ */ jsx14("div", { className: "rounded-[20px] border border-[#D5DCE8] bg-white px-5 py-4 text-[15px] leading-6 font-semibold text-[#111C30] dark:border-white/10 dark:bg-[#171F2E] dark:text-foreground", children: block.content }),
|
|
1247
|
+
/* @__PURE__ */ jsx14(
|
|
1248
|
+
MessageActionOverlay,
|
|
1249
|
+
{
|
|
1250
|
+
align: "right",
|
|
1251
|
+
timestamp: block.createdAt,
|
|
1252
|
+
labels,
|
|
1253
|
+
value: block.content
|
|
1254
|
+
}
|
|
1255
|
+
)
|
|
1256
|
+
] });
|
|
1257
|
+
}
|
|
1258
|
+
function TextBlock({
|
|
1259
|
+
block,
|
|
1260
|
+
autoOpenProcess,
|
|
1261
|
+
suppressActions,
|
|
1262
|
+
labels
|
|
1263
|
+
}) {
|
|
1264
|
+
const { content, tone } = block;
|
|
1265
|
+
if (tone === "process") {
|
|
1266
|
+
return /* @__PURE__ */ jsx14(ProcessBlock, { block, autoOpen: autoOpenProcess, labels });
|
|
1267
|
+
}
|
|
1268
|
+
return /* @__PURE__ */ jsxs8(
|
|
1269
|
+
"div",
|
|
1270
|
+
{
|
|
1271
|
+
className: cn(
|
|
1272
|
+
"group/message relative text-[15px] leading-7 font-semibold text-[#111C30] dark:text-[#D8D8D8]",
|
|
1273
|
+
tone === "final" && "pt-1"
|
|
1274
|
+
),
|
|
1275
|
+
children: [
|
|
1276
|
+
/* @__PURE__ */ jsx14(
|
|
1277
|
+
MarkdownViewer,
|
|
1278
|
+
{
|
|
1279
|
+
className: "text-inherit [&>:first-child]:mt-0 [&>:last-child]:mb-0",
|
|
1280
|
+
copyLabel: labels.copy,
|
|
1281
|
+
copiedLabel: labels.copied,
|
|
1282
|
+
children: content
|
|
1283
|
+
}
|
|
1284
|
+
),
|
|
1285
|
+
suppressActions ? null : /* @__PURE__ */ jsx14(
|
|
1286
|
+
MessageActionOverlay,
|
|
1287
|
+
{
|
|
1288
|
+
align: "left",
|
|
1289
|
+
timestamp: block.createdAt,
|
|
1290
|
+
labels,
|
|
1291
|
+
value: block.content
|
|
1292
|
+
}
|
|
1293
|
+
)
|
|
1294
|
+
]
|
|
1295
|
+
}
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
function ClarifyRecordBlock({
|
|
1299
|
+
question,
|
|
1300
|
+
answer,
|
|
1301
|
+
label,
|
|
1302
|
+
answerLabel
|
|
1303
|
+
}) {
|
|
1304
|
+
return /* @__PURE__ */ jsxs8("div", { className: "rounded-[20px] border border-[#D5DCE8] bg-transparent px-5 py-4 dark:border-white/10", children: [
|
|
1305
|
+
/* @__PURE__ */ jsx14("div", { className: "text-[13px] leading-5 font-semibold text-[#7A828F] dark:text-[#AAB6CB]", children: label }),
|
|
1306
|
+
/* @__PURE__ */ jsx14("div", { className: "mt-2 text-[15px] leading-6 font-medium text-[#56657A] dark:text-[#AAB6CB]", children: question }),
|
|
1307
|
+
answer ? /* @__PURE__ */ jsxs8("div", { className: "mt-4 border-l-2 border-[#7A8DA8] pl-3 text-[15px] leading-6 text-[#111C30] dark:border-[#AAB6CB] dark:text-[#D8D8D8]", children: [
|
|
1308
|
+
/* @__PURE__ */ jsx14("span", { className: "font-semibold", children: answerLabel }),
|
|
1309
|
+
/* @__PURE__ */ jsx14("span", { className: "ml-1 font-bold", children: answer })
|
|
1310
|
+
] }) : null
|
|
1311
|
+
] });
|
|
1312
|
+
}
|
|
1313
|
+
function UserReplyBlock({ content, label }) {
|
|
1314
|
+
return /* @__PURE__ */ jsxs8("div", { className: "text-[15px] leading-7 font-semibold text-[#111C30] dark:text-[#D8D8D8]", children: [
|
|
1315
|
+
/* @__PURE__ */ jsx14("span", { children: label }),
|
|
1316
|
+
/* @__PURE__ */ jsx14("span", { className: "ml-1", children: content })
|
|
1317
|
+
] });
|
|
1318
|
+
}
|
|
1319
|
+
function ErrorBlock({ content }) {
|
|
1320
|
+
return /* @__PURE__ */ jsx14("div", { className: "rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-[13px] leading-6 font-medium text-red-700 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-200", children: content });
|
|
1321
|
+
}
|
|
1322
|
+
function TimelineBlockItem({
|
|
1323
|
+
block,
|
|
1324
|
+
autoOpenProcess = false,
|
|
1325
|
+
suppressActions = false,
|
|
1326
|
+
labels
|
|
1327
|
+
}) {
|
|
1328
|
+
switch (block.kind) {
|
|
1329
|
+
case "prompt":
|
|
1330
|
+
return /* @__PURE__ */ jsx14(PromptBlock, { block, labels });
|
|
1331
|
+
case "text":
|
|
1332
|
+
return /* @__PURE__ */ jsx14(
|
|
1333
|
+
TextBlock,
|
|
1334
|
+
{
|
|
1335
|
+
block,
|
|
1336
|
+
autoOpenProcess,
|
|
1337
|
+
suppressActions,
|
|
1338
|
+
labels
|
|
1339
|
+
}
|
|
1340
|
+
);
|
|
1341
|
+
case "step":
|
|
1342
|
+
return null;
|
|
1343
|
+
case "clarify":
|
|
1344
|
+
return /* @__PURE__ */ jsx14(
|
|
1345
|
+
ClarifyRecordBlock,
|
|
1346
|
+
{
|
|
1347
|
+
question: block.question,
|
|
1348
|
+
answer: block.answer,
|
|
1349
|
+
label: labels.clarifyQuestion,
|
|
1350
|
+
answerLabel: labels.userSelected
|
|
1351
|
+
}
|
|
1352
|
+
);
|
|
1353
|
+
case "userReply":
|
|
1354
|
+
return /* @__PURE__ */ jsx14(UserReplyBlock, { content: block.content, label: labels.userSelected });
|
|
1355
|
+
case "error":
|
|
1356
|
+
return /* @__PURE__ */ jsx14(ErrorBlock, { content: block.content });
|
|
1357
|
+
default:
|
|
1358
|
+
return null;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function StreamingLoadingRow({ label }) {
|
|
1362
|
+
return /* @__PURE__ */ jsx14("div", { className: "flex items-center text-[13px] leading-6 font-medium", children: /* @__PURE__ */ jsx14("span", { className: "agent-loading-shimmer", children: label }) });
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// src/components/conversation-view/index.tsx
|
|
1366
|
+
import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1367
|
+
var SCROLL_BUTTON_CLASS_NAME = "gap-1.5 border border-[#D5DCE8] bg-white px-3 text-[13px] font-medium text-[#111C30] shadow-sm ring-0 transition-colors hover:border-[#C5CEDD] hover:bg-[#F7F9FC] hover:text-[#111C30] dark:border-white/10 dark:bg-[#17243A] dark:text-foreground dark:hover:border-white/15 dark:hover:bg-[#1D2D49]";
|
|
1368
|
+
function isProcessBlock(block) {
|
|
1369
|
+
return block.kind === "text" && block.tone === "process";
|
|
1370
|
+
}
|
|
1371
|
+
function isUserReplyBoundary(block) {
|
|
1372
|
+
return block.kind === "prompt" || block.kind === "userReply";
|
|
1373
|
+
}
|
|
1374
|
+
function getActiveProcessBlockId(blocks) {
|
|
1375
|
+
let processBlockId;
|
|
1376
|
+
for (const block of blocks) {
|
|
1377
|
+
if (block.kind === "prompt") processBlockId = void 0;
|
|
1378
|
+
if (isProcessBlock(block)) processBlockId = block.id;
|
|
1379
|
+
}
|
|
1380
|
+
return processBlockId;
|
|
1381
|
+
}
|
|
1382
|
+
function getActiveReplyActionHiddenIds(blocks) {
|
|
1383
|
+
const hiddenIds = /* @__PURE__ */ new Set();
|
|
1384
|
+
let afterUserReplyBoundary = false;
|
|
1385
|
+
for (const block of blocks) {
|
|
1386
|
+
if (isUserReplyBoundary(block)) {
|
|
1387
|
+
hiddenIds.clear();
|
|
1388
|
+
afterUserReplyBoundary = true;
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
if (afterUserReplyBoundary && block.kind === "text") hiddenIds.add(block.id);
|
|
1392
|
+
}
|
|
1393
|
+
return hiddenIds;
|
|
1394
|
+
}
|
|
1395
|
+
function ConversationView({
|
|
1396
|
+
session,
|
|
1397
|
+
status,
|
|
1398
|
+
awaitingUserReply = false,
|
|
1399
|
+
labels,
|
|
1400
|
+
conversationRef
|
|
1401
|
+
}) {
|
|
1402
|
+
const isStreaming = status === "streaming" || status === "retrying";
|
|
1403
|
+
const isReplyActive = isStreaming || awaitingUserReply;
|
|
1404
|
+
const timelineBlocks = buildTimelineBlocks(session.messages);
|
|
1405
|
+
const isNew = timelineBlocks.length === 0;
|
|
1406
|
+
const activeProcessBlockId = isReplyActive ? getActiveProcessBlockId(timelineBlocks) : void 0;
|
|
1407
|
+
const actionHiddenIds = isReplyActive ? getActiveReplyActionHiddenIds(timelineBlocks) : void 0;
|
|
1408
|
+
return /* @__PURE__ */ jsxs9(
|
|
1409
|
+
AIConversation,
|
|
1410
|
+
{
|
|
1411
|
+
ref: conversationRef,
|
|
1412
|
+
stickToBottom: true,
|
|
1413
|
+
className: cn(
|
|
1414
|
+
"agent-conversation-stable min-h-0 text-[#111C30] select-text dark:text-[#D8D8D8] [&_*]:!select-text",
|
|
1415
|
+
isNew ? "!h-auto flex-none pt-0 pb-3" : "flex-1 p-0"
|
|
1416
|
+
),
|
|
1417
|
+
children: [
|
|
1418
|
+
/* @__PURE__ */ jsxs9(
|
|
1419
|
+
AIConversationContent,
|
|
1420
|
+
{
|
|
1421
|
+
className: cn("mx-auto flex w-full max-w-[900px] flex-col px-6", isNew ? "gap-2" : "gap-8"),
|
|
1422
|
+
children: [
|
|
1423
|
+
timelineBlocks.length ? timelineBlocks.map((block) => /* @__PURE__ */ jsx15(
|
|
1424
|
+
TimelineBlockItem,
|
|
1425
|
+
{
|
|
1426
|
+
block,
|
|
1427
|
+
autoOpenProcess: block.id === activeProcessBlockId,
|
|
1428
|
+
suppressActions: actionHiddenIds?.has(block.id),
|
|
1429
|
+
labels
|
|
1430
|
+
},
|
|
1431
|
+
`${block.kind}:${block.id}`
|
|
1432
|
+
)) : null,
|
|
1433
|
+
isStreaming ? /* @__PURE__ */ jsx15(StreamingLoadingRow, { label: labels.thinking }) : null
|
|
1434
|
+
]
|
|
1435
|
+
}
|
|
1436
|
+
),
|
|
1437
|
+
/* @__PURE__ */ jsx15(AIConversationScrollButton, { className: SCROLL_BUTTON_CLASS_NAME })
|
|
1438
|
+
]
|
|
1439
|
+
}
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// src/components/surface/index.tsx
|
|
1444
|
+
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
1445
|
+
import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1446
|
+
function AgentConversationSurface({
|
|
1447
|
+
agent,
|
|
1448
|
+
labels,
|
|
1449
|
+
initialQuestions = [],
|
|
1450
|
+
className
|
|
1451
|
+
}) {
|
|
1452
|
+
const isNew = agent.currentSession.messages.length === 0;
|
|
1453
|
+
const pendingClarify = getPendingClarify(agent.currentSession.messages);
|
|
1454
|
+
const conversationRef = useRef3(null);
|
|
1455
|
+
useEffect3(() => {
|
|
1456
|
+
if (isNew) return;
|
|
1457
|
+
requestAnimationFrame(() => conversationRef.current?.scrollToBottom());
|
|
1458
|
+
}, [agent.currentSession.id, isNew]);
|
|
1459
|
+
const handleSubmit = async (content) => {
|
|
1460
|
+
requestAnimationFrame(() => conversationRef.current?.scrollToBottom());
|
|
1461
|
+
await agent.sendMessage(content);
|
|
1462
|
+
};
|
|
1463
|
+
const handleClarifyOption = async (option, chainReplyTo) => {
|
|
1464
|
+
requestAnimationFrame(() => conversationRef.current?.scrollToBottom());
|
|
1465
|
+
await agent.sendClarifyOption(option, chainReplyTo);
|
|
1466
|
+
};
|
|
1467
|
+
return /* @__PURE__ */ jsxs10(
|
|
1468
|
+
"div",
|
|
1469
|
+
{
|
|
1470
|
+
className: cn(
|
|
1471
|
+
"flex min-h-0 flex-1 flex-col overflow-hidden",
|
|
1472
|
+
isNew && "pt-[20vh]",
|
|
1473
|
+
className
|
|
1474
|
+
),
|
|
1475
|
+
children: [
|
|
1476
|
+
/* @__PURE__ */ jsx16(
|
|
1477
|
+
ConversationView,
|
|
1478
|
+
{
|
|
1479
|
+
conversationRef,
|
|
1480
|
+
session: agent.currentSession,
|
|
1481
|
+
status: agent.status,
|
|
1482
|
+
awaitingUserReply: Boolean(pendingClarify),
|
|
1483
|
+
labels: labels.conversation
|
|
1484
|
+
}
|
|
1485
|
+
),
|
|
1486
|
+
/* @__PURE__ */ jsx16(
|
|
1487
|
+
AgentComposer,
|
|
1488
|
+
{
|
|
1489
|
+
status: agent.status,
|
|
1490
|
+
contextUsage: agent.contextUsage,
|
|
1491
|
+
pendingClarify,
|
|
1492
|
+
isNew,
|
|
1493
|
+
initialQuestions,
|
|
1494
|
+
labels: labels.composer,
|
|
1495
|
+
onSubmit: handleSubmit,
|
|
1496
|
+
onClarifyOption: handleClarifyOption,
|
|
1497
|
+
onStop: agent.stopStreaming
|
|
1498
|
+
}
|
|
1499
|
+
)
|
|
1500
|
+
]
|
|
1501
|
+
}
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// src/components/history/index.tsx
|
|
1506
|
+
import { useMemo as useMemo3, useState as useState7 } from "react";
|
|
1507
|
+
import useSWR from "swr";
|
|
1508
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1509
|
+
var HISTORY_LIST_HEIGHT_CLASS_NAME = 'max-h-[420px] [&_[cmdk-item][data-selected="true"]:not(:hover)]:!bg-transparent [&_[cmdk-item][data-selected="true"]:not(:hover)]:!text-inherit [&_[cmdk-item]:hover]:!bg-accent [&_[cmdk-item]:hover]:!text-accent-foreground';
|
|
1510
|
+
var SKELETON_ITEM_COUNT = 6;
|
|
1511
|
+
function formatSessionTime(value) {
|
|
1512
|
+
if (!value) return "-";
|
|
1513
|
+
const timestamp = new Date(value).getTime();
|
|
1514
|
+
if (Number.isNaN(timestamp)) return "-";
|
|
1515
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
1516
|
+
month: "2-digit",
|
|
1517
|
+
day: "2-digit",
|
|
1518
|
+
hour: "2-digit",
|
|
1519
|
+
minute: "2-digit",
|
|
1520
|
+
hour12: false
|
|
1521
|
+
}).format(timestamp);
|
|
1522
|
+
}
|
|
1523
|
+
function createSessionDescription({
|
|
1524
|
+
session,
|
|
1525
|
+
labels
|
|
1526
|
+
}) {
|
|
1527
|
+
return `${formatSessionTime(session.updated_at ?? session.created_at)} | ${labels.messageCount(
|
|
1528
|
+
session.message_count ?? 0
|
|
1529
|
+
)}`;
|
|
1530
|
+
}
|
|
1531
|
+
function getHistoryEmptyText({
|
|
1532
|
+
error,
|
|
1533
|
+
selectError,
|
|
1534
|
+
labels
|
|
1535
|
+
}) {
|
|
1536
|
+
if (selectError) return selectError;
|
|
1537
|
+
if (error) return labels.loadError;
|
|
1538
|
+
return labels.empty;
|
|
1539
|
+
}
|
|
1540
|
+
function getCommandValue(session) {
|
|
1541
|
+
return [session.title, session.id, session.created_at, session.updated_at].filter(Boolean).join(" ");
|
|
1542
|
+
}
|
|
1543
|
+
function createHistoryGroups({
|
|
1544
|
+
sessions,
|
|
1545
|
+
busy,
|
|
1546
|
+
labels
|
|
1547
|
+
}) {
|
|
1548
|
+
return [
|
|
1549
|
+
{
|
|
1550
|
+
key: "recent",
|
|
1551
|
+
heading: labels.recent,
|
|
1552
|
+
items: sessions.map((session) => ({
|
|
1553
|
+
key: session.id,
|
|
1554
|
+
label: session.title?.trim() || labels.untitled,
|
|
1555
|
+
description: createSessionDescription({ session, labels }),
|
|
1556
|
+
keywords: [getCommandValue(session)],
|
|
1557
|
+
disabled: busy
|
|
1558
|
+
}))
|
|
1559
|
+
}
|
|
1560
|
+
];
|
|
1561
|
+
}
|
|
1562
|
+
function createSkeletonGroups(heading) {
|
|
1563
|
+
const titleWidths = ["60%", "75%", "50%", "65%", "55%", "70%"];
|
|
1564
|
+
return [
|
|
1565
|
+
{
|
|
1566
|
+
key: "skeleton",
|
|
1567
|
+
heading,
|
|
1568
|
+
items: Array.from({ length: SKELETON_ITEM_COUNT }, (_, i) => ({
|
|
1569
|
+
key: `skeleton-${i}`,
|
|
1570
|
+
label: /* @__PURE__ */ jsx17(Skeleton, { className: "h-4 rounded", style: { width: titleWidths[i] } }),
|
|
1571
|
+
description: /* @__PURE__ */ jsx17(Skeleton, { className: "h-3 w-[40%] rounded" }),
|
|
1572
|
+
disabled: true
|
|
1573
|
+
}))
|
|
1574
|
+
}
|
|
1575
|
+
];
|
|
1576
|
+
}
|
|
1577
|
+
function AgentHistoryDialog({
|
|
1578
|
+
client,
|
|
1579
|
+
open,
|
|
1580
|
+
busy,
|
|
1581
|
+
onOpenChange,
|
|
1582
|
+
onSelectSession,
|
|
1583
|
+
labels
|
|
1584
|
+
}) {
|
|
1585
|
+
const [selectError, setSelectError] = useState7(null);
|
|
1586
|
+
const swrKey = open ? client.endpoints.sessions : null;
|
|
1587
|
+
const { data, error, isLoading } = useSWR(swrKey, () => client.listSessions());
|
|
1588
|
+
const sessions = useMemo3(() => data ?? [], [data]);
|
|
1589
|
+
const showEmpty = Boolean(error || selectError || !isLoading && sessions.length === 0);
|
|
1590
|
+
const emptyText = getHistoryEmptyText({ error, selectError, labels });
|
|
1591
|
+
const groups = useMemo3(() => {
|
|
1592
|
+
if (isLoading) return createSkeletonGroups(labels.recent);
|
|
1593
|
+
if (showEmpty) return [];
|
|
1594
|
+
return createHistoryGroups({ sessions, busy, labels });
|
|
1595
|
+
}, [busy, isLoading, labels, sessions, showEmpty]);
|
|
1596
|
+
const handleSelect = async (sessionId) => {
|
|
1597
|
+
setSelectError(null);
|
|
1598
|
+
try {
|
|
1599
|
+
await onSelectSession(sessionId);
|
|
1600
|
+
onOpenChange(false);
|
|
1601
|
+
} catch (err) {
|
|
1602
|
+
setSelectError(err instanceof Error ? err.message : labels.selectError);
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
return /* @__PURE__ */ jsx17(
|
|
1606
|
+
CommandPalette,
|
|
1607
|
+
{
|
|
1608
|
+
open,
|
|
1609
|
+
onOpenChange,
|
|
1610
|
+
title: labels.title,
|
|
1611
|
+
description: labels.description,
|
|
1612
|
+
contentClassName: "max-w-lg",
|
|
1613
|
+
groups,
|
|
1614
|
+
placeholder: labels.search,
|
|
1615
|
+
emptyText,
|
|
1616
|
+
listClassName: HISTORY_LIST_HEIGHT_CLASS_NAME,
|
|
1617
|
+
onItemSelect: (item) => void handleSelect(String(item.key))
|
|
1618
|
+
}
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
export {
|
|
1622
|
+
AgentComposer,
|
|
1623
|
+
AgentConversationSurface,
|
|
1624
|
+
AgentHistoryDialog,
|
|
1625
|
+
ContextUsageIndicator,
|
|
1626
|
+
ConversationView,
|
|
1627
|
+
DEFAULT_AGENT_ENDPOINTS,
|
|
1628
|
+
appendMessageChainNode,
|
|
1629
|
+
appendMessageToolCalls,
|
|
1630
|
+
appendSessionMessages,
|
|
1631
|
+
appendUserReplyChainNode,
|
|
1632
|
+
consumeAgentSseStream,
|
|
1633
|
+
createAgentClient,
|
|
1634
|
+
createAgentMessage,
|
|
1635
|
+
createAgentSession,
|
|
1636
|
+
createClarifyMessage,
|
|
1637
|
+
extractClarifyFromChain,
|
|
1638
|
+
findLastClarifyCall,
|
|
1639
|
+
getContextUsageEndpoint,
|
|
1640
|
+
getPendingClarify,
|
|
1641
|
+
getSessionDetailEndpoint,
|
|
1642
|
+
getSessionTitle,
|
|
1643
|
+
isClarifyCallAnswered,
|
|
1644
|
+
loadInitialChatSession,
|
|
1645
|
+
parseSseEnvelope,
|
|
1646
|
+
parseStreamEvent,
|
|
1647
|
+
removeSessionMessage,
|
|
1648
|
+
replaceSession,
|
|
1649
|
+
resolveAgentEndpoints,
|
|
1650
|
+
runAgentStream,
|
|
1651
|
+
sortSessions,
|
|
1652
|
+
streamEventToChainNode,
|
|
1653
|
+
updateClarifyMessage,
|
|
1654
|
+
updateMessageContent,
|
|
1655
|
+
updateMessageReasoning,
|
|
1656
|
+
updateMessageType,
|
|
1657
|
+
upsertSession,
|
|
1658
|
+
useAgentSession
|
|
1659
|
+
};
|