@tangle-network/ui 6.0.0 → 7.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 +14 -0
- package/README.md +2 -0
- package/dist/chat.d.ts +3 -3
- package/dist/chat.js +3 -3
- package/dist/{chunk-SJ6IL4HI.js → chunk-5CS3I7Y3.js} +9 -23
- package/dist/{chunk-PN3S2MTV.js → chunk-DLSGUNRD.js} +1 -1
- package/dist/{chunk-O6NUUCT2.js → chunk-IWQZXL6A.js} +1 -1
- package/dist/{chunk-LASW7CYH.js → chunk-QIRVZMQY.js} +11 -23
- package/dist/{chunk-EOGJX2TU.js → chunk-RKQDBRTC.js} +3 -3
- package/dist/hooks.js +3 -3
- package/dist/index.js +5 -5
- package/dist/run.js +3 -3
- package/dist/sdk-hooks.js +3 -3
- package/package.json +2 -2
- package/src/chat/chat-message.stories.tsx +28 -15
- package/src/chat/chat-message.tsx +15 -38
- package/src/run/inline-tool-item.tsx +5 -5
- package/src/run/run-group.tsx +11 -25
- package/src/stories/theme-showcase.stories.tsx +117 -0
- package/src/run/tool-call-feed.stories.tsx +0 -294
- package/src/run/tool-call-step.stories.tsx +0 -198
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @tangle-network/ui
|
|
2
2
|
|
|
3
|
+
## 7.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 46592b3: Calmer chat/run design + named multi-theme system.
|
|
8
|
+
|
|
9
|
+
- `ChatMessage`/`RunGroup`: role labels move above the bubble (plain text-xs), avatar circles removed (`avatar`/`hideAvatar` are deprecated no-ops), `InlineToolItem` rows are taller with quiet inline failed/running text instead of uppercase pills. `ToolCallStep`/`ToolCallFeed` stories leave Storybook (source adapters remain).
|
|
10
|
+
- `@tangle-network/brand` adds `themes.css`: `[data-theme]` scopes (`aubergine`, `aubergine-light`, `arena`, `arena-light`, `tangle-light`) that re-skin every component through the `@theme` semantic mappings, plus a `Foundations/Theme Showcase` story.
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies [46592b3]
|
|
15
|
+
- @tangle-network/brand@0.8.0
|
|
16
|
+
|
|
3
17
|
## 6.0.0
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Generic React UI components for Tangle products. Imports tokens from `@tangle-network/brand` (peer dep). Tailwind v4-native.
|
|
4
4
|
|
|
5
|
+
Component visual precedent is governed by the repo-level [brand guidelines](../../docs/brand-guidelines.md) and [component audit](../../docs/component-audit.md). Storybook examples are test surfaces; they are not automatically approved brand patterns.
|
|
6
|
+
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```sh
|
package/dist/chat.d.ts
CHANGED
|
@@ -158,12 +158,12 @@ interface ChatMessageProps {
|
|
|
158
158
|
assistantLabel?: string;
|
|
159
159
|
/** Hide the role label row entirely */
|
|
160
160
|
hideRoleLabel?: boolean;
|
|
161
|
-
/**
|
|
161
|
+
/** @deprecated Avatars were removed from the bubble design; this prop is ignored. */
|
|
162
162
|
hideAvatar?: boolean;
|
|
163
|
-
/**
|
|
163
|
+
/** @deprecated Avatars were removed from the bubble design; this prop is ignored. */
|
|
164
164
|
avatar?: ReactNode;
|
|
165
165
|
}
|
|
166
|
-
declare function ChatMessage({ role, content, toolCalls, isStreaming, timestamp, className, userLabel, assistantLabel, hideRoleLabel,
|
|
166
|
+
declare function ChatMessage({ role, content, toolCalls, isStreaming, timestamp, className, userLabel, assistantLabel, hideRoleLabel, }: ChatMessageProps): react_jsx_runtime.JSX.Element;
|
|
167
167
|
|
|
168
168
|
interface ThinkingIndicatorProps {
|
|
169
169
|
className?: string;
|
package/dist/chat.js
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
MessageList,
|
|
7
7
|
ThinkingIndicator,
|
|
8
8
|
UserMessage
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5CS3I7Y3.js";
|
|
10
10
|
import "./chunk-AZWDI2JG.js";
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-QIRVZMQY.js";
|
|
12
|
+
import "./chunk-RKQDBRTC.js";
|
|
13
13
|
import "./chunk-ULDNFLIM.js";
|
|
14
14
|
import "./chunk-AAUNOHVL.js";
|
|
15
15
|
import "./chunk-52Y3FMFI.js";
|
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
InlineThinkingItem,
|
|
8
8
|
RunGroup
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QIRVZMQY.js";
|
|
10
10
|
import {
|
|
11
11
|
ToolCallGroup,
|
|
12
12
|
ToolCallStep
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-RKQDBRTC.js";
|
|
14
14
|
import {
|
|
15
15
|
getToolDisplayMetadata
|
|
16
16
|
} from "./chunk-ULDNFLIM.js";
|
|
@@ -930,7 +930,6 @@ var ChatContainer = memo3(
|
|
|
930
930
|
ChatContainer.displayName = "ChatContainer";
|
|
931
931
|
|
|
932
932
|
// src/chat/chat-message.tsx
|
|
933
|
-
import { User, Bot } from "lucide-react";
|
|
934
933
|
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
935
934
|
function ChatMessage({
|
|
936
935
|
role,
|
|
@@ -941,31 +940,22 @@ function ChatMessage({
|
|
|
941
940
|
className,
|
|
942
941
|
userLabel = "You",
|
|
943
942
|
assistantLabel = "Agent",
|
|
944
|
-
hideRoleLabel
|
|
945
|
-
hideAvatar,
|
|
946
|
-
avatar
|
|
943
|
+
hideRoleLabel
|
|
947
944
|
}) {
|
|
948
945
|
const isUser = role === "user";
|
|
949
946
|
return /* @__PURE__ */ jsxs6(
|
|
950
947
|
"div",
|
|
951
948
|
{
|
|
952
949
|
className: cn(
|
|
953
|
-
"flex gap-
|
|
954
|
-
isUser ? "
|
|
950
|
+
"flex flex-col gap-1",
|
|
951
|
+
isUser ? "items-end" : "items-start",
|
|
955
952
|
className
|
|
956
953
|
),
|
|
957
954
|
children: [
|
|
958
|
-
!
|
|
959
|
-
"
|
|
960
|
-
{
|
|
961
|
-
|
|
962
|
-
"mt-0.5 flex shrink-0 items-center justify-center rounded-[calc(var(--radius-md)+2px)] border",
|
|
963
|
-
"h-[var(--avatar-size)] w-[var(--avatar-size)]",
|
|
964
|
-
isUser ? "border-border bg-[var(--accent-surface-soft)] text-[var(--accent-text)]" : "border-border bg-muted text-[var(--brand-cool)]"
|
|
965
|
-
),
|
|
966
|
-
children: isUser ? /* @__PURE__ */ jsx7(User, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx7(Bot, { className: "h-3.5 w-3.5" })
|
|
967
|
-
}
|
|
968
|
-
)),
|
|
955
|
+
!hideRoleLabel && /* @__PURE__ */ jsxs6("div", { className: cn("flex items-center gap-2 px-1", isUser && "flex-row-reverse"), children: [
|
|
956
|
+
/* @__PURE__ */ jsx7("span", { className: "font-medium text-foreground text-xs", children: isUser ? userLabel : assistantLabel }),
|
|
957
|
+
timestamp && /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground text-xs", children: formatTime2(timestamp) })
|
|
958
|
+
] }),
|
|
969
959
|
/* @__PURE__ */ jsxs6(
|
|
970
960
|
"div",
|
|
971
961
|
{
|
|
@@ -975,10 +965,6 @@ function ChatMessage({
|
|
|
975
965
|
isUser ? "border-border bg-muted/50" : "border-border bg-card"
|
|
976
966
|
),
|
|
977
967
|
children: [
|
|
978
|
-
!hideRoleLabel && /* @__PURE__ */ jsxs6("div", { className: cn("flex items-center gap-2", isUser && "flex-row-reverse"), children: [
|
|
979
|
-
/* @__PURE__ */ jsx7("span", { className: "text-[var(--font-size-xs)] font-[var(--chat-label-weight,600)] uppercase tracking-[var(--chat-label-tracking,0.14em)] text-foreground", children: isUser ? userLabel : assistantLabel }),
|
|
980
|
-
timestamp && /* @__PURE__ */ jsx7("span", { className: "text-[var(--font-size-xs)] text-muted-foreground", children: formatTime2(timestamp) })
|
|
981
|
-
] }),
|
|
982
968
|
isUser ? /* @__PURE__ */ jsx7("div", { className: "whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)] text-foreground", children: content }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
983
969
|
content && /* @__PURE__ */ jsx7(Markdown, { className: "tangle-prose text-[var(--font-size-base)] leading-[var(--line-height-base)]", children: content }),
|
|
984
970
|
isStreaming && /* @__PURE__ */ jsx7("span", { className: "ml-0.5 inline-block h-4 w-2 animate-pulse rounded-sm bg-[var(--brand-cool)] align-text-bottom" })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
InlineToolItem,
|
|
3
3
|
LiveDuration
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-RKQDBRTC.js";
|
|
5
5
|
import {
|
|
6
6
|
formatDuration,
|
|
7
7
|
truncateText
|
|
@@ -142,18 +142,15 @@ function AssistantShell({
|
|
|
142
142
|
isStreaming,
|
|
143
143
|
children
|
|
144
144
|
}) {
|
|
145
|
-
return /* @__PURE__ */ jsxs2("div", { className: "flex gap-
|
|
146
|
-
/* @__PURE__ */
|
|
147
|
-
|
|
148
|
-
/* @__PURE__ */ jsxs2("
|
|
149
|
-
/* @__PURE__ */ jsx2(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
] }),
|
|
155
|
-
children
|
|
156
|
-
] })
|
|
145
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1", children: [
|
|
146
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 px-1 font-medium text-muted-foreground text-xs", children: [
|
|
147
|
+
/* @__PURE__ */ jsx2("span", { children: branding.label }),
|
|
148
|
+
isStreaming ? /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
149
|
+
/* @__PURE__ */ jsx2(Loader2, { className: "h-3 w-3 animate-spin" }),
|
|
150
|
+
"Thinking"
|
|
151
|
+
] }) : null
|
|
152
|
+
] }),
|
|
153
|
+
/* @__PURE__ */ jsx2("div", { className: ASSISTANT_SHELL, children })
|
|
157
154
|
] });
|
|
158
155
|
}
|
|
159
156
|
var CATEGORY_ICON_MAP = {
|
|
@@ -378,16 +375,7 @@ var RunGroup = memo2(
|
|
|
378
375
|
"bg-transparent hover:bg-transparent"
|
|
379
376
|
),
|
|
380
377
|
children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
381
|
-
/* @__PURE__ */ jsx2(
|
|
382
|
-
"div",
|
|
383
|
-
{
|
|
384
|
-
className: cn(
|
|
385
|
-
"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--brand-primary)] text-white"
|
|
386
|
-
),
|
|
387
|
-
children: /* @__PURE__ */ jsx2(Bot, { className: "h-3.5 w-3.5" })
|
|
388
|
-
}
|
|
389
|
-
),
|
|
390
|
-
/* @__PURE__ */ jsx2("span", { className: cn("text-sm font-semibold", branding.textClass), children: branding.label }),
|
|
378
|
+
/* @__PURE__ */ jsx2("span", { className: cn("font-semibold text-sm", branding.textClass), children: branding.label }),
|
|
391
379
|
renderSummary(run) ? /* @__PURE__ */ jsx2("span", { className: "text-[11px] text-muted-foreground", children: renderSummary(run) }) : null,
|
|
392
380
|
collapsed && run.summaryText ? /* @__PURE__ */ jsx2("span", { className: "min-w-0 truncate text-[11px] text-foreground/70", children: run.summaryText }) : null,
|
|
393
381
|
/* @__PURE__ */ jsxs2("div", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: [
|
|
@@ -233,7 +233,7 @@ var InlineToolItem = memo2(
|
|
|
233
233
|
className
|
|
234
234
|
),
|
|
235
235
|
children: [
|
|
236
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 px-
|
|
236
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [
|
|
237
237
|
/* @__PURE__ */ jsx3("div", { className: cn(
|
|
238
238
|
"shrink-0",
|
|
239
239
|
isRunning && "text-primary",
|
|
@@ -246,8 +246,8 @@ var InlineToolItem = memo2(
|
|
|
246
246
|
/* @__PURE__ */ jsxs2("div", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: [
|
|
247
247
|
isRunning && startTime ? /* @__PURE__ */ jsx3(LiveDuration, { startTime }) : null,
|
|
248
248
|
!isRunning && durationMs != null ? /* @__PURE__ */ jsx3("span", { className: "text-[10px] font-mono tabular-nums text-muted-foreground", children: formatDuration(durationMs) }) : null,
|
|
249
|
-
isError ? /* @__PURE__ */ jsx3("span", { className: "
|
|
250
|
-
isRunning ? /* @__PURE__ */ jsx3("span", { className: "
|
|
249
|
+
isError ? /* @__PURE__ */ jsx3("span", { className: "text-[10px] font-medium text-[var(--surface-danger-text)]", children: "failed" }) : null,
|
|
250
|
+
isRunning ? /* @__PURE__ */ jsx3("span", { className: "text-[10px] font-medium text-primary", children: "running" }) : null,
|
|
251
251
|
open ? /* @__PURE__ */ jsx3(ChevronDown, { className: "h-3 w-3 text-muted-foreground" }) : /* @__PURE__ */ jsx3(ChevronRight, { className: "h-3 w-3 text-muted-foreground" })
|
|
252
252
|
] })
|
|
253
253
|
] }),
|
package/dist/hooks.js
CHANGED
|
@@ -11,15 +11,15 @@ import {
|
|
|
11
11
|
useSSEStream,
|
|
12
12
|
useSdkSession,
|
|
13
13
|
useToolCallStream
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-DLSGUNRD.js";
|
|
15
15
|
import "./chunk-OEX7NZE3.js";
|
|
16
16
|
import {
|
|
17
17
|
useAutoScroll,
|
|
18
18
|
useRunCollapseState,
|
|
19
19
|
useRunGroups
|
|
20
20
|
} from "./chunk-AZWDI2JG.js";
|
|
21
|
-
import "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-IWQZXL6A.js";
|
|
22
|
+
import "./chunk-RKQDBRTC.js";
|
|
23
23
|
import "./chunk-ULDNFLIM.js";
|
|
24
24
|
import "./chunk-AAUNOHVL.js";
|
|
25
25
|
import "./chunk-ZRVH3WCA.js";
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
useSSEStream,
|
|
18
18
|
useSdkSession,
|
|
19
19
|
useToolCallStream
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-DLSGUNRD.js";
|
|
21
21
|
import {
|
|
22
22
|
addMessage,
|
|
23
23
|
addParts,
|
|
@@ -138,7 +138,7 @@ import {
|
|
|
138
138
|
MessageList,
|
|
139
139
|
ThinkingIndicator,
|
|
140
140
|
UserMessage
|
|
141
|
-
} from "./chunk-
|
|
141
|
+
} from "./chunk-5CS3I7Y3.js";
|
|
142
142
|
import {
|
|
143
143
|
useAutoScroll,
|
|
144
144
|
useRunCollapseState,
|
|
@@ -148,18 +148,18 @@ import "./chunk-LQS34IGP.js";
|
|
|
148
148
|
import {
|
|
149
149
|
ToolCallFeed,
|
|
150
150
|
parseToolEvent
|
|
151
|
-
} from "./chunk-
|
|
151
|
+
} from "./chunk-IWQZXL6A.js";
|
|
152
152
|
import {
|
|
153
153
|
InlineThinkingItem,
|
|
154
154
|
RunGroup
|
|
155
|
-
} from "./chunk-
|
|
155
|
+
} from "./chunk-QIRVZMQY.js";
|
|
156
156
|
import {
|
|
157
157
|
ExpandedToolDetail,
|
|
158
158
|
InlineToolItem,
|
|
159
159
|
LiveDuration,
|
|
160
160
|
ToolCallGroup,
|
|
161
161
|
ToolCallStep
|
|
162
|
-
} from "./chunk-
|
|
162
|
+
} from "./chunk-RKQDBRTC.js";
|
|
163
163
|
import {
|
|
164
164
|
TOOL_CATEGORY_ICONS,
|
|
165
165
|
formatBytes,
|
package/dist/run.js
CHANGED
|
@@ -2,18 +2,18 @@ import "./chunk-LQS34IGP.js";
|
|
|
2
2
|
import {
|
|
3
3
|
ToolCallFeed,
|
|
4
4
|
parseToolEvent
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-IWQZXL6A.js";
|
|
6
6
|
import {
|
|
7
7
|
InlineThinkingItem,
|
|
8
8
|
RunGroup
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QIRVZMQY.js";
|
|
10
10
|
import {
|
|
11
11
|
ExpandedToolDetail,
|
|
12
12
|
InlineToolItem,
|
|
13
13
|
LiveDuration,
|
|
14
14
|
ToolCallGroup,
|
|
15
15
|
ToolCallStep
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-RKQDBRTC.js";
|
|
17
17
|
import "./chunk-ULDNFLIM.js";
|
|
18
18
|
import "./chunk-AAUNOHVL.js";
|
|
19
19
|
import "./chunk-52Y3FMFI.js";
|
package/dist/sdk-hooks.js
CHANGED
|
@@ -5,15 +5,15 @@ import {
|
|
|
5
5
|
useSSEStream,
|
|
6
6
|
useSdkSession,
|
|
7
7
|
useToolCallStream
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DLSGUNRD.js";
|
|
9
9
|
import "./chunk-OEX7NZE3.js";
|
|
10
10
|
import {
|
|
11
11
|
useAutoScroll,
|
|
12
12
|
useRunCollapseState,
|
|
13
13
|
useRunGroups
|
|
14
14
|
} from "./chunk-AZWDI2JG.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-IWQZXL6A.js";
|
|
16
|
+
import "./chunk-RKQDBRTC.js";
|
|
17
17
|
import "./chunk-ULDNFLIM.js";
|
|
18
18
|
import "./chunk-AAUNOHVL.js";
|
|
19
19
|
import "./chunk-ZRVH3WCA.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.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.
|
|
135
|
+
"@tangle-network/brand": "^0.8.0"
|
|
136
136
|
},
|
|
137
137
|
"peerDependenciesMeta": {
|
|
138
138
|
"@nanostores/react": {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
2
|
import { ChatMessage } from './chat-message'
|
|
3
|
-
import {
|
|
3
|
+
import { InlineToolItem } from '../run/inline-tool-item'
|
|
4
|
+
import type { ToolPart } from '../types/parts'
|
|
4
5
|
|
|
5
6
|
const meta: Meta<typeof ChatMessage> = {
|
|
6
7
|
title: 'Chat/ChatMessage',
|
|
@@ -26,6 +27,30 @@ type Story = StoryObj<typeof ChatMessage>
|
|
|
26
27
|
const ts = (offsetMinutes = 0) =>
|
|
27
28
|
new Date(Date.now() - offsetMinutes * 60 * 1000)
|
|
28
29
|
|
|
30
|
+
const readToolPart: ToolPart = {
|
|
31
|
+
type: 'tool',
|
|
32
|
+
id: 'chat-read',
|
|
33
|
+
tool: 'read',
|
|
34
|
+
state: {
|
|
35
|
+
status: 'completed',
|
|
36
|
+
input: { file_path: 'src/hooks/useFetchData.ts' },
|
|
37
|
+
output: `export function useFetchData<T>(url: string) {\n const [data, setData] = useState<T | null>(null)\n const [loading, setLoading] = useState(true)\n // ...`,
|
|
38
|
+
time: { start: Date.now() - 1200, end: Date.now() - 1152 },
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const searchToolPart: ToolPart = {
|
|
43
|
+
type: 'tool',
|
|
44
|
+
id: 'chat-search',
|
|
45
|
+
tool: 'grep',
|
|
46
|
+
state: {
|
|
47
|
+
status: 'completed',
|
|
48
|
+
input: { pattern: 'cache', path: 'src/hooks/useFetchData.ts' },
|
|
49
|
+
output: 'No cache layer found. Data fetched on every mount.',
|
|
50
|
+
time: { start: Date.now() - 900, end: Date.now() - 888 },
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
export const UserMessage: Story = {
|
|
30
55
|
args: {
|
|
31
56
|
role: 'user',
|
|
@@ -117,20 +142,8 @@ export const AssistantWithToolCalls: Story = {
|
|
|
117
142
|
content: 'Let me read the current implementation first.',
|
|
118
143
|
toolCalls: (
|
|
119
144
|
<div className="mt-3 space-y-2">
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
label="Read src/hooks/useFetchData.ts"
|
|
123
|
-
status="success"
|
|
124
|
-
output={`export function useFetchData<T>(url: string) {\n const [data, setData] = useState<T | null>(null)\n const [loading, setLoading] = useState(true)\n // ...`}
|
|
125
|
-
duration={48}
|
|
126
|
-
/>
|
|
127
|
-
<ToolCallStep
|
|
128
|
-
type="grep"
|
|
129
|
-
label="Search for cache references"
|
|
130
|
-
status="success"
|
|
131
|
-
output="No cache layer found. Data fetched on every mount."
|
|
132
|
-
duration={12}
|
|
133
|
-
/>
|
|
145
|
+
<InlineToolItem part={readToolPart} groupPosition="first" />
|
|
146
|
+
<InlineToolItem part={searchToolPart} groupPosition="last" />
|
|
134
147
|
</div>
|
|
135
148
|
),
|
|
136
149
|
timestamp: ts(1),
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { type ReactNode } from "react";
|
|
9
|
-
import { User, Bot } from "lucide-react";
|
|
10
9
|
import { cn } from "../lib/utils";
|
|
11
10
|
import { Markdown } from "../markdown/markdown";
|
|
12
11
|
|
|
@@ -28,9 +27,9 @@ export interface ChatMessageProps {
|
|
|
28
27
|
assistantLabel?: string;
|
|
29
28
|
/** Hide the role label row entirely */
|
|
30
29
|
hideRoleLabel?: boolean;
|
|
31
|
-
/**
|
|
30
|
+
/** @deprecated Avatars were removed from the bubble design; this prop is ignored. */
|
|
32
31
|
hideAvatar?: boolean;
|
|
33
|
-
/**
|
|
32
|
+
/** @deprecated Avatars were removed from the bubble design; this prop is ignored. */
|
|
34
33
|
avatar?: ReactNode;
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -44,36 +43,28 @@ export function ChatMessage({
|
|
|
44
43
|
userLabel = "You",
|
|
45
44
|
assistantLabel = "Agent",
|
|
46
45
|
hideRoleLabel,
|
|
47
|
-
hideAvatar,
|
|
48
|
-
avatar,
|
|
49
46
|
}: ChatMessageProps) {
|
|
50
47
|
const isUser = role === "user";
|
|
51
48
|
|
|
52
49
|
return (
|
|
53
50
|
<div
|
|
54
51
|
className={cn(
|
|
55
|
-
"flex gap-
|
|
56
|
-
isUser ? "
|
|
52
|
+
"flex flex-col gap-1",
|
|
53
|
+
isUser ? "items-end" : "items-start",
|
|
57
54
|
className,
|
|
58
55
|
)}
|
|
59
56
|
>
|
|
60
|
-
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
className=
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
: "border-border bg-muted text-[var(--brand-cool)]",
|
|
72
|
-
)}
|
|
73
|
-
>
|
|
74
|
-
{isUser ? <User className="h-3.5 w-3.5" /> : <Bot className="h-3.5 w-3.5" />}
|
|
75
|
-
</div>
|
|
76
|
-
)
|
|
57
|
+
{!hideRoleLabel && (
|
|
58
|
+
<div className={cn("flex items-center gap-2 px-1", isUser && "flex-row-reverse")}>
|
|
59
|
+
<span className="font-medium text-foreground text-xs">
|
|
60
|
+
{isUser ? userLabel : assistantLabel}
|
|
61
|
+
</span>
|
|
62
|
+
{timestamp && (
|
|
63
|
+
<span className="text-muted-foreground text-xs">
|
|
64
|
+
{formatTime(timestamp)}
|
|
65
|
+
</span>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
77
68
|
)}
|
|
78
69
|
|
|
79
70
|
{/* Bubble */}
|
|
@@ -86,20 +77,6 @@ export function ChatMessage({
|
|
|
86
77
|
: "border-border bg-card",
|
|
87
78
|
)}
|
|
88
79
|
>
|
|
89
|
-
{/* Role label + timestamp */}
|
|
90
|
-
{!hideRoleLabel && (
|
|
91
|
-
<div className={cn("flex items-center gap-2", isUser && "flex-row-reverse")}>
|
|
92
|
-
<span className="text-[var(--font-size-xs)] font-[var(--chat-label-weight,600)] uppercase tracking-[var(--chat-label-tracking,0.14em)] text-foreground">
|
|
93
|
-
{isUser ? userLabel : assistantLabel}
|
|
94
|
-
</span>
|
|
95
|
-
{timestamp && (
|
|
96
|
-
<span className="text-[var(--font-size-xs)] text-muted-foreground">
|
|
97
|
-
{formatTime(timestamp)}
|
|
98
|
-
</span>
|
|
99
|
-
)}
|
|
100
|
-
</div>
|
|
101
|
-
)}
|
|
102
|
-
|
|
103
80
|
{/* Message body */}
|
|
104
81
|
{isUser ? (
|
|
105
82
|
<div className="whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)] text-foreground">
|
|
@@ -115,7 +115,7 @@ export const InlineToolItem = memo(
|
|
|
115
115
|
className,
|
|
116
116
|
)}
|
|
117
117
|
>
|
|
118
|
-
<div className="flex items-center gap-2 px-
|
|
118
|
+
<div className="flex items-center gap-2.5 px-3 py-2.5">
|
|
119
119
|
<div className={cn(
|
|
120
120
|
"shrink-0",
|
|
121
121
|
isRunning && "text-primary",
|
|
@@ -151,13 +151,13 @@ export const InlineToolItem = memo(
|
|
|
151
151
|
</span>
|
|
152
152
|
) : null}
|
|
153
153
|
{isError ? (
|
|
154
|
-
<span className="
|
|
155
|
-
|
|
154
|
+
<span className="text-[10px] font-medium text-[var(--surface-danger-text)]">
|
|
155
|
+
failed
|
|
156
156
|
</span>
|
|
157
157
|
) : null}
|
|
158
158
|
{isRunning ? (
|
|
159
|
-
<span className="
|
|
160
|
-
|
|
159
|
+
<span className="text-[10px] font-medium text-primary">
|
|
160
|
+
running
|
|
161
161
|
</span>
|
|
162
162
|
) : null}
|
|
163
163
|
{open ? (
|
package/src/run/run-group.tsx
CHANGED
|
@@ -58,23 +58,17 @@ function AssistantShell({
|
|
|
58
58
|
children: ReactNode;
|
|
59
59
|
}) {
|
|
60
60
|
return (
|
|
61
|
-
<div className="flex gap-
|
|
62
|
-
<div className="
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<span className="inline-flex items-center gap-1.5 text-[var(--text-muted)]">
|
|
71
|
-
<Loader2 className="h-3 w-3 animate-spin" />
|
|
72
|
-
Thinking
|
|
73
|
-
</span>
|
|
74
|
-
) : null}
|
|
75
|
-
</div>
|
|
76
|
-
{children}
|
|
61
|
+
<div className="flex flex-col gap-1">
|
|
62
|
+
<div className="flex items-center gap-2 px-1 font-medium text-muted-foreground text-xs">
|
|
63
|
+
<span>{branding.label}</span>
|
|
64
|
+
{isStreaming ? (
|
|
65
|
+
<span className="inline-flex items-center gap-1.5">
|
|
66
|
+
<Loader2 className="h-3 w-3 animate-spin" />
|
|
67
|
+
Thinking
|
|
68
|
+
</span>
|
|
69
|
+
) : null}
|
|
77
70
|
</div>
|
|
71
|
+
<div className={ASSISTANT_SHELL}>{children}</div>
|
|
78
72
|
</div>
|
|
79
73
|
);
|
|
80
74
|
}
|
|
@@ -411,15 +405,7 @@ export const RunGroup = memo(
|
|
|
411
405
|
)}
|
|
412
406
|
>
|
|
413
407
|
<div className="flex items-center gap-2">
|
|
414
|
-
<
|
|
415
|
-
className={cn(
|
|
416
|
-
"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--brand-primary)] text-white",
|
|
417
|
-
)}
|
|
418
|
-
>
|
|
419
|
-
<Bot className="h-3.5 w-3.5" />
|
|
420
|
-
</div>
|
|
421
|
-
|
|
422
|
-
<span className={cn("text-sm font-semibold", branding.textClass)}>
|
|
408
|
+
<span className={cn("font-semibold text-sm", branding.textClass)}>
|
|
423
409
|
{branding.label}
|
|
424
410
|
</span>
|
|
425
411
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { InlineToolItem } from "../run/inline-tool-item";
|
|
3
|
+
import type { ToolPart } from "../types/parts";
|
|
4
|
+
|
|
5
|
+
const meta: Meta = {
|
|
6
|
+
title: "Foundations/Theme Showcase",
|
|
7
|
+
parameters: { layout: "fullscreen" },
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj;
|
|
11
|
+
|
|
12
|
+
const THEMES: { name: string; theme?: string }[] = [
|
|
13
|
+
{ name: "Tangle (neutral)", theme: undefined }, // default :root dark
|
|
14
|
+
{ name: "Tangle · light", theme: "tangle-light" },
|
|
15
|
+
{ name: "Aubergine (bazaar)", theme: "aubergine" },
|
|
16
|
+
{ name: "Aubergine · light", theme: "aubergine-light" },
|
|
17
|
+
{ name: "Experimental green", theme: "arena" },
|
|
18
|
+
{ name: "Experimental green · light", theme: "arena-light" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const NOW = Date.now();
|
|
22
|
+
|
|
23
|
+
const themeToolParts: ToolPart[] = [
|
|
24
|
+
{
|
|
25
|
+
type: "tool",
|
|
26
|
+
id: "theme-read",
|
|
27
|
+
tool: "read",
|
|
28
|
+
state: {
|
|
29
|
+
status: "completed",
|
|
30
|
+
input: { file_path: "src/batch-writer.ts" },
|
|
31
|
+
time: { start: NOW - 3100, end: NOW - 2500 },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: "tool",
|
|
36
|
+
id: "theme-search",
|
|
37
|
+
tool: "grep",
|
|
38
|
+
state: {
|
|
39
|
+
status: "completed",
|
|
40
|
+
input: { pattern: "await sleep\\(" },
|
|
41
|
+
time: { start: NOW - 2200, end: NOW - 1800 },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "tool",
|
|
46
|
+
id: "theme-build",
|
|
47
|
+
tool: "bash",
|
|
48
|
+
state: {
|
|
49
|
+
status: "error",
|
|
50
|
+
input: { command: "pnpm test" },
|
|
51
|
+
error: "1 failing test",
|
|
52
|
+
time: { start: NOW - 1600, end: NOW - 400 },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function Cell({ name, theme }: { name: string; theme?: string }) {
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
data-theme={theme}
|
|
61
|
+
className="flex flex-col gap-3 rounded-2xl border border-border bg-background p-5"
|
|
62
|
+
>
|
|
63
|
+
<div className="flex items-center justify-between">
|
|
64
|
+
<span className="font-semibold text-foreground text-sm">{name}</span>
|
|
65
|
+
<span className="font-medium text-muted-foreground text-xs">
|
|
66
|
+
{theme?.includes("light") ? "Light" : "Dark"}
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div className="space-y-1.5 rounded-xl border border-[var(--border-subtle)] bg-surface-container-high p-3">
|
|
71
|
+
<div className="flex items-center gap-2">
|
|
72
|
+
<span className="size-2 rounded-full bg-primary" />
|
|
73
|
+
<span className="font-semibold text-foreground text-xs">Agent</span>
|
|
74
|
+
<span className="text-[11px] text-muted-foreground">4 tools · 3s</span>
|
|
75
|
+
</div>
|
|
76
|
+
{themeToolParts.map((part, index) => (
|
|
77
|
+
<InlineToolItem
|
|
78
|
+
key={part.id}
|
|
79
|
+
part={part}
|
|
80
|
+
groupPosition={index === 0 ? "first" : index === themeToolParts.length - 1 ? "last" : "middle"}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<p className="text-muted-foreground text-xs leading-relaxed">
|
|
86
|
+
Body copy on the canvas. Surfaces separate by fill; the border is a quiet edge.
|
|
87
|
+
</p>
|
|
88
|
+
|
|
89
|
+
<div className="flex items-center gap-2">
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
className="rounded-lg bg-primary px-3 py-1.5 font-medium text-primary-foreground text-xs"
|
|
93
|
+
>
|
|
94
|
+
Primary
|
|
95
|
+
</button>
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
className="rounded-lg border border-border bg-muted px-3 py-1.5 font-medium text-foreground text-xs"
|
|
99
|
+
>
|
|
100
|
+
Secondary
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const AllThemes: Story = {
|
|
108
|
+
render: () => (
|
|
109
|
+
<div className="min-h-screen bg-[#0a0a0c] p-6">
|
|
110
|
+
<div className="grid grid-cols-1 gap-5 md:grid-cols-2">
|
|
111
|
+
{THEMES.map((t) => (
|
|
112
|
+
<Cell key={t.name} name={t.name} theme={t.theme} />
|
|
113
|
+
))}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
),
|
|
117
|
+
};
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
-
import { ToolCallFeed, type FeedSegment } from './tool-call-feed'
|
|
3
|
-
|
|
4
|
-
// -- Fixtures --
|
|
5
|
-
|
|
6
|
-
const debuggingSession: FeedSegment[] = [
|
|
7
|
-
{
|
|
8
|
-
kind: 'text',
|
|
9
|
-
content: "I'll investigate the failing test and fix it. Let me start by finding the relevant files.",
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
kind: 'tool_group',
|
|
13
|
-
title: 'Exploration',
|
|
14
|
-
calls: [
|
|
15
|
-
{
|
|
16
|
-
id: 'tc-glob-1',
|
|
17
|
-
type: 'glob',
|
|
18
|
-
label: 'Find **/*.test.{ts,tsx}',
|
|
19
|
-
status: 'success',
|
|
20
|
-
duration: 28,
|
|
21
|
-
output: 'src/utils/format.test.ts\nsrc/utils/tool-display.test.ts\nsrc/run/run-group.test.ts',
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: 'tc-read-1',
|
|
25
|
-
type: 'read',
|
|
26
|
-
label: 'Read src/run/run-group.test.ts',
|
|
27
|
-
status: 'success',
|
|
28
|
-
duration: 55,
|
|
29
|
-
detail: 'src/run/run-group.test.ts',
|
|
30
|
-
output: "import { render } from '@testing-library/react'\nimport { RunGroup } from './run-group'\n\ndescribe('RunGroup', () => {\n it('renders tool parts', () => { ... })\n})",
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
id: 'tc-read-2',
|
|
34
|
-
type: 'read',
|
|
35
|
-
label: 'Read src/run/run-group.tsx',
|
|
36
|
-
status: 'success',
|
|
37
|
-
duration: 61,
|
|
38
|
-
detail: 'src/run/run-group.tsx',
|
|
39
|
-
output: 'const allParts = useMemo(() => {\n const parts = []\n for (const msg of run.messages) {\n const msgParts = partMap[msg.id] ?? []\n ...',
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
kind: 'text',
|
|
45
|
-
content: "I see the issue. The test is passing `undefined` for `partMap` in one of the cases, but the component always expects a `Record<string, SessionPart[]>`. Let me check the test setup.",
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
kind: 'tool_call',
|
|
49
|
-
call: {
|
|
50
|
-
id: 'tc-grep-1',
|
|
51
|
-
type: 'grep',
|
|
52
|
-
label: 'Search for partMap in run-group.test.ts',
|
|
53
|
-
status: 'success',
|
|
54
|
-
duration: 44,
|
|
55
|
-
output: 'src/run/run-group.test.ts:18: render(<RunGroup run={mockRun} partMap={undefined} collapsed={false} onToggle={jest.fn()} />)',
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
kind: 'text',
|
|
60
|
-
content: "Found it — the test is passing `partMap={undefined}`. The prop type should default to `{}` rather than requiring the caller to pass a defined value. I'll add a default parameter.",
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
kind: 'tool_call',
|
|
64
|
-
call: {
|
|
65
|
-
id: 'tc-edit-1',
|
|
66
|
-
type: 'edit',
|
|
67
|
-
label: 'Edit src/run/run-group.tsx',
|
|
68
|
-
status: 'success',
|
|
69
|
-
duration: 38,
|
|
70
|
-
detail: 'Added partMap = {} default',
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
kind: 'tool_call',
|
|
75
|
-
call: {
|
|
76
|
-
id: 'tc-bash-1',
|
|
77
|
-
type: 'bash',
|
|
78
|
-
label: 'pnpm test --run src/run/run-group.test.ts',
|
|
79
|
-
status: 'success',
|
|
80
|
-
duration: 1240,
|
|
81
|
-
output: ' ✓ src/run/run-group.test.ts (3)\n\nTest Files 1 passed (1)\n Tests 3 passed (3)\n Duration 1.24s',
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
kind: 'text',
|
|
86
|
-
content: 'All tests pass. The fix was a one-line change: adding `partMap = {}` as a default parameter so callers that omit it (or pass `undefined`) get a safe empty map rather than a crash.',
|
|
87
|
-
},
|
|
88
|
-
]
|
|
89
|
-
|
|
90
|
-
const multiPhaseSession: FeedSegment[] = [
|
|
91
|
-
{
|
|
92
|
-
kind: 'text',
|
|
93
|
-
content: "I'll refactor the `formatDuration` utility to support minutes, add tests, and verify the full suite still passes.",
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
kind: 'tool_group',
|
|
97
|
-
title: 'Read current implementation',
|
|
98
|
-
calls: [
|
|
99
|
-
{
|
|
100
|
-
id: 'mp-read-1',
|
|
101
|
-
type: 'read',
|
|
102
|
-
label: 'Read src/utils/format.ts',
|
|
103
|
-
status: 'success',
|
|
104
|
-
duration: 42,
|
|
105
|
-
output: "export function formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`\n return `${(ms / 1000).toFixed(1)}s`\n}",
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
id: 'mp-read-2',
|
|
109
|
-
type: 'read',
|
|
110
|
-
label: 'Read src/utils/format.test.ts',
|
|
111
|
-
status: 'success',
|
|
112
|
-
duration: 38,
|
|
113
|
-
output: "describe('formatDuration', () => {\n it('returns ms for sub-second values', ...)\n it('returns seconds for 1000-59999ms', ...)\n})",
|
|
114
|
-
},
|
|
115
|
-
],
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
kind: 'tool_group',
|
|
119
|
-
title: 'Implement changes',
|
|
120
|
-
calls: [
|
|
121
|
-
{
|
|
122
|
-
id: 'mp-write-1',
|
|
123
|
-
type: 'write',
|
|
124
|
-
label: 'Write src/utils/format.ts',
|
|
125
|
-
status: 'success',
|
|
126
|
-
duration: 51,
|
|
127
|
-
detail: 'Added minutes branch and truncateText helper',
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
id: 'mp-write-2',
|
|
131
|
-
type: 'write',
|
|
132
|
-
label: 'Write src/utils/format.test.ts',
|
|
133
|
-
status: 'success',
|
|
134
|
-
duration: 48,
|
|
135
|
-
detail: 'Added 3 new test cases for minutes and truncation',
|
|
136
|
-
},
|
|
137
|
-
],
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
kind: 'tool_call',
|
|
141
|
-
call: {
|
|
142
|
-
id: 'mp-bash-1',
|
|
143
|
-
type: 'bash',
|
|
144
|
-
label: 'pnpm test --run',
|
|
145
|
-
status: 'success',
|
|
146
|
-
duration: 1870,
|
|
147
|
-
output: 'Test Files 3 passed (3)\n Tests 18 passed (18)\n Duration 1.87s',
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
kind: 'text',
|
|
152
|
-
content: 'Done. `formatDuration` now returns `"2m 14s"` for values over 60 seconds. All 18 tests pass including the 3 new cases.',
|
|
153
|
-
},
|
|
154
|
-
]
|
|
155
|
-
|
|
156
|
-
const streamingSession: FeedSegment[] = [
|
|
157
|
-
{
|
|
158
|
-
kind: 'text',
|
|
159
|
-
content: "Let me run the full test suite and check for any issues.",
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
kind: 'tool_call',
|
|
163
|
-
call: {
|
|
164
|
-
id: 'stream-bash-1',
|
|
165
|
-
type: 'bash',
|
|
166
|
-
label: 'pnpm test --run',
|
|
167
|
-
status: 'running',
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
]
|
|
171
|
-
|
|
172
|
-
const errorSession: FeedSegment[] = [
|
|
173
|
-
{
|
|
174
|
-
kind: 'text',
|
|
175
|
-
content: "Attempting to build the project.",
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
kind: 'tool_call',
|
|
179
|
-
call: {
|
|
180
|
-
id: 'err-bash-1',
|
|
181
|
-
type: 'bash',
|
|
182
|
-
label: 'pnpm build',
|
|
183
|
-
status: 'error',
|
|
184
|
-
duration: 3200,
|
|
185
|
-
detail: 'Build failed with TypeScript errors',
|
|
186
|
-
output: `error TS2322: Type 'string | undefined' is not assignable to type 'string'.\n src/utils/format.ts:8:5\n\nerror TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'.\n src/run/run-item-primitives.tsx:6:48\n\nFound 2 errors.`,
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
kind: 'text',
|
|
191
|
-
content: "Two TypeScript errors. The first is in `format.ts` — I need to add a null guard before calling `.toFixed()`. The second is in `run-item-primitives.tsx` — `startTime` can be undefined per the `ToolTime` type.",
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
kind: 'tool_group',
|
|
195
|
-
title: 'Apply fixes',
|
|
196
|
-
calls: [
|
|
197
|
-
{
|
|
198
|
-
id: 'err-edit-1',
|
|
199
|
-
type: 'edit',
|
|
200
|
-
label: 'Edit src/utils/format.ts',
|
|
201
|
-
status: 'success',
|
|
202
|
-
duration: 35,
|
|
203
|
-
detail: 'Added undefined guard on ms parameter',
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
id: 'err-edit-2',
|
|
207
|
-
type: 'edit',
|
|
208
|
-
label: 'Edit src/run/run-item-primitives.tsx',
|
|
209
|
-
status: 'success',
|
|
210
|
-
duration: 29,
|
|
211
|
-
detail: 'Changed startTime prop type to number (non-optional at callsite)',
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
kind: 'tool_call',
|
|
217
|
-
call: {
|
|
218
|
-
id: 'err-bash-2',
|
|
219
|
-
type: 'bash',
|
|
220
|
-
label: 'pnpm build',
|
|
221
|
-
status: 'success',
|
|
222
|
-
duration: 4100,
|
|
223
|
-
output: 'dist/index.js 42.3 kB\ndist/index.js.map 88.1 kB\n✓ built in 4.10s',
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
kind: 'text',
|
|
228
|
-
content: 'Build succeeds. Both TypeScript errors resolved.',
|
|
229
|
-
},
|
|
230
|
-
]
|
|
231
|
-
|
|
232
|
-
// --
|
|
233
|
-
|
|
234
|
-
const meta: Meta<typeof ToolCallFeed> = {
|
|
235
|
-
title: 'Run/ToolCallFeed',
|
|
236
|
-
component: ToolCallFeed,
|
|
237
|
-
parameters: {
|
|
238
|
-
layout: 'fullscreen',
|
|
239
|
-
backgrounds: { default: 'dark' },
|
|
240
|
-
},
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export default meta
|
|
244
|
-
type Story = StoryObj<typeof ToolCallFeed>
|
|
245
|
-
|
|
246
|
-
export const DebuggingSession: Story = {
|
|
247
|
-
name: 'Debugging a failing test',
|
|
248
|
-
args: { segments: debuggingSession },
|
|
249
|
-
render: (args) => (
|
|
250
|
-
<div className="p-6 max-w-2xl">
|
|
251
|
-
<ToolCallFeed {...args} />
|
|
252
|
-
</div>
|
|
253
|
-
),
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export const MultiPhaseRefactor: Story = {
|
|
257
|
-
name: 'Multi-phase refactor',
|
|
258
|
-
args: { segments: multiPhaseSession },
|
|
259
|
-
render: (args) => (
|
|
260
|
-
<div className="p-6 max-w-2xl">
|
|
261
|
-
<ToolCallFeed {...args} />
|
|
262
|
-
</div>
|
|
263
|
-
),
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export const Streaming: Story = {
|
|
267
|
-
name: 'In-progress (running)',
|
|
268
|
-
args: { segments: streamingSession },
|
|
269
|
-
render: (args) => (
|
|
270
|
-
<div className="p-6 max-w-2xl">
|
|
271
|
-
<ToolCallFeed {...args} />
|
|
272
|
-
</div>
|
|
273
|
-
),
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export const BuildErrorAndFix: Story = {
|
|
277
|
-
name: 'Build error → fix → success',
|
|
278
|
-
args: { segments: errorSession },
|
|
279
|
-
render: (args) => (
|
|
280
|
-
<div className="p-6 max-w-2xl">
|
|
281
|
-
<ToolCallFeed {...args} />
|
|
282
|
-
</div>
|
|
283
|
-
),
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export const Empty: Story = {
|
|
287
|
-
args: { segments: [] },
|
|
288
|
-
render: (args) => (
|
|
289
|
-
<div className="p-6 max-w-2xl">
|
|
290
|
-
<ToolCallFeed {...args} />
|
|
291
|
-
<p className="text-sm text-muted-foreground italic">(empty feed — renders nothing)</p>
|
|
292
|
-
</div>
|
|
293
|
-
),
|
|
294
|
-
}
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
-
import { ToolCallStep, ToolCallGroup } from './tool-call-step'
|
|
3
|
-
|
|
4
|
-
const meta: Meta<typeof ToolCallStep> = {
|
|
5
|
-
title: 'Run/ToolCallStep',
|
|
6
|
-
component: ToolCallStep,
|
|
7
|
-
parameters: {
|
|
8
|
-
layout: 'fullscreen',
|
|
9
|
-
backgrounds: { default: 'dark' },
|
|
10
|
-
},
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default meta
|
|
14
|
-
type Story = StoryObj<typeof ToolCallStep>
|
|
15
|
-
|
|
16
|
-
// -- Single step stories --
|
|
17
|
-
|
|
18
|
-
export const BashSuccess: Story = {
|
|
19
|
-
args: {
|
|
20
|
-
type: 'bash',
|
|
21
|
-
label: 'pnpm test --run --reporter=verbose',
|
|
22
|
-
status: 'success',
|
|
23
|
-
duration: 1420,
|
|
24
|
-
output: `Test Files 2 passed (2)\n Tests 15 passed (15)\n Duration 1.42s`,
|
|
25
|
-
},
|
|
26
|
-
render: (args) => (
|
|
27
|
-
<div className="p-6 max-w-2xl">
|
|
28
|
-
<ToolCallStep {...args} />
|
|
29
|
-
</div>
|
|
30
|
-
),
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const BashRunning: Story = {
|
|
34
|
-
args: {
|
|
35
|
-
type: 'bash',
|
|
36
|
-
label: 'pnpm build',
|
|
37
|
-
status: 'running',
|
|
38
|
-
},
|
|
39
|
-
render: (args) => (
|
|
40
|
-
<div className="p-6 max-w-2xl">
|
|
41
|
-
<ToolCallStep {...args} />
|
|
42
|
-
</div>
|
|
43
|
-
),
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export const BashError: Story = {
|
|
47
|
-
args: {
|
|
48
|
-
type: 'bash',
|
|
49
|
-
label: 'pnpm test --run',
|
|
50
|
-
status: 'error',
|
|
51
|
-
duration: 890,
|
|
52
|
-
detail: 'src/components/RunGroup.test.tsx',
|
|
53
|
-
output: `FAIL src/components/RunGroup.test.tsx\n ● RunGroup › renders tool parts\n\n TypeError: Cannot read properties of undefined (reading 'map')\n at RunGroup (src/run/run-group.tsx:42:40)`,
|
|
54
|
-
},
|
|
55
|
-
render: (args) => (
|
|
56
|
-
<div className="p-6 max-w-2xl">
|
|
57
|
-
<ToolCallStep {...args} />
|
|
58
|
-
</div>
|
|
59
|
-
),
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const ReadFile: Story = {
|
|
63
|
-
args: {
|
|
64
|
-
type: 'read',
|
|
65
|
-
label: 'Read src/run/run-group.tsx',
|
|
66
|
-
status: 'success',
|
|
67
|
-
duration: 62,
|
|
68
|
-
detail: '/home/user/project/src/run/run-group.tsx',
|
|
69
|
-
output: `import { memo, useMemo } from 'react'\nexport const RunGroup = memo(({ run, partMap, ...`,
|
|
70
|
-
},
|
|
71
|
-
render: (args) => (
|
|
72
|
-
<div className="p-6 max-w-2xl">
|
|
73
|
-
<ToolCallStep {...args} />
|
|
74
|
-
</div>
|
|
75
|
-
),
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export const WriteFile: Story = {
|
|
79
|
-
args: {
|
|
80
|
-
type: 'write',
|
|
81
|
-
label: 'Write src/utils/format.ts',
|
|
82
|
-
status: 'success',
|
|
83
|
-
duration: 45,
|
|
84
|
-
detail: '/home/user/project/src/utils/format.ts',
|
|
85
|
-
},
|
|
86
|
-
render: (args) => (
|
|
87
|
-
<div className="p-6 max-w-2xl">
|
|
88
|
-
<ToolCallStep {...args} />
|
|
89
|
-
</div>
|
|
90
|
-
),
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export const EditFile: Story = {
|
|
94
|
-
args: {
|
|
95
|
-
type: 'edit',
|
|
96
|
-
label: 'Edit src/run/run-group.tsx',
|
|
97
|
-
status: 'success',
|
|
98
|
-
duration: 38,
|
|
99
|
-
detail: 'Replaced 3 lines at L42',
|
|
100
|
-
},
|
|
101
|
-
render: (args) => (
|
|
102
|
-
<div className="p-6 max-w-2xl">
|
|
103
|
-
<ToolCallStep {...args} />
|
|
104
|
-
</div>
|
|
105
|
-
),
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export const GlobFind: Story = {
|
|
109
|
-
args: {
|
|
110
|
-
type: 'glob',
|
|
111
|
-
label: 'Find **/*.test.ts',
|
|
112
|
-
status: 'success',
|
|
113
|
-
duration: 28,
|
|
114
|
-
output: `src/utils/format.test.ts\nsrc/utils/tool-display.test.ts\nsrc/run/run-group.test.ts`,
|
|
115
|
-
},
|
|
116
|
-
render: (args) => (
|
|
117
|
-
<div className="p-6 max-w-2xl">
|
|
118
|
-
<ToolCallStep {...args} />
|
|
119
|
-
</div>
|
|
120
|
-
),
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export const GrepSearch: Story = {
|
|
124
|
-
args: {
|
|
125
|
-
type: 'grep',
|
|
126
|
-
label: 'Search for partMap\\[',
|
|
127
|
-
status: 'success',
|
|
128
|
-
duration: 44,
|
|
129
|
-
output: `src/run/run-group.tsx:184: const msgParts = partMap[msg.id] ?? []\nsrc/hooks/useRunGroup.ts:44: setPartMap(prev => ({ ...prev, [msgId]: parts }))`,
|
|
130
|
-
},
|
|
131
|
-
render: (args) => (
|
|
132
|
-
<div className="p-6 max-w-2xl">
|
|
133
|
-
<ToolCallStep {...args} />
|
|
134
|
-
</div>
|
|
135
|
-
),
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export const NoExpandable: Story = {
|
|
139
|
-
name: 'No detail/output (not expandable)',
|
|
140
|
-
args: {
|
|
141
|
-
type: 'bash',
|
|
142
|
-
label: 'git add -p',
|
|
143
|
-
status: 'success',
|
|
144
|
-
duration: 120,
|
|
145
|
-
},
|
|
146
|
-
render: (args) => (
|
|
147
|
-
<div className="p-6 max-w-2xl">
|
|
148
|
-
<ToolCallStep {...args} />
|
|
149
|
-
</div>
|
|
150
|
-
),
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// -- Group stories --
|
|
154
|
-
|
|
155
|
-
export const GroupedToolCalls: Story = {
|
|
156
|
-
name: 'ToolCallGroup — exploration phase',
|
|
157
|
-
render: () => (
|
|
158
|
-
<div className="p-6 max-w-2xl">
|
|
159
|
-
<ToolCallGroup title="Exploration">
|
|
160
|
-
<ToolCallStep type="glob" label="Find **/*.test.ts" status="success" duration={28} output="src/utils/format.test.ts\nsrc/utils/tool-display.test.ts\nsrc/run/run-group.test.ts" />
|
|
161
|
-
<ToolCallStep type="read" label="Read src/run/run-group.tsx" status="success" duration={62} detail="Checking for partMap access patterns" output="export const RunGroup = memo(({ run, partMap, ..." />
|
|
162
|
-
<ToolCallStep type="grep" label="Search for partMap\[" status="success" duration={44} output="src/run/run-group.tsx:184\nsrc/hooks/useRunGroup.ts:44" />
|
|
163
|
-
</ToolCallGroup>
|
|
164
|
-
</div>
|
|
165
|
-
),
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export const MixedStatuses: Story = {
|
|
169
|
-
name: 'Mixed statuses in a group',
|
|
170
|
-
render: () => (
|
|
171
|
-
<div className="p-6 max-w-2xl">
|
|
172
|
-
<ToolCallGroup title="Test cycle">
|
|
173
|
-
<ToolCallStep type="bash" label="pnpm test --run src/utils/" status="success" duration={890} />
|
|
174
|
-
<ToolCallStep type="edit" label="Edit src/run/run-group.tsx" status="success" duration={38} />
|
|
175
|
-
<ToolCallStep type="bash" label="pnpm test --run" status="error" duration={740} output="FAIL src/run/run-group.test.ts\n ● 1 test failed" />
|
|
176
|
-
<ToolCallStep type="bash" label="pnpm test --run" status="running" />
|
|
177
|
-
</ToolCallGroup>
|
|
178
|
-
</div>
|
|
179
|
-
),
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export const AllTypes: Story = {
|
|
183
|
-
render: () => (
|
|
184
|
-
<div className="p-6 max-w-2xl space-y-2">
|
|
185
|
-
<p className="text-xs font-mono uppercase tracking-widest text-muted-foreground px-1 pb-1">All tool types</p>
|
|
186
|
-
<ToolCallStep type="bash" label="npm run build" status="success" duration={4200} />
|
|
187
|
-
<ToolCallStep type="read" label="Read package.json" status="success" duration={18} />
|
|
188
|
-
<ToolCallStep type="write" label="Write dist/index.js" status="success" duration={55} />
|
|
189
|
-
<ToolCallStep type="edit" label="Edit tsconfig.json" status="success" duration={30} />
|
|
190
|
-
<ToolCallStep type="glob" label="Find src/**/*.tsx" status="success" duration={22} />
|
|
191
|
-
<ToolCallStep type="grep" label="Search for export default" status="success" duration={66} />
|
|
192
|
-
<ToolCallStep type="list" label="List node_modules/.cache" status="success" duration={14} />
|
|
193
|
-
<ToolCallStep type="download" label="Download schema.json" status="success" duration={380} />
|
|
194
|
-
<ToolCallStep type="inspect" label="Inspect bundle size" status="running" />
|
|
195
|
-
<ToolCallStep type="audit" label="Audit dependencies" status="error" duration={1100} />
|
|
196
|
-
</div>
|
|
197
|
-
),
|
|
198
|
-
}
|