feat: Kimi k2.5参数适配+AI日志导出完善(20260419_2249)

- 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 字段
This commit is contained in:
2026-04-19 22:54:00 +08:00
parent 2e634ff832
commit 3bec69986e
4 changed files with 269 additions and 9 deletions

View File

@@ -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. 段落必须使用 <p> 标签包裹,段落间绝对不要使用 <br> 标签,也不要使用换行符 (\\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>('systemSettings', {} as SystemSettings); return s.activeAiProvider || 'kimi'; })(),