@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/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
+ };