From decebd143ca64004ad5e79f891623eeb056fb29f Mon Sep 17 00:00:00 2001
From: cat_shark <1716967236@qq.com>
Date: Sat, 4 Jul 2026 17:31:50 +0800
Subject: [PATCH] =?UTF-8?q?cleanup(lpt-ai)=EF=BC=9A=E7=A7=BB=E9=99=A4?=
=?UTF-8?q?=E5=BA=9F=E5=BC=83=E7=9A=84=E5=90=8C=E6=AD=A5=20API=20+=20?=
=?UTF-8?q?=E7=AE=A1=E7=90=86=E9=9D=A2=E6=9D=BF=E5=A2=9E=E5=8A=A0=E5=A4=8D?=
=?UTF-8?q?=E5=88=B6=E6=8C=89=E9=92=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
routes/ai.ts:
- 移除 /ai/aggregate-report、/ai/generate-mind-map、/ai/compare-recall
三个同步端点,仅保留 /health + 异步任务路由
- 清除不再需要的 import(chat、prompts、LlmError、logStore)
admin/routes.ts:
- 日志详情增加「复制到剪贴板」按钮,一键复制完整请求/响应/
prompt/参数文本,方便粘贴到外部工具调试
---
src/admin/routes.ts | 48 ++++++++++++-
src/routes/ai.ts | 166 +-------------------------------------------
2 files changed, 47 insertions(+), 167 deletions(-)
diff --git a/src/admin/routes.ts b/src/admin/routes.ts
index 6de57a0..fbf93f2 100644
--- a/src/admin/routes.ts
+++ b/src/admin/routes.ts
@@ -134,8 +134,9 @@ async function toggleDetail(id, el) {
const respStr = typeof log.responseBody === 'string' ? log.responseBody : JSON.stringify(log.responseBody ?? {}, null, 2);
const paramsStr = log.params ? JSON.stringify(log.params, null, 2) : null;
const errorHtml = log.error ? ''+escapeHtml(log.error)+'
' : '';
+ const copyId = 'copy-'+id;
- // 渲染 prompt messages:system 蓝色背景,user 灰色
+ // 渲染 prompt messages
const renderMessages = (msgs) => {
if (!msgs || msgs.length === 0) return '';
return msgs.map((m,i) => {
@@ -149,12 +150,39 @@ async function toggleDetail(id, el) {
}).join('');
};
+ // 构建复制的纯文本
+ const buildCopyText = () => {
+ let txt = '=== lpt-ai 请求日志 ===\n';
+ txt += '端点: '+(log.endpoint||'')+'\n';
+ txt += '模型: '+(log.model||'')+'\n';
+ txt += '耗时: '+log.durationMs+'ms\n';
+ txt += '状态: '+log.status+'\n';
+ if (paramsStr) txt += '参数: '+paramsStr+'\n';
+ if (log.messages) {
+ log.messages.forEach((m,i) => {
+ txt += '\n--- '+(m.role==='system'?'System Prompt':'User Message')+' ---\n';
+ txt += m.content;
+ });
+ }
+ txt += '\n\n--- 原始请求体 ---\n';
+ txt += reqStr;
+ txt += '\n\n--- 响应体 ---\n';
+ if (log.error) txt += '错误: '+log.error+'\n';
+ txt += respStr;
+ return txt;
+ };
+
let html = '';
- // prompt 区域(如果有)
+ // 复制按钮
+ 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)+'
';
@@ -180,6 +208,20 @@ function escapeHtml(s) {
return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');
}
+function copyLog(id, btn) {
+ const ta = document.getElementById(id);
+ if (!ta) return;
+ ta.style.position = 'fixed';
+ ta.style.left = '0';
+ ta.style.top = '0';
+ ta.style.width = '1px';
+ ta.style.height = '1px';
+ ta.select();
+ try { document.execCommand('copy'); btn.textContent = '✅ 已复制'; setTimeout(()=>{btn.textContent='📋 复制到剪贴板';},2000); } catch(e) {}
+ ta.style.position = 'absolute';
+ ta.style.left = '-9999px';
+}
+
$('#filterEndpoint').addEventListener('change', () => { loadedDetails = {}; loadLogs(); });
async function init() {
diff --git a/src/routes/ai.ts b/src/routes/ai.ts
index 28d5272..5ca68b9 100644
--- a/src/routes/ai.ts
+++ b/src/routes/ai.ts
@@ -1,32 +1,11 @@
/**
- * AI 能力路由
+ * AI 能力路由(仅异步任务模式,旧同步端点已移除)
*/
import type { FastifyInstance } from "fastify";
-import { chat, isAvailable, loadConfig, LlmError } from "../llm/client.js";
-import { aggregateReportPrompt, compareRecallPrompt, generateMindMapPrompt, type FragmentInput } from "../llm/prompts.js";
-import { logStore } from "../admin/store.js";
+import { isAvailable, loadConfig } from "../llm/client.js";
import { createTask, getTask, type TaskType } from "../task-queue.js";
-interface AggregateReportBody {
- taskName: string;
- fragments: FragmentInput[];
- expectation?: string;
-}
-
-interface GenerateMindMapBody {
- taskName: string;
- taskDescription?: string;
- reports: string[];
- fragments: string[];
-}
-
-interface CompareRecallBody {
- taskName: string;
- standardOutline: string;
- recallOutline: string;
-}
-
export async function aiRoutes(app: FastifyInstance): Promise
{
const config = loadConfig();
@@ -36,147 +15,6 @@ export async function aiRoutes(app: FastifyInstance): Promise {
model: config.model,
}));
- /**
- * 残片聚合为学习报告
- */
- 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 ?? {}, params,
- responseBody: { error: "taskName 与 fragments 不可为空" },
- });
- return reply.status(400).send({ error: "taskName 与 fragments 不可为空" });
- }
-
- try {
- const messages = aggregateReportPrompt(taskName, fragments, expectation);
- const report = await chat(config, messages);
- const resp = { report: report.trim(), model: config.model };
- logStore.add({
- timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report",
- model: config.model, durationMs: Date.now() - t0, status: 200,
- requestBody: request.body ?? {}, messages, params, responseBody: resp,
- });
- return resp;
- } catch (err) {
- const status = err instanceof LlmError ? err.statusCode : 500;
- const errMsg = err instanceof Error ? err.message : String(err);
- logStore.add({
- timestamp: new Date().toISOString(), endpoint: "/ai/aggregate-report",
- model: config.model, durationMs: Date.now() - t0, status,
- requestBody: request.body ?? {}, params, responseBody: {}, error: errMsg,
- });
- if (err instanceof LlmError) {
- return reply.status(err.statusCode).send({ error: err.message });
- }
- throw err;
- }
- });
-
- /**
- * 从学习数据生成思维导图大纲(缩进文本,与后端 MindMapTreeTool 格式一致)
- */
- app.post<{ Body: GenerateMindMapBody }>("/ai/generate-mind-map", async (request, reply) => {
- 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 ?? {}, params,
- responseBody: { error: "taskName 不可为空且 reports/fragments 至少一项非空" },
- });
- return reply.status(400).send({ error: "taskName 不可为空且 reports/fragments 至少一项非空" });
- }
-
- try {
- const messages = generateMindMapPrompt(
- taskName,
- taskDescription ?? "",
- reports ?? [],
- fragments ?? [],
- );
- const outline = await chat(config, messages, { maxTokens: 4096 });
- const resp = { outline: outline.trim(), model: config.model };
- logStore.add({
- timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map",
- model: config.model, durationMs: Date.now() - t0, status: 200,
- requestBody: request.body ?? {}, messages, params, responseBody: resp,
- });
- return resp;
- } catch (err) {
- const status = err instanceof LlmError ? err.statusCode : 500;
- const errMsg = err instanceof Error ? err.message : String(err);
- logStore.add({
- timestamp: new Date().toISOString(), endpoint: "/ai/generate-mind-map",
- model: config.model, durationMs: Date.now() - t0, status,
- requestBody: request.body ?? {}, params, responseBody: {}, error: errMsg,
- });
- if (err instanceof LlmError) {
- return reply.status(err.statusCode).send({ error: err.message });
- }
- throw err;
- }
- });
-
- /**
- * 回忆对比:将用户凭记忆写的导图大纲与标准导图语义对比。
- */
- app.post<{ Body: CompareRecallBody }>("/ai/compare-recall", async (request, reply) => {
- const { taskName, standardOutline, recallOutline } = request.body ?? ({} as CompareRecallBody);
- const t0 = Date.now();
- const params = { temperature: 0.2, max_tokens: 4096 };
-
- if (!taskName || !standardOutline?.trim() || !recallOutline?.trim()) {
- logStore.add({
- timestamp: new Date().toISOString(), endpoint: "/ai/compare-recall",
- model: config.model, durationMs: Date.now() - t0, status: 400,
- requestBody: request.body ?? {}, params,
- responseBody: { error: "taskName、standardOutline、recallOutline 均不可为空" },
- });
- return reply.status(400).send({ error: "taskName、standardOutline、recallOutline 均不可为空" });
- }
-
- try {
- const messages = compareRecallPrompt(taskName, standardOutline, recallOutline);
- const raw = await chat(config, messages, { temperature: 0.2, maxTokens: 4096 });
- // LLM 可能把 JSON 包在 ```json ... ``` 里,做一次清理
- const clean = raw.trim().replace(/^```(?:json)?\s*/, "").replace(/\s*```$/, "");
- const result = JSON.parse(clean);
- logStore.add({
- timestamp: new Date().toISOString(), endpoint: "/ai/compare-recall",
- model: config.model, durationMs: Date.now() - t0, status: 200,
- requestBody: request.body ?? {}, messages, params,
- responseBody: result,
- });
- return result;
- } catch (err) {
- const status = err instanceof LlmError ? err.statusCode
- : err instanceof SyntaxError ? 502 : 500;
- const errMsg = err instanceof Error ? err.message : String(err);
- logStore.add({
- timestamp: new Date().toISOString(), endpoint: "/ai/compare-recall",
- model: config.model, durationMs: Date.now() - t0, status,
- requestBody: request.body ?? {}, params, responseBody: {}, error: errMsg,
- });
- if (err instanceof LlmError) {
- return reply.status(err.statusCode).send({ error: err.message });
- }
- throw err;
- }
- });
-
- // ============ 异步任务(提交 + 轮询)============
-
/** 提交 AI 任务,立即返回 taskId */
app.post<{ Body: { type: string; params: Record } }>("/ai/tasks", async (request, reply) => {
const { type, params } = request.body ?? {};