Skip to content

Commit 8f7432e

Browse files
committed
Merge branch 'next' of github.com:devforth/adminforth into next
2 parents 419b628 + bcd8c31 commit 8f7432e

5 files changed

Lines changed: 265 additions & 3 deletions

File tree

dev-demo/custom/TurnDebugShow.vue

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
<template>
2+
<div class="space-y-5">
3+
<div class="rounded-xl border border-slate-200 bg-slate-50 p-4 dark:border-slate-700 dark:bg-slate-900/60">
4+
<div class="flex flex-wrap items-center justify-between gap-3">
5+
<div>
6+
<div class="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500 dark:text-slate-400">
7+
Agent Debug
8+
</div>
9+
<div class="mt-1 text-sm text-slate-700 dark:text-slate-200">
10+
Sequence-by-sequence trace for this turn.
11+
</div>
12+
</div>
13+
<div class="rounded-full bg-white px-3 py-1 text-xs font-semibold text-slate-700 shadow-sm dark:bg-slate-800 dark:text-slate-200">
14+
{{ debugSequences.length }} sequence{{ debugSequences.length === 1 ? '' : 's' }}
15+
</div>
16+
</div>
17+
18+
<div class="mt-4 grid gap-3 md:grid-cols-4">
19+
<div class="rounded-lg bg-white p-3 shadow-sm dark:bg-slate-800">
20+
<div class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400">Tool Calls</div>
21+
<div class="mt-1 text-2xl font-semibold text-slate-900 dark:text-white">{{ totalToolCalls }}</div>
22+
</div>
23+
<div class="rounded-lg bg-white p-3 shadow-sm dark:bg-slate-800">
24+
<div class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400">Final Text Sequences</div>
25+
<div class="mt-1 text-2xl font-semibold text-slate-900 dark:text-white">{{ finalTextSequences }}</div>
26+
</div>
27+
<div class="rounded-lg bg-white p-3 shadow-sm dark:bg-slate-800">
28+
<div class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400">Tool Sequences</div>
29+
<div class="mt-1 text-2xl font-semibold text-slate-900 dark:text-white">{{ toolCallSequences }}</div>
30+
</div>
31+
</div>
32+
</div>
33+
34+
<div
35+
v-if="!debugSequences.length"
36+
class="rounded-xl border border-dashed border-slate-300 bg-white p-6 text-sm text-slate-500 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400"
37+
>
38+
No debug data stored for this turn.
39+
</div>
40+
41+
<div
42+
v-for="sequence in debugSequences"
43+
:key="sequence.sequenceId"
44+
class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-900"
45+
>
46+
<div class="flex flex-wrap items-start justify-between gap-3">
47+
<div>
48+
<div class="text-lg font-semibold text-slate-900 dark:text-white">
49+
Sequence #{{ sequence.sequenceId }}
50+
</div>
51+
<div class="mt-1 text-xs text-slate-500 dark:text-slate-400">
52+
Started {{ formatTimestamp(sequence.startedAt) }}
53+
<span v-if="sequence.endedAt"> • Ended {{ formatTimestamp(sequence.endedAt) }}</span>
54+
<span v-if="sequenceDuration(sequence)"> • {{ sequenceDuration(sequence) }}</span>
55+
</div>
56+
</div>
57+
58+
<div
59+
class="rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-wide"
60+
:class="sequence.resultType === 'tool_calls'
61+
? 'bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200'
62+
: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900/50 dark:text-emerald-200'"
63+
>
64+
{{ sequence.resultType.replace('_', ' ') }}
65+
</div>
66+
</div>
67+
68+
<div class="mt-4 grid gap-3 md:grid-cols-4">
69+
<div class="rounded-lg bg-slate-50 p-3 dark:bg-slate-800/70">
70+
<div class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400">Prompt</div>
71+
<div class="mt-1 text-sm font-semibold text-slate-900 dark:text-white">
72+
{{ sequence.prompt ? `${sequence.prompt.length} chars` : 'Empty' }}
73+
</div>
74+
</div>
75+
<div class="rounded-lg bg-slate-50 p-3 dark:bg-slate-800/70">
76+
<div class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400">Reasoning</div>
77+
<div class="mt-1 text-sm font-semibold text-slate-900 dark:text-white">
78+
{{ sequence.reasoning ? `${sequence.reasoning.length} chars` : 'Empty' }}
79+
</div>
80+
</div>
81+
<div class="rounded-lg bg-slate-50 p-3 dark:bg-slate-800/70">
82+
<div class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400">Text</div>
83+
<div class="mt-1 text-sm font-semibold text-slate-900 dark:text-white">
84+
{{ sequence.text ? `${sequence.text.length} chars` : 'Empty' }}
85+
</div>
86+
</div>
87+
<div class="rounded-lg bg-slate-50 p-3 dark:bg-slate-800/70">
88+
<div class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400">Tool Calls</div>
89+
<div class="mt-1 text-sm font-semibold text-slate-900 dark:text-white">
90+
{{ sequence.toolCalls.length }}
91+
</div>
92+
</div>
93+
</div>
94+
95+
<section v-if="sequence.prompt" class="mt-4">
96+
<div class="mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-slate-400">
97+
Prompt Sent To LLM
98+
</div>
99+
<pre class="max-h-80 overflow-auto rounded-lg bg-slate-950 p-4 text-xs leading-6 text-sky-100 whitespace-pre-wrap">{{ sequence.prompt }}</pre>
100+
</section>
101+
102+
<section v-if="sequence.reasoning" class="mt-4">
103+
<div class="mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-slate-400">
104+
Reasoning
105+
</div>
106+
<pre class="max-h-80 overflow-auto rounded-lg bg-slate-950 p-4 text-xs leading-6 text-slate-100 whitespace-pre-wrap">{{ sequence.reasoning }}</pre>
107+
</section>
108+
109+
<section v-if="sequence.text" class="mt-4">
110+
<div class="mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-slate-400">
111+
Text
112+
</div>
113+
<pre class="max-h-80 overflow-auto rounded-lg bg-slate-100 p-4 text-xs leading-6 text-slate-800 whitespace-pre-wrap dark:bg-slate-800 dark:text-slate-100">{{ sequence.text }}</pre>
114+
</section>
115+
116+
<section v-if="sequence.toolCalls.length" class="mt-4 space-y-3">
117+
<div class="text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-slate-400">
118+
Tool Calls
119+
</div>
120+
121+
<details
122+
v-for="(toolCall, toolCallIndex) in sequence.toolCalls"
123+
:key="`${sequence.sequenceId}-${toolCallIndex}-${toolCall.toolName}`"
124+
:open="toolCallIndex === 0"
125+
class="overflow-hidden rounded-lg border border-slate-200 bg-slate-50 dark:border-slate-700 dark:bg-slate-800/60"
126+
>
127+
<summary class="cursor-pointer list-none px-4 py-3">
128+
<div class="flex flex-wrap items-center justify-between gap-3">
129+
<div class="font-mono text-sm font-semibold text-slate-900 dark:text-white">
130+
{{ toolCall.toolName }}
131+
</div>
132+
<div
133+
class="rounded-full px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wide"
134+
:class="toolCall.error
135+
? 'bg-rose-100 text-rose-800 dark:bg-rose-900/50 dark:text-rose-200'
136+
: 'bg-sky-100 text-sky-800 dark:bg-sky-900/50 dark:text-sky-200'"
137+
>
138+
{{ toolCall.error ? 'error' : 'ok' }}
139+
</div>
140+
</div>
141+
</summary>
142+
143+
<div class="grid gap-3 border-t border-slate-200 p-4 dark:border-slate-700">
144+
<div>
145+
<div class="mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-slate-400">
146+
Input YAML
147+
</div>
148+
<pre class="max-h-72 overflow-auto rounded-lg bg-slate-950 p-4 text-xs leading-6 text-slate-100 whitespace-pre-wrap">{{ toolCall.input }}</pre>
149+
</div>
150+
151+
<div v-if="toolCall.output">
152+
<div class="mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-slate-400">
153+
Output YAML
154+
</div>
155+
<pre class="max-h-72 overflow-auto rounded-lg bg-slate-950 p-4 text-xs leading-6 text-emerald-100 whitespace-pre-wrap">{{ toolCall.output }}</pre>
156+
</div>
157+
158+
<div v-if="toolCall.error">
159+
<div class="mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-rose-500 dark:text-rose-300">
160+
Error YAML
161+
</div>
162+
<pre class="max-h-72 overflow-auto rounded-lg bg-rose-950 p-4 text-xs leading-6 text-rose-100 whitespace-pre-wrap">{{ toolCall.error }}</pre>
163+
</div>
164+
</div>
165+
</details>
166+
</section>
167+
</div>
168+
169+
<details class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-900">
170+
<summary class="cursor-pointer text-sm font-semibold text-slate-900 dark:text-white">
171+
Raw JSON
172+
</summary>
173+
<pre class="mt-4 max-h-[32rem] overflow-auto rounded-lg bg-slate-950 p-4 text-xs leading-6 text-slate-100 whitespace-pre-wrap">{{ rawJson }}</pre>
174+
</details>
175+
</div>
176+
</template>
177+
178+
<script setup lang="ts">
179+
import { computed } from 'vue';
180+
import type {
181+
AdminForthResourceColumnCommon,
182+
AdminForthResourceCommon,
183+
AdminUser,
184+
} from "@/types/Common";
185+
186+
type DebugToolCall = {
187+
toolName: string;
188+
input: string;
189+
output: string | null;
190+
error: string | null;
191+
};
192+
193+
type DebugSequence = {
194+
sequenceId: number;
195+
startedAt: string;
196+
prompt: string;
197+
reasoning: string;
198+
text: string;
199+
toolCalls: DebugToolCall[];
200+
endedAt: string;
201+
resultType: 'tool_calls' | 'final_text';
202+
};
203+
204+
const props = defineProps<{
205+
column: AdminForthResourceColumnCommon;
206+
record: any;
207+
meta: any;
208+
resource: AdminForthResourceCommon;
209+
adminUser: AdminUser;
210+
}>();
211+
212+
const debugSequences = computed<DebugSequence[]>(() => props.record[props.column.name] ?? []);
213+
214+
const totalToolCalls = computed(() =>
215+
debugSequences.value.reduce((sum, sequence) => sum + sequence.toolCalls.length, 0),
216+
);
217+
218+
const finalTextSequences = computed(() =>
219+
debugSequences.value.filter((sequence) => sequence.resultType === 'final_text').length,
220+
);
221+
222+
const toolCallSequences = computed(() =>
223+
debugSequences.value.filter((sequence) => sequence.resultType === 'tool_calls').length,
224+
);
225+
226+
const rawJson = computed(() => JSON.stringify(debugSequences.value, null, 2));
227+
228+
function formatTimestamp(value: string) {
229+
return new Date(value).toLocaleString();
230+
}
231+
232+
function sequenceDuration(sequence: DebugSequence) {
233+
const startedAtMs = new Date(sequence.startedAt).getTime();
234+
const endedAtMs = new Date(sequence.endedAt).getTime();
235+
const durationMs = endedAtMs - startedAtMs;
236+
237+
if (durationMs < 1000) {
238+
return `${durationMs} ms`;
239+
}
240+
241+
return `${(durationMs / 1000).toFixed(2)} s`;
242+
}
243+
</script>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE "turns" ADD COLUMN "dubbug" TEXT;

