quickcommand.askAI支持流式响应,支持使用默认api配置

This commit is contained in:
fofolee
2025-02-21 11:31:45 +08:00
parent 0090442101
commit 1e5a4c1ff5
7 changed files with 143 additions and 265 deletions

View File

@@ -72,7 +72,7 @@ function buildRequestConfig(apiConfig) {
}
// 构建请求数据
function buildRequestData(content, apiConfig, stream = false) {
function buildRequestData(content, apiConfig) {
const { model } = apiConfig;
const { prompt, role, context = [] } = content;
const rolePrompt = ROLE_PROMPTS[role] || role;
@@ -105,7 +105,7 @@ function buildRequestData(content, apiConfig, stream = false) {
return {
model,
messages,
stream,
stream: true,
};
}
@@ -222,7 +222,7 @@ async function handleStreamResponse(response, apiConfig, controller, onStream) {
reader.releaseLock();
}
return { success: true, result: "流式请求完成" };
return { success: true };
}
/**
@@ -234,28 +234,24 @@ async function handleStreamResponse(response, apiConfig, controller, onStream) {
*/
async function chat(content, apiConfig, options = {}) {
try {
const { showLoadingBar = true, stream = false, onStream } = options;
const { showProcessBar = true, onStream = () => {} } = options;
// 验证必要参数
if (!apiConfig.apiUrl || !content.prompt || !apiConfig.model) {
throw new Error("API地址、模型名称和提示词不能为空");
}
if (stream && !onStream) {
throw new Error("使用流式请求时必须提供onStream回调函数");
}
// 构建请求URL和配置
const url = buildApiUrl(
apiConfig.apiUrl,
API_ENDPOINTS[apiConfig.apiType].chat
);
const config = buildRequestConfig(apiConfig, stream);
const requestData = buildRequestData(content, apiConfig, stream);
const config = buildRequestConfig(apiConfig);
const requestData = buildRequestData(content, apiConfig);
// 显示加载
const loadingBar = showLoadingBar
? await quickcommand.showLoadingBar({
// 显示进度
const processBar = showProcessBar
? await quickcommand.showProcessBar({
text: "AI思考中...",
onClose: () => {
if (controller) {
@@ -265,6 +261,26 @@ async function chat(content, apiConfig, options = {}) {
})
: null;
// 用于收集完整响应
let fullResponse = "";
// 包装 onStream 回调以收集完整响应并更新进度条
const streamHandler = (chunk, controller, isDone) => {
if (!isDone) {
fullResponse += chunk;
// 更新进度条显示最新的响应内容
if (processBar) {
quickcommand.updateProcessBar(
{
text: fullResponse, // 只显示最后100个字符避免内容过长
},
processBar
);
}
}
onStream(chunk, controller, isDone);
};
// 统一使用 fetch 处理请求
const controller = new AbortController();
const response = await fetch(url, {
@@ -278,24 +294,35 @@ async function chat(content, apiConfig, options = {}) {
throw new Error(`HTTP error! status: ${response.status}`);
}
let result;
if (stream) {
result = await handleStreamResponse(
response,
apiConfig,
controller,
onStream
);
} else {
const responseData = await response.json();
result = {
success: true,
result: parseResponse({ data: responseData }, apiConfig.apiType),
};
const result = await handleStreamResponse(
response,
apiConfig,
controller,
streamHandler
);
// 如果请求被取消,返回取消状态
if (!result.success) {
processBar?.close();
return result;
}
loadingBar?.close();
return result;
// 完成时更新进度条并关闭
if (processBar) {
quickcommand.updateProcessBar(
{
text: "AI响应完成",
complete: true,
},
processBar
);
}
// 返回完整的响应内容
return {
success: true,
result: fullResponse,
};
} catch (error) {
if (error.name === "AbortError") {
return {

View File

@@ -305,7 +305,7 @@ let lastProcessBar = null;
* @param {object} options - 配置选项
* @param {string} [options.title="进度"] - 对话框标题
* @param {string} [options.text="处理中..."] - 进度条上方的文本
* @param {number} [options.value=0] - 初始进度值(0-100)
* @param {number} [options.value] - 初始进度值(0-100),不传则显示加载动画
* @param {string} [options.position="bottom-right"] - 进度条位置可选值top-left, top-right, bottom-left, bottom-right
* @param {Function} [options.onClose] - 关闭按钮点击时的回调函数
* @param {Function} [options.onPause] - 暂停按钮点击时的回调函数
@@ -316,7 +316,7 @@ let lastProcessBar = null;
const showProcessBar = async (options = {}) => {
const {
text = "处理中...",
value = 0,
value,
position = "bottom-right",
onClose,
onPause,
@@ -328,7 +328,7 @@ const showProcessBar = async (options = {}) => {
throw new Error("onPause 和 onResume 必须同时配置");
}
const windowWidth = 350;
const windowWidth = 250;
const windowHeight = 60;
// 计算窗口位置
@@ -357,10 +357,11 @@ const showProcessBar = async (options = {}) => {
ipcRenderer.sendTo(windowId, "dialog-config", {
type: "process",
text,
value,
value: value === undefined ? 0 : value,
isDark: utools.isDarkColors(),
platform: process.platform,
showPause: Boolean(onPause && onResume),
isLoading: value === undefined, // 当不传value时显示加载动画
});
// 监听窗口准备就绪
@@ -410,7 +411,7 @@ const showProcessBar = async (options = {}) => {
/**
* 更新进度条的进度
* @param {object} options - 配置选项
* @param {number} options.value - 新的进度值(0-100)
* @param {number} [options.value] - 新的进度值(0-100),不传则显示加载动画
* @param {string} [options.text] - 新的进度文本
* @param {boolean} [options.complete] - 是否完成并关闭进度条
* @param {{id: number, close: Function}|undefined} processBar - 进度条对象, 如果不传入则使用上一次创建的进度条
@@ -433,7 +434,11 @@ const updateProcessBar = (options = {}, processBar = null) => {
}
const { value, text, complete } = options;
ipcRenderer.sendTo(processBar.id, "update-process", { value, text });
ipcRenderer.sendTo(processBar.id, "update-process", {
value,
text,
isLoading: value === undefined,
});
if (complete) {
setTimeout(() => {
@@ -442,97 +447,6 @@ const updateProcessBar = (options = {}, processBar = null) => {
}
};
let lastLoadingBar = null;
/**
* 显示一个加载条对话框
* @param {object} options - 配置选项
* @param {string} [options.text="加载中..."] - 加载条上方的文本
* @param {string} [options.position="bottom-right"] - 加载条位置可选值top-left, top-right, bottom-left, bottom-right
* @param {Function} [options.onClose] - 关闭按钮点击时的回调函数
* @returns {Promise<{id: number, close: Function}>} 返回加载条窗口ID和关闭函数
*/
const showLoadingBar = async (options = {}) => {
const { text = "加载中...", position = "bottom-right", onClose } = options;
const windowWidth = 250;
const windowHeight = 60;
// 计算窗口位置
const { x, y } = calculateWindowPosition({
position,
width: windowWidth,
height: windowHeight,
});
return new Promise((resolve) => {
const UBrowser = createBrowserWindow(
dialogPath,
{
width: windowWidth,
height: windowHeight,
x,
y,
opacity: 0,
focusable: false,
...commonBrowserWindowOptions,
},
() => {
const windowId = UBrowser.webContents.id;
// 发送配置到子窗口
ipcRenderer.sendTo(windowId, "dialog-config", {
type: "process",
text,
value: 0,
isDark: utools.isDarkColors(),
platform: process.platform,
isLoading: true, // 标记为加载条模式
});
// 监听窗口准备就绪
ipcRenderer.once("dialog-ready", () => {
UBrowser.setOpacity(1);
});
// 监听对话框结果
ipcRenderer.once("dialog-result", (event, result) => {
if (result === "close" && typeof onClose === "function") {
onClose();
}
UBrowser.destroy();
});
const loadingBar = {
id: windowId,
close: () => {
if (typeof onClose === "function") {
onClose();
}
lastLoadingBar = null;
UBrowser.destroy();
},
};
lastLoadingBar = loadingBar;
// 返回窗口ID和关闭函数
resolve(loadingBar);
}
);
});
};
const closeLoadingBar = (loadingBar) => {
if (!loadingBar) {
if (!lastLoadingBar) {
throw new Error("没有找到已创建的加载条");
}
loadingBar = lastLoadingBar;
}
loadingBar.close();
};
module.exports = {
showSystemMessageBox,
showSystemInputBox,
@@ -543,6 +457,4 @@ module.exports = {
showSystemWaitButton,
showProcessBar,
updateProcessBar,
showLoadingBar,
closeLoadingBar,
};

View File

@@ -8,6 +8,8 @@ const axios = require("axios");
const marked = require("marked");
const { chat, getModels } = require("./ai");
const { dbStorage } = utools;
window.getModelsFromAiApi = getModels;
const systemDialog = require("./dialog/service");
@@ -197,6 +199,9 @@ const quickcommand = {
},
askAI: async function (content, apiConfig, options) {
if (window.lodashM.isEmpty(apiConfig)) {
apiConfig = dbStorage.getItem("cfg_aiConfigs")?.[0] || {};
}
return await chat(content, apiConfig, options);
},

View File

@@ -8,7 +8,7 @@ const stopMonitor = () => {
// 监控剪贴板变化
const watchClipboard = async function () {
const args = ["-type", "clipboard", "-once"];
const loadingBar = await quickcommand.showLoadingBar({
const loadingBar = await quickcommand.showProcessBar({
text: "等待剪贴板变化...",
onClose: () => {
stopMonitor();
@@ -44,7 +44,7 @@ const watchFileSystem = async function (watchPath, options = {}) {
args.push("-recursive", "false");
}
const loadingBar = await quickcommand.showLoadingBar({
const loadingBar = await quickcommand.showProcessBar({
text: "等待文件变化...",
onClose: () => {
stopMonitor();