修复AI助手把思考内容当成代码写入的问题

This commit is contained in:
Mango committed 2026-10-13 00:40:15 +08:00
1 parent f092fb655c
commit 52c2ad4846
3 files changed
+52 -32

No files matched your search

+24 -20
View File
@@ -167,18 +167,19 @@ function parseModelsResponse(response, apiType) {
}
}
let reasoning_content_start = false;
function processContentWithReason(response, onStream) {
// 思考内容的状态跟着单次请求走:之前用模块级变量,请求中断在思考阶段时会残留 true,
// 下一次请求的思考就不再包 <think>,导致思考里的代码片段被当成正文取走
function processContentWithReason(response, onStream, reasoningState) {
if (response.reasoning_content) {
if (!reasoning_content_start) {
reasoning_content_start = true;
if (!reasoningState.started) {
reasoningState.started = true;
onStream("<think>", false);
}
onStream(response.reasoning_content, false);
}
if (response.content) {
if (reasoning_content_start) {
reasoning_content_start = false;
if (reasoningState.started) {
reasoningState.started = false;
onStream("</think>", false);
}
onStream(response.content, false);
@@ -186,7 +187,7 @@ function processContentWithReason(response, onStream) {
}
// 处理 OpenAI 流式响应
async function handleOpenAIStreamResponse(line, onStream) {
async function handleOpenAIStreamResponse(line, onStream, reasoningState) {
if (line.startsWith("data:")) {
const jsonStr = line.replace(/^data:[ ]*/, "").trim();
if (jsonStr === "[DONE]") {
@@ -196,13 +197,13 @@ async function handleOpenAIStreamResponse(line, onStream) {
const json = JSON.parse(jsonStr);
const response = json.choices[0]?.delta;
if (response) {
processContentWithReason(response, onStream);
processContentWithReason(response, onStream, reasoningState);
}
}
}
// 处理 Ollama 流式响应
async function handleOllamaStreamResponse(line, onStream) {
async function handleOllamaStreamResponse(line, onStream, reasoningState) {
const json = JSON.parse(line);
if (json.done) {
onStream("", true);
@@ -210,21 +211,21 @@ async function handleOllamaStreamResponse(line, onStream) {
}
const response = json.message;
if (response) {
processContentWithReason(response, onStream);
processContentWithReason(response, onStream, reasoningState);
}
}
// 处理 uTools AI 流式响应
async function handleUToolsAIStreamResponse(response, onStream) {
processContentWithReason(response, onStream);
async function handleUToolsAIStreamResponse(response, onStream, reasoningState) {
processContentWithReason(response, onStream, reasoningState);
}
// 处理流式响应
async function handleStreamResponse(response, apiConfig, onStream) {
async function handleStreamResponse(response, apiConfig, onStream, reasoningState) {
// 处理 uTools AI 响应
if (apiConfig.apiType === API_TYPES.UTOOLS) {
try {
await handleUToolsAIStreamResponse(response, onStream);
await handleUToolsAIStreamResponse(response, onStream, reasoningState);
return { success: true };
} catch (error) {
throw error;
@@ -249,9 +250,9 @@ async function handleStreamResponse(response, apiConfig, onStream) {
if (line.trim()) {
try {
if (apiConfig.apiType === API_TYPES.OPENAI) {
await handleOpenAIStreamResponse(line, onStream);
await handleOpenAIStreamResponse(line, onStream, reasoningState);
} else {
await handleOllamaStreamResponse(line, onStream);
await handleOllamaStreamResponse(line, onStream, reasoningState);
}
} catch (e) {
console.error("解析响应失败:", e);
@@ -264,9 +265,9 @@ async function handleStreamResponse(response, apiConfig, onStream) {
if (buffer.trim()) {
try {
if (apiConfig.apiType === API_TYPES.OPENAI) {
await handleOpenAIStreamResponse(buffer, onStream);
await handleOpenAIStreamResponse(buffer, onStream, reasoningState);
} else {
await handleOllamaStreamResponse(buffer, onStream);
await handleOllamaStreamResponse(buffer, onStream, reasoningState);
}
} catch (e) {
console.error("解析剩余响应失败:", e);
@@ -335,6 +336,8 @@ async function chat(content, apiConfig, options = {}) {
let fullResponse = "";
// 保证 done 只回调一次
let doneEmitted = false;
// 本次请求的思考内容状态
const reasoningState = { started: false };
// 包装 onStream 回调以收集完整响应并更新进度条
const streamHandler = (chunk, isDone) => {
@@ -366,7 +369,7 @@ async function chat(content, apiConfig, options = {}) {
messages: messages,
},
(chunk) => {
handleUToolsAIStreamResponse(chunk, streamHandler);
handleUToolsAIStreamResponse(chunk, streamHandler, reasoningState);
}
);
onFetch(controller);
@@ -430,7 +433,8 @@ async function chat(content, apiConfig, options = {}) {
const result = await handleStreamResponse(
response,
apiConfig,
streamHandler
streamHandler,
reasoningState
);
// 如果请求被取消,返回取消状态
+18 -11
View File
@@ -266,26 +266,33 @@ export default defineComponent({
updateCodeFromResponse() {
if (!this.autoUpdateCode) return;
const code = this.extractCode(
const codes = this.extractCodes(
this.chatHistory[this.chatHistory.length - 1]?.content
);
if (code) {
this.$emit("update-code", "replace", code);
} else {
if (codes.length === 1) {
this.$emit("update-code", "replace", codes[0]);
} else if (!codes.length) {
window.utools.showNotification("未找到完整代码块,已跳过自动更新");
} else {
// 多段代码无法判断该用哪一段,直接替换会把脚本换成片段
window.utools.showNotification(
`回复里有 ${codes.length} 个代码块,已跳过自动更新,请用代码块右上角按钮选择替换`
);
}
},
extractCode(content = "") {
// 推理模型的思考内容里常有代码片段,先剔除,避免取错
const answer = content.replace(/<think>[\s\S]*?<\/think>/g, "");
const blocks = [
extractCodes(content = "") {
// 只取最后一个 </think> 之后的内容:思考里常有代码片段,何况流式标记可能残缺
// (没有 <think> 开头但有一个 </think> 收尾),照搬全篇会把思考里的片段当成结果
const answer = content
.split(/<\/think(?:ing)?>/i)
.pop()
.replace(/<think(?:ing)?>[\s\S]*$/i, "");
return [
...answer.matchAll(
/^[ \t]*```[ \t]*[^\s`]*[ \t]*\r?\n([\s\S]*?)\r?\n?[ \t]*```/gm
),
];
// 取最后一个完整代码块:模型常见“先给片段,再给完整代码”
return blocks.length ? blocks[blocks.length - 1][1] : "";
].map((match) => match[1]);
},
stopStreaming() {
this.streamingResponse = false;
+10 -1
View File
@@ -411,7 +411,16 @@ export default defineComponent({
},
updateEditorValue(type, value) {
if (type === "replace") {
this.codeEditor.setValue(value);
// 走 executeEdits 而不是 setValue,保留撤销记录,AI 更新错了可以 Ctrl+Z 恢复
this.codeEditor.pushUndoStop();
this.codeEditor.executeEdits("ai-assistant", [
{
range: this.codeEditor.getModel().getFullModelRange(),
text: value,
forceMoveMarkers: true,
},
]);
this.codeEditor.pushUndoStop();
} else if (type === "insert") {
this.replaceEditorSelection(value);
}