feat(admin):日志面板增加 prompt 详情 + 模型调用参数
展开日志详情后新增: - Prompt 区域:System Prompt(蓝色背景)和 User Message(灰色背景)完整显示 - 调用参数:temperature、max_tokens 等 - 耗时:独立显示 修改: - LogEntry 新增 messages? 和 params? 字段 - ai.ts 两个 handler 在日志中记录构造后的 messages 和调用参数 - 面板 toggleDetail 渲染 messages/params 区域
This commit is contained in:
+32
-3
@@ -132,9 +132,38 @@ async function toggleDetail(id, el) {
|
|||||||
loadedDetails[id] = true;
|
loadedDetails[id] = true;
|
||||||
const reqStr = JSON.stringify(log.requestBody ?? {}, null, 2);
|
const reqStr = JSON.stringify(log.requestBody ?? {}, null, 2);
|
||||||
const respStr = typeof log.responseBody === 'string' ? log.responseBody : JSON.stringify(log.responseBody ?? {}, null, 2);
|
const respStr = typeof log.responseBody === 'string' ? log.responseBody : JSON.stringify(log.responseBody ?? {}, null, 2);
|
||||||
let html = '<div class="side">';
|
const paramsStr = log.params ? JSON.stringify(log.params, null, 2) : null;
|
||||||
html += '<div><h4>📤 请求体 ('+escapeHtml(log.endpoint)+')</h4><pre>'+escapeHtml(reqStr)+'</pre></div>';
|
const errorHtml = log.error ? '<span style="color:#c62828">'+escapeHtml(log.error)+'</span><br><br>' : '';
|
||||||
html += '<div><h4>📥 响应体</h4><pre>'+(log.error ? '<span style="color:#c62828">'+escapeHtml(log.error)+'</span><br><br>' : '')+escapeHtml(respStr)+'</pre></div>';
|
|
||||||
|
// 渲染 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 '<div style="margin-bottom:8px">'+
|
||||||
|
'<span style="font-size:11px;font-weight:600;color:'+(m.role==='system'?'#1565c0':'#616161')+'">'+label+'</span>'+
|
||||||
|
'<pre style="background:'+bg+';border-color:'+border+';max-height:160px">'+escapeHtml(m.content)+'</pre>'+
|
||||||
|
'</div>';
|
||||||
|
}).join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
// prompt 区域(如果有)
|
||||||
|
if (log.messages && log.messages.length > 0) {
|
||||||
|
html += '<div style="margin-bottom:12px"><h4>'+(log.messages.filter(m=>m.role==='system').length>0?'🧠':'')+' Prompt</h4>'+renderMessages(log.messages)+'</div>';
|
||||||
|
}
|
||||||
|
// 参数 + 请求/响应
|
||||||
|
html += '<div class="side" style="margin-bottom:12px">';
|
||||||
|
if (paramsStr) {
|
||||||
|
html += '<div><h4>⚙️ 调用参数</h4><pre style="background:#fff3e0;border-color:#ffe0b2;font-size:12px">'+escapeHtml(paramsStr)+'</pre></div>';
|
||||||
|
}
|
||||||
|
html += '<div><h4>⏱️ 耗时</h4><pre style="font-size:12px">'+log.durationMs+'ms</pre></div>';
|
||||||
|
html += '</div>';
|
||||||
|
html += '<div class="side">';
|
||||||
|
html += '<div><h4>📤 原始请求体</h4><pre>'+escapeHtml(reqStr)+'</pre></div>';
|
||||||
|
html += '<div><h4>📥 响应体</h4><pre>'+errorHtml+escapeHtml(respStr)+'</pre></div>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
row.querySelector('.detail-card').innerHTML = html;
|
row.querySelector('.detail-card').innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -16,7 +16,9 @@ export interface LogEntry {
|
|||||||
model: string;
|
model: string;
|
||||||
durationMs: number;
|
durationMs: number;
|
||||||
status: number; // HTTP status code
|
status: number; // HTTP status code
|
||||||
requestBody: unknown;
|
requestBody: unknown; // Java 端发来的原始请求体
|
||||||
|
messages?: unknown[]; // 构造后发给 LLM 的 messages(含 system prompt)
|
||||||
|
params?: Record<string, unknown>; // 模型调用参数(temperature、max_tokens 等)
|
||||||
responseBody: unknown;
|
responseBody: unknown;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -35,12 +35,14 @@ export async function aiRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
app.post<{ Body: AggregateReportBody }>("/ai/aggregate-report", async (request, reply) => {
|
app.post<{ Body: AggregateReportBody }>("/ai/aggregate-report", async (request, reply) => {
|
||||||
const { taskName, fragments, expectation } = request.body ?? ({} as AggregateReportBody);
|
const { taskName, fragments, expectation } = request.body ?? ({} as AggregateReportBody);
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
|
const params = { temperature: 0.3, max_tokens: 2048 };
|
||||||
|
|
||||||
if (!taskName || !Array.isArray(fragments) || fragments.length === 0) {
|
if (!taskName || !Array.isArray(fragments) || fragments.length === 0) {
|
||||||
logStore.add({
|
logStore.add({
|
||||||
timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report",
|
timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report",
|
||||||
model: config.model, durationMs: Date.now() - t0, status: 400,
|
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 不可为空" });
|
return reply.status(400).send({ error: "taskName 与 fragments 不可为空" });
|
||||||
}
|
}
|
||||||
@@ -52,7 +54,7 @@ export async function aiRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
logStore.add({
|
logStore.add({
|
||||||
timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report",
|
timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report",
|
||||||
model: config.model, durationMs: Date.now() - t0, status: 200,
|
model: config.model, durationMs: Date.now() - t0, status: 200,
|
||||||
requestBody: request.body ?? {}, responseBody: resp,
|
requestBody: request.body ?? {}, messages, params, responseBody: resp,
|
||||||
});
|
});
|
||||||
return resp;
|
return resp;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -61,7 +63,7 @@ export async function aiRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
logStore.add({
|
logStore.add({
|
||||||
timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report",
|
timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report",
|
||||||
model: config.model, durationMs: Date.now() - t0, status,
|
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) {
|
if (err instanceof LlmError) {
|
||||||
return reply.status(err.statusCode).send({ error: err.message });
|
return reply.status(err.statusCode).send({ error: err.message });
|
||||||
@@ -77,12 +79,14 @@ export async function aiRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
const { taskName, taskDescription, reports, fragments } =
|
const { taskName, taskDescription, reports, fragments } =
|
||||||
request.body ?? ({} as GenerateMindMapBody);
|
request.body ?? ({} as GenerateMindMapBody);
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
|
const params = { temperature: 0.3, max_tokens: 4096 };
|
||||||
|
|
||||||
if (!taskName || ((reports?.length ?? 0) === 0 && (fragments?.length ?? 0) === 0)) {
|
if (!taskName || ((reports?.length ?? 0) === 0 && (fragments?.length ?? 0) === 0)) {
|
||||||
logStore.add({
|
logStore.add({
|
||||||
timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map",
|
timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map",
|
||||||
model: config.model, durationMs: Date.now() - t0, status: 400,
|
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 至少一项非空" });
|
return reply.status(400).send({ error: "taskName 不可为空且 reports/fragments 至少一项非空" });
|
||||||
}
|
}
|
||||||
@@ -99,7 +103,7 @@ export async function aiRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
logStore.add({
|
logStore.add({
|
||||||
timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map",
|
timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map",
|
||||||
model: config.model, durationMs: Date.now() - t0, status: 200,
|
model: config.model, durationMs: Date.now() - t0, status: 200,
|
||||||
requestBody: request.body ?? {}, responseBody: resp,
|
requestBody: request.body ?? {}, messages, params, responseBody: resp,
|
||||||
});
|
});
|
||||||
return resp;
|
return resp;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -108,7 +112,7 @@ export async function aiRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
logStore.add({
|
logStore.add({
|
||||||
timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map",
|
timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map",
|
||||||
model: config.model, durationMs: Date.now() - t0, status,
|
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) {
|
if (err instanceof LlmError) {
|
||||||
return reply.status(err.statusCode).send({ error: err.message });
|
return reply.status(err.statusCode).send({ error: err.message });
|
||||||
|
|||||||
Reference in New Issue
Block a user