Format model inputs and tool calls as code blocks in the analysis drawer
Render the prompt as Markdown with each inline JSON payload (prefetched market data, recent decisions) turned into a formatted json block, and split each tool call into separate argument and result blocks. Short arrays and objects stay on one line so klines read one bar per line.
This commit is contained in:
1 parent
010d89e0f5
commit
ac1e278b3b
1 file changed
+99
-9
@@ -27,14 +27,56 @@ const toolResults = computed(() => {
|
||||
const duration = computed(() => detail.value?.finished_at ? Math.round((new Date(detail.value.finished_at).getTime() - new Date(detail.value.started_at).getTime()) / 1000) : null)
|
||||
const toolCallCount = computed(() => (detail.value?.transcript ?? []).reduce((n, m) => n + (m.role === "assistant" ? m.tool_calls.length : 0), 0))
|
||||
|
||||
function pretty(raw: string | undefined) {
|
||||
function parseJson(raw: string): unknown {
|
||||
try {
|
||||
return JSON.parse(raw)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function inlineJson(value: unknown): string {
|
||||
if (Array.isArray(value))
|
||||
return `[${value.map(inlineJson).join(", ")}]`
|
||||
if (value && typeof value === "object")
|
||||
return `{${Object.entries(value).map(([k, v]) => `${JSON.stringify(k)}: ${inlineJson(v)}`).join(", ")}}`
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
|
||||
/** 缩进展开,但放得下一行的数组、对象不再拆开:K 线每根一行,而不是每个数字一行 */
|
||||
function formatJson(value: unknown, indent = ""): string {
|
||||
const flat = inlineJson(value)
|
||||
if (flat.length <= 100 || !value || typeof value !== "object")
|
||||
return flat
|
||||
const inner = `${indent} `
|
||||
if (Array.isArray(value))
|
||||
return `[\n${value.map(v => inner + formatJson(v, inner)).join(",\n")}\n${indent}]`
|
||||
return `{\n${Object.entries(value).map(([k, v]) => `${inner}${JSON.stringify(k)}: ${formatJson(v, inner)}`).join(",\n")}\n${indent}}`
|
||||
}
|
||||
|
||||
function formatResult(raw: string | undefined) {
|
||||
if (raw === undefined)
|
||||
return "无结果"
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(raw), null, 2)
|
||||
} catch {
|
||||
return raw
|
||||
}
|
||||
const value = parseJson(raw)
|
||||
return value === undefined ? raw : formatJson(value)
|
||||
}
|
||||
|
||||
/** 发给模型的消息是 Markdown,其中整行的 JSON(预取行情、最近决策)改成格式化的代码块 */
|
||||
function promptMarkdown(text: string) {
|
||||
return text.split("\n").map((line) => {
|
||||
const start = line.indexOf("{")
|
||||
const value = start < 0 ? undefined : parseJson(line.slice(start))
|
||||
if (value === undefined)
|
||||
return line
|
||||
const prefix = line.slice(0, start)
|
||||
// 标题里的是工具参数,短,保持在标题行内
|
||||
if (prefix.startsWith("#"))
|
||||
return `${prefix}\`${inlineJson(value)}\``
|
||||
// 列表项里的代码块要缩进到列表内容的位置,才属于这一项
|
||||
const pad = " ".repeat(/^\s*[-*] /.exec(prefix)?.[0].length ?? 0)
|
||||
const block = ["```json", ...formatJson(value).split("\n"), "```"].map(l => pad + l).join("\n")
|
||||
return `${prefix}\n\n${block}\n`
|
||||
}).join("\n")
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -99,14 +141,24 @@ function pretty(raw: string | undefined) {
|
||||
<el-collapse>
|
||||
<template v-for="(m, i) in detail.transcript ?? []" :key="i">
|
||||
<el-collapse-item v-if="m.role === 'user'" :name="`u${i}`" :title="i === 0 ? '发给模型的数据(账户、持仓、预取行情)' : '程序提示'">
|
||||
<pre class="m-0 max-h-120 overflow-auto whitespace-pre-wrap break-all rounded bg-slate-50 p-2 text-xs">{{ m.text }}</pre>
|
||||
<div class="prompt markdown-body max-h-160 overflow-auto pr-1" v-html="renderMarkdown(promptMarkdown(m.text))" />
|
||||
</el-collapse-item>
|
||||
<template v-else-if="m.role === 'assistant'">
|
||||
<el-collapse-item v-for="call in m.tool_calls" :key="call.id" :name="call.id">
|
||||
<template #title>
|
||||
<span class="inline-block max-w-[90%] truncate text-xs font-mono">{{ call.name }} {{ JSON.stringify(call.arguments) }}</span>
|
||||
<span class="flex items-center gap-2 overflow-hidden">
|
||||
<span class="text-slate-900 font-semibold font-mono">{{ call.name }}</span>
|
||||
<span class="truncate text-xs text-slate-400 font-mono">{{ inlineJson(call.arguments) }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<pre class="m-0 max-h-120 overflow-auto whitespace-pre-wrap break-all rounded bg-slate-50 p-2 text-xs">{{ pretty(toolResults.get(call.id)) }}</pre>
|
||||
<div class="block-label">
|
||||
参数
|
||||
</div>
|
||||
<pre class="code-block">{{ formatJson(call.arguments) }}</pre>
|
||||
<div class="block-label mt-3">
|
||||
返回
|
||||
</div>
|
||||
<pre class="code-block">{{ formatResult(toolResults.get(call.id)) }}</pre>
|
||||
</el-collapse-item>
|
||||
</template>
|
||||
</template>
|
||||
@@ -129,4 +181,42 @@ function pretty(raw: string | undefined) {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.block-label {
|
||||
margin-bottom: 4px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.code-block,
|
||||
.prompt :deep(pre) {
|
||||
max-height: 24rem;
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 6px;
|
||||
background: var(--el-fill-color-light);
|
||||
font-family: var(--ap-font-mono);
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.prompt {
|
||||
font-size: 13px;
|
||||
}
|
||||
/* 标题里的参数预览过长时截断,不把右侧箭头挤出去 */
|
||||
:deep(.el-collapse-item__title) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.prompt :deep(pre) {
|
||||
margin: 6px 0 10px;
|
||||
}
|
||||
.prompt :deep(h2) {
|
||||
margin-top: 1.2em;
|
||||
font-size: 1.05em;
|
||||
}
|
||||
.prompt :deep(h3) {
|
||||
font-size: 1em;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user