@tangle-network/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.
Files changed (220) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +33 -0
  4. package/dist/active-sessions-store-CeOmXgv5.d.ts +85 -0
  5. package/dist/artifact-pane-DvJyPWV4.d.ts +24 -0
  6. package/dist/auth.d.ts +74 -0
  7. package/dist/auth.js +15 -0
  8. package/dist/button-CMQuQEW_.d.ts +17 -0
  9. package/dist/chat.d.ts +232 -0
  10. package/dist/chat.js +30 -0
  11. package/dist/chunk-2NFQRQOD.js +1009 -0
  12. package/dist/chunk-2VH6PUXD.js +186 -0
  13. package/dist/chunk-34A66VBG.js +214 -0
  14. package/dist/chunk-3OI2QKFD.js +0 -0
  15. package/dist/chunk-4CLN43XT.js +45 -0
  16. package/dist/chunk-54SQQMMM.js +156 -0
  17. package/dist/chunk-5Z5ZYMOJ.js +0 -0
  18. package/dist/chunk-66BNMOVT.js +167 -0
  19. package/dist/chunk-6BGQA4BQ.js +0 -0
  20. package/dist/chunk-7UO2ZMRQ.js +133 -0
  21. package/dist/chunk-BX6AQMUS.js +183 -0
  22. package/dist/chunk-CD53GZOM.js +59 -0
  23. package/dist/chunk-CSAIKY36.js +54 -0
  24. package/dist/chunk-EEE55AVS.js +1201 -0
  25. package/dist/chunk-GYPQXTJU.js +230 -0
  26. package/dist/chunk-HFL6R6IF.js +37 -0
  27. package/dist/chunk-HJKCSXCH.js +737 -0
  28. package/dist/chunk-LISXUB4D.js +1222 -0
  29. package/dist/chunk-LQS34IGP.js +0 -0
  30. package/dist/chunk-MKTSMWVD.js +109 -0
  31. package/dist/chunk-NKDZ7GZE.js +192 -0
  32. package/dist/chunk-OEX7NZE3.js +321 -0
  33. package/dist/chunk-Q56BYXQF.js +61 -0
  34. package/dist/chunk-Q7EIIWTC.js +0 -0
  35. package/dist/chunk-REJESC5U.js +117 -0
  36. package/dist/chunk-RQGKSCEZ.js +0 -0
  37. package/dist/chunk-RQHJBTEU.js +10 -0
  38. package/dist/chunk-TMFOPHHN.js +299 -0
  39. package/dist/chunk-XGKULLYE.js +40 -0
  40. package/dist/chunk-XIHMJ7ZQ.js +614 -0
  41. package/dist/chunk-YJ2G3XO5.js +1048 -0
  42. package/dist/chunk-YNN4O57I.js +754 -0
  43. package/dist/code-block-DjXf8eOG.d.ts +19 -0
  44. package/dist/document-editor-pane-A5LT5H4N.js +12 -0
  45. package/dist/document-editor-pane-DyDEX_Zm.d.ts +124 -0
  46. package/dist/editor.d.ts +120 -0
  47. package/dist/editor.js +34 -0
  48. package/dist/files.d.ts +175 -0
  49. package/dist/files.js +20 -0
  50. package/dist/hooks.d.ts +56 -0
  51. package/dist/hooks.js +41 -0
  52. package/dist/index.d.ts +43 -0
  53. package/dist/index.js +446 -0
  54. package/dist/markdown.d.ts +15 -0
  55. package/dist/markdown.js +14 -0
  56. package/dist/message-BHWbxBtT.d.ts +15 -0
  57. package/dist/openui.d.ts +115 -0
  58. package/dist/openui.js +12 -0
  59. package/dist/parts-dj7AcUg0.d.ts +36 -0
  60. package/dist/primitives.d.ts +332 -0
  61. package/dist/primitives.js +191 -0
  62. package/dist/run-PfLmDAox.d.ts +41 -0
  63. package/dist/run.d.ts +69 -0
  64. package/dist/run.js +36 -0
  65. package/dist/sdk-hooks.d.ts +285 -0
  66. package/dist/sdk-hooks.js +31 -0
  67. package/dist/stores.d.ts +17 -0
  68. package/dist/stores.js +76 -0
  69. package/dist/tool-call-feed-Bs3MyQMT.d.ts +68 -0
  70. package/dist/tool-display-z4JcDmMQ.d.ts +32 -0
  71. package/dist/tool-previews.d.ts +48 -0
  72. package/dist/tool-previews.js +21 -0
  73. package/dist/types.d.ts +19 -0
  74. package/dist/types.js +1 -0
  75. package/dist/utils.d.ts +45 -0
  76. package/dist/utils.js +32 -0
  77. package/package.json +193 -0
  78. package/src/auth/auth.tsx +228 -0
  79. package/src/auth/index.ts +13 -0
  80. package/src/auth/login-layout.tsx +46 -0
  81. package/src/chat/agent-timeline.stories.tsx +429 -0
  82. package/src/chat/agent-timeline.tsx +360 -0
  83. package/src/chat/chat-container.tsx +486 -0
  84. package/src/chat/chat-input.stories.tsx +142 -0
  85. package/src/chat/chat-input.tsx +389 -0
  86. package/src/chat/chat-message.stories.tsx +237 -0
  87. package/src/chat/chat-message.tsx +129 -0
  88. package/src/chat/index.ts +18 -0
  89. package/src/chat/message-list.stories.tsx +336 -0
  90. package/src/chat/message-list.tsx +79 -0
  91. package/src/chat/thinking-indicator.stories.tsx +56 -0
  92. package/src/chat/thinking-indicator.tsx +30 -0
  93. package/src/chat/user-message.stories.tsx +92 -0
  94. package/src/chat/user-message.tsx +43 -0
  95. package/src/editor/document-editor-pane.tsx +351 -0
  96. package/src/editor/editor-provider.tsx +428 -0
  97. package/src/editor/editor-toolbar.tsx +130 -0
  98. package/src/editor/index.ts +31 -0
  99. package/src/editor/markdown-conversion.ts +21 -0
  100. package/src/editor/markdown-document-editor.tsx +137 -0
  101. package/src/editor/tiptap-editor.tsx +331 -0
  102. package/src/editor/use-editor.ts +221 -0
  103. package/src/files/file-artifact-pane.tsx +183 -0
  104. package/src/files/file-preview.tsx +342 -0
  105. package/src/files/file-tabs.tsx +71 -0
  106. package/src/files/file-tree.tsx +258 -0
  107. package/src/files/index.ts +17 -0
  108. package/src/files/rich-file-tree.stories.tsx +104 -0
  109. package/src/files/rich-file-tree.test.tsx +42 -0
  110. package/src/files/rich-file-tree.tsx +232 -0
  111. package/src/hooks/index.ts +10 -0
  112. package/src/hooks/use-auth.ts +153 -0
  113. package/src/hooks/use-auto-scroll.ts +59 -0
  114. package/src/hooks/use-dropdown-menu.ts +40 -0
  115. package/src/hooks/use-live-time.test.tsx +40 -0
  116. package/src/hooks/use-live-time.ts +27 -0
  117. package/src/hooks/use-realtime-session.ts +319 -0
  118. package/src/hooks/use-run-collapse-state.ts +25 -0
  119. package/src/hooks/use-run-groups.ts +111 -0
  120. package/src/hooks/use-sdk-session.ts +575 -0
  121. package/src/hooks/use-sse-stream.ts +475 -0
  122. package/src/hooks/use-tool-call-stream.ts +96 -0
  123. package/src/index.ts +14 -0
  124. package/src/lib/utils.ts +6 -0
  125. package/src/markdown/code-block.tsx +198 -0
  126. package/src/markdown/index.ts +2 -0
  127. package/src/markdown/markdown.stories.tsx +190 -0
  128. package/src/markdown/markdown.tsx +62 -0
  129. package/src/openui/index.ts +20 -0
  130. package/src/openui/openui-artifact-renderer.tsx +542 -0
  131. package/src/primitives/artifact-pane.tsx +91 -0
  132. package/src/primitives/avatar.stories.tsx +95 -0
  133. package/src/primitives/avatar.tsx +47 -0
  134. package/src/primitives/badge.stories.tsx +57 -0
  135. package/src/primitives/badge.tsx +97 -0
  136. package/src/primitives/button.stories.tsx +48 -0
  137. package/src/primitives/button.tsx +115 -0
  138. package/src/primitives/card.stories.tsx +53 -0
  139. package/src/primitives/card.tsx +98 -0
  140. package/src/primitives/code-block.stories.tsx +115 -0
  141. package/src/primitives/code-block.tsx +22 -0
  142. package/src/primitives/design-tokens.stories.tsx +162 -0
  143. package/src/primitives/dialog.stories.tsx +176 -0
  144. package/src/primitives/dialog.tsx +137 -0
  145. package/src/primitives/drop-zone.stories.tsx +123 -0
  146. package/src/primitives/drop-zone.tsx +131 -0
  147. package/src/primitives/dropdown-menu.stories.tsx +122 -0
  148. package/src/primitives/dropdown-menu.tsx +214 -0
  149. package/src/primitives/empty-state.stories.tsx +81 -0
  150. package/src/primitives/empty-state.tsx +40 -0
  151. package/src/primitives/index.ts +118 -0
  152. package/src/primitives/input.stories.tsx +113 -0
  153. package/src/primitives/input.tsx +136 -0
  154. package/src/primitives/label.stories.tsx +84 -0
  155. package/src/primitives/label.tsx +24 -0
  156. package/src/primitives/progress.stories.tsx +93 -0
  157. package/src/primitives/progress.tsx +50 -0
  158. package/src/primitives/segmented-control.test.tsx +328 -0
  159. package/src/primitives/segmented-control.tsx +154 -0
  160. package/src/primitives/select.stories.tsx +164 -0
  161. package/src/primitives/select.tsx +158 -0
  162. package/src/primitives/sidebar-drop-zone.stories.tsx +100 -0
  163. package/src/primitives/sidebar-drop-zone.tsx +149 -0
  164. package/src/primitives/skeleton.stories.tsx +79 -0
  165. package/src/primitives/skeleton.tsx +55 -0
  166. package/src/primitives/stat-card.stories.tsx +137 -0
  167. package/src/primitives/stat-card.tsx +97 -0
  168. package/src/primitives/switch.stories.tsx +85 -0
  169. package/src/primitives/switch.tsx +28 -0
  170. package/src/primitives/table.stories.tsx +170 -0
  171. package/src/primitives/table.tsx +116 -0
  172. package/src/primitives/tabs.stories.tsx +180 -0
  173. package/src/primitives/tabs.tsx +71 -0
  174. package/src/primitives/terminal-display.stories.tsx +191 -0
  175. package/src/primitives/terminal-display.tsx +189 -0
  176. package/src/primitives/theme-toggle.stories.tsx +32 -0
  177. package/src/primitives/theme-toggle.tsx +96 -0
  178. package/src/primitives/toast.stories.tsx +155 -0
  179. package/src/primitives/toast.tsx +190 -0
  180. package/src/primitives/upload-progress.stories.tsx +120 -0
  181. package/src/primitives/upload-progress.tsx +110 -0
  182. package/src/run/expanded-tool-detail.stories.tsx +182 -0
  183. package/src/run/expanded-tool-detail.tsx +186 -0
  184. package/src/run/index.ts +13 -0
  185. package/src/run/inline-thinking-item.stories.tsx +136 -0
  186. package/src/run/inline-thinking-item.tsx +120 -0
  187. package/src/run/inline-tool-item.stories.tsx +222 -0
  188. package/src/run/inline-tool-item.tsx +190 -0
  189. package/src/run/run-group.stories.tsx +322 -0
  190. package/src/run/run-group.tsx +569 -0
  191. package/src/run/run-item-primitives.tsx +17 -0
  192. package/src/run/tool-call-feed.stories.tsx +294 -0
  193. package/src/run/tool-call-feed.tsx +192 -0
  194. package/src/run/tool-call-step.stories.tsx +198 -0
  195. package/src/run/tool-call-step.tsx +240 -0
  196. package/src/sdk-hooks.ts +38 -0
  197. package/src/stores/active-sessions-store.ts +455 -0
  198. package/src/stores/chat-store.ts +43 -0
  199. package/src/stores/index.ts +2 -0
  200. package/src/tool-previews/command-preview.tsx +116 -0
  201. package/src/tool-previews/diff-preview.tsx +85 -0
  202. package/src/tool-previews/glob-results-preview.tsx +98 -0
  203. package/src/tool-previews/grep-results-preview.tsx +157 -0
  204. package/src/tool-previews/index.ts +22 -0
  205. package/src/tool-previews/preview-primitives.tsx +84 -0
  206. package/src/tool-previews/question-preview.tsx +101 -0
  207. package/src/tool-previews/web-search-preview.tsx +117 -0
  208. package/src/tool-previews/write-file-preview.tsx +80 -0
  209. package/src/types/branding.ts +11 -0
  210. package/src/types/index.ts +5 -0
  211. package/src/types/message.ts +13 -0
  212. package/src/types/parts.ts +51 -0
  213. package/src/types/run.ts +56 -0
  214. package/src/types/tool-display.ts +41 -0
  215. package/src/utils/copy-text.ts +30 -0
  216. package/src/utils/format.test.ts +43 -0
  217. package/src/utils/format.ts +56 -0
  218. package/src/utils/index.ts +10 -0
  219. package/src/utils/time-ago.ts +9 -0
  220. package/src/utils/tool-display.ts +238 -0
