@tangle-network/ui 5.1.0 → 6.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/run.js CHANGED
@@ -2,20 +2,19 @@ import "./chunk-LQS34IGP.js";
2
2
  import {
3
3
  ToolCallFeed,
4
4
  parseToolEvent
5
- } from "./chunk-U4YRT6MT.js";
5
+ } from "./chunk-O6NUUCT2.js";
6
6
  import {
7
- ExpandedToolDetail,
8
7
  InlineThinkingItem,
9
- InlineToolItem,
10
- LiveDuration,
11
8
  RunGroup
12
- } from "./chunk-TAWY3KOH.js";
9
+ } from "./chunk-LASW7CYH.js";
13
10
  import {
11
+ ExpandedToolDetail,
12
+ InlineToolItem,
13
+ LiveDuration,
14
14
  ToolCallGroup,
15
15
  ToolCallStep
16
- } from "./chunk-3O4XJCOE.js";
17
- import "./chunk-4CLN43XT.js";
18
- import "./chunk-BX6AQMUS.js";
16
+ } from "./chunk-EOGJX2TU.js";
17
+ import "./chunk-ULDNFLIM.js";
19
18
  import "./chunk-AAUNOHVL.js";
20
19
  import "./chunk-52Y3FMFI.js";
21
20
  import "./chunk-GYPQXTJU.js";
package/dist/sdk-hooks.js CHANGED
@@ -5,16 +5,18 @@ import {
5
5
  useSSEStream,
6
6
  useSdkSession,
7
7
  useToolCallStream
8
- } from "./chunk-CJJMKDNN.js";
8
+ } from "./chunk-PN3S2MTV.js";
9
9
  import "./chunk-OEX7NZE3.js";
10
10
  import {
11
11
  useAutoScroll,
12
12
  useRunCollapseState,
13
13
  useRunGroups
14
- } from "./chunk-54SQQMMM.js";
15
- import "./chunk-U4YRT6MT.js";
16
- import "./chunk-3O4XJCOE.js";
17
- import "./chunk-BX6AQMUS.js";
14
+ } from "./chunk-AZWDI2JG.js";
15
+ import "./chunk-O6NUUCT2.js";
16
+ import "./chunk-EOGJX2TU.js";
17
+ import "./chunk-ULDNFLIM.js";
18
+ import "./chunk-AAUNOHVL.js";
19
+ import "./chunk-ZRVH3WCA.js";
18
20
  import "./chunk-FJBTCTZM.js";
19
21
  import "./chunk-WUQDUBJG.js";
20
22
  import "./chunk-RQHJBTEU.js";
package/dist/utils.js CHANGED
@@ -3,17 +3,15 @@ import {
3
3
  timeAgo
4
4
  } from "./chunk-XGKULLYE.js";
5
5
  import {
6
+ TOOL_CATEGORY_ICONS,
6
7
  formatBytes,
7
8
  formatDuration,
8
9
  formatUptime,
9
- truncateText
10
- } from "./chunk-4CLN43XT.js";
11
- import {
12
- TOOL_CATEGORY_ICONS,
13
10
  getToolCategory,
14
11
  getToolDisplayMetadata,
15
- getToolErrorText
16
- } from "./chunk-BX6AQMUS.js";
12
+ getToolErrorText,
13
+ truncateText
14
+ } from "./chunk-ULDNFLIM.js";
17
15
  import {
18
16
  cn
19
17
  } from "./chunk-RQHJBTEU.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "5.1.0",
3
+ "version": "6.0.0",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -132,7 +132,7 @@
132
132
  "react": "^18 || ^19",
133
133
  "react-dom": "^18 || ^19",
134
134
  "react-router": "^7",
135
- "@tangle-network/brand": "^0.6.0"
135
+ "@tangle-network/brand": "^0.7.0"
136
136
  },
