diff --git a/plugin/lib/ai.js b/plugin/lib/ai.js
index 6eb3cd7..8b59b46 100644
--- a/plugin/lib/ai.js
+++ b/plugin/lib/ai.js
@@ -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 {
diff --git a/plugin/lib/dialog/service.js b/plugin/lib/dialog/service.js
index 836aaf1..ed25cad 100644
--- a/plugin/lib/dialog/service.js
+++ b/plugin/lib/dialog/service.js
@@ -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,
};
diff --git a/plugin/lib/quickcommand.js b/plugin/lib/quickcommand.js
index b8a371d..9ad9323 100644
--- a/plugin/lib/quickcommand.js
+++ b/plugin/lib/quickcommand.js
@@ -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);
},
diff --git a/plugin/lib/quickcomposer/windows/monitor.js b/plugin/lib/quickcomposer/windows/monitor.js
index f896a6c..f7b50c9 100644
--- a/plugin/lib/quickcomposer/windows/monitor.js
+++ b/plugin/lib/quickcomposer/windows/monitor.js
@@ -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();
diff --git a/src/components/ai/AIAssistantSideBar.vue b/src/components/ai/AIAssistantSideBar.vue
index 77f2478..7a1f0ed 100644
--- a/src/components/ai/AIAssistantSideBar.vue
+++ b/src/components/ai/AIAssistantSideBar.vue
@@ -91,7 +91,9 @@
:icon="streamingResponse ? 'stop' : 'send'"
@click="handleSubmit"
>
- Enter 发送,Shift+Enter 换行
+
+ Enter 发送,Shift+Enter 换行
+
@@ -183,8 +185,7 @@ export default defineComponent({
},
this.selectedApi,
{
- showLoadingBar: false,
- stream: true,
+ showProcessBar: false,
onStream: (text, controller, done) => {
this.currentRequest = controller;
if (text) {
@@ -251,7 +252,8 @@ export default defineComponent({
const languageMap = {
quickcommand: "NodeJS",
javascript: "NodeJS",
- cmd: "bat",
+ cmd: "windows 批处理脚本",
+ shell: "liunx shell脚本",
};
const commonInstructions = `请根据我的需求编写${languageMap[language]}代码,并请遵循以下原则:
- 编写简洁、可读性强的代码
@@ -275,7 +277,7 @@ export default defineComponent({
languageSpecific[language.toLowerCase()] || "";
const lastInstructions =
- "\n请直接给我代码,任何情况下都不需要做解释和说明";
+ "\n请直接给我MARKDOWN格式的代码,任何情况下都不需要做解释和说明";
return commonInstructions + specificInstructions + lastInstructions;
},
@@ -306,13 +308,13 @@ export default defineComponent({
},
];
- if (this.submitDocs) {
+ if (this.submitDocs && this.language === "quickcommand") {
const docs = this.getLanguageDocs(this.language);
presetContext.push(
{
role: "user",
- content: `你现在使用的是一种特殊的环境,支持uTools和quickcommand两种特殊的接口,请优先使用uTools和quickcommand接口解决需求`,
+ content: `你现在使用的是一种特殊的环境,支持uTools和quickcommand两种特殊的接口,请优先使用uTools和quickcommand接口解决需求,然后再使用当前语言通用的解决方案`,
},
{
role: "assistant",
diff --git a/src/js/composer/commands/uiCommands.js b/src/js/composer/commands/uiCommands.js
index 2981351..413727a 100644
--- a/src/js/composer/commands/uiCommands.js
+++ b/src/js/composer/commands/uiCommands.js
@@ -446,8 +446,9 @@ export const uiCommands = {
width: 4,
},
value: {
- label: "初始进度值",
+ label: "初始进度值(0-100)",
component: "VariableInput",
+ placeholder: "留空则显示加载动画",
disableToggleType: true,
width: 4,
},
@@ -486,7 +487,7 @@ export const uiCommands = {
defaultValue: {
title: newVarInputVal("str", "进度"),
text: newVarInputVal("str", "处理中..."),
- value: newVarInputVal("var", "0"),
+ value: newVarInputVal("var"),
position: "bottom-right",
onClose: newVarInputVal("var"),
onPause: newVarInputVal("var"),
@@ -504,8 +505,9 @@ export const uiCommands = {
component: "OptionEditor",
options: {
value: {
- label: "进度值",
+ label: "进度值(0-100)",
component: "VariableInput",
+ placeholder: "留空则显示加载动画",
width: 4,
disableToggleType: true,
},
@@ -522,7 +524,7 @@ export const uiCommands = {
},
},
defaultValue: {
- value: newVarInputVal("var", "0"),
+ value: newVarInputVal("var", "100"),
text: newVarInputVal("str"),
complete: false,
},
@@ -537,65 +539,6 @@ export const uiCommands = {
},
],
},
- {
- value: "quickcommand.showLoadingBar",
- label: "显示载入界面",
- neverHasOutput: true,
- asyncMode: "await",
- config: [
- {
- component: "OptionEditor",
- options: {
- text: {
- label: "文本",
- component: "VariableInput",
- width: 4,
- },
- position: {
- label: "位置",
- component: "QSelect",
- width: 4,
- options: [
- { label: "屏幕左上角", value: "top-left" },
- { label: "屏幕右上角", value: "top-right" },
- { label: "屏幕左下角", value: "bottom-left" },
- { label: "屏幕右下角", value: "bottom-right" },
- ],
- },
- onClose: {
- label: "关闭按钮回调函数",
- component: "VariableInput",
- disableToggleType: true,
- width: 4,
- },
- },
- defaultValue: {
- text: newVarInputVal("str", "加载中..."),
- position: "bottom-right",
- onClose: newVarInputVal("var"),
- },
- },
- ],
- outputs: {
- label: "载入条对象",
- suggestName: "loadingBar",
- },
- },
- {
- value: "quickcommand.closeLoadingBar",
- label: "关闭载入界面",
- neverHasOutput: true,
- config: [
- {
- label: "载入条对象",
- component: "VariableInput",
- placeholder: "不传则关闭最近的载入条",
- width: 12,
- defaultValue: newVarInputVal("var"),
- disableToggleType: true,
- },
- ],
- },
{
value: "utools.showOpenDialog",
label: "文件选择框",
diff --git a/src/plugins/monaco/types/quickcommand.api.d.ts b/src/plugins/monaco/types/quickcommand.api.d.ts
index 2f66d6f..76eeb17 100644
--- a/src/plugins/monaco/types/quickcommand.api.d.ts
+++ b/src/plugins/monaco/types/quickcommand.api.d.ts
@@ -750,7 +750,7 @@ interface quickcommandApi {
* 显示一个带有暂停、恢复、关闭回调功能的进度条,支持动态更新进度
* @param {object} options - 配置选项
* @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] - 暂停按钮点击时的回调函数,必须和onResume一起配置
@@ -758,13 +758,19 @@ interface quickcommandApi {
* @returns {Promise<{id: number, close: Function}>} 返回进度条对象
*
* ```js
- * // 基本使用
+ * // 显示进度条
* const processBar = await quickcommand.showProcessBar({
* text: "正在下载文件...",
* value: 0,
* position: "bottom-right"
* });
*
+ * // 显示加载动画
+ * const processBar = await quickcommand.showProcessBar({
+ * text: "正在加载...",
+ * position: "bottom-right"
+ * });
+ *
* // 带暂停/恢复,关闭回调功能
* let isPaused = false;
* const processBar = await quickcommand.showProcessBar({
@@ -824,18 +830,23 @@ interface quickcommandApi {
/**
* 更新进度条的进度
* @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 - 进度条对象,如果不传入则使用上一次创建的进度条
*
* ```js
- * // 使用最近创建的进度条
+ * // 更新进度
* quickcommand.updateProcessBar({
* value: 50,
* text: "已完成50%"
* });
*
+ * // 切换为加载动画
+ * quickcommand.updateProcessBar({
+ * text: "正在加载..."
+ * });
+ *
* // 使用指定的进度条
* quickcommand.updateProcessBar({
* value: 50,
@@ -852,7 +863,7 @@ interface quickcommandApi {
*/
updateProcessBar(
options: {
- value: number;
+ value?: number;
text?: string;
complete?: boolean;
},
@@ -862,71 +873,29 @@ interface quickcommandApi {
}
): void;
- /**
- * 显示一个循环动画的加载条
- * @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}>} 返回加载条对象
- *
- * ```js
- * // 基本使用
- * const loadingBar = await quickcommand.showLoadingBar({
- * text: "正在加载...",
- * position: "bottom-right"
- * });
- *
- * // 带关闭回调
- * const loadingBar = await quickcommand.showLoadingBar({
- * text: "正在加载...",
- * onClose: () => {
- * console.log("用户关闭了加载条");
- * }
- * });
- *
- * // 手动关闭
- * loadingBar.close();
- * // 或者
- * quickcommand.closeLoadingBar();
- * ```
- */
- showLoadingBar(options?: {
- text?: string;
- position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
- onClose?: () => void;
- }): Promise<{
- id: number;
- close: () => void;
- }>;
-
- /**
- * 关闭加载条
- * @param {{id: number, close: Function}|undefined} loadingBar - 加载条对象,如果不传入则关闭上一次创建的加载条
- *
- * ```js
- * // 关闭最近创建的加载条
- * quickcommand.closeLoadingBar();
- *
- * // 关闭指定的加载条
- * quickcommand.closeLoadingBar(loadingBar);
- * ```
- */
- closeLoadingBar(loadingBar?: { id: number; close: () => void }): void;
-
/**
* 与 AI 进行问答
* @param content 对话内容
* @param content.prompt 提示词
* @param content.role 预设角色
- * @param apiConfig API配置
+ * @param apiConfig API配置,不传或传入null则使用用户配置的第一个API配置
* @param apiConfig.apiType 模型类型:openai/ollama
* @param apiConfig.apiUrl API地址
* @param apiConfig.apiToken API令牌(仅 OpenAI 需要)
* @param apiConfig.model 模型名称
* @param options 其他选项
- * @param options.showLoadingBar 是否显示加载条
- * @example
+ * @param options.showProcessBar 是否显示进度条
+ * @param options.onStream 流式请求回调
+ *
+ *
+ * ```js
+ * // 不传apiConfig时,需在配置页面-右下角菜单-AI配置中进行配置
+ * const response = await quickcommand.askAI(
+ * {
+ * prompt: "你好",
+ * }
+ * );
+ *
* // OpenAI 示例
* const response = await quickcommand.askAI(
* {
@@ -939,9 +908,10 @@ interface quickcommandApi {
* model: "gpt-3.5-turbo"
* }
* );
+ * console.log(response);
*
- * // Ollama 示例
- * const response = await quickcommand.askAI(
+ * // Ollama 示例 (流式回调)
+ * await quickcommand.askAI(
* {
* prompt: "查找进程名为chrome的进程并关闭",
* role: "shell"
@@ -950,8 +920,20 @@ interface quickcommandApi {
* apiType: "ollama",
* apiUrl: "http://localhost:11434",
* model: "qwen2.5:32b"
+ * },
+ * {
+ * onStream: (chunk, controller, isDone) => {
+ * console.log(chunk);
+ * if (某个特定条件,中断请求) {
+ * controller.abort();
+ * }
+ * if (isDone) {
+ * console.log("流式请求完成");
+ * }
+ * }
* }
* );
+ * ```
*/
askAI(
content: {
@@ -960,7 +942,8 @@ interface quickcommandApi {
/** 预设角色 */
role?: "translate" | "shell" | "summarize";
},
- apiConfig: {
+ /** API配置,不传或传入null则使用用户配置的第一个API配置 */
+ apiConfig?: {
/** 模型类型:openai/ollama */
apiType: "openai" | "ollama";
/** API地址 */
@@ -971,8 +954,14 @@ interface quickcommandApi {
model: string;
},
options?: {
- /** 是否显示加载条, 默认 true */
- showLoadingBar?: boolean;
+ /** 是否显示进度条, 默认 true */
+ showProcessBar?: boolean;
+ /** 流式请求回调 */
+ onStream?: (
+ chunk: string,
+ controller: AbortController,
+ isDone: boolean
+ ) => void;
}
): Promise<{
/** 是否成功 */