From 3bec69986e528f68fce132e17e2979291a994096 Mon Sep 17 00:00:00 2001
From: admin <572701190@qq.com>
Date: Sun, 19 Apr 2026 22:54:00 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20Kimi=20k2.5=E5=8F=82=E6=95=B0=E9=80=82?=
=?UTF-8?q?=E9=85=8D+AI=E6=97=A5=E5=BF=97=E5=AF=BC=E5=87=BA=E5=AE=8C?=
=?UTF-8?q?=E5=96=84(20260419=5F2249)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Kimi k2.5 强制传参拦截: 当 provider=kimi 且 model 包含 k2.5 时,
从请求体中 delete temperature/top_p/presence_penalty/frequency_penalty,
彻底解决 HTTP 400 报错
- 完善导出AI日志: 新增 lastExchangeLog 状态, 记录每次调用的
完整请求体(requestPayload)、原始响应(responsePayload)、
错误详情(errorDetail含status/statusText/responseText)、模型配置
- 更新导出按钮 JSON 结构, 包含 lastExchange 字段
---
src/pages/ReportEditor.tsx | 53 ++++++--
.../20260419_2249/功能变更实现方案文档.md | 117 ++++++++++++++++++
工程分析/20260419_2249/功能变更测试文档.md | 64 ++++++++++
工程分析/20260419_2249/功能变更需求文档.md | 44 +++++++
4 files changed, 269 insertions(+), 9 deletions(-)
create mode 100644 工程分析/20260419_2249/功能变更实现方案文档.md
create mode 100644 工程分析/20260419_2249/功能变更测试文档.md
create mode 100644 工程分析/20260419_2249/功能变更需求文档.md
diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx
index e5364ee..7a2c0b6 100644
--- a/src/pages/ReportEditor.tsx
+++ b/src/pages/ReportEditor.tsx
@@ -73,6 +73,13 @@ export default function ReportEditor() {
]);
const [isEditingPrompts, setIsEditingPrompts] = useState(false);
const [diffModal, setDiffModal] = useState<{isOpen: boolean, originalHtml: string, newHtml: string, targetId: string} | null>(null);
+ const [lastExchangeLog, setLastExchangeLog] = useState<{
+ startTime: string;
+ modelConfig: { provider: string; endpoint: string; modelName: string };
+ requestPayload: any;
+ responsePayload: any | null;
+ errorDetail: { status: number; statusText: string; responseText: string; message: string } | null;
+ } | null>(null);
useEffect(() => {
stateRef.current.chatMessages = chatMessages;
@@ -945,23 +952,50 @@ export default function ReportEditor() {
const systemPrompt = aiModifyEnabled
? '你是一名专业的外科医生助理。当前处于【修改模式】。\n我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据用户的【医生指令】,直接重写并输出目标区域的 HTML。\n\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 必须包含 "reply"(简短回复)和 "updatedHtml"(修改后的完整 HTML 代码)两个字段\n3. 【内容边界】:全局参考内容仅供你理解上下文。你的 updatedHtml 只能包含目标区域本身的内容(例如:如果目标区域是"手术步骤",你就只写步骤)。严禁输出签名、落款、术后总结等属于报告其他部分的结构!\n4. 段落必须使用
标签包裹,段落间绝对不要使用
标签,也不要使用换行符 (\\n)\n5. 输出的 HTML 必须紧凑,标签之间不要有空格或换行\n6. 绝对不要包含任何 Markdown 标记(如 ```json)'
: '你是一名专业的外科医生助理。当前处于【对话模式】。\n请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"(你的专业回答)一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记';
+ const payload: any = {
+ model: modelName,
+ messages: [
+ { role: 'system', content: systemPrompt },
+ { role: 'user', content: messageContent }
+ ],
+ temperature: 0.3
+ };
+ const isKimiK25 = settings.activeAiProvider === 'kimi' && /k2\.5/i.test(modelName);
+ if (isKimiK25) {
+ delete payload.temperature;
+ delete payload.top_p;
+ delete payload.presence_penalty;
+ delete payload.frequency_penalty;
+ }
+ const logEntry = {
+ startTime: new Date().toISOString(),
+ modelConfig: { provider: settings.activeAiProvider || 'kimi', endpoint: apiEndpoint, modelName },
+ requestPayload: JSON.parse(JSON.stringify(payload)),
+ responsePayload: null as any | null,
+ errorDetail: null as { status: number; statusText: string; responseText: string; message: string } | null
+ };
const response = await fetch(`${apiEndpoint}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
- body: JSON.stringify({
- model: modelName,
- messages: [
- { role: 'system', content: systemPrompt },
- { role: 'user', content: messageContent }
- ],
- temperature: 0.3
- })
+ body: JSON.stringify(payload)
});
- if (!response.ok) throw new Error(`API 请求失败: ${response.status}`);
+ if (!response.ok) {
+ const errorText = await response.text().catch(() => '');
+ logEntry.errorDetail = {
+ status: response.status,
+ statusText: response.statusText,
+ responseText: errorText,
+ message: `API 请求失败: ${response.status}`
+ };
+ setLastExchangeLog(logEntry);
+ throw new Error(`API 请求失败: ${response.status}${errorText ? ' - ' + errorText : ''}`);
+ }
const data = await response.json();
+ logEntry.responsePayload = data;
+ setLastExchangeLog(logEntry);
const responseText = data.choices[0].message.content.trim();
const cleanedText = responseText.replace(/```json\n?|```/g, '');
let responseJson: any = {};
@@ -2386,6 +2420,7 @@ export default function ReportEditor() {
exportAt: new Date().toISOString(),
url: window.location.href,
messages: chatMessages,
+ lastExchange: lastExchangeLog,
metadata: {
user: currentUser?.username || 'anonymous',
activeProvider: (() => { const s = storage.get('systemSettings', {} as SystemSettings); return s.activeAiProvider || 'kimi'; })(),
diff --git a/工程分析/20260419_2249/功能变更实现方案文档.md b/工程分析/20260419_2249/功能变更实现方案文档.md
new file mode 100644
index 0000000..88ec93e
--- /dev/null
+++ b/工程分析/20260419_2249/功能变更实现方案文档.md
@@ -0,0 +1,117 @@
+# 功能变更实现方案文档(20260419_2249)
+
+## 实现方案 A:Kimi k2.5 参数拦截
+
+### 变更点
+`ReportEditor.tsx` 中 `handleAIGenerate` 函数的请求体构建部分(当前行号约 948-962)。
+
+### 具体实现
+1. 在构建 `body` 前,先创建一个 `payload` 对象:
+ ```ts
+ const payload: any = {
+ model: modelName,
+ messages: [
+ { role: 'system', content: systemPrompt },
+ { role: 'user', content: messageContent }
+ ],
+ temperature: 0.3
+ };
+ ```
+2. 增加条件判断:
+ ```ts
+ const isKimiK25 = settings.activeAiProvider === 'kimi' && /k2\.5/i.test(modelName);
+ if (isKimiK25) {
+ delete payload.temperature;
+ delete payload.top_p;
+ delete payload.presence_penalty;
+ delete payload.frequency_penalty;
+ }
+ ```
+3. `fetch` 的 `body: JSON.stringify(payload)`
+
+### 设计理由
+- 使用 `delete` 而非覆盖为固定值,是因为 Kimi 官方文档明确禁止这些参数出现;即使设为 `1.0` 仍可能触发校验失败
+- 条件判断放在 `settings` 读取后立即执行,确保使用用户实际配置的 provider 和 modelName
+- 正则 `/k2\.5/i` 兼容可能的模型 ID 变体(如 `kimi-k2.5`、`kimi-k2.5-preview` 等)
+
+---
+
+## 实现方案 B:完善 AI 日志导出
+
+### 变更点 1:新增 `lastExchangeLog` 状态
+在 `ReportEditor.tsx` 的 state 定义区(`chatMessages` 附近)新增:
+```ts
+const [lastExchangeLog, setLastExchangeLog] = useState<{
+ startTime: string;
+ modelConfig: { provider: string; endpoint: string; modelName: string };
+ requestPayload: any;
+ responsePayload: any | null;
+ errorDetail: { status: number; statusText: string; responseText: string; message: string } | null;
+} | null>(null);
+```
+
+### 变更点 2:在 `handleAIGenerate` 中记录日志
+将函数重构为在关键节点捕获数据:
+
+**A. 请求前记录:**
+```ts
+const logEntry = {
+ startTime: new Date().toISOString(),
+ modelConfig: { provider: settings.activeAiProvider || 'kimi', endpoint: apiEndpoint, modelName },
+ requestPayload: { ...payload },
+ responsePayload: null,
+ errorDetail: null
+};
+```
+
+**B. 错误处理增强:**
+当前错误处理只有:
+```ts
+if (!response.ok) throw new Error(`API 请求失败: ${response.status}`);
+```
+改为:
+```ts
+if (!response.ok) {
+ const errorText = await response.text();
+ logEntry.errorDetail = {
+ status: response.status,
+ statusText: response.statusText,
+ responseText: errorText,
+ message: `API 请求失败: ${response.status}`
+ };
+ setLastExchangeLog(logEntry);
+ throw new Error(`API 请求失败: ${response.status} - ${errorText}`);
+}
+```
+
+**C. 成功响应记录:**
+在解析 `responseJson` 后:
+```ts
+logEntry.responsePayload = responseJson;
+setLastExchangeLog(logEntry);
+```
+
+### 变更点 3:更新导出按钮
+在「导出 AI 日志」按钮的 `onClick` 中,将 `lastExchangeLog` 加入导出数据:
+```ts
+const data = {
+ exportAt: new Date().toISOString(),
+ url: window.location.href,
+ messages: chatMessages,
+ lastExchange: lastExchangeLog,
+ metadata: { ... }
+};
+```
+
+### 设计理由
+- `lastExchangeLog` 只记录最后一次调用,避免无限增长导致内存/状态膨胀
+- 错误时捕获 `response.text()` 获取 Kimi 官方返回的详细错误 JSON(通常包含 `error.code` 和 `error.message`)
+- `requestPayload` 深拷贝防止后续 `delete` 操作污染日志记录
+- 使用 `useState` 而非 `useRef`,因为导出按钮需要读取最新值并触发重渲染显示状态
+
+---
+
+## 依赖与兼容性
+- 无新增 npm 依赖
+- TypeScript 类型在组件内部定义,不影响 `src/types.ts`
+- 向后兼容:旧数据无 `lastExchangeLog`,导出时字段为 `null`
diff --git a/工程分析/20260419_2249/功能变更测试文档.md b/工程分析/20260419_2249/功能变更测试文档.md
new file mode 100644
index 0000000..7bbd15f
--- /dev/null
+++ b/工程分析/20260419_2249/功能变更测试文档.md
@@ -0,0 +1,64 @@
+# 功能变更测试文档(20260419_2249)
+
+## 测试项 1:Kimi k2.5 参数拦截
+
+### 测试场景 A:Kimi + k2.5 模型
+1. 进入系统设置 → AI 接口集成
+2. 供应商选择「Kimi (Moonshot)」,模型名填写 `kimi-k2.5`
+3. 填写有效 API Key,点击「测试连接」确认配置有效
+4. 进入报告编辑器,打开 AI 面板,发送任意消息
+5. **预期结果**:
+ - 网络请求成功(HTTP 200),无 400 报错
+ - 浏览器 DevTools → Network → 请求体中**不包含** `temperature`、`top_p`、`presence_penalty`、`frequency_penalty`
+
+### 测试场景 B:Kimi + 非 k2.5 模型
+1. 系统设置中模型名改为 `moonshot-v1-32k`
+2. 发送 AI 消息
+3. **预期结果**:
+ - 请求体中**包含** `temperature: 0.3`
+ - 调用正常
+
+### 测试场景 C:DeepSeek / OpenAI / Custom
+1. 切换供应商为 DeepSeek,模型 `deepseek-chat`
+2. 发送 AI 消息
+3. **预期结果**:
+ - 请求体中**包含** `temperature: 0.3`
+ - 调用正常
+
+---
+
+## 测试项 2:AI 日志导出完善
+
+### 测试场景 A:成功调用后的导出
+1. 发送一条 AI 消息并等待成功返回
+2. 点击「导出 AI 日志」
+3. **预期结果**:
+ - 下载的 JSON 中 `lastExchange` 字段非空
+ - `lastExchange.requestPayload` 包含完整的 `model`、`messages`(system + user)
+ - `lastExchange.requestPayload.messages[0].content` 包含系统提示词全文
+ - `lastExchange.responsePayload` 包含 AI 返回的原始 JSON(含 `reply` 和 `updatedHtml`)
+ - `lastExchange.modelConfig` 包含 provider、endpoint、modelName
+ - `lastExchange.errorDetail` 为 `null`
+
+### 测试场景 B:失败调用后的导出
+1. 故意填写错误的 API Key 或断开网络
+2. 发送 AI 消息,等待报错
+3. 点击「导出 AI 日志」
+4. **预期结果**:
+ - `lastExchange.errorDetail` 非空
+ - 包含 `status`(如 401/400/403)、`statusText`、`responseText`(服务端返回的原始错误 JSON)、`message`
+ - `lastExchange.responsePayload` 为 `null`
+
+### 测试场景 C:未进行任何 AI 调用时的导出
+1. 刷新页面后直接点击「导出 AI 日志」
+2. **预期结果**:
+ - `lastExchange` 为 `null`
+ - 其他字段(messages、metadata)正常导出
+
+---
+
+## 回归测试
+- `tsc --noEmit` 零错误
+- `npm run build` 构建成功
+- 预览服务器正常启动并可访问
+- 现有 AI 对话功能不受影响的供应商(deepseek/openai)调用正常
diff --git a/工程分析/20260419_2249/功能变更需求文档.md b/工程分析/20260419_2249/功能变更需求文档.md
new file mode 100644
index 0000000..a7f4b6d
--- /dev/null
+++ b/工程分析/20260419_2249/功能变更需求文档.md
@@ -0,0 +1,44 @@
+# 功能变更需求文档(20260419_2249)
+
+## 需求 1:Kimi k2.5 模型强制传参规则适配
+
+### 问题背景
+Kimi 最新版 `kimi-k2.5` 模型对 API 请求体有极其苛刻的要求,不允许出现非标准的温度和概率参数。当前系统在向所有模型发送请求时均硬编码了 `temperature: 0.3`,导致调用 `kimi-k2.5` 时返回 HTTP 400 错误。
+
+### 需求描述
+在封装向大模型发起 `fetch` 请求的地方,增加条件判断:
+- **触发条件**:当前激活供应商为 `kimi` 且模型名包含 `k2.5`(大小写不敏感)
+- **数据处理**:强制从请求体中 `delete` 移除 `temperature`、`top_p`、`presence_penalty`、`frequency_penalty` 等可选参数,让 Kimi 官方服务器使用其默认安全值
+- **兼容性**:其他供应商(deepseek/openai/custom)及 Kimi 非 k2.5 模型不受影响,继续保留 `temperature: 0.3`
+
+### 参考文档
+https://platform.kimi.com/docs/guide/kimi-k2-5-quickstart
+
+---
+
+## 需求 2:完善「导出 AI 日志」功能
+
+### 问题背景
+当前「导出 AI 日志」按钮仅导出 `chatMessages`(UI 对话历史)和少量元数据,缺少:
+- 实际发往 AI 的完整请求体(System Prompt + Messages + Parameters)
+- AI 返回的原始 JSON 响应
+- API 调用失败时的具体错误信息(HTTP 状态码 + 响应体)
+- 当前生效的模型完整配置
+
+这些信息对于排查大模型幻觉、优化提示词、定位网络/接口故障至关重要。
+
+### 需求描述
+1. 在 `ReportEditor.tsx` 中建立 `lastExchangeLog` 状态,每次 `handleAIGenerate` 调用时记录:
+ - `startTime`:请求发起时间
+ - `requestPayload`:完整请求体(model、messages、实际发送的参数)
+ - `responsePayload`:AI 原始响应 JSON
+ - `errorDetail`:失败时的完整错误信息(含 HTTP 状态码、错误响应体文本)
+ - `modelConfig`:当前 provider、endpoint、modelName
+2. 更新「导出 AI 日志」按钮,将 `lastExchangeLog` 一并写入导出的 JSON
+3. 保持向后兼容:无 AI 调用记录时,`lastExchangeLog` 为 `null`,导出时不影响其他字段
+
+---
+
+## 影响范围
+- `src/pages/ReportEditor.tsx`(主要修改文件)
+- 无新增依赖