137
137
  "peerDependenciesMeta": {
138
138
  "@nanostores/react": {
@@ -53,6 +53,10 @@ export interface InlineToolItemProps {
53
53
  className?: string;
54
54
  contentClassName?: string;
55
55
  actions?: ReactNode;
56
+ /** Override the derived title (default: from `getToolDisplayMetadata`). */
57
+ title?: string;
58
+ /** Override the derived inline description. */
59
+ description?: string;
56
60
  }
57
61
 
58
62
  /**
@@ -68,9 +72,13 @@ export const InlineToolItem = memo(
68
72
  className,
69
73
  contentClassName,
70
74
  actions,
75
+ title: titleOverride,
76
+ description: descriptionOverride,
71
77
  }: InlineToolItemProps) => {
72
78
  const [open, setOpen] = useState(false);
73
79
  const meta = getToolDisplayMetadata(part);
80
+ const title = titleOverride ?? meta.title;
81
+ const description = descriptionOverride ?? meta.description;
74
82
  const { status } = part.state;
75
83
  const errorText = getToolErrorText(part);
76
84
 
@@ -127,11 +135,11 @@ export const InlineToolItem = memo(
127
135
  </div>
128
136
 
129
137
  <span className="truncate text-xs font-medium text-foreground">
130
- {meta.title}
138
+ {title}
131
139
  </span>
132
- {meta.description ? (
140
+ {description ? (
133
141
  <span className="hidden truncate text-xs font-mono text-muted-foreground sm:inline">
134
- {meta.description}
142
+ {description}
135
143
  </span>
136
144
  ) : null}
137
145
 
@@ -1,27 +1,17 @@
1
1
  /**
2
- * ToolCallStep — renders a single agent tool invocation as a collapsible activity step.
2
+ * ToolCallStep — a single agent tool invocation as a collapsible activity step.
3
3
  *
4
- * Inspired by Conductor's workspace activity feed.
5
- * Each step shows: icon, label, optional output, expandable detail.
4
+ * Now a thin adapter over the canonical `InlineToolItem`: it maps the flat
5
+ * `ToolCallData`-style props (label / status / detail / output / duration) onto
6
+ * a `ToolPart` and delegates rendering, so the timeline feed (`AgentTimeline`,
7
+ * `ToolCallFeed`) and the run group share ONE row implementation and one look.
8
+ * The bespoke row markup is gone; only the prop adapter remains.
6
9
  */
7
10
 
8
- import { useState, type ReactNode } from "react";
9
- import {
10
- Terminal,
11
- FileText,
12
- FileCode,
13
- Search,
14
- CheckCircle,
15
- AlertCircle,
16
- ChevronRight,
17
- Loader2,
18
- FolderOpen,
19
- Download,
20
- Pencil,
21
- Eye,
22
- } from "lucide-react";
23
- import { cn } from "../lib/utils";
11
+ import { type ReactNode } from "react";
24
12
  import { CodeBlock } from "../markdown/code-block";
13
+ import type { ToolPart, ToolStatus } from "../types/parts";
14
+ import { InlineToolItem } from "./inline-tool-item";
25
15
 
26
16
  export type ToolCallType =
27
17
  | "bash"
@@ -65,52 +55,19 @@ const EXT_LANGUAGE: Record<string, string> = {
65
55
  go: "go",
66
56
  sql: "sql",
67
57
  xml: "xml",
68
- }
58
+ };
69
59
 
70
60
  function inferLanguage(detail?: string, language?: string): string | undefined {
71
- if (language) return language
72
- if (!detail) return undefined
73
- const ext = detail.split(".").pop()?.toLowerCase()
74
- return ext ? EXT_LANGUAGE[ext] : undefined
75
- }
76
-
77
- function isFilePath(detail: string): boolean {
78
- return /[/\\]/.test(detail) || /\.\w{1,6}$/.test(detail)
61
+ if (language) return language;
62
+ if (!detail) return undefined;
63
+ const ext = detail.split(".").pop()?.toLowerCase();
64
+ return ext ? EXT_LANGUAGE[ext] : undefined;
79
65
  }
80
66
 
81
- function FilePathChip({ path }: { path: string }) {
82
- const parts = path.replace(/\\/g, "/").split("/")
83
- const filename = parts.pop() ?? path
84
- const dir = parts.length > 0 ? parts.join("/") + "/" : ""
85
- return (
86
- <div className="flex items-center gap-1.5 rounded-[var(--radius-sm)] border border-border bg-background px-2.5 py-1.5 font-mono text-xs min-w-0">
87
- <FileCode className="h-3.5 w-3.5 shrink-0 text-primary" />
88
- {dir && (
89
- <span className="truncate text-muted-foreground">{dir}</span>
90
- )}
91
- <span className="shrink-0 font-semibold text-foreground">{filename}</span>
92
- </div>
93
- )
94
- }
95
-
96
- const ICONS: Record<ToolCallType, typeof Terminal> = {
97
- bash: Terminal,
98
- read: Eye,
99
- write: FileText,
100
- edit: Pencil,
101
- glob: FolderOpen,
102
- grep: Search,
103
- list: FolderOpen,
104
- download: Download,
105
- inspect: Search,
106
- audit: CheckCircle,
107
- unknown: FileCode,
108
- };
109
-
110
- const STATUS_COLORS: Record<ToolCallStatus, string> = {
111
- running: "text-primary",
112
- success: "text-[var(--code-success)]",
113
- error: "text-[var(--code-error)]",
67
+ const STATUS_MAP: Record<ToolCallStatus, ToolStatus> = {
68
+ running: "running",
69
+ success: "completed",
70
+ error: "error",
114
71
  };
115
72
 
116
73
  export function ToolCallStep({
@@ -123,93 +80,38 @@ export function ToolCallStep({
123
80
  duration,
124
81
  className,
125
82
  }: ToolCallStepProps) {
126
- const [expanded, setExpanded] = useState(false);
127
- const Icon = ICONS[type] || ICONS.unknown;
128
- const hasExpandable = !!(detail || output);
83
+ const part: ToolPart = {
84
+ type: "tool",
85
+ id: `${type}:${label}`,
86
+ tool: type,
87
+ state: {
88
+ status: STATUS_MAP[status],
89
+ input: detail ? { detail } : undefined,
90
+ output,
91
+ time: duration != null ? { start: 0, end: duration } : undefined,
92
+ },
93
+ };
94
+
129
95
  const lang = inferLanguage(detail, language);
130
96
 
131
97
  return (
132
- <div
133
- className={cn(
134
- "group overflow-hidden rounded-[var(--radius-lg)] border bg-card/40 transition-colors",
135
- "border-[var(--border-subtle)] hover:border-border",
136
- status === "error" && "border-[var(--surface-danger-border)]/60",
137
- className,
138
- )}
139
- >
140
- <button
141
- onClick={() => hasExpandable && setExpanded(!expanded)}
142
- disabled={!hasExpandable}
143
- className={cn(
144
- "flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm",
145
- hasExpandable && "cursor-pointer",
146
- )}
147
- >
148
- <div
149
- className={cn(
150
- "flex h-6 w-6 shrink-0 items-center justify-center rounded-[var(--radius-sm)]",
151
- status === "running" && "bg-[var(--accent-surface-soft)] text-primary",
152
- status === "success" && "bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]",
153
- status === "error" && "bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]",
154
- )}
155
- >
156
- {status === "running" ? (
157
- <Loader2 className="h-3 w-3 animate-spin shrink-0" />
158
- ) : (
159
- <Icon className={cn("h-3 w-3 shrink-0", STATUS_COLORS[status])} />
160
- )}
161
- </div>
162
-
163
- {/* Label */}
164
- <span className="truncate flex-1 font-sans text-foreground">
165
- {label}
166
- </span>
167
-
168
- {/* Duration — quiet, before the status glyph */}
169
- {duration !== undefined && status !== "running" && (
170
- <span className="shrink-0 text-[11px] tabular-nums text-muted-foreground">
171
- {duration < 1000 ? `${duration}ms` : `${(duration / 1000).toFixed(1)}s`}
172
- </span>
173
- )}
174
-
175
- {/* Quiet status glyph — the loud uppercase pill read as harsh; the colored
176
- icon carries the same signal calmly (running spins in the left badge). */}
177
- {status === "success" && (
178
- <CheckCircle className="h-3.5 w-3.5 shrink-0 text-[var(--surface-success-text)]" />
179
- )}
180
- {status === "error" && (
181
- <AlertCircle className="h-3.5 w-3.5 shrink-0 text-[var(--surface-danger-text)]" />
182
- )}
183
-
184
- {/* Expand chevron */}
185
- {hasExpandable && (
186
- <ChevronRight
187
- className={cn(
188
- "h-3 w-3 text-muted-foreground transition-transform shrink-0",
189
- expanded && "rotate-90",
190
- )}
191
- />
192
- )}
193
- </button>
194
-
195
- {/* Expandable content */}
196
- {expanded && (detail || output) && (
197
- <div className="space-y-2 border-t border-[var(--border-subtle)] bg-muted/40 px-3 py-2.5">
198
- {detail && (
199
- isFilePath(detail)
200
- ? <FilePathChip path={detail} />
201
- : <div className="text-xs font-mono text-muted-foreground">{detail}</div>
202
- )}
203
- {output && (
204
- <CodeBlock
205
- code={output}
206
- language={lang}
207
- className="max-h-72 overflow-auto text-xs"
208
- />
209
- )}
210
- </div>
211
- )}
212
- </div>
98
+ <InlineToolItem
99
+ part={part}
100
+ title={label}
101
+ description={detail}
102
+ className={className}
103
+ renderToolDetail={
104
+ output
105
+ ? () => (
106
+ <CodeBlock
107
+ code={output}
108
+ language={lang}
109
+ className="max-h-72 overflow-auto text-xs"
110
+ />
111
+ )
112
+ : () => null
113
+ }
114
+ />
213
115
  );
214
116
  }
215
117
 
@@ -224,7 +126,7 @@ export interface ToolCallGroupProps {
224
126
 
225
127
  export function ToolCallGroup({ title, children, className }: ToolCallGroupProps) {
226
128
  return (
227
- <div className={cn("my-2 space-y-2", className)}>
129
+ <div className={["my-2 space-y-2", className].filter(Boolean).join(" ")}>
228
130
  {title && (
229
131
  <div className="mb-1 px-1 text-xs font-medium uppercase tracking-wider text-muted-foreground">
230
132
  {title}
@@ -1,176 +0,0 @@
1
- import {
2
- CodeBlock
3
- } from "./chunk-WUQDUBJG.js";
4
- import {
5
- cn
6
- } from "./chunk-RQHJBTEU.js";
7
-
8
- // src/run/tool-call-step.tsx
9
- import { useState } from "react";
10
- import {
11
- Terminal,
12
- FileText,
13
- FileCode,
14
- Search,
15
- CheckCircle,
16
- AlertCircle,
17
- ChevronRight,
18
- Loader2,
19
- FolderOpen,
20
- Download,
21
- Pencil,
22
- Eye
23
- } from "lucide-react";
24
- import { jsx, jsxs } from "react/jsx-runtime";
25
- var EXT_LANGUAGE = {
26
- ts: "typescript",
27
- tsx: "typescript",
28
- js: "javascript",
29
- jsx: "javascript",
30
- mjs: "javascript",
31
- cjs: "javascript",
32
- css: "css",
33
- scss: "scss",
34
- json: "json",
35
- jsonc: "json",
36
- md: "markdown",
37
- mdx: "markdown",
38
- py: "python",
39
- sh: "bash",
40
- bash: "bash",
41
- zsh: "bash",
42
- html: "html",
43
- htm: "html",
44
- yaml: "yaml",
45
- yml: "yaml",
46
- toml: "toml",
47
- rs: "rust",
48
- go: "go",
49
- sql: "sql",
50
- xml: "xml"
51
- };
52
- function inferLanguage(detail, language) {
53
- if (language) return language;
54
- if (!detail) return void 0;
55
- const ext = detail.split(".").pop()?.toLowerCase();
56
- return ext ? EXT_LANGUAGE[ext] : void 0;
57
- }
58
- function isFilePath(detail) {
59
- return /[/\\]/.test(detail) || /\.\w{1,6}$/.test(detail);
60
- }
61
- function FilePathChip({ path }) {
62
- const parts = path.replace(/\\/g, "/").split("/");
63
- const filename = parts.pop() ?? path;
64
- const dir = parts.length > 0 ? parts.join("/") + "/" : "";
65
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 rounded-[var(--radius-sm)] border border-border bg-background px-2.5 py-1.5 font-mono text-xs min-w-0", children: [
66
- /* @__PURE__ */ jsx(FileCode, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
67
- dir && /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: dir }),
68
- /* @__PURE__ */ jsx("span", { className: "shrink-0 font-semibold text-foreground", children: filename })
69
- ] });
70
- }
71
- var ICONS = {
72
- bash: Terminal,
73
- read: Eye,
74
- write: FileText,
75
- edit: Pencil,
76
- glob: FolderOpen,
77
- grep: Search,
78
- list: FolderOpen,
79
- download: Download,
80
- inspect: Search,
81
- audit: CheckCircle,
82
- unknown: FileCode
83
- };
84
- var STATUS_COLORS = {
85
- running: "text-primary",
86
- success: "text-[var(--code-success)]",
87
- error: "text-[var(--code-error)]"
88
- };
89
- function ToolCallStep({
90
- type,
91
- label,
92
- status,
93
- detail,
94
- output,
95
- language,
96
- duration,
97
- className
98
- }) {
99
- const [expanded, setExpanded] = useState(false);
100
- const Icon = ICONS[type] || ICONS.unknown;
101
- const hasExpandable = !!(detail || output);
102
- const lang = inferLanguage(detail, language);
103
- return /* @__PURE__ */ jsxs(
104
- "div",
105
- {
106
- className: cn(
107
- "group overflow-hidden rounded-[var(--radius-lg)] border bg-card/40 transition-colors",
108
- "border-[var(--border-subtle)] hover:border-border",
109
- status === "error" && "border-[var(--surface-danger-border)]/60",
110
- className
111
- ),
112
- children: [
113
- /* @__PURE__ */ jsxs(
114
- "button",
115
- {
116
- onClick: () => hasExpandable && setExpanded(!expanded),
117
- disabled: !hasExpandable,
118
- className: cn(
119
- "flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm",
120
- hasExpandable && "cursor-pointer"
121
- ),
122
- children: [
123
- /* @__PURE__ */ jsx(
124
- "div",
125
- {
126
- className: cn(
127
- "flex h-6 w-6 shrink-0 items-center justify-center rounded-[var(--radius-sm)]",
128
- status === "running" && "bg-[var(--accent-surface-soft)] text-primary",
129
- status === "success" && "bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]",
130
- status === "error" && "bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]"
131
- ),
132
- children: status === "running" ? /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin shrink-0" }) : /* @__PURE__ */ jsx(Icon, { className: cn("h-3 w-3 shrink-0", STATUS_COLORS[status]) })
133
- }
134
- ),
135
- /* @__PURE__ */ jsx("span", { className: "truncate flex-1 font-sans text-foreground", children: label }),
136
- duration !== void 0 && status !== "running" && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] tabular-nums text-muted-foreground", children: duration < 1e3 ? `${duration}ms` : `${(duration / 1e3).toFixed(1)}s` }),
137
- status === "success" && /* @__PURE__ */ jsx(CheckCircle, { className: "h-3.5 w-3.5 shrink-0 text-[var(--surface-success-text)]" }),
138
- status === "error" && /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 shrink-0 text-[var(--surface-danger-text)]" }),
139
- hasExpandable && /* @__PURE__ */ jsx(
140
- ChevronRight,
141
- {
142
- className: cn(
143
- "h-3 w-3 text-muted-foreground transition-transform shrink-0",
144
- expanded && "rotate-90"
145
- )
146
- }
147
- )
148
- ]
149
- }
150
- ),
151
- expanded && (detail || output) && /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t border-[var(--border-subtle)] bg-muted/40 px-3 py-2.5", children: [
152
- detail && (isFilePath(detail) ? /* @__PURE__ */ jsx(FilePathChip, { path: detail }) : /* @__PURE__ */ jsx("div", { className: "text-xs font-mono text-muted-foreground", children: detail })),
153
- output && /* @__PURE__ */ jsx(
154
- CodeBlock,
155
- {
156
- code: output,
157
- language: lang,
158
- className: "max-h-72 overflow-auto text-xs"
159
- }
160
- )
161
- ] })
162
- ]
163
- }
164
- );
165
- }
166
- function ToolCallGroup({ title, children, className }) {
167
- return /* @__PURE__ */ jsxs("div", { className: cn("my-2 space-y-2", className), children: [
168
- title && /* @__PURE__ */ jsx("div", { className: "mb-1 px-1 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: title }),
169
- children
170
- ] });
171
- }
172
-
173
- export {
174
- ToolCallStep,
175
- ToolCallGroup
176
- };
@@ -1,45 +0,0 @@
1
- // src/utils/format.ts
2
- function formatDuration(ms) {
3
- if (ms < 1e3) return "<1s";
4
- const seconds = Math.floor(ms / 1e3);
5
- if (seconds < 60) return `${seconds}s`;
6
- const minutes = Math.floor(seconds / 60);
7
- const remaining = seconds % 60;
8
- return remaining > 0 ? `${minutes}m ${remaining}s` : `${minutes}m`;
9
- }
10
- function truncateText(text, max) {
11
- const cleaned = text.replace(/\s+/g, " ").trim();
12
- if (cleaned.length <= max) return cleaned;
13
- return cleaned.slice(0, max).trim() + "...";
14
- }
15
- function formatUptime(ms) {
16
- if (!Number.isFinite(ms) || ms < 0) return "\u2014";
17
- const totalSeconds = Math.floor(ms / 1e3);
18
- if (totalSeconds < 60) return `${totalSeconds}s`;
19
- const minutes = Math.floor(totalSeconds / 60);
20
- const seconds = totalSeconds % 60;
21
- if (minutes < 60) return `${minutes}m ${seconds}s`;
22
- const hours = Math.floor(minutes / 60);
23
- const remMinutes = minutes % 60;
24
- if (hours < 24) return `${hours}h ${remMinutes}m`;
25
- const days = Math.floor(hours / 24);
26
- const remHours = hours % 24;
27
- return `${days}d ${remHours}h`;
28
- }
29
- function formatBytes(bytes) {
30
- if (!Number.isFinite(bytes) || bytes < 0) return "\u2014";
31
- if (bytes < 1024) return `${Math.round(bytes)} B`;
32
- const kb = bytes / 1024;
33
- if (kb < 1024) return `${kb < 10 ? kb.toFixed(1) : Math.round(kb)} KB`;
34
- const mb = kb / 1024;
35
- if (mb < 1024) return `${mb < 10 ? mb.toFixed(1) : Math.round(mb)} MB`;
36
- const gb = mb / 1024;
37
- return `${gb < 10 ? gb.toFixed(2) : gb.toFixed(1)} GB`;
38
- }
39
-
40
- export {
41
- formatDuration,
42
- truncateText,
43
- formatUptime,
44
- formatBytes
45
- };