diff --git a/src/admin/routes.ts b/src/admin/routes.ts index 6b16d0c..6de57a0 100644 --- a/src/admin/routes.ts +++ b/src/admin/routes.ts @@ -132,9 +132,38 @@ async function toggleDetail(id, el) { loadedDetails[id] = true; const reqStr = JSON.stringify(log.requestBody ?? {}, null, 2); const respStr = typeof log.responseBody === 'string' ? log.responseBody : JSON.stringify(log.responseBody ?? {}, null, 2); - let html = '
'; - html += '

📤 请求体 ('+escapeHtml(log.endpoint)+')

'+escapeHtml(reqStr)+'
'; - html += '

📥 响应体

'+(log.error ? ''+escapeHtml(log.error)+'

' : '')+escapeHtml(respStr)+'
'; + const paramsStr = log.params ? JSON.stringify(log.params, null, 2) : null; + const errorHtml = log.error ? ''+escapeHtml(log.error)+'

' : ''; + + // 渲染 prompt messages:system 蓝色背景,user 灰色 + const renderMessages = (msgs) => { + if (!msgs || msgs.length === 0) return ''; + return msgs.map((m,i) => { + const label = m.role === 'system' ? '🤖 System Prompt' : '👤 User Message'; + const bg = m.role === 'system' ? '#e3f2fd' : '#f5f5f5'; + const border = m.role === 'system' ? '#90caf9' : '#e0e0e0'; + return '
'+ + ''+label+''+ + '
'+escapeHtml(m.content)+'
'+ + '
'; + }).join(''); + }; + + let html = ''; + // prompt 区域(如果有) + if (log.messages && log.messages.length > 0) { + html += '

'+(log.messages.filter(m=>m.role==='system').length>0?'🧠':'')+' Prompt

'+renderMessages(log.messages)+'
'; + } + // 参数 + 请求/响应 + html += '
'; + if (paramsStr) { + html += '

⚙️ 调用参数

'+escapeHtml(paramsStr)+'
'; + } + html += '

⏱️ 耗时

'+log.durationMs+'ms
'; + html += '
'; + html += '
'; + html += '

📤 原始请求体

'+escapeHtml(reqStr)+'
'; + html += '

📥 响应体

'+errorHtml+escapeHtml(respStr)+'
'; html += '
'; row.querySelector('.detail-card').innerHTML = html; } diff --git a/src/admin/store.ts b/src/admin/store.ts index dbab103..354a819 100644 --- a/src/admin/store.ts +++ b/src/admin/store.ts @@ -16,7 +16,9 @@ export interface LogEntry { model: string; durationMs: number; status: number; // HTTP status code - requestBody: unknown; + requestBody: unknown; // Java 端发来的原始请求体 + messages?: unknown[]; // 构造后发给 LLM 的 messages(含 system prompt) + params?: Record; // 模型调用参数(temperature、max_tokens 等) responseBody: unknown; error?: string; } diff --git a/src/routes/ai.ts b/src/routes/ai.ts index 6466040..7d8f0af 100644 --- a/src/routes/ai.ts +++ b/src/routes/ai.ts @@ -35,12 +35,14 @@ export async function aiRoutes(app: FastifyInstance): Promise { app.post<{ Body: AggregateReportBody }>("/ai/aggregate-report", async (request, reply) => { const { taskName, fragments, expectation } = request.body ?? ({} as AggregateReportBody); const t0 = Date.now(); + const params = { temperature: 0.3, max_tokens: 2048 }; if (!taskName || !Array.isArray(fragments) || fragments.length === 0) { logStore.add({ timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report", model: config.model, durationMs: Date.now() - t0, status: 400, - requestBody: request.body ?? {}, responseBody: { error: "taskName 与 fragments 不可为空" }, + requestBody: request.body ?? {}, params, + responseBody: { error: "taskName 与 fragments 不可为空" }, }); return reply.status(400).send({ error: "taskName 与 fragments 不可为空" }); } @@ -52,7 +54,7 @@ export async function aiRoutes(app: FastifyInstance): Promise { logStore.add({ timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report", model: config.model, durationMs: Date.now() - t0, status: 200, - requestBody: request.body ?? {}, responseBody: resp, + requestBody: request.body ?? {}, messages, params, responseBody: resp, }); return resp; } catch (err) { @@ -61,7 +63,7 @@ export async function aiRoutes(app: FastifyInstance): Promise { logStore.add({ timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report", model: config.model, durationMs: Date.now() - t0, status, - requestBody: request.body ?? {}, responseBody: {}, error: errMsg, + requestBody: request.body ?? {}, params, responseBody: {}, error: errMsg, }); if (err instanceof LlmError) { return reply.status(err.statusCode).send({ error: err.message }); @@ -77,12 +79,14 @@ export async function aiRoutes(app: FastifyInstance): Promise { const { taskName, taskDescription, reports, fragments } = request.body ?? ({} as GenerateMindMapBody); const t0 = Date.now(); + const params = { temperature: 0.3, max_tokens: 4096 }; if (!taskName || ((reports?.length ?? 0) === 0 && (fragments?.length ?? 0) === 0)) { logStore.add({ timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map", model: config.model, durationMs: Date.now() - t0, status: 400, - requestBody: request.body ?? {}, responseBody: { error: "taskName 不可为空且 reports/fragments 至少一项非空" }, + requestBody: request.body ?? {}, params, + responseBody: { error: "taskName 不可为空且 reports/fragments 至少一项非空" }, }); return reply.status(400).send({ error: "taskName 不可为空且 reports/fragments 至少一项非空" }); } @@ -99,7 +103,7 @@ export async function aiRoutes(app: FastifyInstance): Promise { logStore.add({ timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map", model: config.model, durationMs: Date.now() - t0, status: 200, - requestBody: request.body ?? {}, responseBody: resp, + requestBody: request.body ?? {}, messages, params, responseBody: resp, }); return resp; } catch (err) { @@ -108,7 +112,7 @@ export async function aiRoutes(app: FastifyInstance): Promise { logStore.add({ timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map", model: config.model, durationMs: Date.now() - t0, status, - requestBody: request.body ?? {}, responseBody: {}, error: errMsg, + requestBody: request.body ?? {}, params, responseBody: {}, error: errMsg, }); if (err instanceof LlmError) { return reply.status(err.statusCode).send({ error: err.message });