mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2026-10-25 17:36:37 +08:00
修复AI助手把思考内容当成代码写入的问题
This commit is contained in:
1 parent
f092fb655c
commit
52c2ad4846
3 files changed
+52
-32
No files matched your search
+24
-20
@@ -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
|
||||
);
|
||||
|
||||
// 如果请求被取消,返回取消状态
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user