@tangle-network/ui 8.1.0 → 9.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/CHANGELOG.md +12 -0
- package/dist/chat.d.ts +1 -10
- package/dist/chat.js +2 -2
- package/dist/{chunk-C3BIVG72.js → chunk-2TRMNB6L.js} +123 -153
- package/dist/{chunk-QUAU6ZNC.js → chunk-LHOGIUGY.js} +42 -90
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -5
- package/dist/run.d.ts +1 -29
- package/dist/run.js +2 -4
- package/package.json +1 -1
- package/src/chat/agent-timeline.tsx +45 -139
- package/src/run/index.ts +0 -1
- package/src/run/run-group.tsx +166 -105
- package/src/run/assistant-run-shell.tsx +0 -115
- /package/dist/{chunk-LQS34IGP.js → chunk-47XH56SV.js} +0 -0
package/src/run/run-group.tsx
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { memo, useMemo, type ComponentType, type ReactNode } from "react";
|
|
2
|
+
import * as Collapsible from "@radix-ui/react-collapsible";
|
|
2
3
|
import {
|
|
3
4
|
Bot,
|
|
4
5
|
Loader2,
|
|
6
|
+
ChevronDown,
|
|
7
|
+
ChevronRight,
|
|
5
8
|
Terminal,
|
|
6
9
|
FileEdit,
|
|
7
10
|
FileSearch,
|
|
@@ -10,8 +13,10 @@ import {
|
|
|
10
13
|
Globe,
|
|
11
14
|
ClipboardList,
|
|
12
15
|
Settings,
|
|
16
|
+
Sparkles,
|
|
13
17
|
type LucideProps,
|
|
14
18
|
} from "lucide-react";
|
|
19
|
+
import { cn } from "../lib/utils";
|
|
15
20
|
import { formatDuration } from "../utils/format";
|
|
16
21
|
import type { Run, ToolCategory } from "../types/run";
|
|
17
22
|
import type { SessionPart, ToolPart, ReasoningPart } from "../types/parts";
|
|
@@ -19,8 +24,39 @@ import type { AgentBranding } from "../types/branding";
|
|
|
19
24
|
import type { CustomToolRenderer } from "../types/tool-display";
|
|
20
25
|
import { InlineToolItem } from "./inline-tool-item";
|
|
21
26
|
import { InlineThinkingItem } from "./inline-thinking-item";
|
|
22
|
-
import { AssistantRunShell } from "./assistant-run-shell";
|
|
23
27
|
import { Markdown } from "../markdown/markdown";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* One row on the run's timeline spine: a connector line + accent dot in a
|
|
31
|
+
* narrow gutter, content to the right. Mirrors AgentTimeline's row so a run
|
|
32
|
+
* reads as separated, distinct steps — not one filled box.
|
|
33
|
+
*/
|
|
34
|
+
function SpineRow({
|
|
35
|
+
accentClassName,
|
|
36
|
+
isLast,
|
|
37
|
+
children,
|
|
38
|
+
}: {
|
|
39
|
+
accentClassName: string;
|
|
40
|
+
isLast: boolean;
|
|
41
|
+
children: ReactNode;
|
|
42
|
+
}) {
|
|
43
|
+
return (
|
|
44
|
+
<div className="grid grid-cols-[1.25rem_minmax(0,1fr)] gap-x-3">
|
|
45
|
+
<div className="relative flex justify-center">
|
|
46
|
+
{!isLast && (
|
|
47
|
+
<span className="absolute top-3.5 bottom-[-0.75rem] left-1/2 w-px -translate-x-1/2 bg-[var(--border-subtle)]" />
|
|
48
|
+
)}
|
|
49
|
+
<span
|
|
50
|
+
className={cn(
|
|
51
|
+
"relative mt-1.5 h-[var(--timeline-dot-size,0.5rem)] w-[var(--timeline-dot-size,0.5rem)] rounded-full ring-4 ring-[var(--bg-root)]",
|
|
52
|
+
accentClassName,
|
|
53
|
+
)}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="min-w-0 pb-3">{children}</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
24
60
|
import {
|
|
25
61
|
OpenUIArtifactRenderer,
|
|
26
62
|
type OpenUIAction,
|
|
@@ -241,21 +277,6 @@ function renderSummary(run: Run) {
|
|
|
241
277
|
return parts.join(", ");
|
|
242
278
|
}
|
|
243
279
|
|
|
244
|
-
function getToolGroupPosition(
|
|
245
|
-
currentIndex: number,
|
|
246
|
-
parts: Array<{ part: SessionPart; msgId: string; index: number }>,
|
|
247
|
-
) {
|
|
248
|
-
const previous = parts[currentIndex - 1]?.part;
|
|
249
|
-
const next = parts[currentIndex + 1]?.part;
|
|
250
|
-
const previousIsTool = previous?.type === "tool";
|
|
251
|
-
const nextIsTool = next?.type === "tool";
|
|
252
|
-
|
|
253
|
-
if (previousIsTool && nextIsTool) return "middle" as const;
|
|
254
|
-
if (previousIsTool) return "last" as const;
|
|
255
|
-
if (nextIsTool) return "first" as const;
|
|
256
|
-
return "single" as const;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
280
|
// ---------------------------------------------------------------------------
|
|
260
281
|
// Component
|
|
261
282
|
// ---------------------------------------------------------------------------
|
|
@@ -388,107 +409,147 @@ export const RunGroup = memo(
|
|
|
388
409
|
);
|
|
389
410
|
}
|
|
390
411
|
|
|
412
|
+
// Renderable rows: skip empty/synthetic text so spine dots map to real steps.
|
|
413
|
+
const rows = allParts.filter(({ part }) => {
|
|
414
|
+
if (part.type === "tool" || part.type === "reasoning") return true;
|
|
415
|
+
return part.type === "text" && !part.synthetic && part.text.trim().length > 0;
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const dotAccent = (part: SessionPart): string => {
|
|
419
|
+
if (part.type === "reasoning") return "bg-[var(--brand-glow)]";
|
|
420
|
+
if (part.type === "text") return "bg-primary";
|
|
421
|
+
return "bg-[var(--border-hover)]";
|
|
422
|
+
};
|
|
423
|
+
|
|
391
424
|
return (
|
|
392
|
-
<
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
425
|
+
<Collapsible.Root open={!collapsed} onOpenChange={() => onToggle()}>
|
|
426
|
+
<div className="flex flex-col gap-1">
|
|
427
|
+
{/* Header — a quiet row, not a filled box */}
|
|
428
|
+
<div className="flex items-start gap-2">
|
|
429
|
+
<Collapsible.Trigger asChild>
|
|
430
|
+
<button
|
|
431
|
+
type="button"
|
|
432
|
+
className="group flex min-w-0 flex-1 items-center gap-2 rounded-md px-1 py-0.5 text-left transition-colors hover:bg-[var(--surface-container-high)]/40"
|
|
433
|
+
>
|
|
434
|
+
{collapsed ? (
|
|
435
|
+
<ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
436
|
+
) : (
|
|
437
|
+
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
438
|
+
)}
|
|
439
|
+
<span className={cn("font-semibold text-sm", branding.textClass)}>
|
|
440
|
+
{branding.label}
|
|
441
|
+
</span>
|
|
442
|
+
{renderSummary(run) ? (
|
|
443
|
+
<span className="text-[11px] text-muted-foreground">{renderSummary(run)}</span>
|
|
444
|
+
) : null}
|
|
445
|
+
{collapsed && run.summaryText ? (
|
|
446
|
+
<span className="min-w-0 truncate text-[11px] text-foreground/70">
|
|
447
|
+
{run.summaryText}
|
|
448
|
+
</span>
|
|
449
|
+
) : null}
|
|
450
|
+
<span className="ml-auto flex shrink-0 items-center gap-1.5">
|
|
451
|
+
<CategoryBadges categories={stats.toolCategories} />
|
|
452
|
+
{isStreaming ? (
|
|
453
|
+
<span className="inline-flex items-center gap-1 rounded-full border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] px-2 py-px text-[10px] font-semibold uppercase text-[var(--accent-text)]">
|
|
454
|
+
<Loader2 className="h-2.5 w-2.5 animate-spin" />
|
|
455
|
+
Running
|
|
456
|
+
</span>
|
|
457
|
+
) : (
|
|
458
|
+
<span className="inline-flex items-center gap-1 rounded-full border border-border px-2 py-px text-[10px] font-semibold uppercase text-muted-foreground">
|
|
459
|
+
<Sparkles className="h-2.5 w-2.5" />
|
|
460
|
+
Done
|
|
461
|
+
</span>
|
|
462
|
+
)}
|
|
463
|
+
</span>
|
|
464
|
+
</button>
|
|
465
|
+
</Collapsible.Trigger>
|
|
466
|
+
{headerActions ? (
|
|
467
|
+
<div className="flex shrink-0 flex-wrap items-center justify-end gap-1.5 pt-1">
|
|
468
|
+
{headerActions}
|
|
469
|
+
</div>
|
|
470
|
+
) : null}
|
|
471
|
+
</div>
|
|
404
472
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
<div className="
|
|
431
|
-
|
|
473
|
+
{/* Collapsed preview */}
|
|
474
|
+
{collapsed && run.summaryText ? (
|
|
475
|
+
<div className="line-clamp-2 pl-6 text-sm leading-6 text-muted-foreground">
|
|
476
|
+
{run.summaryText}
|
|
477
|
+
</div>
|
|
478
|
+
) : null}
|
|
479
|
+
|
|
480
|
+
{/* Expanded — separated steps on a timeline spine, no wrapping box */}
|
|
481
|
+
<Collapsible.Content className="overflow-hidden data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp">
|
|
482
|
+
<div className="pt-1.5">
|
|
483
|
+
{rows.map(({ part, msgId, index }, rowIndex) => {
|
|
484
|
+
const key = `${msgId}-${index}`;
|
|
485
|
+
const isLast = rowIndex === rows.length - 1;
|
|
486
|
+
let node: ReactNode = null;
|
|
487
|
+
|
|
488
|
+
if (part.type === "tool") {
|
|
489
|
+
if (isOpenUITool(part as ToolPart)) {
|
|
490
|
+
const toolPart = part as ToolPart;
|
|
491
|
+
const schema = extractOpenUISchema(toolPart.state.output);
|
|
492
|
+
const summary = getOpenUISummary(toolPart.state.output);
|
|
493
|
+
|
|
494
|
+
if (toolPart.state.status === "completed" && schema) {
|
|
495
|
+
node = (
|
|
496
|
+
<div className="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--bg-card)]">
|
|
497
|
+
{summary ? (
|
|
498
|
+
<div className="border-b border-[var(--border-subtle)] px-4 py-3 text-sm leading-6 text-foreground">
|
|
499
|
+
{summary}
|
|
432
500
|
</div>
|
|
433
|
-
|
|
501
|
+
) : null}
|
|
502
|
+
<div className="p-4">
|
|
503
|
+
<OpenUIArtifactRenderer schema={schema} />
|
|
434
504
|
</div>
|
|
435
|
-
) : null}
|
|
436
|
-
<div className="p-4">
|
|
437
|
-
<OpenUIArtifactRenderer schema={schema} />
|
|
438
505
|
</div>
|
|
439
|
-
|
|
440
|
-
)
|
|
441
|
-
|
|
506
|
+
);
|
|
507
|
+
} else if (toolPart.state.status === "running") {
|
|
508
|
+
node = (
|
|
509
|
+
<div className="flex items-center gap-3 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--bg-card)] px-4 py-3 text-sm text-muted-foreground">
|
|
510
|
+
<Loader2 className="h-4 w-4 animate-spin text-primary" />
|
|
511
|
+
Building view…
|
|
512
|
+
</div>
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (node === null) {
|
|
442
518
|
node = (
|
|
443
|
-
<
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
519
|
+
<InlineToolItem
|
|
520
|
+
part={part as ToolPart}
|
|
521
|
+
renderToolDetail={renderToolDetail}
|
|
522
|
+
actions={renderToolActions?.(part as ToolPart, {
|
|
523
|
+
run,
|
|
524
|
+
messageId: msgId,
|
|
525
|
+
partIndex: index,
|
|
526
|
+
})}
|
|
527
|
+
/>
|
|
447
528
|
);
|
|
448
529
|
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (node === null) {
|
|
530
|
+
} else if (part.type === "reasoning") {
|
|
452
531
|
node = (
|
|
453
|
-
<
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
partIndex: index,
|
|
461
|
-
})}
|
|
462
|
-
/>
|
|
532
|
+
<InlineThinkingItem part={part as ReasoningPart} defaultOpen={isStreaming} />
|
|
533
|
+
);
|
|
534
|
+
} else if (part.type === "text" && !part.synthetic && part.text.trim()) {
|
|
535
|
+
node = (
|
|
536
|
+
<div className="px-1 py-0.5">
|
|
537
|
+
<Markdown className="tangle-prose text-[15px] leading-7">{part.text}</Markdown>
|
|
538
|
+
</div>
|
|
463
539
|
);
|
|
464
540
|
}
|
|
465
|
-
} else if (part.type === "reasoning") {
|
|
466
|
-
node = (
|
|
467
|
-
<InlineThinkingItem
|
|
468
|
-
part={part as ReasoningPart}
|
|
469
|
-
defaultOpen={isStreaming}
|
|
470
|
-
/>
|
|
471
|
-
);
|
|
472
|
-
} else if (
|
|
473
|
-
part.type === "text" &&
|
|
474
|
-
!part.synthetic &&
|
|
475
|
-
part.text.trim()
|
|
476
|
-
) {
|
|
477
|
-
node = (
|
|
478
|
-
<div className="px-1 py-1">
|
|
479
|
-
<Markdown className="tangle-prose text-[15px] leading-7">{part.text}</Markdown>
|
|
480
|
-
</div>
|
|
481
|
-
);
|
|
482
|
-
}
|
|
483
541
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
542
|
+
if (!node) return null;
|
|
543
|
+
return (
|
|
544
|
+
<SpineRow key={key} accentClassName={dotAccent(part)} isLast={isLast}>
|
|
545
|
+
{node}
|
|
546
|
+
</SpineRow>
|
|
547
|
+
);
|
|
548
|
+
})}
|
|
549
|
+
</div>
|
|
550
|
+
</Collapsible.Content>
|
|
551
|
+
</div>
|
|
552
|
+
</Collapsible.Root>
|
|
492
553
|
);
|
|
493
554
|
},
|
|
494
555
|
);
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
2
|
-
import * as Collapsible from "@radix-ui/react-collapsible";
|
|
3
|
-
import { ChevronDown, ChevronRight, Loader2, Sparkles } from "lucide-react";
|
|
4
|
-
import { cn } from "../lib/utils";
|
|
5
|
-
|
|
6
|
-
export interface AssistantRunShellProps {
|
|
7
|
-
/** Header label, e.g. the agent name or "Tools". */
|
|
8
|
-
label: string;
|
|
9
|
-
/** Terse stat line beside the label, e.g. "3 tools, 2s thinking". */
|
|
10
|
-
summary?: string;
|
|
11
|
-
/** One-line preview shown next to the label AND below the header when collapsed. */
|
|
12
|
-
collapsedPreview?: string;
|
|
13
|
-
/** Small trailing glyphs before the status pill (e.g. category badges). */
|
|
14
|
-
badges?: ReactNode;
|
|
15
|
-
/** Drives the status pill and header spinner. */
|
|
16
|
-
isStreaming?: boolean;
|
|
17
|
-
collapsed: boolean;
|
|
18
|
-
onToggle: () => void;
|
|
19
|
-
/** Actions rendered outside the collapse trigger, right of the header. */
|
|
20
|
-
headerActions?: ReactNode;
|
|
21
|
-
children: ReactNode;
|
|
22
|
-
className?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The collapsible "assistant run" container shared by `RunGroup` (session-model
|
|
27
|
-
* driven) and `AgentTimeline` (declarative item list). Owns the header
|
|
28
|
-
* (label · summary · badges · status pill · chevron), the collapsed preview, and
|
|
29
|
-
* the Radix collapse — so both transcripts fold agent activity the same way and
|
|
30
|
-
* there is one implementation of a run, not two. It renders only chrome; callers
|
|
31
|
-
* pass the run body (tool rows, reasoning, text) as `children`.
|
|
32
|
-
*/
|
|
33
|
-
export function AssistantRunShell({
|
|
34
|
-
label,
|
|
35
|
-
summary,
|
|
36
|
-
collapsedPreview,
|
|
37
|
-
badges,
|
|
38
|
-
isStreaming,
|
|
39
|
-
collapsed,
|
|
40
|
-
onToggle,
|
|
41
|
-
headerActions,
|
|
42
|
-
children,
|
|
43
|
-
className,
|
|
44
|
-
}: AssistantRunShellProps) {
|
|
45
|
-
return (
|
|
46
|
-
<Collapsible.Root open={!collapsed} onOpenChange={() => onToggle()}>
|
|
47
|
-
<div
|
|
48
|
-
className={cn(
|
|
49
|
-
"rounded-[28px] border border-[var(--border-subtle)] bg-[var(--bg-card)] shadow-none",
|
|
50
|
-
className,
|
|
51
|
-
)}
|
|
52
|
-
>
|
|
53
|
-
<div className="flex items-start gap-3 px-3 py-2.5">
|
|
54
|
-
<Collapsible.Trigger asChild>
|
|
55
|
-
<button
|
|
56
|
-
type="button"
|
|
57
|
-
className="w-full rounded-[20px] bg-transparent px-0 py-0 text-left transition-colors hover:bg-transparent"
|
|
58
|
-
>
|
|
59
|
-
<div className="flex items-center gap-2">
|
|
60
|
-
<span className="font-semibold text-foreground text-sm">{label}</span>
|
|
61
|
-
|
|
62
|
-
{summary ? (
|
|
63
|
-
<span className="text-[11px] text-muted-foreground">{summary}</span>
|
|
64
|
-
) : null}
|
|
65
|
-
{collapsed && collapsedPreview ? (
|
|
66
|
-
<span className="min-w-0 truncate text-[11px] text-foreground/70">
|
|
67
|
-
{collapsedPreview}
|
|
68
|
-
</span>
|
|
69
|
-
) : null}
|
|
70
|
-
|
|
71
|
-
<div className="ml-auto flex shrink-0 items-center gap-1.5">
|
|
72
|
-
{badges}
|
|
73
|
-
|
|
74
|
-
{isStreaming ? (
|
|
75
|
-
<span className="inline-flex items-center gap-1 rounded-full border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] px-2 py-px text-[10px] font-semibold uppercase text-[var(--accent-text)]">
|
|
76
|
-
<Loader2 className="h-2.5 w-2.5 animate-spin" />
|
|
77
|
-
Running
|
|
78
|
-
</span>
|
|
79
|
-
) : (
|
|
80
|
-
<span className="inline-flex items-center gap-1 rounded-full border border-border px-2 py-px text-[10px] font-semibold uppercase text-muted-foreground">
|
|
81
|
-
<Sparkles className="h-2.5 w-2.5" />
|
|
82
|
-
Done
|
|
83
|
-
</span>
|
|
84
|
-
)}
|
|
85
|
-
|
|
86
|
-
{collapsed ? (
|
|
87
|
-
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground" />
|
|
88
|
-
) : (
|
|
89
|
-
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
</button>
|
|
94
|
-
</Collapsible.Trigger>
|
|
95
|
-
|
|
96
|
-
{headerActions ? (
|
|
97
|
-
<div className="flex shrink-0 flex-wrap items-center justify-end gap-1.5 pt-1">
|
|
98
|
-
{headerActions}
|
|
99
|
-
</div>
|
|
100
|
-
) : null}
|
|
101
|
-
</div>
|
|
102
|
-
|
|
103
|
-
{collapsed && collapsedPreview ? (
|
|
104
|
-
<div className="line-clamp-2 px-4 pb-4 text-sm leading-6 text-muted-foreground">
|
|
105
|
-
{collapsedPreview}
|
|
106
|
-
</div>
|
|
107
|
-
) : null}
|
|
108
|
-
|
|
109
|
-
<Collapsible.Content className="overflow-hidden data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp">
|
|
110
|
-
<div className="border-t border-[var(--border-subtle)] px-4 pb-4 pt-3">{children}</div>
|
|
111
|
-
</Collapsible.Content>
|
|
112
|
-
</div>
|
|
113
|
-
</Collapsible.Root>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
File without changes
|