@ai-sdk/devtools 1.0.0-beta.9 → 1.0.0-canary.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/index-B1quREL_.css +1 -0
- package/dist/client/assets/index-DYruJ8X0.js +181 -0
- package/dist/client/index.html +2 -2
- package/dist/index.d.ts +19 -1
- package/dist/index.js +342 -3
- package/dist/viewer/server.js +42 -5
- package/package.json +28 -4
- package/src/db.ts +32 -5
- package/src/index.ts +1 -0
- package/src/integration.ts +509 -0
- package/src/middleware.ts +5 -5
- package/src/viewer/client/app.tsx +97 -1878
- package/src/viewer/client/components/message-components.tsx +342 -0
- package/src/viewer/client/components/output-components.tsx +145 -0
- package/src/viewer/client/components/shared-components.tsx +691 -0
- package/src/viewer/client/components/step-card.tsx +472 -0
- package/src/viewer/client/components/trace-timeline.tsx +529 -0
- package/src/viewer/client/components/ui/badge.tsx +0 -1
- package/src/viewer/client/components/ui/button.tsx +0 -1
- package/src/viewer/client/types.ts +183 -0
- package/src/viewer/client/utils.ts +708 -0
- package/src/viewer/server.ts +49 -5
- package/dist/client/assets/index-BkyOIjbt.css +0 -1
- package/dist/client/assets/index-BwqXGbSV.js +0 -176
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ChevronRight,
|
|
4
|
+
Copy,
|
|
5
|
+
Check,
|
|
6
|
+
Wrench,
|
|
7
|
+
MessageSquare,
|
|
8
|
+
Brain,
|
|
9
|
+
Settings,
|
|
10
|
+
BarChart3,
|
|
11
|
+
} from 'lucide-react';
|
|
12
|
+
import {
|
|
13
|
+
Drawer,
|
|
14
|
+
DrawerContent,
|
|
15
|
+
DrawerHeader,
|
|
16
|
+
DrawerTitle,
|
|
17
|
+
DrawerTrigger,
|
|
18
|
+
} from '@/components/ui/drawer';
|
|
19
|
+
import type {
|
|
20
|
+
InputTokenBreakdown,
|
|
21
|
+
OutputTokenBreakdown,
|
|
22
|
+
ParsedInput,
|
|
23
|
+
ParsedUsage,
|
|
24
|
+
ToolDefinition,
|
|
25
|
+
} from '../types';
|
|
26
|
+
import {
|
|
27
|
+
safeParseJson,
|
|
28
|
+
formatToolParams,
|
|
29
|
+
getInputTokenBreakdown,
|
|
30
|
+
getOutputTokenBreakdown,
|
|
31
|
+
} from '../utils';
|
|
32
|
+
|
|
33
|
+
export function JsonBlock({
|
|
34
|
+
data,
|
|
35
|
+
compact = false,
|
|
36
|
+
size = 'sm',
|
|
37
|
+
}: {
|
|
38
|
+
data: unknown;
|
|
39
|
+
compact?: boolean;
|
|
40
|
+
size?: 'sm' | 'base' | 'lg';
|
|
41
|
+
}) {
|
|
42
|
+
const [copied, setCopied] = useState(false);
|
|
43
|
+
|
|
44
|
+
const jsonString = JSON.stringify(data, null, 2);
|
|
45
|
+
const displayString =
|
|
46
|
+
compact && jsonString.length > 200 ? JSON.stringify(data) : jsonString;
|
|
47
|
+
|
|
48
|
+
const sizeClasses = {
|
|
49
|
+
sm: 'text-xs',
|
|
50
|
+
base: 'text-sm',
|
|
51
|
+
lg: 'text-base',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleCopy = async () => {
|
|
55
|
+
await navigator.clipboard.writeText(jsonString);
|
|
56
|
+
setCopied(true);
|
|
57
|
+
setTimeout(() => setCopied(false), 2000);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="relative group">
|
|
62
|
+
<button
|
|
63
|
+
onClick={handleCopy}
|
|
64
|
+
className="absolute top-1.5 right-1.5 p-1.5 rounded-md border border-border bg-background opacity-0 group-hover:opacity-100 transition-opacity z-10"
|
|
65
|
+
title="Copy to clipboard"
|
|
66
|
+
>
|
|
67
|
+
{copied ? (
|
|
68
|
+
<Check className="size-3 text-success" />
|
|
69
|
+
) : (
|
|
70
|
+
<Copy className="size-3 text-muted-foreground" />
|
|
71
|
+
)}
|
|
72
|
+
</button>
|
|
73
|
+
<pre
|
|
74
|
+
className={`font-mono ${sizeClasses[size]} text-muted-foreground whitespace-pre-wrap wrap-break-words bg-background rounded p-2 ${
|
|
75
|
+
compact ? 'overflow-hidden max-h-20' : ''
|
|
76
|
+
}`}
|
|
77
|
+
>
|
|
78
|
+
{displayString}
|
|
79
|
+
</pre>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function RawDataSection({
|
|
85
|
+
rawRequest,
|
|
86
|
+
rawResponse,
|
|
87
|
+
rawChunks,
|
|
88
|
+
isStream,
|
|
89
|
+
}: {
|
|
90
|
+
rawRequest: string | null;
|
|
91
|
+
rawResponse: string | null;
|
|
92
|
+
rawChunks: string | null;
|
|
93
|
+
isStream: boolean;
|
|
94
|
+
}) {
|
|
95
|
+
const [expanded, setExpanded] = useState(false);
|
|
96
|
+
const [responseView, setResponseView] = useState<'parsed' | 'raw'>('parsed');
|
|
97
|
+
|
|
98
|
+
if (!rawRequest && !rawResponse) return null;
|
|
99
|
+
|
|
100
|
+
const hasRawChunks = isStream && rawChunks;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className="border-t border-border">
|
|
104
|
+
<button
|
|
105
|
+
className="w-full flex items-center gap-2 px-4 py-2.5 text-[11px] text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors"
|
|
106
|
+
onClick={() => setExpanded(!expanded)}
|
|
107
|
+
>
|
|
108
|
+
<ChevronRight
|
|
109
|
+
className={`size-3 transition-transform ${expanded ? 'rotate-90' : ''}`}
|
|
110
|
+
/>
|
|
111
|
+
<span className="font-medium tracking-wider uppercase">
|
|
112
|
+
Request / Response
|
|
113
|
+
</span>
|
|
114
|
+
</button>
|
|
115
|
+
|
|
116
|
+
{expanded && (
|
|
117
|
+
<div className="grid grid-cols-2 gap-4 px-4 pb-4">
|
|
118
|
+
{rawRequest && (
|
|
119
|
+
<div className="flex flex-col">
|
|
120
|
+
<div className="flex items-end mb-2 h-7">
|
|
121
|
+
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
122
|
+
Request
|
|
123
|
+
</span>
|
|
124
|
+
</div>
|
|
125
|
+
<div className="max-h-[400px] overflow-auto rounded-md border border-border">
|
|
126
|
+
<JsonBlock data={safeParseJson(rawRequest)} />
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
{(rawResponse || rawChunks) && (
|
|
131
|
+
<div className="flex flex-col">
|
|
132
|
+
<div className="flex justify-between items-end mb-2 h-7">
|
|
133
|
+
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
134
|
+
{isStream ? 'Stream' : 'Response'}
|
|
135
|
+
</span>
|
|
136
|
+
{hasRawChunks && (
|
|
137
|
+
<div className="inline-flex rounded-md border border-border bg-muted/30 p-0.5">
|
|
138
|
+
<button
|
|
139
|
+
onClick={() => setResponseView('parsed')}
|
|
140
|
+
className={`px-2 py-0.5 text-[10px] font-medium rounded transition-colors ${
|
|
141
|
+
responseView === 'parsed'
|
|
142
|
+
? 'bg-background text-foreground shadow-sm'
|
|
143
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
144
|
+
}`}
|
|
145
|
+
>
|
|
146
|
+
AI SDK
|
|
147
|
+
</button>
|
|
148
|
+
<button
|
|
149
|
+
onClick={() => setResponseView('raw')}
|
|
150
|
+
className={`px-2 py-0.5 text-[10px] font-medium rounded transition-colors ${
|
|
151
|
+
responseView === 'raw'
|
|
152
|
+
? 'bg-background text-foreground shadow-sm'
|
|
153
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
154
|
+
}`}
|
|
155
|
+
>
|
|
156
|
+
Provider
|
|
157
|
+
</button>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
<div className="max-h-[400px] overflow-auto rounded-md border border-border">
|
|
162
|
+
<JsonBlock
|
|
163
|
+
data={safeParseJson(
|
|
164
|
+
hasRawChunks && responseView === 'raw'
|
|
165
|
+
? rawChunks
|
|
166
|
+
: rawResponse,
|
|
167
|
+
)}
|
|
168
|
+
/>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function UsageDetails({ usage }: { usage: ParsedUsage }) {
|
|
179
|
+
const inputBreakdown = getInputTokenBreakdown(usage?.inputTokens);
|
|
180
|
+
const outputBreakdown = getOutputTokenBreakdown(usage?.outputTokens);
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div className="space-y-6">
|
|
184
|
+
<div className="grid grid-cols-2 gap-4">
|
|
185
|
+
<div className="p-4 rounded-lg border border-border bg-card">
|
|
186
|
+
<div className="mb-2 text-xs font-medium tracking-wider uppercase text-muted-foreground">
|
|
187
|
+
Input Tokens
|
|
188
|
+
</div>
|
|
189
|
+
<div className="text-2xl font-semibold">{inputBreakdown.total}</div>
|
|
190
|
+
{(inputBreakdown.cacheRead !== undefined ||
|
|
191
|
+
inputBreakdown.cacheWrite !== undefined) && (
|
|
192
|
+
<div className="mt-3 space-y-1 text-sm text-muted-foreground">
|
|
193
|
+
{inputBreakdown.cacheRead !== undefined && (
|
|
194
|
+
<div className="flex justify-between">
|
|
195
|
+
<span>Cache read</span>
|
|
196
|
+
<span className="font-mono">{inputBreakdown.cacheRead}</span>
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
{inputBreakdown.cacheWrite !== undefined && (
|
|
200
|
+
<div className="flex justify-between">
|
|
201
|
+
<span>Cache write</span>
|
|
202
|
+
<span className="font-mono">{inputBreakdown.cacheWrite}</span>
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
{inputBreakdown.noCache !== undefined && (
|
|
206
|
+
<div className="flex justify-between">
|
|
207
|
+
<span>No cache</span>
|
|
208
|
+
<span className="font-mono">{inputBreakdown.noCache}</span>
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div className="p-4 rounded-lg border border-border bg-card">
|
|
216
|
+
<div className="mb-2 text-xs font-medium tracking-wider uppercase text-muted-foreground">
|
|
217
|
+
Output Tokens
|
|
218
|
+
</div>
|
|
219
|
+
<div className="text-2xl font-semibold">{outputBreakdown.total}</div>
|
|
220
|
+
{(outputBreakdown.text !== undefined ||
|
|
221
|
+
outputBreakdown.reasoning !== undefined) && (
|
|
222
|
+
<div className="mt-3 space-y-1 text-sm text-muted-foreground">
|
|
223
|
+
{outputBreakdown.text !== undefined && (
|
|
224
|
+
<div className="flex justify-between">
|
|
225
|
+
<span>Text</span>
|
|
226
|
+
<span className="font-mono">{outputBreakdown.text}</span>
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
{outputBreakdown.reasoning !== undefined && (
|
|
230
|
+
<div className="flex justify-between">
|
|
231
|
+
<span>Reasoning</span>
|
|
232
|
+
<span className="font-mono">{outputBreakdown.reasoning}</span>
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
{usage?.raw != null && (
|
|
241
|
+
<div>
|
|
242
|
+
<div className="mb-2 text-xs font-medium tracking-wider uppercase text-muted-foreground">
|
|
243
|
+
Raw Provider Usage
|
|
244
|
+
</div>
|
|
245
|
+
<JsonBlock data={usage.raw} size="sm" />
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
|
|
249
|
+
<div>
|
|
250
|
+
<div className="mb-2 text-xs font-medium tracking-wider uppercase text-muted-foreground">
|
|
251
|
+
Full Usage Object
|
|
252
|
+
</div>
|
|
253
|
+
<JsonBlock data={usage} size="sm" />
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function TokenBreakdownTooltip({
|
|
260
|
+
input,
|
|
261
|
+
output,
|
|
262
|
+
raw,
|
|
263
|
+
}: {
|
|
264
|
+
input: InputTokenBreakdown;
|
|
265
|
+
output: OutputTokenBreakdown;
|
|
266
|
+
raw?: unknown;
|
|
267
|
+
}) {
|
|
268
|
+
const hasInputBreakdown =
|
|
269
|
+
input.noCache !== undefined ||
|
|
270
|
+
input.cacheRead !== undefined ||
|
|
271
|
+
input.cacheWrite !== undefined;
|
|
272
|
+
const hasOutputBreakdown =
|
|
273
|
+
output.text !== undefined || output.reasoning !== undefined;
|
|
274
|
+
const hasBreakdown = hasInputBreakdown || hasOutputBreakdown;
|
|
275
|
+
|
|
276
|
+
if (!hasBreakdown) {
|
|
277
|
+
return (
|
|
278
|
+
<div className="text-xs">
|
|
279
|
+
<div>Input: {input.total}</div>
|
|
280
|
+
<div>Output: {output.total}</div>
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<div className="space-y-2 text-xs">
|
|
287
|
+
<div>
|
|
288
|
+
<div className="mb-1 font-medium">Input: {input.total}</div>
|
|
289
|
+
{input.cacheRead !== undefined && (
|
|
290
|
+
<div className="ml-2 text-muted-foreground">
|
|
291
|
+
Cache read: {input.cacheRead}
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
294
|
+
{input.cacheWrite !== undefined && (
|
|
295
|
+
<div className="ml-2 text-muted-foreground">
|
|
296
|
+
Cache write: {input.cacheWrite}
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
<div>
|
|
301
|
+
<div className="mb-1 font-medium">Output: {output.total}</div>
|
|
302
|
+
{output.text !== undefined && (
|
|
303
|
+
<div className="ml-2 text-muted-foreground">Text: {output.text}</div>
|
|
304
|
+
)}
|
|
305
|
+
{output.reasoning !== undefined && (
|
|
306
|
+
<div className="ml-2 text-muted-foreground">
|
|
307
|
+
Reasoning: {output.reasoning}
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
311
|
+
{raw !== undefined && (
|
|
312
|
+
<div className="pt-1 mt-1 border-t border-border">
|
|
313
|
+
<div className="text-muted-foreground/70 font-mono text-[10px] max-w-[200px] truncate">
|
|
314
|
+
Raw: {JSON.stringify(raw)}
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
)}
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function ReasoningBlock({ content }: { content: string }) {
|
|
323
|
+
const [expanded, setExpanded] = useState(false);
|
|
324
|
+
|
|
325
|
+
const previewContent =
|
|
326
|
+
content.length > 200 ? content.slice(0, 200) + '…' : content;
|
|
327
|
+
|
|
328
|
+
return (
|
|
329
|
+
<div className="overflow-hidden rounded-md border border-amber-500/30">
|
|
330
|
+
<button
|
|
331
|
+
className="flex gap-2 items-center px-3 py-2 w-full transition-colors bg-amber-500/10 hover:bg-amber-500/20"
|
|
332
|
+
onClick={() => setExpanded(!expanded)}
|
|
333
|
+
>
|
|
334
|
+
<ChevronRight
|
|
335
|
+
className={`size-3 text-amber-500 transition-transform shrink-0 ${
|
|
336
|
+
expanded ? 'rotate-90' : ''
|
|
337
|
+
}`}
|
|
338
|
+
/>
|
|
339
|
+
<Brain className="text-amber-500 size-3 shrink-0" />
|
|
340
|
+
<span className="text-xs font-medium text-amber-500">Thinking</span>
|
|
341
|
+
{!expanded && (
|
|
342
|
+
<span className="text-[11px] text-amber-500/70 truncate ml-1">
|
|
343
|
+
{previewContent}
|
|
344
|
+
</span>
|
|
345
|
+
)}
|
|
346
|
+
</button>
|
|
347
|
+
|
|
348
|
+
{expanded && (
|
|
349
|
+
<div className="p-3 border-t bg-card/50 border-amber-500/30">
|
|
350
|
+
<div className="text-xs leading-relaxed whitespace-pre-wrap text-foreground/80">
|
|
351
|
+
{content}
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
)}
|
|
355
|
+
</div>
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function TextBlock({
|
|
360
|
+
content,
|
|
361
|
+
defaultExpanded = false,
|
|
362
|
+
isSystem = false,
|
|
363
|
+
}: {
|
|
364
|
+
content: string;
|
|
365
|
+
defaultExpanded?: boolean;
|
|
366
|
+
isSystem?: boolean;
|
|
367
|
+
}) {
|
|
368
|
+
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
369
|
+
const [copied, setCopied] = useState(false);
|
|
370
|
+
|
|
371
|
+
const previewContent =
|
|
372
|
+
content.length > 200 ? content.slice(0, 200) + '…' : content;
|
|
373
|
+
|
|
374
|
+
const borderColor = isSystem ? 'border-blue-500/30' : 'border-border';
|
|
375
|
+
const bgColor = isSystem ? 'bg-blue-500/10' : 'bg-muted/30';
|
|
376
|
+
const hoverBgColor = isSystem ? 'hover:bg-blue-500/20' : 'hover:bg-muted/50';
|
|
377
|
+
const iconColor = isSystem ? 'text-blue-400' : 'text-muted-foreground';
|
|
378
|
+
const labelColor = isSystem ? 'text-blue-400' : 'text-foreground';
|
|
379
|
+
|
|
380
|
+
const handleCopy = async (e: React.MouseEvent) => {
|
|
381
|
+
e.stopPropagation();
|
|
382
|
+
await navigator.clipboard.writeText(content);
|
|
383
|
+
setCopied(true);
|
|
384
|
+
setTimeout(() => setCopied(false), 2000);
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
return (
|
|
388
|
+
<div className={`overflow-hidden rounded-md border ${borderColor}`}>
|
|
389
|
+
<button
|
|
390
|
+
className={`flex gap-2 items-center px-3 py-2 w-full transition-colors ${bgColor} ${hoverBgColor}`}
|
|
391
|
+
onClick={() => setExpanded(!expanded)}
|
|
392
|
+
>
|
|
393
|
+
<ChevronRight
|
|
394
|
+
className={`size-3 ${iconColor} transition-transform shrink-0 ${
|
|
395
|
+
expanded ? 'rotate-90' : ''
|
|
396
|
+
}`}
|
|
397
|
+
/>
|
|
398
|
+
<MessageSquare className={`size-3 ${iconColor} shrink-0`} />
|
|
399
|
+
<span className={`text-xs font-medium ${labelColor}`}>Text</span>
|
|
400
|
+
{!expanded && (
|
|
401
|
+
<span
|
|
402
|
+
className={`text-[11px] ${isSystem ? 'text-blue-400/70' : 'text-muted-foreground'} truncate ml-1`}
|
|
403
|
+
>
|
|
404
|
+
{previewContent}
|
|
405
|
+
</span>
|
|
406
|
+
)}
|
|
407
|
+
</button>
|
|
408
|
+
|
|
409
|
+
{expanded && (
|
|
410
|
+
<div
|
|
411
|
+
className={`relative p-3 border-t bg-card/50 ${borderColor} group`}
|
|
412
|
+
>
|
|
413
|
+
<button
|
|
414
|
+
onClick={handleCopy}
|
|
415
|
+
className="absolute top-1.5 right-1.5 p-1.5 rounded-md border border-border bg-background opacity-0 group-hover:opacity-100 transition-opacity z-10"
|
|
416
|
+
title="Copy to clipboard"
|
|
417
|
+
>
|
|
418
|
+
{copied ? (
|
|
419
|
+
<Check className="size-3 text-success" />
|
|
420
|
+
) : (
|
|
421
|
+
<Copy className="size-3 text-muted-foreground" />
|
|
422
|
+
)}
|
|
423
|
+
</button>
|
|
424
|
+
<div className="overflow-y-auto max-h-60 text-xs leading-relaxed whitespace-pre-wrap text-foreground">
|
|
425
|
+
{content}
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
)}
|
|
429
|
+
</div>
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function ToolItem({ tool }: { tool: ToolDefinition }) {
|
|
434
|
+
const [expanded, setExpanded] = useState(false);
|
|
435
|
+
|
|
436
|
+
return (
|
|
437
|
+
<div className="overflow-hidden rounded-md border border-border bg-background">
|
|
438
|
+
<button
|
|
439
|
+
className="w-full flex items-center justify-between p-2.5 hover:bg-accent/50 transition-colors"
|
|
440
|
+
onClick={() => setExpanded(!expanded)}
|
|
441
|
+
>
|
|
442
|
+
<span className="font-mono text-sm text-purple">{tool.name}</span>
|
|
443
|
+
{tool.parameters && (
|
|
444
|
+
<ChevronRight
|
|
445
|
+
className={`size-3 text-muted-foreground transition-transform ${
|
|
446
|
+
expanded ? 'rotate-90' : ''
|
|
447
|
+
}`}
|
|
448
|
+
/>
|
|
449
|
+
)}
|
|
450
|
+
</button>
|
|
451
|
+
{expanded && tool.parameters && (
|
|
452
|
+
<div className="px-2.5 pb-2.5 border-t border-border">
|
|
453
|
+
{tool.description && (
|
|
454
|
+
<p className="pt-2 mb-2 text-xs text-muted-foreground">
|
|
455
|
+
{tool.description}
|
|
456
|
+
</p>
|
|
457
|
+
)}
|
|
458
|
+
<JsonBlock data={tool.parameters} compact />
|
|
459
|
+
</div>
|
|
460
|
+
)}
|
|
461
|
+
{!expanded && tool.description && (
|
|
462
|
+
<div className="px-2.5 pb-2 -mt-1">
|
|
463
|
+
<p className="text-[11px] text-muted-foreground truncate">
|
|
464
|
+
{tool.description}
|
|
465
|
+
</p>
|
|
466
|
+
</div>
|
|
467
|
+
)}
|
|
468
|
+
</div>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function CollapsibleToolCall({
|
|
473
|
+
toolName,
|
|
474
|
+
toolCallId,
|
|
475
|
+
data,
|
|
476
|
+
}: {
|
|
477
|
+
toolName: string;
|
|
478
|
+
toolCallId?: string;
|
|
479
|
+
data: unknown;
|
|
480
|
+
}) {
|
|
481
|
+
const [expanded, setExpanded] = useState(false);
|
|
482
|
+
const parsedData = typeof data === 'string' ? safeParseJson(data) : data;
|
|
483
|
+
|
|
484
|
+
return (
|
|
485
|
+
<div className="overflow-hidden rounded-md border border-purple/30">
|
|
486
|
+
<button
|
|
487
|
+
className="flex gap-2 items-center px-3 py-2 w-full transition-colors bg-purple/10 hover:bg-purple/20"
|
|
488
|
+
onClick={() => setExpanded(!expanded)}
|
|
489
|
+
>
|
|
490
|
+
<ChevronRight
|
|
491
|
+
className={`size-3 text-purple transition-transform shrink-0 ${
|
|
492
|
+
expanded ? 'rotate-90' : ''
|
|
493
|
+
}`}
|
|
494
|
+
/>
|
|
495
|
+
<Wrench className="size-3 text-purple shrink-0" />
|
|
496
|
+
<span className="font-mono text-xs font-medium text-purple">
|
|
497
|
+
{toolName}
|
|
498
|
+
</span>
|
|
499
|
+
{!expanded &&
|
|
500
|
+
parsedData != null &&
|
|
501
|
+
typeof parsedData === 'object' &&
|
|
502
|
+
!Array.isArray(parsedData) && (
|
|
503
|
+
<span className="text-[11px] font-mono text-purple/70 truncate">
|
|
504
|
+
{formatToolParams(parsedData as Record<string, unknown>)}
|
|
505
|
+
</span>
|
|
506
|
+
)}
|
|
507
|
+
{toolCallId && (
|
|
508
|
+
<span className="text-[10px] font-mono text-muted-foreground/60 ml-auto shrink-0">
|
|
509
|
+
{toolCallId}
|
|
510
|
+
</span>
|
|
511
|
+
)}
|
|
512
|
+
</button>
|
|
513
|
+
{expanded && (
|
|
514
|
+
<div className="p-3 border-t bg-card/50 border-purple/30">
|
|
515
|
+
<JsonBlock data={parsedData} />
|
|
516
|
+
</div>
|
|
517
|
+
)}
|
|
518
|
+
</div>
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export function CollapsibleToolResult({
|
|
523
|
+
toolName,
|
|
524
|
+
toolCallId,
|
|
525
|
+
data,
|
|
526
|
+
}: {
|
|
527
|
+
toolName?: string;
|
|
528
|
+
toolCallId?: string;
|
|
529
|
+
data: unknown;
|
|
530
|
+
}) {
|
|
531
|
+
const [expanded, setExpanded] = useState(false);
|
|
532
|
+
|
|
533
|
+
return (
|
|
534
|
+
<div className="overflow-hidden rounded-md border border-success/30">
|
|
535
|
+
<button
|
|
536
|
+
className="flex gap-2 items-center px-3 py-2 w-full transition-colors bg-success/10 hover:bg-success/20"
|
|
537
|
+
onClick={() => setExpanded(!expanded)}
|
|
538
|
+
>
|
|
539
|
+
<ChevronRight
|
|
540
|
+
className={`size-3 text-success transition-transform shrink-0 ${
|
|
541
|
+
expanded ? 'rotate-90' : ''
|
|
542
|
+
}`}
|
|
543
|
+
/>
|
|
544
|
+
<span className="text-xs font-medium text-success">Result</span>
|
|
545
|
+
{toolName && (
|
|
546
|
+
<span className="text-[11px] font-mono text-muted-foreground">
|
|
547
|
+
{toolName}
|
|
548
|
+
</span>
|
|
549
|
+
)}
|
|
550
|
+
{toolCallId && (
|
|
551
|
+
<span className="text-[10px] font-mono text-muted-foreground/60 ml-auto shrink-0">
|
|
552
|
+
{toolCallId}
|
|
553
|
+
</span>
|
|
554
|
+
)}
|
|
555
|
+
</button>
|
|
556
|
+
{expanded && (
|
|
557
|
+
<div className="p-3 border-t bg-card/50 border-success/30">
|
|
558
|
+
<JsonBlock data={data} />
|
|
559
|
+
</div>
|
|
560
|
+
)}
|
|
561
|
+
</div>
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export function StepConfigBar({
|
|
566
|
+
modelId,
|
|
567
|
+
provider,
|
|
568
|
+
input,
|
|
569
|
+
providerOptions,
|
|
570
|
+
usage,
|
|
571
|
+
}: {
|
|
572
|
+
modelId?: string;
|
|
573
|
+
provider?: string | null;
|
|
574
|
+
input: ParsedInput | null;
|
|
575
|
+
providerOptions?: Record<string, unknown> | null;
|
|
576
|
+
usage?: ParsedUsage | null;
|
|
577
|
+
}) {
|
|
578
|
+
const toolCount = input?.tools?.length ?? 0;
|
|
579
|
+
|
|
580
|
+
const params: { label: string; value: string }[] = [];
|
|
581
|
+
if (input?.temperature != null)
|
|
582
|
+
params.push({ label: 'temp', value: String(input.temperature) });
|
|
583
|
+
if (input?.maxOutputTokens != null)
|
|
584
|
+
params.push({ label: 'max tokens', value: String(input.maxOutputTokens) });
|
|
585
|
+
if (input?.topP != null)
|
|
586
|
+
params.push({ label: 'topP', value: String(input.topP) });
|
|
587
|
+
if (input?.topK != null)
|
|
588
|
+
params.push({ label: 'topK', value: String(input.topK) });
|
|
589
|
+
if (input?.toolChoice != null) {
|
|
590
|
+
const choice =
|
|
591
|
+
typeof input.toolChoice === 'string'
|
|
592
|
+
? input.toolChoice
|
|
593
|
+
: input.toolChoice.type;
|
|
594
|
+
params.push({ label: 'tool choice', value: choice });
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return (
|
|
598
|
+
<div className="flex items-center gap-2 px-4 py-2 bg-muted/20 border-b border-border text-[11px] text-muted-foreground">
|
|
599
|
+
{provider && (
|
|
600
|
+
<span className="px-1.5 py-0.5 rounded bg-sidebar-primary/10 text-sidebar-primary text-[10px] font-medium">
|
|
601
|
+
{provider}
|
|
602
|
+
</span>
|
|
603
|
+
)}
|
|
604
|
+
<span className="font-mono">{modelId}</span>
|
|
605
|
+
|
|
606
|
+
{params.length > 0 && (
|
|
607
|
+
<>
|
|
608
|
+
<span className="text-muted-foreground/30">·</span>
|
|
609
|
+
{params.map((p, i) => (
|
|
610
|
+
<span key={i}>
|
|
611
|
+
{p.label}: <span className="text-foreground">{p.value}</span>
|
|
612
|
+
{i < params.length - 1 && (
|
|
613
|
+
<span className="mx-1 text-muted-foreground/30">·</span>
|
|
614
|
+
)}
|
|
615
|
+
</span>
|
|
616
|
+
))}
|
|
617
|
+
</>
|
|
618
|
+
)}
|
|
619
|
+
|
|
620
|
+
{toolCount > 0 && (
|
|
621
|
+
<>
|
|
622
|
+
<span className="text-muted-foreground/30">·</span>
|
|
623
|
+
<Drawer direction="right">
|
|
624
|
+
<DrawerTrigger asChild>
|
|
625
|
+
<button className="inline-flex gap-1 items-center transition-colors cursor-pointer hover:text-foreground">
|
|
626
|
+
<Wrench className="size-3" />
|
|
627
|
+
{toolCount} available {toolCount === 1 ? 'tool' : 'tools'}
|
|
628
|
+
</button>
|
|
629
|
+
</DrawerTrigger>
|
|
630
|
+
<DrawerContent className="h-full w-[800px] sm:max-w-[800px] overflow-hidden">
|
|
631
|
+
<DrawerHeader className="border-b border-border shrink-0">
|
|
632
|
+
<DrawerTitle>Available Tools ({toolCount})</DrawerTitle>
|
|
633
|
+
</DrawerHeader>
|
|
634
|
+
<div className="overflow-y-auto flex-1 p-4">
|
|
635
|
+
<div className="space-y-3">
|
|
636
|
+
{input?.tools?.map((tool: ToolDefinition, i: number) => (
|
|
637
|
+
<ToolItem key={i} tool={tool} />
|
|
638
|
+
))}
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
</DrawerContent>
|
|
642
|
+
</Drawer>
|
|
643
|
+
</>
|
|
644
|
+
)}
|
|
645
|
+
|
|
646
|
+
{providerOptions && Object.keys(providerOptions).length > 0 && (
|
|
647
|
+
<>
|
|
648
|
+
<span className="text-muted-foreground/30">·</span>
|
|
649
|
+
<Drawer direction="right">
|
|
650
|
+
<DrawerTrigger asChild>
|
|
651
|
+
<button className="inline-flex gap-1 items-center transition-colors cursor-pointer hover:text-foreground">
|
|
652
|
+
<Settings className="size-3" />
|
|
653
|
+
Provider options
|
|
654
|
+
</button>
|
|
655
|
+
</DrawerTrigger>
|
|
656
|
+
<DrawerContent className="h-full w-[800px] sm:max-w-[800px] overflow-hidden">
|
|
657
|
+
<DrawerHeader className="border-b border-border shrink-0">
|
|
658
|
+
<DrawerTitle>Provider Options</DrawerTitle>
|
|
659
|
+
</DrawerHeader>
|
|
660
|
+
<div className="overflow-y-auto flex-1 p-4">
|
|
661
|
+
<JsonBlock data={providerOptions} size="lg" />
|
|
662
|
+
</div>
|
|
663
|
+
</DrawerContent>
|
|
664
|
+
</Drawer>
|
|
665
|
+
</>
|
|
666
|
+
)}
|
|
667
|
+
|
|
668
|
+
{usage && (
|
|
669
|
+
<>
|
|
670
|
+
<span className="text-muted-foreground/30">·</span>
|
|
671
|
+
<Drawer direction="right">
|
|
672
|
+
<DrawerTrigger asChild>
|
|
673
|
+
<button className="inline-flex gap-1 items-center transition-colors cursor-pointer hover:text-foreground">
|
|
674
|
+
<BarChart3 className="size-3" />
|
|
675
|
+
Usage
|
|
676
|
+
</button>
|
|
677
|
+
</DrawerTrigger>
|
|
678
|
+
<DrawerContent className="h-full w-[800px] sm:max-w-[800px] overflow-hidden">
|
|
679
|
+
<DrawerHeader className="border-b border-border shrink-0">
|
|
680
|
+
<DrawerTitle>Token Usage</DrawerTitle>
|
|
681
|
+
</DrawerHeader>
|
|
682
|
+
<div className="overflow-y-auto flex-1 p-4">
|
|
683
|
+
<UsageDetails usage={usage} />
|
|
684
|
+
</div>
|
|
685
|
+
</DrawerContent>
|
|
686
|
+
</Drawer>
|
|
687
|
+
</>
|
|
688
|
+
)}
|
|
689
|
+
</div>
|
|
690
|
+
);
|
|
691
|
+
}
|