@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.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/active-sessions-store-CeOmXgv5.d.ts +85 -0
- package/dist/artifact-pane-DvJyPWV4.d.ts +24 -0
- package/dist/auth.d.ts +74 -0
- package/dist/auth.js +15 -0
- package/dist/button-CMQuQEW_.d.ts +17 -0
- package/dist/chat.d.ts +232 -0
- package/dist/chat.js +30 -0
- package/dist/chunk-2NFQRQOD.js +1009 -0
- package/dist/chunk-2VH6PUXD.js +186 -0
- package/dist/chunk-34A66VBG.js +214 -0
- package/dist/chunk-3OI2QKFD.js +0 -0
- package/dist/chunk-4CLN43XT.js +45 -0
- package/dist/chunk-54SQQMMM.js +156 -0
- package/dist/chunk-5Z5ZYMOJ.js +0 -0
- package/dist/chunk-66BNMOVT.js +167 -0
- package/dist/chunk-6BGQA4BQ.js +0 -0
- package/dist/chunk-7UO2ZMRQ.js +133 -0
- package/dist/chunk-BX6AQMUS.js +183 -0
- package/dist/chunk-CD53GZOM.js +59 -0
- package/dist/chunk-CSAIKY36.js +54 -0
- package/dist/chunk-EEE55AVS.js +1201 -0
- package/dist/chunk-GYPQXTJU.js +230 -0
- package/dist/chunk-HFL6R6IF.js +37 -0
- package/dist/chunk-HJKCSXCH.js +737 -0
- package/dist/chunk-LISXUB4D.js +1222 -0
- package/dist/chunk-LQS34IGP.js +0 -0
- package/dist/chunk-MKTSMWVD.js +109 -0
- package/dist/chunk-NKDZ7GZE.js +192 -0
- package/dist/chunk-OEX7NZE3.js +321 -0
- package/dist/chunk-Q56BYXQF.js +61 -0
- package/dist/chunk-Q7EIIWTC.js +0 -0
- package/dist/chunk-REJESC5U.js +117 -0
- package/dist/chunk-RQGKSCEZ.js +0 -0
- package/dist/chunk-RQHJBTEU.js +10 -0
- package/dist/chunk-TMFOPHHN.js +299 -0
- package/dist/chunk-XGKULLYE.js +40 -0
- package/dist/chunk-XIHMJ7ZQ.js +614 -0
- package/dist/chunk-YJ2G3XO5.js +1048 -0
- package/dist/chunk-YNN4O57I.js +754 -0
- package/dist/code-block-DjXf8eOG.d.ts +19 -0
- package/dist/document-editor-pane-A5LT5H4N.js +12 -0
- package/dist/document-editor-pane-DyDEX_Zm.d.ts +124 -0
- package/dist/editor.d.ts +120 -0
- package/dist/editor.js +34 -0
- package/dist/files.d.ts +175 -0
- package/dist/files.js +20 -0
- package/dist/hooks.d.ts +56 -0
- package/dist/hooks.js +41 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +446 -0
- package/dist/markdown.d.ts +15 -0
- package/dist/markdown.js +14 -0
- package/dist/message-BHWbxBtT.d.ts +15 -0
- package/dist/openui.d.ts +115 -0
- package/dist/openui.js +12 -0
- package/dist/parts-dj7AcUg0.d.ts +36 -0
- package/dist/primitives.d.ts +332 -0
- package/dist/primitives.js +191 -0
- package/dist/run-PfLmDAox.d.ts +41 -0
- package/dist/run.d.ts +69 -0
- package/dist/run.js +36 -0
- package/dist/sdk-hooks.d.ts +285 -0
- package/dist/sdk-hooks.js +31 -0
- package/dist/stores.d.ts +17 -0
- package/dist/stores.js +76 -0
- package/dist/tool-call-feed-Bs3MyQMT.d.ts +68 -0
- package/dist/tool-display-z4JcDmMQ.d.ts +32 -0
- package/dist/tool-previews.d.ts +48 -0
- package/dist/tool-previews.js +21 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +45 -0
- package/dist/utils.js +32 -0
- package/package.json +193 -0
- package/src/auth/auth.tsx +228 -0
- package/src/auth/index.ts +13 -0
- package/src/auth/login-layout.tsx +46 -0
- package/src/chat/agent-timeline.stories.tsx +429 -0
- package/src/chat/agent-timeline.tsx +360 -0
- package/src/chat/chat-container.tsx +486 -0
- package/src/chat/chat-input.stories.tsx +142 -0
- package/src/chat/chat-input.tsx +389 -0
- package/src/chat/chat-message.stories.tsx +237 -0
- package/src/chat/chat-message.tsx +129 -0
- package/src/chat/index.ts +18 -0
- package/src/chat/message-list.stories.tsx +336 -0
- package/src/chat/message-list.tsx +79 -0
- package/src/chat/thinking-indicator.stories.tsx +56 -0
- package/src/chat/thinking-indicator.tsx +30 -0
- package/src/chat/user-message.stories.tsx +92 -0
- package/src/chat/user-message.tsx +43 -0
- package/src/editor/document-editor-pane.tsx +351 -0
- package/src/editor/editor-provider.tsx +428 -0
- package/src/editor/editor-toolbar.tsx +130 -0
- package/src/editor/index.ts +31 -0
- package/src/editor/markdown-conversion.ts +21 -0
- package/src/editor/markdown-document-editor.tsx +137 -0
- package/src/editor/tiptap-editor.tsx +331 -0
- package/src/editor/use-editor.ts +221 -0
- package/src/files/file-artifact-pane.tsx +183 -0
- package/src/files/file-preview.tsx +342 -0
- package/src/files/file-tabs.tsx +71 -0
- package/src/files/file-tree.tsx +258 -0
- package/src/files/index.ts +17 -0
- package/src/files/rich-file-tree.stories.tsx +104 -0
- package/src/files/rich-file-tree.test.tsx +42 -0
- package/src/files/rich-file-tree.tsx +232 -0
- package/src/hooks/index.ts +10 -0
- package/src/hooks/use-auth.ts +153 -0
- package/src/hooks/use-auto-scroll.ts +59 -0
- package/src/hooks/use-dropdown-menu.ts +40 -0
- package/src/hooks/use-live-time.test.tsx +40 -0
- package/src/hooks/use-live-time.ts +27 -0
- package/src/hooks/use-realtime-session.ts +319 -0
- package/src/hooks/use-run-collapse-state.ts +25 -0
- package/src/hooks/use-run-groups.ts +111 -0
- package/src/hooks/use-sdk-session.ts +575 -0
- package/src/hooks/use-sse-stream.ts +475 -0
- package/src/hooks/use-tool-call-stream.ts +96 -0
- package/src/index.ts +14 -0
- package/src/lib/utils.ts +6 -0
- package/src/markdown/code-block.tsx +198 -0
- package/src/markdown/index.ts +2 -0
- package/src/markdown/markdown.stories.tsx +190 -0
- package/src/markdown/markdown.tsx +62 -0
- package/src/openui/index.ts +20 -0
- package/src/openui/openui-artifact-renderer.tsx +542 -0
- package/src/primitives/artifact-pane.tsx +91 -0
- package/src/primitives/avatar.stories.tsx +95 -0
- package/src/primitives/avatar.tsx +47 -0
- package/src/primitives/badge.stories.tsx +57 -0
- package/src/primitives/badge.tsx +97 -0
- package/src/primitives/button.stories.tsx +48 -0
- package/src/primitives/button.tsx +115 -0
- package/src/primitives/card.stories.tsx +53 -0
- package/src/primitives/card.tsx +98 -0
- package/src/primitives/code-block.stories.tsx +115 -0
- package/src/primitives/code-block.tsx +22 -0
- package/src/primitives/design-tokens.stories.tsx +162 -0
- package/src/primitives/dialog.stories.tsx +176 -0
- package/src/primitives/dialog.tsx +137 -0
- package/src/primitives/drop-zone.stories.tsx +123 -0
- package/src/primitives/drop-zone.tsx +131 -0
- package/src/primitives/dropdown-menu.stories.tsx +122 -0
- package/src/primitives/dropdown-menu.tsx +214 -0
- package/src/primitives/empty-state.stories.tsx +81 -0
- package/src/primitives/empty-state.tsx +40 -0
- package/src/primitives/index.ts +118 -0
- package/src/primitives/input.stories.tsx +113 -0
- package/src/primitives/input.tsx +136 -0
- package/src/primitives/label.stories.tsx +84 -0
- package/src/primitives/label.tsx +24 -0
- package/src/primitives/progress.stories.tsx +93 -0
- package/src/primitives/progress.tsx +50 -0
- package/src/primitives/segmented-control.test.tsx +328 -0
- package/src/primitives/segmented-control.tsx +154 -0
- package/src/primitives/select.stories.tsx +164 -0
- package/src/primitives/select.tsx +158 -0
- package/src/primitives/sidebar-drop-zone.stories.tsx +100 -0
- package/src/primitives/sidebar-drop-zone.tsx +149 -0
- package/src/primitives/skeleton.stories.tsx +79 -0
- package/src/primitives/skeleton.tsx +55 -0
- package/src/primitives/stat-card.stories.tsx +137 -0
- package/src/primitives/stat-card.tsx +97 -0
- package/src/primitives/switch.stories.tsx +85 -0
- package/src/primitives/switch.tsx +28 -0
- package/src/primitives/table.stories.tsx +170 -0
- package/src/primitives/table.tsx +116 -0
- package/src/primitives/tabs.stories.tsx +180 -0
- package/src/primitives/tabs.tsx +71 -0
- package/src/primitives/terminal-display.stories.tsx +191 -0
- package/src/primitives/terminal-display.tsx +189 -0
- package/src/primitives/theme-toggle.stories.tsx +32 -0
- package/src/primitives/theme-toggle.tsx +96 -0
- package/src/primitives/toast.stories.tsx +155 -0
- package/src/primitives/toast.tsx +190 -0
- package/src/primitives/upload-progress.stories.tsx +120 -0
- package/src/primitives/upload-progress.tsx +110 -0
- package/src/run/expanded-tool-detail.stories.tsx +182 -0
- package/src/run/expanded-tool-detail.tsx +186 -0
- package/src/run/index.ts +13 -0
- package/src/run/inline-thinking-item.stories.tsx +136 -0
- package/src/run/inline-thinking-item.tsx +120 -0
- package/src/run/inline-tool-item.stories.tsx +222 -0
- package/src/run/inline-tool-item.tsx +190 -0
- package/src/run/run-group.stories.tsx +322 -0
- package/src/run/run-group.tsx +569 -0
- package/src/run/run-item-primitives.tsx +17 -0
- package/src/run/tool-call-feed.stories.tsx +294 -0
- package/src/run/tool-call-feed.tsx +192 -0
- package/src/run/tool-call-step.stories.tsx +198 -0
- package/src/run/tool-call-step.tsx +240 -0
- package/src/sdk-hooks.ts +38 -0
- package/src/stores/active-sessions-store.ts +455 -0
- package/src/stores/chat-store.ts +43 -0
- package/src/stores/index.ts +2 -0
- package/src/tool-previews/command-preview.tsx +116 -0
- package/src/tool-previews/diff-preview.tsx +85 -0
- package/src/tool-previews/glob-results-preview.tsx +98 -0
- package/src/tool-previews/grep-results-preview.tsx +157 -0
- package/src/tool-previews/index.ts +22 -0
- package/src/tool-previews/preview-primitives.tsx +84 -0
- package/src/tool-previews/question-preview.tsx +101 -0
- package/src/tool-previews/web-search-preview.tsx +117 -0
- package/src/tool-previews/write-file-preview.tsx +80 -0
- package/src/types/branding.ts +11 -0
- package/src/types/index.ts +5 -0
- package/src/types/message.ts +13 -0
- package/src/types/parts.ts +51 -0
- package/src/types/run.ts +56 -0
- package/src/types/tool-display.ts +41 -0
- package/src/utils/copy-text.ts +30 -0
- package/src/utils/format.test.ts +43 -0
- package/src/utils/format.ts +56 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/time-ago.ts +9 -0
- package/src/utils/tool-display.ts +238 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { ExpandedToolDetail } from './expanded-tool-detail'
|
|
3
|
+
import type { ToolPart } from '../types/parts'
|
|
4
|
+
|
|
5
|
+
const NOW = Date.now()
|
|
6
|
+
|
|
7
|
+
// -- Fixtures --
|
|
8
|
+
|
|
9
|
+
const bashPart: ToolPart = {
|
|
10
|
+
type: 'tool',
|
|
11
|
+
id: 'etd-bash-1',
|
|
12
|
+
tool: 'bash',
|
|
13
|
+
state: {
|
|
14
|
+
status: 'completed',
|
|
15
|
+
input: { command: 'pnpm test --run --reporter=verbose 2>&1 | tail -30' },
|
|
16
|
+
output: `> sandbox-ui@0.4.0 test\n> vitest run --reporter=verbose\n\n stdout | src/utils/format.test.ts\n\n âś“ src/utils/format.test.ts (4)\n âś“ formatDuration returns ms for values under 1000\n âś“ formatDuration returns seconds for values 1000-59999\n âś“ formatDuration returns minutes for large values\n âś“ truncateText trims at word boundary\n\n âś“ src/utils/tool-display.test.ts (11)\n âś“ getToolCategory maps bash correctly\n âś“ getToolCategory maps read correctly\n âś“ getToolDisplayMetadata builds bash title\n âś“ getToolDisplayMetadata builds file path\n ... (8 more)\n\nTest Files 2 passed (2)\n Tests 15 passed (15)\n Duration 1.42s`,
|
|
17
|
+
time: { start: NOW - 3800, end: NOW - 2380 },
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const bashError: ToolPart = {
|
|
22
|
+
type: 'tool',
|
|
23
|
+
id: 'etd-bash-error',
|
|
24
|
+
tool: 'bash',
|
|
25
|
+
state: {
|
|
26
|
+
status: 'error',
|
|
27
|
+
input: { command: 'pnpm test --run' },
|
|
28
|
+
error: `FAIL src/components/RunGroup.test.tsx\n ● RunGroup › renders tool parts\n\n TypeError: Cannot read properties of undefined (reading 'map')\n\n 40 | const allParts = useMemo(() => {\n 41 | const parts = []\n > 42 | for (const msg of run.messages.map(m => m.id)) {\n | ^\n 43 | const msgParts = partMap[msg] ?? []\n\n at RunGroup (src/run/run-group.tsx:42:40)`,
|
|
29
|
+
time: { start: NOW - 2100, end: NOW - 1600 },
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const readFilePart: ToolPart = {
|
|
34
|
+
type: 'tool',
|
|
35
|
+
id: 'etd-read-1',
|
|
36
|
+
tool: 'read',
|
|
37
|
+
state: {
|
|
38
|
+
status: 'completed',
|
|
39
|
+
input: { file_path: '/home/user/project/src/run/run-group.tsx' },
|
|
40
|
+
output: `import { memo, useMemo, type ReactNode } from 'react'\nimport type { Run } from '../types/run'\nimport type { SessionPart } from '../types/parts'\n\nexport interface RunGroupProps {\n run: Run\n partMap: Record<string, SessionPart[]>\n collapsed: boolean\n onToggle: () => void\n}\n\nexport const RunGroup = memo(({ run, partMap, collapsed, onToggle }: RunGroupProps) => {\n const allParts = useMemo(() => {\n const parts: Array<{ part: SessionPart; msgId: string; index: number }> = []\n for (const msg of run.messages) {\n const msgParts = partMap[msg.id] ?? []\n msgParts.forEach((part, index) => parts.push({ part, msgId: msg.id, index }))\n }\n return parts\n }, [run.messages, partMap])\n // ...\n})`,
|
|
41
|
+
time: { start: NOW - 1200, end: NOW - 1140 },
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const writeFilePart: ToolPart = {
|
|
46
|
+
type: 'tool',
|
|
47
|
+
id: 'etd-write-1',
|
|
48
|
+
tool: 'write',
|
|
49
|
+
state: {
|
|
50
|
+
status: 'completed',
|
|
51
|
+
input: {
|
|
52
|
+
file_path: '/home/user/project/src/utils/format.ts',
|
|
53
|
+
content: `export function formatDuration(ms: number): string {\n if (ms < 1000) return \`\${ms}ms\`\n if (ms < 60_000) return \`\${(ms / 1000).toFixed(1)}s\`\n const m = Math.floor(ms / 60_000)\n const s = Math.floor((ms % 60_000) / 1000)\n return \`\${m}m \${s}s\`\n}\n\nexport function truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text\n const truncated = text.slice(0, maxLength)\n const lastSpace = truncated.lastIndexOf(' ')\n return (lastSpace > maxLength * 0.8 ? truncated.slice(0, lastSpace) : truncated) + '…'\n}`,
|
|
54
|
+
},
|
|
55
|
+
output: 'File written successfully.',
|
|
56
|
+
time: { start: NOW - 900, end: NOW - 855 },
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const webSearchPart: ToolPart = {
|
|
61
|
+
type: 'tool',
|
|
62
|
+
id: 'etd-web-1',
|
|
63
|
+
tool: 'web_search',
|
|
64
|
+
state: {
|
|
65
|
+
status: 'completed',
|
|
66
|
+
input: { query: 'vitest mock module hoisting typescript esm' },
|
|
67
|
+
output: JSON.stringify({
|
|
68
|
+
results: [
|
|
69
|
+
{
|
|
70
|
+
title: 'Mocking | Vitest',
|
|
71
|
+
url: 'https://vitest.dev/guide/mocking',
|
|
72
|
+
snippet: 'Vitest supports mocking modules and auto-mocking via vi.mock(). Module mocks are hoisted to the top of the file.',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
title: 'vi.mock() | Vitest API',
|
|
76
|
+
url: 'https://vitest.dev/api/vi#vi-mock',
|
|
77
|
+
snippet: 'Mocks a module. Calls to vi.mock are hoisted before any import statements.',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
title: 'ESM module mocking with Vitest',
|
|
81
|
+
url: 'https://github.com/vitest-dev/vitest/discussions/1204',
|
|
82
|
+
snippet: 'When using ESM, vi.mock must be called in the same file as the import you want to mock.',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
}, null, 2),
|
|
86
|
+
time: { start: NOW - 2800, end: NOW - 1900 },
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const grepPart: ToolPart = {
|
|
91
|
+
type: 'tool',
|
|
92
|
+
id: 'etd-grep-1',
|
|
93
|
+
tool: 'grep',
|
|
94
|
+
state: {
|
|
95
|
+
status: 'completed',
|
|
96
|
+
input: { pattern: 'partMap\\[', path: 'src/' },
|
|
97
|
+
output: `src/run/run-group.tsx:184: const msgParts = partMap[msg.id] ?? []\nsrc/run/run-group.tsx:291: key={key}\nsrc/hooks/useRunGroup.ts:44: setPartMap(prev => ({ ...prev, [msgId]: parts }))`,
|
|
98
|
+
time: { start: NOW - 600, end: NOW - 560 },
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const runningPart: ToolPart = {
|
|
103
|
+
type: 'tool',
|
|
104
|
+
id: 'etd-running',
|
|
105
|
+
tool: 'bash',
|
|
106
|
+
state: {
|
|
107
|
+
status: 'running',
|
|
108
|
+
input: { command: 'pnpm build --no-cache' },
|
|
109
|
+
time: { start: NOW - 8000 },
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const genericPart: ToolPart = {
|
|
114
|
+
type: 'tool',
|
|
115
|
+
id: 'etd-generic',
|
|
116
|
+
tool: 'custom_tool',
|
|
117
|
+
state: {
|
|
118
|
+
status: 'completed',
|
|
119
|
+
input: { sessionId: 'sess_abc123', action: 'snapshot', format: 'json' },
|
|
120
|
+
output: { snapshotId: 'snap_xyz789', size: 1048576, timestamp: NOW - 500 },
|
|
121
|
+
time: { start: NOW - 1500, end: NOW - 1200 },
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// --
|
|
126
|
+
|
|
127
|
+
const meta: Meta<typeof ExpandedToolDetail> = {
|
|
128
|
+
title: 'Run/ExpandedToolDetail',
|
|
129
|
+
component: ExpandedToolDetail,
|
|
130
|
+
parameters: {
|
|
131
|
+
layout: 'fullscreen',
|
|
132
|
+
backgrounds: { default: 'dark' },
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default meta
|
|
137
|
+
type Story = StoryObj<typeof ExpandedToolDetail>
|
|
138
|
+
|
|
139
|
+
const wrap = (children: React.ReactNode) => (
|
|
140
|
+
<div className="p-6 max-w-2xl">{children}</div>
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
export const BashCommand: Story = {
|
|
144
|
+
args: { part: bashPart },
|
|
145
|
+
render: (args) => wrap(<ExpandedToolDetail {...args} />),
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const BashError: Story = {
|
|
149
|
+
args: { part: bashError },
|
|
150
|
+
render: (args) => wrap(<ExpandedToolDetail {...args} />),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const ReadFile: Story = {
|
|
154
|
+
args: { part: readFilePart },
|
|
155
|
+
render: (args) => wrap(<ExpandedToolDetail {...args} />),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const WriteFile: Story = {
|
|
159
|
+
args: { part: writeFilePart },
|
|
160
|
+
render: (args) => wrap(<ExpandedToolDetail {...args} />),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const WebSearch: Story = {
|
|
164
|
+
args: { part: webSearchPart },
|
|
165
|
+
render: (args) => wrap(<ExpandedToolDetail {...args} />),
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const GrepResults: Story = {
|
|
169
|
+
args: { part: grepPart },
|
|
170
|
+
render: (args) => wrap(<ExpandedToolDetail {...args} />),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const Running: Story = {
|
|
174
|
+
args: { part: runningPart },
|
|
175
|
+
render: (args) => wrap(<ExpandedToolDetail {...args} />),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const GenericFallback: Story = {
|
|
179
|
+
name: 'Generic (fallback view)',
|
|
180
|
+
args: { part: genericPart },
|
|
181
|
+
render: (args) => wrap(<ExpandedToolDetail {...args} />),
|
|
182
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { memo } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ArrowRight,
|
|
4
|
+
ArrowLeft,
|
|
5
|
+
AlertCircle,
|
|
6
|
+
Loader2,
|
|
7
|
+
FileText,
|
|
8
|
+
} from "lucide-react";
|
|
9
|
+
import type { ToolPart } from "../types/parts";
|
|
10
|
+
import { getToolDisplayMetadata } from "../utils/tool-display";
|
|
11
|
+
import { CommandPreview } from "../tool-previews/command-preview";
|
|
12
|
+
import { WriteFilePreview } from "../tool-previews/write-file-preview";
|
|
13
|
+
import { CodeBlock } from "../markdown/code-block";
|
|
14
|
+
import { GrepResultsPreview } from "../tool-previews/grep-results-preview";
|
|
15
|
+
import { GlobResultsPreview } from "../tool-previews/glob-results-preview";
|
|
16
|
+
import { WebSearchPreview } from "../tool-previews/web-search-preview";
|
|
17
|
+
import { QuestionPreview } from "../tool-previews/question-preview";
|
|
18
|
+
import { DiffPreview } from "../tool-previews/diff-preview";
|
|
19
|
+
import { cn } from "../lib/utils";
|
|
20
|
+
|
|
21
|
+
export interface ExpandedToolDetailProps {
|
|
22
|
+
part: ToolPart;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const EXT_LANG: Record<string, string> = {
|
|
26
|
+
ts: "typescript", tsx: "tsx", js: "javascript", jsx: "jsx",
|
|
27
|
+
rs: "rust", py: "python", go: "go", rb: "ruby",
|
|
28
|
+
json: "json", yaml: "yaml", yml: "yaml", toml: "toml",
|
|
29
|
+
md: "markdown", css: "css", scss: "scss", html: "html",
|
|
30
|
+
sh: "bash", bash: "bash", zsh: "bash", sql: "sql",
|
|
31
|
+
sol: "solidity", proto: "protobuf",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function langFromPath(path?: string): string | undefined {
|
|
35
|
+
if (!path) return undefined;
|
|
36
|
+
const ext = path.split(".").pop()?.toLowerCase();
|
|
37
|
+
return ext ? EXT_LANG[ext] : undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Format an unknown value as a displayable string. */
|
|
41
|
+
function formatOutput(value: unknown): string {
|
|
42
|
+
if (value == null) return "";
|
|
43
|
+
if (typeof value === "string") return value;
|
|
44
|
+
try {
|
|
45
|
+
return JSON.stringify(value, null, 2);
|
|
46
|
+
} catch {
|
|
47
|
+
return String(value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Renders full tool details when a tool item is expanded.
|
|
53
|
+
* Dispatches to specialised previews (CommandPreview, WriteFilePreview)
|
|
54
|
+
* or falls back to a generic input/output code view.
|
|
55
|
+
*/
|
|
56
|
+
export const ExpandedToolDetail = memo(({ part }: ExpandedToolDetailProps) => {
|
|
57
|
+
const meta = getToolDisplayMetadata(part);
|
|
58
|
+
const { status, input, output, error } = part.state;
|
|
59
|
+
|
|
60
|
+
// Specialised previews
|
|
61
|
+
if (meta.displayVariant === "command") {
|
|
62
|
+
return <CommandPreview part={part} />;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (meta.displayVariant === "write-file") {
|
|
66
|
+
return <WriteFilePreview part={part} />;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (meta.displayVariant === "grep") {
|
|
70
|
+
return <GrepResultsPreview part={part} />;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (meta.displayVariant === "glob") {
|
|
74
|
+
return <GlobResultsPreview part={part} />;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (meta.displayVariant === "web-search") {
|
|
78
|
+
return <WebSearchPreview part={part} />;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (meta.displayVariant === "question") {
|
|
82
|
+
return <QuestionPreview part={part} />;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (meta.displayVariant === "diff") {
|
|
86
|
+
return <DiffPreview part={part} />;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (meta.displayVariant === "read-file") {
|
|
90
|
+
return (
|
|
91
|
+
<div className="overflow-hidden rounded-[var(--radius-lg)] border border-border bg-card shadow-[var(--shadow-card)]">
|
|
92
|
+
<div className="flex items-center gap-2.5 border-b border-border bg-card px-3 py-2">
|
|
93
|
+
<div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-[var(--radius-sm)] border border-[var(--border-accent)] bg-muted text-primary">
|
|
94
|
+
<FileText className="h-3.5 w-3.5" />
|
|
95
|
+
</div>
|
|
96
|
+
<div className="min-w-0 flex items-center gap-2">
|
|
97
|
+
<span className="text-xs font-semibold text-foreground">Read file</span>
|
|
98
|
+
{meta.targetPath ? (
|
|
99
|
+
<span className="truncate text-xs font-mono text-muted-foreground">{meta.targetPath}</span>
|
|
100
|
+
) : null}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
<div className="space-y-2 px-3 py-2.5">
|
|
104
|
+
{typeof output === "string" ? (
|
|
105
|
+
<CodeBlock code={output} language={langFromPath(meta.targetPath) ?? "text"} className="rounded-[var(--radius-md)]" />
|
|
106
|
+
) : (
|
|
107
|
+
<div className="rounded-[var(--radius-md)] border border-dashed border-border bg-muted px-3 py-4 text-sm text-muted-foreground">
|
|
108
|
+
No readable file content was returned.
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
{error ? (
|
|
112
|
+
<div className="rounded-[var(--radius-md)] border border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] px-3 py-3 text-sm text-[var(--surface-danger-text)]">
|
|
113
|
+
{error}
|
|
114
|
+
</div>
|
|
115
|
+
) : null}
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Generic fallback — show input/output/error
|
|
122
|
+
const inputStr = formatOutput(input);
|
|
123
|
+
const outputStr = formatOutput(output);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div className="space-y-3">
|
|
127
|
+
{/* Input */}
|
|
128
|
+
{inputStr && (
|
|
129
|
+
<div className="overflow-hidden rounded-[var(--radius-lg)] border border-border bg-card">
|
|
130
|
+
<div className="flex items-center gap-2 border-b border-border bg-background px-3 py-2">
|
|
131
|
+
<ArrowRight className="h-3 w-3 text-primary" />
|
|
132
|
+
<span className="text-xs font-medium uppercase tracking-[0.08em] text-muted-foreground">
|
|
133
|
+
Input
|
|
134
|
+
</span>
|
|
135
|
+
</div>
|
|
136
|
+
<CodeBlock code={inputStr} language="json" className="rounded-none border-0" />
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{/* Output */}
|
|
141
|
+
{status === "completed" && outputStr && (
|
|
142
|
+
<div className="overflow-hidden rounded-[var(--radius-lg)] border border-border bg-card">
|
|
143
|
+
<div className="flex items-center gap-2 border-b border-border bg-background px-3 py-2">
|
|
144
|
+
<ArrowLeft className="h-3 w-3 text-primary" />
|
|
145
|
+
<span className="text-xs font-medium uppercase tracking-[0.08em] text-muted-foreground">
|
|
146
|
+
Output
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
<CodeBlock
|
|
150
|
+
code={
|
|
151
|
+
outputStr.length > 2000
|
|
152
|
+
? outputStr.slice(0, 2000) + "\n...(truncated)"
|
|
153
|
+
: outputStr
|
|
154
|
+
}
|
|
155
|
+
language="json"
|
|
156
|
+
className="rounded-none border-0"
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
{/* Error */}
|
|
162
|
+
{error && (
|
|
163
|
+
<div className="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--surface-danger-border)]">
|
|
164
|
+
<div className="flex items-center gap-2 border-b border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] px-3 py-2">
|
|
165
|
+
<AlertCircle className="h-3 w-3 text-[var(--surface-danger-text)]" />
|
|
166
|
+
<span className="text-xs font-medium uppercase tracking-[0.08em] text-[var(--surface-danger-text)]">
|
|
167
|
+
Error
|
|
168
|
+
</span>
|
|
169
|
+
</div>
|
|
170
|
+
<pre className="bg-[var(--surface-danger-bg)] p-3 text-xs font-mono whitespace-pre-wrap break-all text-[var(--surface-danger-text)]">
|
|
171
|
+
{error}
|
|
172
|
+
</pre>
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
|
|
176
|
+
{/* Running state */}
|
|
177
|
+
{(status === "pending" || status === "running") && (
|
|
178
|
+
<div className="flex items-center gap-2 rounded-[var(--radius-md)] border border-border bg-muted px-3 py-3 text-xs text-muted-foreground">
|
|
179
|
+
<Loader2 className={cn("h-3 w-3 animate-spin text-primary")} />
|
|
180
|
+
Running…
|
|
181
|
+
</div>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
ExpandedToolDetail.displayName = "ExpandedToolDetail";
|
package/src/run/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { RunGroup, type RunGroupProps } from "./run-group";
|
|
2
|
+
export { InlineToolItem, type InlineToolItemProps } from "./inline-tool-item";
|
|
3
|
+
export {
|
|
4
|
+
InlineThinkingItem,
|
|
5
|
+
type InlineThinkingItemProps,
|
|
6
|
+
} from "./inline-thinking-item";
|
|
7
|
+
export {
|
|
8
|
+
ExpandedToolDetail,
|
|
9
|
+
type ExpandedToolDetailProps,
|
|
10
|
+
} from "./expanded-tool-detail";
|
|
11
|
+
export { LiveDuration } from "./run-item-primitives";
|
|
12
|
+
export { ToolCallStep, ToolCallGroup, type ToolCallStepProps, type ToolCallGroupProps, type ToolCallType, type ToolCallStatus } from "./tool-call-step";
|
|
13
|
+
export { ToolCallFeed, parseToolEvent, type ToolCallFeedProps, type ToolCallData, type FeedSegment } from "./tool-call-feed";
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { InlineThinkingItem } from './inline-thinking-item'
|
|
3
|
+
import type { ReasoningPart } from '../types/parts'
|
|
4
|
+
|
|
5
|
+
const NOW = Date.now()
|
|
6
|
+
|
|
7
|
+
// -- Fixtures --
|
|
8
|
+
|
|
9
|
+
const thinkingComplete: ReasoningPart = {
|
|
10
|
+
type: 'reasoning',
|
|
11
|
+
text: `The user wants me to fix the failing test in \`src/utils/format.test.ts\`. Let me read the test file first to understand what's expected, then look at the implementation.
|
|
12
|
+
|
|
13
|
+
The error says "Cannot read properties of undefined (reading 'map')" at line 42. This usually means the data array is undefined — likely an async fetch that hasn't resolved yet.
|
|
14
|
+
|
|
15
|
+
I should:
|
|
16
|
+
1. Read the failing test file
|
|
17
|
+
2. Read the implementation
|
|
18
|
+
3. Check if there's a missing await or an unhandled promise
|
|
19
|
+
4. Fix the issue and re-run tests to confirm`,
|
|
20
|
+
time: { start: NOW - 12400, end: NOW - 6800 },
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const thinkingActive: ReasoningPart = {
|
|
24
|
+
type: 'reasoning',
|
|
25
|
+
text: `I need to carefully think about the approach here. The test is expecting...`,
|
|
26
|
+
time: { start: NOW - 3000 },
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const thinkingShort: ReasoningPart = {
|
|
30
|
+
type: 'reasoning',
|
|
31
|
+
text: 'Read the file, check the types, apply the fix.',
|
|
32
|
+
time: { start: NOW - 800, end: NOW - 500 },
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const thinkingLong: ReasoningPart = {
|
|
36
|
+
type: 'reasoning',
|
|
37
|
+
text: `This is a complex TypeScript type error. Let me trace through the issue systematically.
|
|
38
|
+
|
|
39
|
+
The component \`RunGroup\` accepts a \`partMap\` of type \`Record<string, SessionPart[]>\`. When we flatten the parts, we iterate \`run.messages\` and look up each message ID in the map. The issue is that \`run.messages\` contains message IDs that might not exist in \`partMap\` yet — this happens during streaming when parts arrive before the message index is updated.
|
|
40
|
+
|
|
41
|
+
The fix is straightforward: add a null-coalescing fallback (\`?? []\`) when accessing \`partMap[msg.id]\`. This is already present in the code, so the bug must be elsewhere.
|
|
42
|
+
|
|
43
|
+
Looking more carefully at the type definitions... \`SessionMessage\` has an \`id\` field of type \`string\`, but the \`partMap\` key is constructed from \`message.messageId\` in some callsites and \`message.id\` in others. That's the mismatch.
|
|
44
|
+
|
|
45
|
+
I'll grep for all callsites to confirm before patching.`,
|
|
46
|
+
time: { start: NOW - 18000, end: NOW - 9000 },
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const thinkingEmpty: ReasoningPart = {
|
|
50
|
+
type: 'reasoning',
|
|
51
|
+
text: '',
|
|
52
|
+
time: { start: NOW - 400, end: NOW - 200 },
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// --
|
|
56
|
+
|
|
57
|
+
const meta: Meta<typeof InlineThinkingItem> = {
|
|
58
|
+
title: 'Run/InlineThinkingItem',
|
|
59
|
+
component: InlineThinkingItem,
|
|
60
|
+
parameters: {
|
|
61
|
+
layout: 'fullscreen',
|
|
62
|
+
backgrounds: { default: 'dark' },
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default meta
|
|
67
|
+
type Story = StoryObj<typeof InlineThinkingItem>
|
|
68
|
+
|
|
69
|
+
export const Complete: Story = {
|
|
70
|
+
args: { part: thinkingComplete, defaultOpen: false, autoCollapse: false },
|
|
71
|
+
render: (args) => (
|
|
72
|
+
<div className="p-6 max-w-2xl">
|
|
73
|
+
<InlineThinkingItem {...args} />
|
|
74
|
+
</div>
|
|
75
|
+
),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const CompleteOpen: Story = {
|
|
79
|
+
name: 'Complete (expanded)',
|
|
80
|
+
args: { part: thinkingComplete, defaultOpen: true, autoCollapse: false },
|
|
81
|
+
render: (args) => (
|
|
82
|
+
<div className="p-6 max-w-2xl">
|
|
83
|
+
<InlineThinkingItem {...args} />
|
|
84
|
+
</div>
|
|
85
|
+
),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const Active: Story = {
|
|
89
|
+
name: 'Active (streaming)',
|
|
90
|
+
args: { part: thinkingActive, defaultOpen: true, autoCollapse: false },
|
|
91
|
+
render: (args) => (
|
|
92
|
+
<div className="p-6 max-w-2xl">
|
|
93
|
+
<InlineThinkingItem {...args} />
|
|
94
|
+
</div>
|
|
95
|
+
),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const ShortReasoning: Story = {
|
|
99
|
+
args: { part: thinkingShort, defaultOpen: false, autoCollapse: false },
|
|
100
|
+
render: (args) => (
|
|
101
|
+
<div className="p-6 max-w-2xl">
|
|
102
|
+
<InlineThinkingItem {...args} />
|
|
103
|
+
</div>
|
|
104
|
+
),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const LongReasoning: Story = {
|
|
108
|
+
args: { part: thinkingLong, defaultOpen: true, autoCollapse: false },
|
|
109
|
+
render: (args) => (
|
|
110
|
+
<div className="p-6 max-w-2xl">
|
|
111
|
+
<InlineThinkingItem {...args} />
|
|
112
|
+
</div>
|
|
113
|
+
),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const EmptyText: Story = {
|
|
117
|
+
args: { part: thinkingEmpty, defaultOpen: true, autoCollapse: false },
|
|
118
|
+
render: (args) => (
|
|
119
|
+
<div className="p-6 max-w-2xl">
|
|
120
|
+
<InlineThinkingItem {...args} />
|
|
121
|
+
</div>
|
|
122
|
+
),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const AllStates: Story = {
|
|
126
|
+
render: () => (
|
|
127
|
+
<div className="p-6 max-w-2xl space-y-3">
|
|
128
|
+
<p className="text-xs font-mono uppercase tracking-widest text-muted-foreground px-1">Active</p>
|
|
129
|
+
<InlineThinkingItem part={thinkingActive} defaultOpen={true} autoCollapse={false} />
|
|
130
|
+
<p className="text-xs font-mono uppercase tracking-widest text-muted-foreground px-1 pt-2">Complete (collapsed)</p>
|
|
131
|
+
<InlineThinkingItem part={thinkingComplete} defaultOpen={false} autoCollapse={false} />
|
|
132
|
+
<p className="text-xs font-mono uppercase tracking-widest text-muted-foreground px-1 pt-2">Complete (expanded)</p>
|
|
133
|
+
<InlineThinkingItem part={thinkingLong} defaultOpen={true} autoCollapse={false} />
|
|
134
|
+
</div>
|
|
135
|
+
),
|
|
136
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { memo, useEffect, useRef, useState } from "react";
|
|
2
|
+
import * as Collapsible from "@radix-ui/react-collapsible";
|
|
3
|
+
import { Brain, ChevronRight } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
import { formatDuration, truncateText } from "../utils/format";
|
|
6
|
+
import type { ReasoningPart } from "../types/parts";
|
|
7
|
+
import { Markdown } from "../markdown/markdown";
|
|
8
|
+
import { LiveDuration } from "./run-item-primitives";
|
|
9
|
+
|
|
10
|
+
export interface InlineThinkingItemProps {
|
|
11
|
+
part: ReasoningPart;
|
|
12
|
+
defaultOpen?: boolean;
|
|
13
|
+
autoCollapse?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
contentClassName?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const InlineThinkingItem = memo(
|
|
19
|
+
({
|
|
20
|
+
part,
|
|
21
|
+
defaultOpen = false,
|
|
22
|
+
autoCollapse = true,
|
|
23
|
+
className,
|
|
24
|
+
contentClassName,
|
|
25
|
+
}: InlineThinkingItemProps) => {
|
|
26
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
27
|
+
const autoCollapsedRef = useRef(false);
|
|
28
|
+
|
|
29
|
+
const startTime = part.time?.start;
|
|
30
|
+
const endTime = part.time?.end;
|
|
31
|
+
const durationMs = startTime && endTime ? endTime - startTime : undefined;
|
|
32
|
+
const isActive = startTime != null && endTime == null;
|
|
33
|
+
const preview = part.text ? truncateText(part.text, 120) : undefined;
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (isActive) {
|
|
37
|
+
autoCollapsedRef.current = false;
|
|
38
|
+
setOpen(true);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (autoCollapse && !autoCollapsedRef.current && durationMs != null) {
|
|
43
|
+
const timer = window.setTimeout(() => {
|
|
44
|
+
setOpen(false);
|
|
45
|
+
autoCollapsedRef.current = true;
|
|
46
|
+
}, 900);
|
|
47
|
+
|
|
48
|
+
return () => window.clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
}, [autoCollapse, durationMs, isActive]);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Collapsible.Root open={open} onOpenChange={setOpen}>
|
|
54
|
+
<div
|
|
55
|
+
className={cn(
|
|
56
|
+
"overflow-hidden rounded-[var(--radius-lg)] border bg-card transition-colors",
|
|
57
|
+
isActive
|
|
58
|
+
? "border-[var(--border-accent)]"
|
|
59
|
+
: "border-border hover:border-[var(--border-accent)]",
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
<Collapsible.Trigger asChild>
|
|
64
|
+
<button
|
|
65
|
+
className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm cursor-pointer"
|
|
66
|
+
>
|
|
67
|
+
<div
|
|
68
|
+
className={cn(
|
|
69
|
+
"flex h-6 w-6 shrink-0 items-center justify-center rounded-[var(--radius-sm)] border",
|
|
70
|
+
isActive
|
|
71
|
+
? "border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary"
|
|
72
|
+
: "border-border bg-muted text-muted-foreground",
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
<Brain className={cn("h-3 w-3 shrink-0", isActive && "animate-pulse")} />
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<p className="min-w-0 flex-1 truncate font-[var(--font-sans)] text-muted-foreground">
|
|
79
|
+
{preview ?? (isActive ? "Thinking…" : "Reasoning")}
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
83
|
+
{isActive && startTime ? <LiveDuration startTime={startTime} /> : null}
|
|
84
|
+
{!isActive && durationMs != null ? (
|
|
85
|
+
<span className="shrink-0 text-xs tabular-nums text-muted-foreground">
|
|
86
|
+
{formatDuration(durationMs)}
|
|
87
|
+
</span>
|
|
88
|
+
) : null}
|
|
89
|
+
<ChevronRight
|
|
90
|
+
className={cn(
|
|
91
|
+
"h-3 w-3 text-muted-foreground transition-transform shrink-0",
|
|
92
|
+
open && "rotate-90",
|
|
93
|
+
)}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</button>
|
|
97
|
+
</Collapsible.Trigger>
|
|
98
|
+
|
|
99
|
+
<Collapsible.Content className="overflow-hidden data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp">
|
|
100
|
+
{part.text ? (
|
|
101
|
+
<div
|
|
102
|
+
className={cn(
|
|
103
|
+
"max-h-60 overflow-y-auto border-t border-border bg-muted px-3 py-3 text-sm leading-relaxed text-muted-foreground",
|
|
104
|
+
contentClassName,
|
|
105
|
+
)}
|
|
106
|
+
>
|
|
107
|
+
<Markdown>{part.text}</Markdown>
|
|
108
|
+
</div>
|
|
109
|
+
) : (
|
|
110
|
+
<div className="border-t border-border bg-muted px-3 py-2.5 text-xs text-muted-foreground">
|
|
111
|
+
No reasoning text provided.
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
</Collapsible.Content>
|
|
115
|
+
</div>
|
|
116
|
+
</Collapsible.Root>
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
InlineThinkingItem.displayName = "InlineThinkingItem";
|