@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.
@@ -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(2)}ms`;
30
- if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;
31
- return `${(ms / 60000).toFixed(2)}m`;
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<Session[]>([]);
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 [dimension, setDimension] = useState<ComparisonDimension | null>(null);
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
- fetch("/api/sessions")
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((data) => {
77
- setSessions(data);
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
- // Check if comparison is ready
115
- const isComparisonReady = () => {
116
- if (!dimension) return false;
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
- switch (dimension) {
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 (!dimension || !isComparisonReady()) return;
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
- dimension,
148
- ...(dimension === "model" && { controlModel: agentConfig?.model }),
149
- ...(dimension === "model" && variantModel && { variantModel }),
150
- ...(dimension === "system_prompt" &&
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
- ...(dimension === "tools" &&
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 { id: configId } = await configRes.json();
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 { runId } = await runRes.json();
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="container mx-auto p-8">
205
- {/* Comparison Config Bar */}
206
- <Card className="mb-6">
207
- <CardHeader className="py-4">
208
- <CardTitle className="text-lg">Comparison Configuration</CardTitle>
209
- <CardDescription>
210
- Select what dimension to compare and configure the variant
211
- </CardDescription>
212
- </CardHeader>
213
- <CardContent className="space-y-4">
214
- {/* Dimension selector */}
215
- <div className="space-y-2">
216
- <Label>Compare by</Label>
217
- <Select
218
- value={dimension || ""}
219
- onValueChange={(v) =>
220
- setDimension(v as ComparisonDimension | null)
221
- }
222
- >
223
- <SelectTrigger className="w-[200px]">
224
- <SelectValue placeholder="Select dimension" />
225
- </SelectTrigger>
226
- <SelectContent>
227
- <SelectItem value="model">Model</SelectItem>
228
- <SelectItem value="system_prompt">System Prompt</SelectItem>
229
- <SelectItem value="tools">Tools</SelectItem>
230
- </SelectContent>
231
- </Select>
232
- </div>
233
-
234
- {/* Variant configuration */}
235
- {dimension === "model" && (
236
- <div className="space-y-2">
237
- <Label>
238
- Variant Model{" "}
239
- <span className="text-muted-foreground text-xs">
240
- (Control: {agentConfig?.model || "unknown"})
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
- {model}
258
- {model === agentConfig?.model && " (current)"}
259
- </SelectItem>
260
- ))}
261
- </SelectContent>
262
- </Select>
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
- {dimension === "system_prompt" && (
267
- <div className="space-y-2">
268
- <Label>
269
- Variant System Prompt{" "}
270
- <span className="text-muted-foreground text-xs">
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
- {dimension === "tools" && agentConfig?.tools && (
284
- <div className="space-y-2">
285
- <Label>
286
- Variant Tools{" "}
287
- <span className="text-muted-foreground text-xs">
288
- (Select tools to enable)
289
- </span>
290
- </Label>
291
- <div className="flex flex-wrap gap-2">
292
- {agentConfig.tools.map((tool) => (
293
- <Button
294
- key={tool.name}
295
- variant={
296
- variantTools.includes(tool.name) ? "default" : "outline"
297
- }
298
- size="sm"
299
- onClick={() => {
300
- if (variantTools.includes(tool.name)) {
301
- setVariantTools(
302
- variantTools.filter((t) => t !== tool.name),
303
- );
304
- } else {
305
- setVariantTools([...variantTools, tool.name]);
306
- }
307
- }}
308
- >
309
- {tool.name}
310
- </Button>
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
- {/* Status indicator */}
317
- {dimension && (
318
- <div className="text-sm">
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. Click "Compare" on a session below.
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 a different variant to enable comparison.
492
+ Configure all selected dimensions
326
493
  </span>
327
494
  )}
328
495
  </div>
329
496
  )}
330
- </CardContent>
331
- </Card>
332
-
333
- {/* Session list */}
334
- <div className="flex gap-2 mb-4">
335
- <Button variant="outline" onClick={fetchSessions} disabled={loading}>
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
- {sessions.length === 0 ? (
341
- <div className="text-muted-foreground">No sessions found</div>
342
- ) : (
343
- <div className="space-y-2">
344
- {sessions.map((session) => (
345
- <Card
346
- key={session.session_id}
347
- className="hover:bg-muted/50 transition-colors"
348
- >
349
- <CardHeader className="py-3">
350
- <div className="flex items-center justify-between">
351
- <div className="flex-1">
352
- <CardTitle className="text-base font-medium font-mono">
353
- {session.session_id}
354
- </CardTitle>
355
- <CardDescription className="flex items-center gap-4 mt-1">
356
- <span>{session.trace_count} traces</span>
357
- <span>
358
- Duration:{" "}
359
- {formatDuration(
360
- session.first_trace_time,
361
- session.last_trace_time,
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
- ? "Starting..."
551
+ ? "..."
394
552
  : "Compare"}
395
553
  </Button>
396
554
  </div>
397
555
  </div>
398
- </CardHeader>
399
- </Card>
400
- ))}
556
+ ))}
557
+ </div>
558
+ )}
401
559
  </div>
402
- )}
560
+ </div>
403
561
  </div>
404
562
  </DebuggerLayout>
405
563
  );