@ai-sdk/devtools 1.0.0-beta.10 → 1.0.0-beta.12
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-BMCS-tQq.js +181 -0
- package/dist/client/index.html +2 -2
- package/dist/index.d.ts +25 -1
- package/dist/index.js +341 -3
- package/dist/viewer/server.js +42 -5
- package/package.json +6 -3
- package/src/db.ts +32 -5
- package/src/index.ts +1 -0
- package/src/integration.ts +509 -0
- 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/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 ? 'max-h-20 overflow-hidden' : ''
|
|
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 uppercase tracking-wider">
|
|
112
|
+
Request / Response
|
|
113
|
+
</span>
|
|
114
|
+
</button>
|
|
115
|
+
|
|
116
|
+
{expanded && (
|
|
117
|
+
<div className="px-4 pb-4 grid grid-cols-2 gap-4">
|
|
118
|
+
{rawRequest && (
|
|
119
|
+
<div className="flex flex-col">
|
|
120
|
+
<div className="h-7 flex items-end mb-2">
|
|
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="h-7 flex items-end justify-between mb-2">
|
|
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="rounded-lg border border-border bg-card p-4">
|
|
186
|
+
<div className="text-xs font-medium uppercase tracking-wider text-muted-foreground mb-2">
|
|
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="rounded-lg border border-border bg-card p-4">
|
|
216
|
+
<div className="text-xs font-medium uppercase tracking-wider text-muted-foreground mb-2">
|
|
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="text-xs font-medium uppercase tracking-wider text-muted-foreground mb-2">
|
|
243
|
+
Raw Provider Usage
|
|
244
|
+
</div>
|
|
245
|
+
<JsonBlock data={usage.raw} size="sm" />
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
|
|
249
|
+
<div>
|
|
250
|
+
<div className="text-xs font-medium uppercase tracking-wider text-muted-foreground mb-2">
|
|
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="text-xs space-y-2">
|
|
287
|
+
<div>
|
|
288
|
+
<div className="font-medium mb-1">Input: {input.total}</div>
|
|
289
|
+
{input.cacheRead !== undefined && (
|
|
290
|
+
<div className="text-muted-foreground ml-2">
|
|
291
|
+
Cache read: {input.cacheRead}
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
294
|
+
{input.cacheWrite !== undefined && (
|
|
295
|
+
<div className="text-muted-foreground ml-2">
|
|
296
|
+
Cache write: {input.cacheWrite}
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
<div>
|
|
301
|
+
<div className="font-medium mb-1">Output: {output.total}</div>
|
|
302
|
+
{output.text !== undefined && (
|
|
303
|
+
<div className="text-muted-foreground ml-2">Text: {output.text}</div>
|
|
304
|
+
)}
|
|
305
|
+
{output.reasoning !== undefined && (
|
|
306
|
+
<div className="text-muted-foreground ml-2">
|
|
307
|
+
Reasoning: {output.reasoning}
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
311
|
+
{raw !== undefined && (
|
|
312
|
+
<div className="pt-1 border-t border-border mt-1">
|
|
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="rounded-md border border-amber-500/30 overflow-hidden">
|
|
330
|
+
<button
|
|
331
|
+
className="w-full flex items-center gap-2 px-3 py-2 bg-amber-500/10 hover:bg-amber-500/20 transition-colors"
|
|
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="size-3 text-amber-500 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 bg-card/50 border-t border-amber-500/30">
|
|
350
|
+
<div className="text-xs text-foreground/80 leading-relaxed whitespace-pre-wrap">
|
|
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={`rounded-md border ${borderColor} overflow-hidden`}>
|
|
389
|
+
<button
|
|
390
|
+
className={`w-full flex items-center gap-2 px-3 py-2 ${bgColor} ${hoverBgColor} transition-colors`}
|
|
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={`p-3 bg-card/50 border-t ${borderColor} group relative`}
|
|
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="text-xs text-foreground leading-relaxed whitespace-pre-wrap max-h-60 overflow-y-auto">
|
|
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="rounded-md border border-border bg-background overflow-hidden">
|
|
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="text-sm font-mono 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="text-xs text-muted-foreground mb-2 pt-2">
|
|
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="rounded-md border border-purple/30 overflow-hidden">
|
|
486
|
+
<button
|
|
487
|
+
className="w-full flex items-center gap-2 px-3 py-2 bg-purple/10 hover:bg-purple/20 transition-colors"
|
|
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="text-xs font-mono 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 bg-card/50 border-t 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="rounded-md border border-success/30 overflow-hidden">
|
|
535
|
+
<button
|
|
536
|
+
className="w-full flex items-center gap-2 px-3 py-2 bg-success/10 hover:bg-success/20 transition-colors"
|
|
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 bg-card/50 border-t 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="text-muted-foreground/30 mx-1">·</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 items-center gap-1 hover:text-foreground transition-colors cursor-pointer">
|
|
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="flex-1 overflow-y-auto 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 items-center gap-1 hover:text-foreground transition-colors cursor-pointer">
|
|
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="flex-1 overflow-y-auto 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 items-center gap-1 hover:text-foreground transition-colors cursor-pointer">
|
|
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="flex-1 overflow-y-auto p-4">
|
|
683
|
+
<UsageDetails usage={usage} />
|
|
684
|
+
</div>
|
|
685
|
+
</DrawerContent>
|
|
686
|
+
</Drawer>
|
|
687
|
+
</>
|
|
688
|
+
)}
|
|
689
|
+
</div>
|
|
690
|
+
);
|
|
691
|
+
}
|