const DOMPurify = require("dompurify"); const marked = require("marked"); window.aiResponseParser = (content) => { const markedContent = marked.parse(content.trim()); const processedContent = markedContent .replace("

", "

") .replace("

", "

") .replace("\n\n", ""); const purifiedContent = DOMPurify.sanitize(processedContent, { ADD_TAGS: ["think"], }); return purifiedContent; }; // 支持的模型类型 const API_TYPES = { OPENAI: "openai", OLLAMA: "ollama", }; // 角色提示词 const ROLE_PROMPTS = { // 翻译 translate: `你是一名翻译专家,请将我给你的内容进行翻译,要求: 1. 无论给的内容长短,请直接翻译,不要进行任何解释 2. 提供中文时,翻译成地道的英文,符合英文的表达习惯 3. 提供英文时,翻译成地道的中文,符合中文的表达习惯 4. 保持原文的专业性和准确性 5. 保持原文的段落格式 6. 不要输出原文 7. 不要使用markdown格式,请直接输出翻译后的内容 `, // 生成SHELL命令 shell: `你是一名shell命令专家,请根据我的描述生成 shell 命令,要求: 1. 命令应当简洁高效 2. 优先使用常见的命令行工具 3. 确保命令的安全性和可靠性 4. 对于复杂操作,添加注释说明 5. 如果需要多个命令,使用 && 连接或使用脚本格式 6. 直接输出命令,不要输出任何解释,不要使用markdown格式 `, // 总结 summarize: `你是一名总结专家,请总结我给你的内容的要点,要求: 1. 提取最重要和最有价值的信息 2. 使用简洁的语言 3. 按重要性排序 4. 保持逻辑性和连贯性 5. 请将所有内容放在一个段落内 6. 不要输出原文 7. 不要使用markdown格式 `, // 润色 polish: `你是一名文字润色专家,请对我给你的内容进行润色,要求: 1. 保持原文的核心意思不变 2. 使用更优美、专业的表达方式 3. 改善语言流畅度和可读性 4. 纠正语法错误和不恰当的表达 5. 保持原文的语言风格(中文/英文) 6. 保持原文的段落格式 7. 直接输出润色后的内容,不要解释修改原因 8. 不要使用markdown格式 `, // 扩写 expand: `你是一名文字扩写专家,请对我给你的内容进行扩写,要求: 1. 在保持原意的基础上扩充内容 2. 添加相关的细节和例子 3. 使用生动的描述和丰富的表达 4. 确保扩写内容逻辑连贯 5. 适度扩写,避免过度冗长 6. 保持语言风格的一致性 7. 直接输出扩写后的内容,不要解释扩写原因 8. 不要使用markdown格式 `, }; // API URL 处理 const API_ENDPOINTS = { [API_TYPES.OPENAI]: { chat: "/v1/chat/completions", models: "/v1/models", }, [API_TYPES.OLLAMA]: { chat: "/api/chat", models: "/api/tags", }, }; // 构建API URL function buildApiUrl(baseUrl, endpoint) { if (!baseUrl.endsWith(endpoint)) { return baseUrl.replace(/\/?$/, endpoint); } return baseUrl; } // 构建请求配置 function buildRequestConfig(apiConfig) { const config = { headers: { "Content-Type": "application/json", }, }; if (apiConfig.apiType === API_TYPES.OPENAI && apiConfig.apiToken) { config.headers["Authorization"] = `Bearer ${apiConfig.apiToken}`; } return config; } // 构建请求数据 function buildRequestData(content, apiConfig) { const { model } = apiConfig; const { prompt, role, context = [] } = content; const rolePrompt = ROLE_PROMPTS[role] || role; const roleMessage = rolePrompt ? [ { role: "user", content: rolePrompt, }, ] : []; // 统一的消息格式处理 const messages = [ // 添加系统角色消息(如果有) ...roleMessage, // 添加上下文消息 ...context.map((msg) => ({ role: msg.role || "user", content: msg.content, })), // 添加当前用户消息 { role: "user", content: prompt, }, ]; return { model, messages, stream: true, }; } // 处理普通响应 function parseResponse(response, apiType) { if (apiType === API_TYPES.OPENAI) { if (!response.data.choices || !response.data.choices[0]) { throw new Error("OpenAI 响应格式错误"); } return response.data.choices[0].message.content; } else { if (!response.data.message) { throw new Error("Ollama 响应格式错误"); } return response.data.message.content; } } // 处理模型列表响应 function parseModelsResponse(response, apiType) { if (apiType === API_TYPES.OPENAI) { if (!response.data.data) { throw new Error("OpenAI 响应格式错误"); } return response.data.data.map((model) => model.id); } else { if (!response.data.models) { throw new Error("Ollama 响应格式错误"); } return response.data.models.map((model) => model.name); } } // 处理 OpenAI 流式响应 async function handleOpenAIStreamResponse(line, onStream) { if (line.startsWith("data:")) { const jsonStr = line.replace(/^data:[ ]*/, ""); if (jsonStr === "[DONE]") { onStream("", true); return; } const json = JSON.parse(jsonStr); const content = json.choices[0]?.delta?.content; if (content) { onStream(content, false); } } } // 处理 Ollama 流式响应 async function handleOllamaStreamResponse(line, onStream) { const json = JSON.parse(line); if (json.done) { onStream("", true); return; } if (json.message?.content) { onStream(json.message.content, false); } } // 处理流式响应 async function handleStreamResponse(response, apiConfig, onStream) { const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; try { while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { if (line.trim()) { try { if (apiConfig.apiType === API_TYPES.OPENAI) { await handleOpenAIStreamResponse(line, onStream); } else { await handleOllamaStreamResponse(line, onStream); } } catch (e) { console.error("解析响应失败:", e); } } } } // 处理剩余的缓冲区 if (buffer.trim()) { try { if (apiConfig.apiType === API_TYPES.OPENAI) { await handleOpenAIStreamResponse(buffer, onStream); } else { await handleOllamaStreamResponse(buffer, onStream); } } catch (e) { console.error("解析剩余响应失败:", e); } } } catch (error) { if (error.name === "AbortError") { return { success: false, error: "请求已取消", cancelled: true, }; } throw error; } finally { reader.releaseLock(); } return { success: true }; } /** * AI对话功能 * @param {Object} content - 对话内容参数 * @param {Object} apiConfig - API配置参数 * @param {Object} options - 其他选项 * @returns {Promise} 对话响应 */ async function chat(content, apiConfig, options = {}) { try { const { showProcessBar = true, onStream = () => {}, onFetch = () => {}, } = options; // 验证必要参数 if (!apiConfig.apiUrl || !content.prompt || !apiConfig.model) { throw new Error("API地址、模型名称和提示词不能为空"); } // 构建请求URL和配置 const url = buildApiUrl( apiConfig.apiUrl, API_ENDPOINTS[apiConfig.apiType].chat ); const config = buildRequestConfig(apiConfig); const requestData = buildRequestData(content, apiConfig); // 显示进度条 const processBar = showProcessBar ? await quickcommand.showProcessBar({ text: "AI思考中...", onClose: () => { if (controller) { controller.abort(); } }, }) : null; // 用于收集完整响应 let fullResponse = ""; // 包装 onStream 回调以收集完整响应并更新进度条 const streamHandler = (chunk, isDone) => { if (!isDone) { fullResponse += chunk; // 更新进度条显示最新的响应内容 if (processBar) { quickcommand.updateProcessBar( { text: window.aiResponseParser(fullResponse), }, processBar ); } } onStream(chunk, isDone); }; // 统一使用 fetch 处理请求 const controller = new AbortController(); onFetch(controller); const response = await fetch(url, { method: "POST", headers: config.headers, body: JSON.stringify(requestData), signal: controller.signal, }); if (!response.ok) { processBar?.close(); throw new Error(`HTTP error! status: ${response.status}`); } const result = await handleStreamResponse( response, apiConfig, streamHandler ); // 如果请求被取消,返回取消状态 if (!result.success) { processBar?.close(); return result; } // 完成时更新进度条并关闭 if (processBar) { quickcommand.updateProcessBar( { text: "AI响应完成", complete: true, }, processBar ); } // 返回完整的响应内容 return { success: true, result: fullResponse, }; } catch (error) { if (error.name === "AbortError") { return { success: false, error: "请求已取消", cancelled: true, }; } return { success: false, error: error.response?.data?.error?.message || error.message, }; } } /** * 获取API支持的模型列表 * @param {Object} apiConfig - API配置参数 * @returns {Promise} 模型列表响应 */ async function getModels(apiConfig) { try { if (!apiConfig.apiUrl) { throw new Error("API地址不能为空"); } const url = buildApiUrl( apiConfig.apiUrl, API_ENDPOINTS[apiConfig.apiType].models ); const config = buildRequestConfig(apiConfig); const response = await fetch(url, { method: "GET", headers: config.headers, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const responseData = await response.json(); return { success: true, result: parseModelsResponse({ data: responseData }, apiConfig.apiType), }; } catch (error) { return { success: false, error: error.response?.data?.error?.message || error.message, }; } } module.exports = { chat, getModels };