dev-demo/migrations/prisma/sqlite/schema.prisma

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,5 @@ model turns {
125125
created_at DateTime
126126
prompt String?
127127
response String?
128-
}
128+
dubbug String?
129+
}

dev-demo/resources/adminuser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ export default {
214214
createdAtField: 'created_at',
215215
promptField: 'prompt',
216216
responseField: 'response',
217+
debugField: 'dubbug',
217218
},
218219
}),
219220
],
@@ -237,4 +238,4 @@ export default {
237238
},
238239
},
239240
},
240-
} as AdminForthResourceInput;
241+
} as AdminForthResourceInput;

dev-demo/resources/agent_resources/turns.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,21 @@ export default {
3939
name: 'response',
4040
type: AdminForthDataTypes.TEXT,
4141
},
42+
{
43+
name: 'dubbug',
44+
type: AdminForthDataTypes.JSON,
45+
components: {
46+
show: {
47+
file: '@@/TurnDebugShow.vue',
48+
},
49+
},
50+
showIn: {
51+
list: false,
52+
show: true,
53+
edit: false,
54+
create: false,
55+
filter: false,
56+
},
57+
},
4258
],
43-
} as AdminForthResourceInput;
59+
} as AdminForthResourceInput;

0 commit comments

Comments
 (0)