@@ -0,0 +1,186 @@
1
+ import {
2
+ CodeBlock
3
+ } from "./chunk-66BNMOVT.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
+ ChevronRight,
17
+ Loader2,
18
+ FolderOpen,
19
+ Download,
20
+ Pencil,
21
+ Eye
22
+ } from "lucide-react";
23
+ import { jsx, jsxs } from "react/jsx-runtime";
24
+ var EXT_LANGUAGE = {
25
+ ts: "typescript",
26
+ tsx: "typescript",
27
+ js: "javascript",
28
+ jsx: "javascript",
29
+ mjs: "javascript",
30
+ cjs: "javascript",
31
+ css: "css",
32
+ scss: "scss",
33
+ json: "json",
34
+ jsonc: "json",
35
+ md: "markdown",
36
+ mdx: "markdown",
37
+ py: "python",
38
+ sh: "bash",
39
+ bash: "bash",
40
+ zsh: "bash",
41
+ html: "html",
42
+ htm: "html",
43
+ yaml: "yaml",
44
+ yml: "yaml",
45
+ toml: "toml",
46
+ rs: "rust",
47
+ go: "go",
48
+ sql: "sql",
49
+ xml: "xml"
50
+ };
51
+ function inferLanguage(detail, language) {
52
+ if (language) return language;
53
+ if (!detail) return void 0;
54
+ const ext = detail.split(".").pop()?.toLowerCase();
55
+ return ext ? EXT_LANGUAGE[ext] : void 0;
56
+ }
57
+ function isFilePath(detail) {
58
+ return /[/\\]/.test(detail) || /\.\w{1,6}$/.test(detail);
59
+ }
60
+ function FilePathChip({ path }) {
61
+ const parts = path.replace(/\\/g, "/").split("/");
62
+ const filename = parts.pop() ?? path;
63
+ const dir = parts.length > 0 ? parts.join("/") + "/" : "";
64
+ 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: [
65
+ /* @__PURE__ */ jsx(FileCode, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
66
+ dir && /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: dir }),
67
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 font-semibold text-foreground", children: filename })
68
+ ] });
69
+ }
70
+ var ICONS = {
71
+ bash: Terminal,
72
+ read: Eye,
73
+ write: FileText,
74
+ edit: Pencil,
75
+ glob: FolderOpen,
76
+ grep: Search,
77
+ list: FolderOpen,
78
+ download: Download,
79
+ inspect: Search,
80
+ audit: CheckCircle,
81
+ unknown: FileCode
82
+ };
83
+ var STATUS_COLORS = {
84
+ running: "text-primary",
85
+ success: "text-[var(--code-success)]",
86
+ error: "text-[var(--code-error)]"
87
+ };
88
+ function ToolCallStep({
89
+ type,
90
+ label,
91
+ status,
92
+ detail,
93
+ output,
94
+ language,
95
+ duration,
96
+ className
97
+ }) {
98
+ const [expanded, setExpanded] = useState(false);
99
+ const Icon = ICONS[type] || ICONS.unknown;
100
+ const hasExpandable = !!(detail || output);
101
+ const lang = inferLanguage(detail, language);
102
+ return /* @__PURE__ */ jsxs(
103
+ "div",
104
+ {
105
+ className: cn(
106
+ "group overflow-hidden rounded-[var(--radius-lg)] border bg-card transition-colors",
107
+ status === "running" && "border-border",
108
+ status === "success" && "border-border hover:border-primary/20",
109
+ status === "error" && "border-[var(--surface-danger-border)]",
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)] border",
128
+ status === "running" && "border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary",
129
+ status === "success" && "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]",
130
+ status === "error" && "border-[var(--surface-danger-border)] 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
+ /* @__PURE__ */ jsx(
137
+ "span",
138
+ {
139
+ className: cn(
140
+ "rounded-full border px-2 py-0.5 text-[11px] font-semibold uppercase tracking-[0.06em]",
141
+ status === "running" && "border-border bg-[var(--accent-surface-soft)] text-primary",
142
+ status === "success" && "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]",
143
+ status === "error" && "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]"
144
+ ),
145
+ children: status
146
+ }
147
+ ),
148
+ duration !== void 0 && status !== "running" && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: duration < 1e3 ? `${duration}ms` : `${(duration / 1e3).toFixed(1)}s` }),
149
+ hasExpandable && /* @__PURE__ */ jsx(
150
+ ChevronRight,
151
+ {
152
+ className: cn(
153
+ "h-3 w-3 text-muted-foreground transition-transform shrink-0",
154
+ expanded && "rotate-90"
155
+ )
156
+ }
157
+ )
158
+ ]
159
+ }
160
+ ),
161
+ expanded && (detail || output) && /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t border-border bg-muted px-3 py-2.5", children: [
162
+ detail && (isFilePath(detail) ? /* @__PURE__ */ jsx(FilePathChip, { path: detail }) : /* @__PURE__ */ jsx("div", { className: "text-xs font-mono text-muted-foreground", children: detail })),
163
+ output && /* @__PURE__ */ jsx(
164
+ CodeBlock,
165
+ {
166
+ code: output,
167
+ language: lang,
168
+ className: "max-h-72 overflow-auto text-xs"
169
+ }
170
+ )
171
+ ] })
172
+ ]
173
+ }
174
+ );
175
+ }
176
+ function ToolCallGroup({ title, children, className }) {
177
+ return /* @__PURE__ */ jsxs("div", { className: cn("my-2 space-y-2", className), children: [
178
+ title && /* @__PURE__ */ jsx("div", { className: "mb-1 px-1 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: title }),
179
+ children
180
+ ] });
181
+ }
182
+
183
+ export {
184
+ ToolCallStep,
185
+ ToolCallGroup
186
+ };
@@ -0,0 +1,214 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-RQHJBTEU.js";
4
+
5
+ // src/primitives/avatar.tsx
6
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
7
+ import * as React from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ var Avatar = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
10
+ AvatarPrimitive.Root,
11
+ {
12
+ ref,
13
+ className: cn(
14
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
15
+ className
16
+ ),
17
+ ...props
18
+ }
19
+ ));
20
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
21
+ var AvatarImage = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
22
+ AvatarPrimitive.Image,
23
+ {
24
+ ref,
25
+ className: cn("aspect-square h-full w-full", className),
26
+ ...props
27
+ }
28
+ ));
29
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
30
+ var AvatarFallback = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
31
+ AvatarPrimitive.Fallback,
32
+ {
33
+ ref,
34
+ className: cn(
35
+ "flex h-full w-full items-center justify-center rounded-full bg-muted font-medium text-sm",
36
+ className
37
+ ),
38
+ ...props
39
+ }
40
+ ));
41
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
42
+
43
+ // src/primitives/dropdown-menu.tsx
44
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
45
+ import { Check, ChevronRight, Circle } from "lucide-react";
46
+ import * as React2 from "react";
47
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
48
+ var DropdownMenu = DropdownMenuPrimitive.Root;
49
+ var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
50
+ var DropdownMenuGroup = DropdownMenuPrimitive.Group;
51
+ var DropdownMenuPortal = DropdownMenuPrimitive.Portal;
52
+ var DropdownMenuSub = DropdownMenuPrimitive.Sub;
53
+ var DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
54
+ var DropdownMenuSubTrigger = React2.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs(
55
+ DropdownMenuPrimitive.SubTrigger,
56
+ {
57
+ ref,
58
+ className: cn(
59
+ "flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none",
60
+ "focus:bg-accent data-[state=open]:bg-muted/50",
61
+ inset && "pl-8",
62
+ className
63
+ ),
64
+ ...props,
65
+ children: [
66
+ children,
67
+ /* @__PURE__ */ jsx2(ChevronRight, { className: "ml-auto h-4 w-4" })
68
+ ]
69
+ }
70
+ ));
71
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
72
+ var DropdownMenuSubContent = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx2(
73
+ DropdownMenuPrimitive.SubContent,
74
+ {
75
+ ref,
76
+ className: cn(
77
+ "z-50 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-card p-1 text-foreground shadow-[var(--shadow-card)]",
78
+ "data-[state=closed]:animate-out data-[state=open]:animate-in",
79
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
80
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
81
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
82
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
83
+ className
84
+ ),
85
+ ...props
86
+ }
87
+ ));
88
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
89
+ var DropdownMenuContent = React2.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx2(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx2(
90
+ DropdownMenuPrimitive.Content,
91
+ {
92
+ ref,
93
+ sideOffset,
94
+ className: cn(
95
+ "z-50 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-card p-1 text-foreground shadow-[var(--shadow-card)]",
96
+ "data-[state=closed]:animate-out data-[state=open]:animate-in",
97
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
98
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
99
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
100
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
101
+ className
102
+ ),
103
+ ...props
104
+ }
105
+ ) }));
106
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
107
+ var DropdownMenuItem = React2.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx2(
108
+ DropdownMenuPrimitive.Item,
109
+ {
110
+ ref,
111
+ className: cn(
112
+ "relative flex cursor-pointer select-none items-center rounded-md px-2 py-1.5 text-sm outline-none transition-colors",
113
+ "focus:bg-muted/50 focus:text-foreground",
114
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
115
+ inset && "pl-8",
116
+ className
117
+ ),
118
+ ...props
119
+ }
120
+ ));
121
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
122
+ var DropdownMenuCheckboxItem = React2.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs(
123
+ DropdownMenuPrimitive.CheckboxItem,
124
+ {
125
+ ref,
126
+ className: cn(
127
+ "relative flex cursor-pointer select-none items-center rounded-md py-1.5 pr-2 pl-8 text-sm outline-none transition-colors",
128
+ "focus:bg-muted/50 focus:text-foreground",
129
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
130
+ className
131
+ ),
132
+ checked,
133
+ ...props,
134
+ children: [
135
+ /* @__PURE__ */ jsx2("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx2(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx2(Check, { className: "h-4 w-4" }) }) }),
136
+ children
137
+ ]
138
+ }
139
+ ));
140
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
141
+ var DropdownMenuRadioItem = React2.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(
142
+ DropdownMenuPrimitive.RadioItem,
143
+ {
144
+ ref,
145
+ className: cn(
146
+ "relative flex cursor-pointer select-none items-center rounded-md py-1.5 pr-2 pl-8 text-sm outline-none transition-colors",
147
+ "focus:bg-muted/50 focus:text-foreground",
148
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
149
+ className
150
+ ),
151
+ ...props,
152
+ children: [
153
+ /* @__PURE__ */ jsx2("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx2(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx2(Circle, { className: "h-2 w-2 fill-current" }) }) }),
154
+ children
155
+ ]
156
+ }
157
+ ));
158
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
159
+ var DropdownMenuLabel = React2.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx2(
160
+ DropdownMenuPrimitive.Label,
161
+ {
162
+ ref,
163
+ className: cn(
164
+ "px-2 py-1.5 font-semibold text-sm",
165
+ inset && "pl-8",
166
+ className
167
+ ),
168
+ ...props
169
+ }
170
+ ));
171
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
172
+ var DropdownMenuSeparator = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx2(
173
+ DropdownMenuPrimitive.Separator,
174
+ {
175
+ ref,
176
+ className: cn("-mx-1 my-1 h-px bg-border", className),
177
+ ...props
178
+ }
179
+ ));
180
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
181
+ var DropdownMenuShortcut = ({
182
+ className,
183
+ ...props
184
+ }) => {
185
+ return /* @__PURE__ */ jsx2(
186
+ "span",
187
+ {
188
+ className: cn("ml-auto text-xs tracking-widest opacity-60", className),
189
+ ...props
190
+ }
191
+ );
192
+ };
193
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
194
+
195
+ export {
196
+ Avatar,
197
+ AvatarImage,
198
+ AvatarFallback,
199
+ DropdownMenu,
200
+ DropdownMenuTrigger,
201
+ DropdownMenuGroup,
202
+ DropdownMenuPortal,
203
+ DropdownMenuSub,
204
+ DropdownMenuRadioGroup,
205
+ DropdownMenuSubTrigger,
206
+ DropdownMenuSubContent,
207
+ DropdownMenuContent,
208
+ DropdownMenuItem,
209
+ DropdownMenuCheckboxItem,
210
+ DropdownMenuRadioItem,
211
+ DropdownMenuLabel,
212
+ DropdownMenuSeparator,
213
+ DropdownMenuShortcut
214
+ };
File without changes
@@ -0,0 +1,45 @@
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
+ };
@@ -0,0 +1,156 @@
1
+ import {
2
+ getToolCategory
3
+ } from "./chunk-BX6AQMUS.js";
4
+
5
+ // src/hooks/use-run-groups.ts
6
+ import { useMemo } from "react";
7
+ function computeRunStats(messages, partMap) {
8
+ const stats = {
9
+ toolCount: 0,
10
+ messageCount: messages.length,
11
+ thinkingDurationMs: 0,
12
+ textPartCount: 0,
13
+ toolCategories: /* @__PURE__ */ new Set()
14
+ };
15
+ for (const msg of messages) {
16
+ const parts = partMap[msg.id] ?? [];
17
+ for (const part of parts) {
18
+ switch (part.type) {
19
+ case "tool":
20
+ stats.toolCount++;
21
+ stats.toolCategories.add(getToolCategory(part.tool));
22
+ break;
23
+ case "text":
24
+ if (!part.synthetic) stats.textPartCount++;
25
+ break;
26
+ case "reasoning": {
27
+ const start = part.time?.start;
28
+ const end = part.time?.end;
29
+ if (start && end) stats.thinkingDurationMs += end - start;
30
+ break;
31
+ }
32
+ }
33
+ }
34
+ }
35
+ return stats;
36
+ }
37
+ function getLastTextContent(messages, partMap) {
38
+ for (let i = messages.length - 1; i >= 0; i--) {
39
+ const parts = partMap[messages[i].id] ?? [];
40
+ for (let j = parts.length - 1; j >= 0; j--) {
41
+ const part = parts[j];
42
+ if (part.type === "text" && !part.synthetic && part.text.trim()) {
43
+ return part.text.trim();
44
+ }
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ function useRunGroups({
50
+ messages,
51
+ partMap,
52
+ isStreaming
53
+ }) {
54
+ return useMemo(() => {
55
+ const groups = [];
56
+ let currentRunMessages = [];
57
+ function flushRun(streaming) {
58
+ if (currentRunMessages.length === 0) return;
59
+ const msgs = [...currentRunMessages];
60
+ const stats = computeRunStats(msgs, partMap);
61
+ const summaryText = getLastTextContent(msgs, partMap);
62
+ const isComplete = !streaming;
63
+ groups.push({
64
+ type: "run",
65
+ run: {
66
+ id: msgs[0].id,
67
+ messages: msgs,
68
+ isComplete,
69
+ isStreaming: streaming,
70
+ stats,
71
+ summaryText,
72
+ finalTextPart: null
73
+ }
74
+ });
75
+ currentRunMessages = [];
76
+ }
77
+ for (let i = 0; i < messages.length; i++) {
78
+ const msg = messages[i];
79
+ if (msg.role === "user") {
80
+ flushRun(false);
81
+ groups.push({ type: "user", message: msg });
82
+ } else {
83
+ currentRunMessages.push(msg);
84
+ }
85
+ }
86
+ if (currentRunMessages.length > 0) {
87
+ flushRun(isStreaming);
88
+ }
89
+ return groups;
90
+ }, [messages, partMap, isStreaming]);
91
+ }
92
+
93
+ // src/hooks/use-run-collapse-state.ts
94
+ import { useCallback, useState } from "react";
95
+ function useRunCollapseState(runs) {
96
+ const [collapsedMap, setCollapsedMap] = useState({});
97
+ const isCollapsed = useCallback(
98
+ (runId) => {
99
+ return collapsedMap[runId] ?? false;
100
+ },
101
+ [collapsedMap]
102
+ );
103
+ const toggleCollapse = useCallback((runId) => {
104
+ setCollapsedMap((prev) => ({ ...prev, [runId]: !prev[runId] }));
105
+ }, []);
106
+ return { isCollapsed, toggleCollapse };
107
+ }
108
+
109
+ // src/hooks/use-auto-scroll.ts
110
+ import {
111
+ useCallback as useCallback2,
112
+ useEffect,
113
+ useRef,
114
+ useState as useState2
115
+ } from "react";
116
+ var BOTTOM_THRESHOLD = 40;
117
+ function useAutoScroll(containerRef, deps = []) {
118
+ const [isAtBottom, setIsAtBottom] = useState2(true);
119
+ const userScrolledUp = useRef(false);
120
+ const checkBottom = useCallback2(() => {
121
+ const el = containerRef.current;
122
+ if (!el) return true;
123
+ return el.scrollHeight - el.scrollTop - el.clientHeight < BOTTOM_THRESHOLD;
124
+ }, [containerRef]);
125
+ useEffect(() => {
126
+ const el = containerRef.current;
127
+ if (!el) return;
128
+ const onScroll = () => {
129
+ const atBottom = checkBottom();
130
+ setIsAtBottom(atBottom);
131
+ userScrolledUp.current = !atBottom;
132
+ };
133
+ el.addEventListener("scroll", onScroll, { passive: true });
134
+ return () => el.removeEventListener("scroll", onScroll);
135
+ }, [containerRef, checkBottom]);
136
+ useEffect(() => {
137
+ if (userScrolledUp.current) return;
138
+ const el = containerRef.current;
139
+ if (!el) return;
140
+ el.scrollTop = el.scrollHeight;
141
+ }, deps);
142
+ const scrollToBottom = useCallback2(() => {
143
+ const el = containerRef.current;
144
+ if (!el) return;
145
+ userScrolledUp.current = false;
146
+ el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
147
+ setIsAtBottom(true);
148
+ }, [containerRef]);
149
+ return { isAtBottom, scrollToBottom };
150
+ }
151
+
152
+ export {
153
+ useRunGroups,
154
+ useRunCollapseState,
155
+ useAutoScroll
156
+ };
File without changes