@townco/debugger 0.1.5 → 0.1.7
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 +6 -3
- package/src/App.tsx +12 -3
- package/src/components/DebuggerHeader.tsx +85 -0
- package/src/components/DebuggerLayout.tsx +30 -0
- package/src/components/LogList.tsx +2 -2
- package/src/components/MessageBubble.tsx +28 -0
- package/src/components/SessionTraceList.tsx +38 -33
- package/src/components/SpanDetailsPanel.tsx +371 -0
- package/src/components/SpanIcon.tsx +42 -0
- package/src/components/SpanTimeline.tsx +450 -0
- package/src/components/SpanTree.tsx +28 -8
- package/src/components/TraceDetailContent.tsx +97 -68
- package/src/components/TurnHeader.tsx +50 -0
- package/src/components/TurnMetadataPanel.tsx +63 -0
- package/src/components/ui/tabs.tsx +50 -0
- package/src/db.ts +33 -10
- package/src/index.ts +7 -1
- package/src/lib/spanTypeDetector.ts +35 -0
- package/src/lib/timelineCalculator.ts +32 -0
- package/src/lib/turnExtractor.ts +132 -0
- package/src/pages/SessionList.tsx +142 -0
- package/src/pages/SessionView.tsx +8 -19
- package/src/pages/TraceDetail.tsx +5 -8
- package/src/pages/TraceList.tsx +66 -61
- package/src/server.ts +53 -1
- package/src/types.ts +23 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
} from "@/components/ui/card";
|
|
10
|
+
import { DebuggerLayout } from "../components/DebuggerLayout";
|
|
11
|
+
import type { Session } from "../types";
|
|
12
|
+
|
|
13
|
+
function formatDuration(startNano: number, endNano: number): string {
|
|
14
|
+
const ms = (endNano - startNano) / 1_000_000;
|
|
15
|
+
if (ms < 1000) return `${ms.toFixed(2)}ms`;
|
|
16
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;
|
|
17
|
+
return `${(ms / 60000).toFixed(2)}m`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatTimestamp(nanoseconds: number): string {
|
|
21
|
+
return new Date(nanoseconds / 1_000_000).toLocaleString();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function formatRelativeTime(nanoseconds: number): string {
|
|
25
|
+
const ms = Date.now() - nanoseconds / 1_000_000;
|
|
26
|
+
const seconds = Math.floor(ms / 1000);
|
|
27
|
+
const minutes = Math.floor(seconds / 60);
|
|
28
|
+
const hours = Math.floor(minutes / 60);
|
|
29
|
+
const days = Math.floor(hours / 24);
|
|
30
|
+
|
|
31
|
+
if (days > 0) return `${days}d ago`;
|
|
32
|
+
if (hours > 0) return `${hours}h ago`;
|
|
33
|
+
if (minutes > 0) return `${minutes}m ago`;
|
|
34
|
+
if (seconds > 5) return `${seconds}s ago`;
|
|
35
|
+
return "just now";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function SessionList() {
|
|
39
|
+
const [sessions, setSessions] = useState<Session[]>([]);
|
|
40
|
+
const [loading, setLoading] = useState(true);
|
|
41
|
+
const [error, setError] = useState<string | null>(null);
|
|
42
|
+
|
|
43
|
+
// Fetch sessions function
|
|
44
|
+
const fetchSessions = useCallback(() => {
|
|
45
|
+
setLoading(true);
|
|
46
|
+
fetch("/api/sessions")
|
|
47
|
+
.then((res) => {
|
|
48
|
+
if (!res.ok) throw new Error("Failed to fetch sessions");
|
|
49
|
+
return res.json();
|
|
50
|
+
})
|
|
51
|
+
.then((data) => {
|
|
52
|
+
setSessions(data);
|
|
53
|
+
setLoading(false);
|
|
54
|
+
})
|
|
55
|
+
.catch((err) => {
|
|
56
|
+
setError(err.message);
|
|
57
|
+
setLoading(false);
|
|
58
|
+
});
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
// Fetch sessions on mount
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
fetchSessions();
|
|
64
|
+
}, [fetchSessions]);
|
|
65
|
+
|
|
66
|
+
if (loading) {
|
|
67
|
+
return (
|
|
68
|
+
<DebuggerLayout title="Sessions" showNav>
|
|
69
|
+
<div className="container mx-auto p-8">
|
|
70
|
+
<div className="text-muted-foreground">Loading sessions...</div>
|
|
71
|
+
</div>
|
|
72
|
+
</DebuggerLayout>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (error) {
|
|
77
|
+
return (
|
|
78
|
+
<DebuggerLayout title="Sessions" showNav>
|
|
79
|
+
<div className="container mx-auto p-8">
|
|
80
|
+
<div className="text-red-500">Error: {error}</div>
|
|
81
|
+
</div>
|
|
82
|
+
</DebuggerLayout>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<DebuggerLayout title="Sessions" showNav>
|
|
88
|
+
<div className="container mx-auto p-8">
|
|
89
|
+
<div className="flex gap-2 mb-4">
|
|
90
|
+
<Button variant="outline" onClick={fetchSessions} disabled={loading}>
|
|
91
|
+
Refresh
|
|
92
|
+
</Button>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{sessions.length === 0 ? (
|
|
96
|
+
<div className="text-muted-foreground">No sessions found</div>
|
|
97
|
+
) : (
|
|
98
|
+
<div className="space-y-2">
|
|
99
|
+
{sessions.map((session) => (
|
|
100
|
+
<a
|
|
101
|
+
key={session.session_id}
|
|
102
|
+
href={`/sessions/${session.session_id}`}
|
|
103
|
+
className="block"
|
|
104
|
+
>
|
|
105
|
+
<Card className="hover:bg-muted/50 transition-colors cursor-pointer">
|
|
106
|
+
<CardHeader className="py-3">
|
|
107
|
+
<div className="flex items-center justify-between">
|
|
108
|
+
<CardTitle className="text-base font-medium font-mono">
|
|
109
|
+
{session.session_id}
|
|
110
|
+
</CardTitle>
|
|
111
|
+
<span className="text-sm text-muted-foreground">
|
|
112
|
+
{formatRelativeTime(session.last_trace_time)}
|
|
113
|
+
</span>
|
|
114
|
+
</div>
|
|
115
|
+
<CardDescription className="flex items-center gap-4">
|
|
116
|
+
<span>{session.trace_count} traces</span>
|
|
117
|
+
<span>
|
|
118
|
+
Duration:{" "}
|
|
119
|
+
{formatDuration(
|
|
120
|
+
session.first_trace_time,
|
|
121
|
+
session.last_trace_time,
|
|
122
|
+
)}
|
|
123
|
+
</span>
|
|
124
|
+
</CardDescription>
|
|
125
|
+
</CardHeader>
|
|
126
|
+
<CardContent className="py-2 pt-0">
|
|
127
|
+
<div className="text-xs text-muted-foreground">
|
|
128
|
+
Started: {formatTimestamp(session.first_trace_time)}
|
|
129
|
+
</div>
|
|
130
|
+
<div className="text-xs text-muted-foreground">
|
|
131
|
+
Last activity: {formatTimestamp(session.last_trace_time)}
|
|
132
|
+
</div>
|
|
133
|
+
</CardContent>
|
|
134
|
+
</Card>
|
|
135
|
+
</a>
|
|
136
|
+
))}
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
</DebuggerLayout>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { useCallback, useState } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { DebuggerLayout } from "../components/DebuggerLayout";
|
|
3
3
|
import { SessionTraceList } from "../components/SessionTraceList";
|
|
4
4
|
import { TraceDetailContent } from "../components/TraceDetailContent";
|
|
5
|
-
import type { Trace } from "../types";
|
|
6
5
|
|
|
7
6
|
function getInitialTraceId(): string | null {
|
|
8
7
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -31,9 +30,10 @@ export function SessionView({ sessionId }: SessionViewProps) {
|
|
|
31
30
|
};
|
|
32
31
|
|
|
33
32
|
const handleTracesLoaded = useCallback(
|
|
34
|
-
(traces:
|
|
33
|
+
(traces: { trace_id: string }[]) => {
|
|
35
34
|
// Auto-select most recent trace if none selected yet
|
|
36
|
-
|
|
35
|
+
// Most recent is now last in array due to ASC ordering
|
|
36
|
+
const mostRecent = traces[traces.length - 1];
|
|
37
37
|
if (!autoSelectDone && mostRecent) {
|
|
38
38
|
setSelectedTraceId(mostRecent.trace_id);
|
|
39
39
|
setAutoSelectDone(true);
|
|
@@ -48,22 +48,11 @@ export function SessionView({ sessionId }: SessionViewProps) {
|
|
|
48
48
|
);
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
|
-
<
|
|
52
|
-
{/* Header */}
|
|
53
|
-
<div className="border-b p-4 shrink-0">
|
|
54
|
-
<Button variant="outline" asChild>
|
|
55
|
-
<a href="/">Back to Traces</a>
|
|
56
|
-
</Button>
|
|
57
|
-
<h1 className="text-xl font-bold mt-2">Session</h1>
|
|
58
|
-
<div className="text-sm text-muted-foreground font-mono truncate">
|
|
59
|
-
{sessionId}
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
|
|
51
|
+
<DebuggerLayout title={`Session: ${sessionId}`} showBackButton backHref="/">
|
|
63
52
|
{/* Split pane container */}
|
|
64
|
-
<div className="flex flex-1 overflow-hidden">
|
|
53
|
+
<div className="flex flex-1 overflow-hidden h-full">
|
|
65
54
|
{/* Left pane - trace list */}
|
|
66
|
-
<div className="w-
|
|
55
|
+
<div className="w-[480px] border-r overflow-y-auto shrink-0">
|
|
67
56
|
<SessionTraceList
|
|
68
57
|
sessionId={sessionId}
|
|
69
58
|
selectedTraceId={selectedTraceId}
|
|
@@ -83,6 +72,6 @@ export function SessionView({ sessionId }: SessionViewProps) {
|
|
|
83
72
|
)}
|
|
84
73
|
</div>
|
|
85
74
|
</div>
|
|
86
|
-
</
|
|
75
|
+
</DebuggerLayout>
|
|
87
76
|
);
|
|
88
77
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DebuggerLayout } from "../components/DebuggerLayout";
|
|
2
2
|
import { TraceDetailContent } from "../components/TraceDetailContent";
|
|
3
3
|
|
|
4
4
|
interface TraceDetailProps {
|
|
@@ -7,13 +7,10 @@ interface TraceDetailProps {
|
|
|
7
7
|
|
|
8
8
|
export function TraceDetail({ traceId }: TraceDetailProps) {
|
|
9
9
|
return (
|
|
10
|
-
<
|
|
11
|
-
<div className="
|
|
12
|
-
<
|
|
13
|
-
<a href="/">Back to Traces</a>
|
|
14
|
-
</Button>
|
|
10
|
+
<DebuggerLayout title="Trace Detail" showBackButton backHref="/traces">
|
|
11
|
+
<div className="container mx-auto">
|
|
12
|
+
<TraceDetailContent traceId={traceId} />
|
|
15
13
|
</div>
|
|
16
|
-
|
|
17
|
-
</div>
|
|
14
|
+
</DebuggerLayout>
|
|
18
15
|
);
|
|
19
16
|
}
|
package/src/pages/TraceList.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
CardTitle,
|
|
9
9
|
} from "@/components/ui/card";
|
|
10
10
|
import { Input } from "@/components/ui/input";
|
|
11
|
+
import { DebuggerLayout } from "../components/DebuggerLayout";
|
|
11
12
|
import type { Trace } from "../types";
|
|
12
13
|
|
|
13
14
|
function formatDuration(startNano: number, endNano: number): string {
|
|
@@ -77,77 +78,81 @@ export function TraceList() {
|
|
|
77
78
|
|
|
78
79
|
if (loading) {
|
|
79
80
|
return (
|
|
80
|
-
<
|
|
81
|
-
<div className="
|
|
82
|
-
|
|
81
|
+
<DebuggerLayout title="Traces" showNav>
|
|
82
|
+
<div className="container mx-auto p-8">
|
|
83
|
+
<div className="text-muted-foreground">Loading traces...</div>
|
|
84
|
+
</div>
|
|
85
|
+
</DebuggerLayout>
|
|
83
86
|
);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
if (error) {
|
|
87
90
|
return (
|
|
88
|
-
<
|
|
89
|
-
<div className="
|
|
90
|
-
|
|
91
|
+
<DebuggerLayout title="Traces" showNav>
|
|
92
|
+
<div className="container mx-auto p-8">
|
|
93
|
+
<div className="text-red-500">Error: {error}</div>
|
|
94
|
+
</div>
|
|
95
|
+
</DebuggerLayout>
|
|
91
96
|
);
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
return (
|
|
95
|
-
<
|
|
96
|
-
<
|
|
100
|
+
<DebuggerLayout title="Traces" showNav>
|
|
101
|
+
<div className="container mx-auto p-8">
|
|
102
|
+
<div className="flex gap-2 mb-4">
|
|
103
|
+
<Input
|
|
104
|
+
placeholder="Filter by session ID..."
|
|
105
|
+
value={sessionId}
|
|
106
|
+
onChange={(e) => setSessionId(e.target.value)}
|
|
107
|
+
className="max-w-sm"
|
|
108
|
+
/>
|
|
109
|
+
<Button variant="outline" onClick={fetchTraces} disabled={loading}>
|
|
110
|
+
Refresh
|
|
111
|
+
</Button>
|
|
112
|
+
</div>
|
|
97
113
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
{traces.length === 0 ? (
|
|
115
|
+
<div className="text-muted-foreground">No traces found</div>
|
|
116
|
+
) : (
|
|
117
|
+
<div className="space-y-2">
|
|
118
|
+
{traces.map((trace) => (
|
|
119
|
+
<a
|
|
120
|
+
key={trace.trace_id}
|
|
121
|
+
href={`/trace/${trace.trace_id}`}
|
|
122
|
+
className="block"
|
|
123
|
+
>
|
|
124
|
+
<Card className="hover:bg-muted/50 transition-colors cursor-pointer">
|
|
125
|
+
<CardHeader className="py-3">
|
|
126
|
+
<div className="flex items-center justify-between">
|
|
127
|
+
<CardTitle className="text-base font-medium">
|
|
128
|
+
{trace.first_span_name || "Unknown"}
|
|
129
|
+
</CardTitle>
|
|
130
|
+
<span className="text-sm text-muted-foreground">
|
|
131
|
+
{formatDuration(
|
|
132
|
+
trace.start_time_unix_nano,
|
|
133
|
+
trace.end_time_unix_nano,
|
|
134
|
+
)}
|
|
135
|
+
</span>
|
|
136
|
+
</div>
|
|
137
|
+
<CardDescription className="flex items-center gap-4">
|
|
138
|
+
<span>{trace.service_name || "Unknown service"}</span>
|
|
139
|
+
<span>{trace.span_count} spans</span>
|
|
140
|
+
</CardDescription>
|
|
141
|
+
</CardHeader>
|
|
142
|
+
<CardContent className="py-2 pt-0">
|
|
143
|
+
<div className="text-xs text-muted-foreground font-mono">
|
|
144
|
+
{trace.trace_id}
|
|
145
|
+
</div>
|
|
146
|
+
<div className="text-xs text-muted-foreground">
|
|
147
|
+
{formatTimestamp(trace.start_time_unix_nano)}
|
|
148
|
+
</div>
|
|
149
|
+
</CardContent>
|
|
150
|
+
</Card>
|
|
151
|
+
</a>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
108
155
|
</div>
|
|
109
|
-
|
|
110
|
-
{traces.length === 0 ? (
|
|
111
|
-
<div className="text-muted-foreground">No traces found</div>
|
|
112
|
-
) : (
|
|
113
|
-
<div className="space-y-2">
|
|
114
|
-
{traces.map((trace) => (
|
|
115
|
-
<a
|
|
116
|
-
key={trace.trace_id}
|
|
117
|
-
href={`/trace/${trace.trace_id}`}
|
|
118
|
-
className="block"
|
|
119
|
-
>
|
|
120
|
-
<Card className="hover:bg-muted/50 transition-colors cursor-pointer">
|
|
121
|
-
<CardHeader className="py-3">
|
|
122
|
-
<div className="flex items-center justify-between">
|
|
123
|
-
<CardTitle className="text-base font-medium">
|
|
124
|
-
{trace.first_span_name || "Unknown"}
|
|
125
|
-
</CardTitle>
|
|
126
|
-
<span className="text-sm text-muted-foreground">
|
|
127
|
-
{formatDuration(
|
|
128
|
-
trace.start_time_unix_nano,
|
|
129
|
-
trace.end_time_unix_nano,
|
|
130
|
-
)}
|
|
131
|
-
</span>
|
|
132
|
-
</div>
|
|
133
|
-
<CardDescription className="flex items-center gap-4">
|
|
134
|
-
<span>{trace.service_name || "Unknown service"}</span>
|
|
135
|
-
<span>{trace.span_count} spans</span>
|
|
136
|
-
</CardDescription>
|
|
137
|
-
</CardHeader>
|
|
138
|
-
<CardContent className="py-2 pt-0">
|
|
139
|
-
<div className="text-xs text-muted-foreground font-mono">
|
|
140
|
-
{trace.trace_id}
|
|
141
|
-
</div>
|
|
142
|
-
<div className="text-xs text-muted-foreground">
|
|
143
|
-
{formatTimestamp(trace.start_time_unix_nano)}
|
|
144
|
-
</div>
|
|
145
|
-
</CardContent>
|
|
146
|
-
</Card>
|
|
147
|
-
</a>
|
|
148
|
-
))}
|
|
149
|
-
</div>
|
|
150
|
-
)}
|
|
151
|
-
</div>
|
|
156
|
+
</DebuggerLayout>
|
|
152
157
|
);
|
|
153
158
|
}
|
package/src/server.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { createOtlpServer } from "@townco/otlp-server/http";
|
|
|
2
2
|
import { serve } from "bun";
|
|
3
3
|
import { DebuggerDb } from "./db";
|
|
4
4
|
import index from "./index.html";
|
|
5
|
+
import { extractTurnMessages } from "./lib/turnExtractor";
|
|
6
|
+
import type { ConversationTrace } from "./types";
|
|
5
7
|
|
|
6
8
|
export const DEFAULT_DEBUGGER_PORT = 4000;
|
|
7
9
|
export const DEFAULT_OTLP_PORT = 4318;
|
|
@@ -10,6 +12,7 @@ export interface DebuggerServerOptions {
|
|
|
10
12
|
port?: number;
|
|
11
13
|
otlpPort?: number;
|
|
12
14
|
dbPath: string;
|
|
15
|
+
agentName?: string;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
export interface DebuggerServerResult {
|
|
@@ -25,6 +28,7 @@ export function startDebuggerServer(
|
|
|
25
28
|
port = DEFAULT_DEBUGGER_PORT,
|
|
26
29
|
otlpPort = DEFAULT_OTLP_PORT,
|
|
27
30
|
dbPath,
|
|
31
|
+
agentName = "Agent",
|
|
28
32
|
} = options;
|
|
29
33
|
|
|
30
34
|
// Start OTLP server (initializes database internally)
|
|
@@ -41,6 +45,22 @@ export function startDebuggerServer(
|
|
|
41
45
|
const server = serve({
|
|
42
46
|
port,
|
|
43
47
|
routes: {
|
|
48
|
+
"/api/config": {
|
|
49
|
+
GET() {
|
|
50
|
+
return Response.json({ agentName });
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
"/api/sessions": {
|
|
55
|
+
GET(req) {
|
|
56
|
+
const url = new URL(req.url);
|
|
57
|
+
const limit = Number.parseInt(url.searchParams.get("limit") || "50");
|
|
58
|
+
const offset = Number.parseInt(url.searchParams.get("offset") || "0");
|
|
59
|
+
const sessions = db.listSessions(limit, offset);
|
|
60
|
+
return Response.json(sessions);
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
|
|
44
64
|
"/api/traces": {
|
|
45
65
|
GET(req) {
|
|
46
66
|
const url = new URL(req.url);
|
|
@@ -59,7 +79,39 @@ export function startDebuggerServer(
|
|
|
59
79
|
if (!data.trace) {
|
|
60
80
|
return Response.json({ error: "Trace not found" }, { status: 404 });
|
|
61
81
|
}
|
|
62
|
-
|
|
82
|
+
// Extract messages on the server side
|
|
83
|
+
const messages = extractTurnMessages(data.spans, data.logs);
|
|
84
|
+
return Response.json({ ...data, messages });
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
"/api/session-conversation": {
|
|
89
|
+
GET(req) {
|
|
90
|
+
const url = new URL(req.url);
|
|
91
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
92
|
+
if (!sessionId) {
|
|
93
|
+
return Response.json(
|
|
94
|
+
{ error: "sessionId parameter is required" },
|
|
95
|
+
{ status: 400 },
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Get all traces for the session (already sorted ASC)
|
|
100
|
+
const traces = db.listTraces(50, 0, sessionId);
|
|
101
|
+
|
|
102
|
+
// Extract messages for each trace
|
|
103
|
+
const conversation: ConversationTrace[] = traces.map((trace) => {
|
|
104
|
+
const data = db.getTraceById(trace.trace_id);
|
|
105
|
+
const messages = extractTurnMessages(data.spans, data.logs);
|
|
106
|
+
return {
|
|
107
|
+
trace_id: trace.trace_id,
|
|
108
|
+
start_time_unix_nano: trace.start_time_unix_nano,
|
|
109
|
+
userInput: messages.userInput,
|
|
110
|
+
llmOutput: messages.llmOutput,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return Response.json(conversation);
|
|
63
115
|
},
|
|
64
116
|
},
|
|
65
117
|
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export interface Trace {
|
|
2
2
|
trace_id: string;
|
|
3
|
+
session_id: string | null;
|
|
3
4
|
service_name: string | null;
|
|
4
5
|
first_span_name: string | null;
|
|
5
6
|
start_time_unix_nano: number;
|
|
@@ -41,8 +42,29 @@ export interface SpanNode extends Span {
|
|
|
41
42
|
durationMs: number;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
export interface
|
|
45
|
+
export interface TraceDetailRaw {
|
|
45
46
|
trace: Trace | null;
|
|
46
47
|
spans: Span[];
|
|
47
48
|
logs: Log[];
|
|
48
49
|
}
|
|
50
|
+
|
|
51
|
+
export interface TraceDetail extends TraceDetailRaw {
|
|
52
|
+
messages: {
|
|
53
|
+
userInput: string | null;
|
|
54
|
+
llmOutput: string | null;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ConversationTrace {
|
|
59
|
+
trace_id: string;
|
|
60
|
+
start_time_unix_nano: number;
|
|
61
|
+
userInput: string | null;
|
|
62
|
+
llmOutput: string | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Session {
|
|
66
|
+
session_id: string;
|
|
67
|
+
trace_count: number;
|
|
68
|
+
first_trace_time: number;
|
|
69
|
+
last_trace_time: number;
|
|
70
|
+
}
|