@townco/debugger 0.1.28 → 0.1.30
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/package.json +7 -4
- package/src/App.tsx +6 -0
- package/src/analysis/analyzer.ts +272 -0
- package/src/analysis/embeddings.ts +97 -0
- package/src/analysis/schema.ts +91 -0
- package/src/analysis/types.ts +157 -0
- package/src/analysis-db.ts +238 -0
- package/src/comparison-db.test.ts +28 -5
- package/src/comparison-db.ts +57 -9
- package/src/components/AnalyzeAllButton.tsx +81 -0
- package/src/components/DebuggerHeader.tsx +12 -0
- package/src/components/SessionAnalysisButton.tsx +109 -0
- package/src/components/SessionAnalysisDialog.tsx +240 -0
- package/src/components/UnifiedTimeline.tsx +3 -3
- package/src/components/ui/dialog.tsx +120 -0
- package/src/db.ts +3 -2
- package/src/lib/metrics.ts +131 -11
- package/src/pages/ComparisonView.tsx +618 -177
- package/src/pages/FindSessions.tsx +247 -0
- package/src/pages/SessionList.tsx +76 -10
- package/src/pages/SessionView.tsx +33 -1
- package/src/pages/TownHall.tsx +345 -187
- package/src/schemas.ts +27 -8
- package/src/server.ts +423 -3
- package/src/types.ts +11 -2
package/src/pages/TownHall.tsx
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from "react";
|
|
2
2
|
import { Button } from "@/components/ui/button";
|
|
3
|
-
import {
|
|
4
|
-
Card,
|
|
5
|
-
CardContent,
|
|
6
|
-
CardDescription,
|
|
7
|
-
CardHeader,
|
|
8
|
-
CardTitle,
|
|
9
|
-
} from "@/components/ui/card";
|
|
3
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
10
4
|
import { Label } from "@/components/ui/label";
|
|
11
5
|
import {
|
|
12
6
|
Select,
|
|
@@ -21,14 +15,15 @@ import type {
|
|
|
21
15
|
AgentConfig,
|
|
22
16
|
ComparisonConfig,
|
|
23
17
|
ComparisonDimension,
|
|
18
|
+
ComparisonRun,
|
|
24
19
|
Session,
|
|
25
20
|
} from "../types";
|
|
26
21
|
|
|
27
22
|
function formatDuration(startNano: number, endNano: number): string {
|
|
28
23
|
const ms = (endNano - startNano) / 1_000_000;
|
|
29
|
-
if (ms < 1000) return `${ms.toFixed(
|
|
30
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(
|
|
31
|
-
return `${(ms / 60000).toFixed(
|
|
24
|
+
if (ms < 1000) return `${ms.toFixed(0)}ms`;
|
|
25
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
26
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
function formatRelativeTime(nanoseconds: number): string {
|
|
@@ -45,13 +40,23 @@ function formatRelativeTime(nanoseconds: number): string {
|
|
|
45
40
|
return "just now";
|
|
46
41
|
}
|
|
47
42
|
|
|
43
|
+
interface SessionWithMessage extends Session {
|
|
44
|
+
firstMessage?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
48
47
|
export function TownHall() {
|
|
49
|
-
const [sessions, setSessions] = useState<
|
|
48
|
+
const [sessions, setSessions] = useState<SessionWithMessage[]>([]);
|
|
49
|
+
const [comparisonRuns, setComparisonRuns] = useState<ComparisonRun[]>([]);
|
|
50
|
+
const [comparisonSessionIds, setComparisonSessionIds] = useState<Set<string>>(
|
|
51
|
+
new Set(),
|
|
52
|
+
);
|
|
50
53
|
const [loading, setLoading] = useState(true);
|
|
51
54
|
const [error, setError] = useState<string | null>(null);
|
|
52
55
|
|
|
53
|
-
// Comparison config state
|
|
54
|
-
const [
|
|
56
|
+
// Comparison config state - now supports multiple dimensions
|
|
57
|
+
const [selectedDimensions, setSelectedDimensions] = useState<
|
|
58
|
+
Set<ComparisonDimension>
|
|
59
|
+
>(new Set());
|
|
55
60
|
const [availableModels, setAvailableModels] = useState<string[]>([]);
|
|
56
61
|
const [agentConfig, setAgentConfig] = useState<AgentConfig | null>(null);
|
|
57
62
|
|
|
@@ -64,17 +69,53 @@ export function TownHall() {
|
|
|
64
69
|
const [runningComparison, setRunningComparison] = useState<string | null>(
|
|
65
70
|
null,
|
|
66
71
|
);
|
|
72
|
+
const [comparisonError, setComparisonError] = useState<string | null>(null);
|
|
67
73
|
|
|
68
|
-
// Fetch sessions
|
|
74
|
+
// Fetch sessions, comparison runs, and comparison session IDs
|
|
69
75
|
const fetchSessions = useCallback(() => {
|
|
70
76
|
setLoading(true);
|
|
71
|
-
|
|
72
|
-
.then((res) => {
|
|
77
|
+
Promise.all([
|
|
78
|
+
fetch("/api/sessions").then((res) => {
|
|
73
79
|
if (!res.ok) throw new Error("Failed to fetch sessions");
|
|
74
80
|
return res.json();
|
|
75
|
-
})
|
|
76
|
-
.then((
|
|
77
|
-
|
|
81
|
+
}),
|
|
82
|
+
fetch("/api/comparison-session-ids").then((res) => {
|
|
83
|
+
if (!res.ok) return { sessionIds: [] };
|
|
84
|
+
return res.json();
|
|
85
|
+
}),
|
|
86
|
+
fetch("/api/comparison-runs").then((res) => {
|
|
87
|
+
if (!res.ok) return [];
|
|
88
|
+
return res.json();
|
|
89
|
+
}),
|
|
90
|
+
])
|
|
91
|
+
.then(async ([sessionsData, comparisonData, runsData]) => {
|
|
92
|
+
const comparisonIds = new Set<string>(comparisonData.sessionIds || []);
|
|
93
|
+
setComparisonSessionIds(comparisonIds);
|
|
94
|
+
setComparisonRuns(runsData || []);
|
|
95
|
+
|
|
96
|
+
// Filter out comparison sessions, then fetch first messages
|
|
97
|
+
const filteredSessions = (sessionsData as Session[]).filter(
|
|
98
|
+
(s) => !comparisonIds.has(s.session_id),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Fetch first messages for filtered sessions in parallel
|
|
102
|
+
const sessionsWithMessages = await Promise.all(
|
|
103
|
+
filteredSessions.map(async (session) => {
|
|
104
|
+
try {
|
|
105
|
+
const res = await fetch(
|
|
106
|
+
`/api/session-first-message/${session.session_id}`,
|
|
107
|
+
);
|
|
108
|
+
if (res.ok) {
|
|
109
|
+
const data = await res.json();
|
|
110
|
+
return { ...session, firstMessage: data.message };
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// Ignore errors fetching first message
|
|
114
|
+
}
|
|
115
|
+
return session;
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
setSessions(sessionsWithMessages);
|
|
78
119
|
setLoading(false);
|
|
79
120
|
})
|
|
80
121
|
.catch((err) => {
|
|
@@ -111,11 +152,22 @@ export function TownHall() {
|
|
|
111
152
|
fetchSessions();
|
|
112
153
|
}, [fetchSessions]);
|
|
113
154
|
|
|
114
|
-
//
|
|
115
|
-
const
|
|
116
|
-
|
|
155
|
+
// Toggle a dimension on/off
|
|
156
|
+
const toggleDimension = (dim: ComparisonDimension) => {
|
|
157
|
+
setSelectedDimensions((prev) => {
|
|
158
|
+
const next = new Set(prev);
|
|
159
|
+
if (next.has(dim)) {
|
|
160
|
+
next.delete(dim);
|
|
161
|
+
} else {
|
|
162
|
+
next.add(dim);
|
|
163
|
+
}
|
|
164
|
+
return next;
|
|
165
|
+
});
|
|
166
|
+
};
|
|
117
167
|
|
|
118
|
-
|
|
168
|
+
// Check if a specific dimension is valid
|
|
169
|
+
const isDimensionValid = (dim: ComparisonDimension): boolean => {
|
|
170
|
+
switch (dim) {
|
|
119
171
|
case "model":
|
|
120
172
|
return !!variantModel && variantModel !== agentConfig?.model;
|
|
121
173
|
case "system_prompt":
|
|
@@ -135,23 +187,42 @@ export function TownHall() {
|
|
|
135
187
|
}
|
|
136
188
|
};
|
|
137
189
|
|
|
190
|
+
// Check if comparison is ready - at least one dimension selected and all selected dimensions are valid
|
|
191
|
+
const isComparisonReady = () => {
|
|
192
|
+
if (selectedDimensions.size === 0) return false;
|
|
193
|
+
|
|
194
|
+
// All selected dimensions must be valid
|
|
195
|
+
for (const dim of selectedDimensions) {
|
|
196
|
+
if (!isDimensionValid(dim)) return false;
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
};
|
|
200
|
+
|
|
138
201
|
// Start comparison
|
|
139
202
|
const startComparison = async (sessionId: string) => {
|
|
140
|
-
if (
|
|
203
|
+
if (selectedDimensions.size === 0 || !isComparisonReady()) return;
|
|
141
204
|
|
|
142
205
|
setRunningComparison(sessionId);
|
|
206
|
+
setComparisonError(null);
|
|
143
207
|
|
|
144
208
|
try {
|
|
145
|
-
// Create comparison config
|
|
209
|
+
// Create comparison config with all selected dimensions
|
|
210
|
+
const dimensions = Array.from(selectedDimensions);
|
|
146
211
|
const config: Partial<ComparisonConfig> = {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
...(
|
|
150
|
-
|
|
212
|
+
dimensions,
|
|
213
|
+
// Include model if selected
|
|
214
|
+
...(selectedDimensions.has("model") && {
|
|
215
|
+
controlModel: agentConfig?.model,
|
|
216
|
+
}),
|
|
217
|
+
...(selectedDimensions.has("model") &&
|
|
218
|
+
variantModel && { variantModel }),
|
|
219
|
+
// Include system prompt if selected
|
|
220
|
+
...(selectedDimensions.has("system_prompt") &&
|
|
151
221
|
variantSystemPrompt && {
|
|
152
222
|
variantSystemPrompt,
|
|
153
223
|
}),
|
|
154
|
-
|
|
224
|
+
// Include tools if selected
|
|
225
|
+
...(selectedDimensions.has("tools") &&
|
|
155
226
|
variantTools.length > 0 && { variantTools }),
|
|
156
227
|
};
|
|
157
228
|
|
|
@@ -161,7 +232,11 @@ export function TownHall() {
|
|
|
161
232
|
headers: { "Content-Type": "application/json" },
|
|
162
233
|
body: JSON.stringify(config),
|
|
163
234
|
});
|
|
164
|
-
const
|
|
235
|
+
const configData = await configRes.json();
|
|
236
|
+
if (!configRes.ok || !configData.id) {
|
|
237
|
+
throw new Error(configData.error || "Failed to save comparison config");
|
|
238
|
+
}
|
|
239
|
+
const configId = configData.id;
|
|
165
240
|
|
|
166
241
|
// Start comparison run
|
|
167
242
|
const runRes = await fetch("/api/run-comparison", {
|
|
@@ -169,12 +244,18 @@ export function TownHall() {
|
|
|
169
244
|
headers: { "Content-Type": "application/json" },
|
|
170
245
|
body: JSON.stringify({ sessionId, configId }),
|
|
171
246
|
});
|
|
172
|
-
const
|
|
247
|
+
const runData = await runRes.json();
|
|
248
|
+
if (!runRes.ok || !runData.runId) {
|
|
249
|
+
throw new Error(runData.error || "Failed to start comparison run");
|
|
250
|
+
}
|
|
173
251
|
|
|
174
252
|
// Navigate to comparison view
|
|
175
|
-
window.location.href = `/town-hall/compare/${runId}`;
|
|
253
|
+
window.location.href = `/town-hall/compare/${runData.runId}`;
|
|
176
254
|
} catch (err) {
|
|
177
255
|
console.error("Failed to start comparison:", err);
|
|
256
|
+
setComparisonError(
|
|
257
|
+
err instanceof Error ? err.message : "Failed to start comparison",
|
|
258
|
+
);
|
|
178
259
|
setRunningComparison(null);
|
|
179
260
|
}
|
|
180
261
|
};
|
|
@@ -201,175 +282,251 @@ export function TownHall() {
|
|
|
201
282
|
|
|
202
283
|
return (
|
|
203
284
|
<DebuggerLayout title="Town Hall" showNav>
|
|
204
|
-
<div className="
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
</span>
|
|
242
|
-
</Label>
|
|
243
|
-
<Select
|
|
244
|
-
value={variantModel || ""}
|
|
245
|
-
onValueChange={setVariantModel}
|
|
246
|
-
>
|
|
247
|
-
<SelectTrigger className="w-[300px]">
|
|
248
|
-
<SelectValue placeholder="Select variant model" />
|
|
249
|
-
</SelectTrigger>
|
|
250
|
-
<SelectContent>
|
|
251
|
-
{availableModels.map((model) => (
|
|
252
|
-
<SelectItem
|
|
253
|
-
key={model}
|
|
254
|
-
value={model}
|
|
255
|
-
disabled={model === agentConfig?.model}
|
|
285
|
+
<div className="h-[calc(100vh-4rem)] flex flex-col p-8 overflow-hidden">
|
|
286
|
+
<div className="flex gap-2 mb-4 shrink-0">
|
|
287
|
+
<Button variant="outline" onClick={fetchSessions} disabled={loading}>
|
|
288
|
+
Refresh
|
|
289
|
+
</Button>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* Two-column layout for comparisons and sessions */}
|
|
293
|
+
<div className="grid grid-cols-2 gap-6 flex-1 min-h-0 overflow-hidden">
|
|
294
|
+
{/* Recent Comparisons */}
|
|
295
|
+
<div className="flex flex-col min-h-0">
|
|
296
|
+
<h3 className="text-sm font-medium mb-2 shrink-0">
|
|
297
|
+
Recent Comparisons ({comparisonRuns.length})
|
|
298
|
+
</h3>
|
|
299
|
+
{comparisonRuns.length === 0 ? (
|
|
300
|
+
<div className="text-muted-foreground text-sm">
|
|
301
|
+
No comparisons yet
|
|
302
|
+
</div>
|
|
303
|
+
) : (
|
|
304
|
+
<div className="space-y-1 overflow-y-auto flex-1">
|
|
305
|
+
{comparisonRuns.map((run) => (
|
|
306
|
+
<a
|
|
307
|
+
key={run.id}
|
|
308
|
+
href={`/town-hall/compare/${run.id}`}
|
|
309
|
+
className="block"
|
|
310
|
+
>
|
|
311
|
+
<div className="flex items-center gap-3 px-3 py-2 rounded-md hover:bg-muted/50 transition-colors border border-l-2 border-l-orange-500">
|
|
312
|
+
<span
|
|
313
|
+
className={`text-[10px] px-1.5 py-0.5 rounded shrink-0 ${
|
|
314
|
+
run.status === "completed"
|
|
315
|
+
? "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300"
|
|
316
|
+
: run.status === "running"
|
|
317
|
+
? "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300"
|
|
318
|
+
: run.status === "failed"
|
|
319
|
+
? "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300"
|
|
320
|
+
: "bg-gray-100 text-gray-700 dark:bg-gray-900 dark:text-gray-300"
|
|
321
|
+
}`}
|
|
256
322
|
>
|
|
257
|
-
{
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
323
|
+
{run.status}
|
|
324
|
+
</span>
|
|
325
|
+
<span className="text-sm truncate flex-1">
|
|
326
|
+
{run.firstUserMessage}
|
|
327
|
+
</span>
|
|
328
|
+
<span className="text-xs text-muted-foreground shrink-0">
|
|
329
|
+
{formatRelativeTime(
|
|
330
|
+
new Date(run.startedAt).getTime() * 1_000_000,
|
|
331
|
+
)}
|
|
332
|
+
</span>
|
|
333
|
+
</div>
|
|
334
|
+
</a>
|
|
335
|
+
))}
|
|
263
336
|
</div>
|
|
264
337
|
)}
|
|
338
|
+
</div>
|
|
265
339
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
(Edit to create variant)
|
|
272
|
-
</span>
|
|
273
|
-
</Label>
|
|
274
|
-
<Textarea
|
|
275
|
-
value={variantSystemPrompt}
|
|
276
|
-
onChange={(e) => setVariantSystemPrompt(e.target.value)}
|
|
277
|
-
className="min-h-[150px] font-mono text-sm"
|
|
278
|
-
placeholder="Enter variant system prompt..."
|
|
279
|
-
/>
|
|
280
|
-
</div>
|
|
281
|
-
)}
|
|
340
|
+
{/* Sessions column with config and list */}
|
|
341
|
+
<div className="flex flex-col min-h-0 overflow-hidden">
|
|
342
|
+
<h3 className="text-sm font-medium mb-2 shrink-0">
|
|
343
|
+
Sessions ({sessions.length})
|
|
344
|
+
</h3>
|
|
282
345
|
|
|
283
|
-
{
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
346
|
+
{/* Comparison Config */}
|
|
347
|
+
<Card className="mb-4 shrink-0">
|
|
348
|
+
<CardContent className="py-3 space-y-3">
|
|
349
|
+
{/* Dimension checkboxes */}
|
|
350
|
+
<div className="space-y-2">
|
|
351
|
+
<Label className="text-xs text-muted-foreground">
|
|
352
|
+
Compare by (select one or more)
|
|
353
|
+
</Label>
|
|
354
|
+
<div className="flex gap-4">
|
|
355
|
+
<label className="flex items-center gap-2 cursor-pointer">
|
|
356
|
+
<input
|
|
357
|
+
type="checkbox"
|
|
358
|
+
checked={selectedDimensions.has("model")}
|
|
359
|
+
onChange={() => toggleDimension("model")}
|
|
360
|
+
className="h-4 w-4 rounded border-gray-300 accent-primary cursor-pointer"
|
|
361
|
+
/>
|
|
362
|
+
<span className="text-sm">Model</span>
|
|
363
|
+
</label>
|
|
364
|
+
<label className="flex items-center gap-2 cursor-pointer">
|
|
365
|
+
<input
|
|
366
|
+
type="checkbox"
|
|
367
|
+
checked={selectedDimensions.has("system_prompt")}
|
|
368
|
+
onChange={() => toggleDimension("system_prompt")}
|
|
369
|
+
className="h-4 w-4 rounded border-gray-300 accent-primary cursor-pointer"
|
|
370
|
+
/>
|
|
371
|
+
<span className="text-sm">System Prompt</span>
|
|
372
|
+
</label>
|
|
373
|
+
<label className="flex items-center gap-2 cursor-pointer">
|
|
374
|
+
<input
|
|
375
|
+
type="checkbox"
|
|
376
|
+
checked={selectedDimensions.has("tools")}
|
|
377
|
+
onChange={() => toggleDimension("tools")}
|
|
378
|
+
className="h-4 w-4 rounded border-gray-300 accent-primary cursor-pointer"
|
|
379
|
+
/>
|
|
380
|
+
<span className="text-sm">Tools</span>
|
|
381
|
+
</label>
|
|
382
|
+
</div>
|
|
312
383
|
</div>
|
|
313
|
-
</div>
|
|
314
|
-
)}
|
|
315
384
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
385
|
+
{/* Model configuration */}
|
|
386
|
+
{selectedDimensions.has("model") && (
|
|
387
|
+
<div className="space-y-1">
|
|
388
|
+
<Label className="text-xs text-muted-foreground">
|
|
389
|
+
Variant Model
|
|
390
|
+
</Label>
|
|
391
|
+
<Select
|
|
392
|
+
value={variantModel || ""}
|
|
393
|
+
onValueChange={setVariantModel}
|
|
394
|
+
>
|
|
395
|
+
<SelectTrigger className="w-full">
|
|
396
|
+
<SelectValue placeholder="Select variant model" />
|
|
397
|
+
</SelectTrigger>
|
|
398
|
+
<SelectContent>
|
|
399
|
+
{availableModels.map((model) => (
|
|
400
|
+
<SelectItem
|
|
401
|
+
key={model}
|
|
402
|
+
value={model}
|
|
403
|
+
disabled={model === agentConfig?.model}
|
|
404
|
+
>
|
|
405
|
+
{model}
|
|
406
|
+
{model === agentConfig?.model && " (current)"}
|
|
407
|
+
</SelectItem>
|
|
408
|
+
))}
|
|
409
|
+
</SelectContent>
|
|
410
|
+
</Select>
|
|
411
|
+
{selectedDimensions.has("model") &&
|
|
412
|
+
!isDimensionValid("model") && (
|
|
413
|
+
<p className="text-xs text-yellow-600 dark:text-yellow-400">
|
|
414
|
+
Select a different model than current
|
|
415
|
+
</p>
|
|
416
|
+
)}
|
|
417
|
+
</div>
|
|
418
|
+
)}
|
|
419
|
+
|
|
420
|
+
{/* System prompt configuration */}
|
|
421
|
+
{selectedDimensions.has("system_prompt") && (
|
|
422
|
+
<div className="space-y-1">
|
|
423
|
+
<Label className="text-xs text-muted-foreground">
|
|
424
|
+
Variant System Prompt
|
|
425
|
+
</Label>
|
|
426
|
+
<Textarea
|
|
427
|
+
value={variantSystemPrompt}
|
|
428
|
+
onChange={(e) => setVariantSystemPrompt(e.target.value)}
|
|
429
|
+
className="min-h-[80px] font-mono text-xs"
|
|
430
|
+
placeholder="Enter variant system prompt..."
|
|
431
|
+
/>
|
|
432
|
+
{selectedDimensions.has("system_prompt") &&
|
|
433
|
+
!isDimensionValid("system_prompt") && (
|
|
434
|
+
<p className="text-xs text-yellow-600 dark:text-yellow-400">
|
|
435
|
+
Modify the system prompt
|
|
436
|
+
</p>
|
|
437
|
+
)}
|
|
438
|
+
</div>
|
|
439
|
+
)}
|
|
440
|
+
|
|
441
|
+
{/* Tools configuration */}
|
|
442
|
+
{selectedDimensions.has("tools") && agentConfig?.tools && (
|
|
443
|
+
<div className="space-y-1">
|
|
444
|
+
<Label className="text-xs text-muted-foreground">
|
|
445
|
+
Variant Tools (click to toggle)
|
|
446
|
+
</Label>
|
|
447
|
+
<div className="flex flex-wrap gap-1">
|
|
448
|
+
{agentConfig.tools.map((tool) => (
|
|
449
|
+
<Button
|
|
450
|
+
key={tool.name}
|
|
451
|
+
variant={
|
|
452
|
+
variantTools.includes(tool.name)
|
|
453
|
+
? "default"
|
|
454
|
+
: "outline"
|
|
455
|
+
}
|
|
456
|
+
size="sm"
|
|
457
|
+
className="h-6 text-xs"
|
|
458
|
+
onClick={() => {
|
|
459
|
+
if (variantTools.includes(tool.name)) {
|
|
460
|
+
setVariantTools(
|
|
461
|
+
variantTools.filter((t) => t !== tool.name),
|
|
462
|
+
);
|
|
463
|
+
} else {
|
|
464
|
+
setVariantTools([...variantTools, tool.name]);
|
|
465
|
+
}
|
|
466
|
+
}}
|
|
467
|
+
>
|
|
468
|
+
{tool.name}
|
|
469
|
+
</Button>
|
|
470
|
+
))}
|
|
471
|
+
</div>
|
|
472
|
+
{selectedDimensions.has("tools") &&
|
|
473
|
+
!isDimensionValid("tools") && (
|
|
474
|
+
<p className="text-xs text-yellow-600 dark:text-yellow-400">
|
|
475
|
+
Change tool selection from current
|
|
476
|
+
</p>
|
|
477
|
+
)}
|
|
478
|
+
</div>
|
|
479
|
+
)}
|
|
480
|
+
</CardContent>
|
|
481
|
+
</Card>
|
|
482
|
+
{/* Status indicator - inline below card */}
|
|
483
|
+
{selectedDimensions.size > 0 && (
|
|
484
|
+
<div className="text-xs mb-2 -mt-2">
|
|
319
485
|
{isComparisonReady() ? (
|
|
320
486
|
<span className="text-green-600 dark:text-green-400">
|
|
321
|
-
Ready to compare.
|
|
487
|
+
✓ Ready to compare ({selectedDimensions.size} dimension
|
|
488
|
+
{selectedDimensions.size > 1 ? "s" : ""})
|
|
322
489
|
</span>
|
|
323
490
|
) : (
|
|
324
491
|
<span className="text-yellow-600 dark:text-yellow-400">
|
|
325
|
-
Configure
|
|
492
|
+
Configure all selected dimensions
|
|
326
493
|
</span>
|
|
327
494
|
)}
|
|
328
495
|
</div>
|
|
329
496
|
)}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
Refresh
|
|
337
|
-
</Button>
|
|
338
|
-
</div>
|
|
497
|
+
{/* Error display */}
|
|
498
|
+
{comparisonError && (
|
|
499
|
+
<div className="text-xs mb-2 p-2 bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400 rounded">
|
|
500
|
+
Error: {comparisonError}
|
|
501
|
+
</div>
|
|
502
|
+
)}
|
|
339
503
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
)}
|
|
363
|
-
</span>
|
|
364
|
-
<span>
|
|
365
|
-
{formatRelativeTime(session.last_trace_time)}
|
|
366
|
-
</span>
|
|
367
|
-
</CardDescription>
|
|
368
|
-
</div>
|
|
369
|
-
<div className="flex items-center gap-2">
|
|
504
|
+
{/* Sessions list */}
|
|
505
|
+
{sessions.length === 0 ? (
|
|
506
|
+
<div className="text-muted-foreground text-sm">
|
|
507
|
+
No sessions found
|
|
508
|
+
</div>
|
|
509
|
+
) : (
|
|
510
|
+
<div className="space-y-1 overflow-y-auto flex-1">
|
|
511
|
+
{sessions.map((session) => (
|
|
512
|
+
<div
|
|
513
|
+
key={session.session_id}
|
|
514
|
+
className="flex items-center gap-3 px-3 py-2 rounded-md hover:bg-muted/50 transition-colors border"
|
|
515
|
+
>
|
|
516
|
+
<code className="text-xs text-muted-foreground shrink-0">
|
|
517
|
+
{session.session_id.slice(0, 8)}
|
|
518
|
+
</code>
|
|
519
|
+
<span className="text-sm truncate flex-1">
|
|
520
|
+
{session.firstMessage || "No message"}
|
|
521
|
+
</span>
|
|
522
|
+
<span className="text-xs text-muted-foreground shrink-0">
|
|
523
|
+
{formatRelativeTime(session.last_trace_time)}
|
|
524
|
+
</span>
|
|
525
|
+
<div className="flex items-center gap-1 shrink-0">
|
|
370
526
|
<Button
|
|
371
527
|
variant="outline"
|
|
372
528
|
size="sm"
|
|
529
|
+
className="h-7 px-2 text-xs"
|
|
373
530
|
onClick={() =>
|
|
374
531
|
(window.location.href = `/sessions/${session.session_id}`)
|
|
375
532
|
}
|
|
@@ -378,6 +535,7 @@ export function TownHall() {
|
|
|
378
535
|
</Button>
|
|
379
536
|
<Button
|
|
380
537
|
size="sm"
|
|
538
|
+
className="h-7 px-2 text-xs"
|
|
381
539
|
disabled={
|
|
382
540
|
!isComparisonReady() ||
|
|
383
541
|
runningComparison === session.session_id
|
|
@@ -390,16 +548,16 @@ export function TownHall() {
|
|
|
390
548
|
}
|
|
391
549
|
>
|
|
392
550
|
{runningComparison === session.session_id
|
|
393
|
-
? "
|
|
551
|
+
? "..."
|
|
394
552
|
: "Compare"}
|
|
395
553
|
</Button>
|
|
396
554
|
</div>
|
|
397
555
|
</div>
|
|
398
|
-
|
|
399
|
-
</
|
|
400
|
-
)
|
|
556
|
+
))}
|
|
557
|
+
</div>
|
|
558
|
+
)}
|
|
401
559
|
</div>
|
|
402
|
-
|
|
560
|
+
</div>
|
|
403
561
|
</div>
|
|
404
562
|
</DebuggerLayout>
|
|
405
563
|
);
|