diff --git a/plugin/lib/dialog/controller.js b/plugin/lib/dialog/controller.js index 0067691..a1e84be 100644 --- a/plugin/lib/dialog/controller.js +++ b/plugin/lib/dialog/controller.js @@ -361,12 +361,19 @@ document.addEventListener("DOMContentLoaded", () => { case "process": document.getElementById("process").style.display = "block"; document.body.classList.add("dialog-process"); + // 创建进度条 + const processBarInner = document.getElementById("process-bar-inner"); + if (config.isLoading) { + // 如果是加载条模式,使用动画效果 + processBarInner.className = "process-bar-loading"; + } else { + // 如果是进度条模式,设置初始进度 + processBarInner.className = "process-bar-inner"; + processBarInner.style.width = `${config.value}%`; + } - // 设置初始文本和进度 + // 设置初始文本 document.getElementById("process-text").textContent = config.text; - document.getElementById( - "process-bar-inner" - ).style.width = `${config.value}%`; // 如果需要显示暂停按钮 if (config.showPause) { @@ -393,8 +400,13 @@ document.addEventListener("DOMContentLoaded", () => { // 监听进度条更新事件 ipcRenderer.on("update-process", (event, data) => { const { value, text } = data; - if (typeof value === "number") { - document.getElementById("process-bar-inner").style.width = `${value}%`; + const processBarInner = document.getElementById("process-bar-inner"); + if ( + processBarInner && + processBarInner.className === "process-bar-inner" && + typeof value === "number" + ) { + processBarInner.style.width = `${value}%`; } if (text) { document.getElementById("process-text").textContent = text; diff --git a/plugin/lib/dialog/service.js b/plugin/lib/dialog/service.js index 280f8a9..836aaf1 100644 --- a/plugin/lib/dialog/service.js +++ b/plugin/lib/dialog/service.js @@ -1,6 +1,25 @@ const { ipcRenderer } = require("electron"); const os = require("os"); const { createBrowserWindow } = utools; + +const dialogPath = "lib/dialog/view.html"; +const preloadPath = "lib/dialog/controller.js"; + +const commonBrowserWindowOptions = { + resizable: false, + minimizable: false, + maximizable: false, + fullscreenable: false, + skipTaskbar: true, + alwaysOnTop: true, + frame: false, + movable: true, + webPreferences: { + preload: preloadPath, + devTools: utools.isDev(), + }, +}; + /** * 创建对话框窗口 * @param {object} config - 对话框配置 @@ -9,9 +28,6 @@ const { createBrowserWindow } = utools; */ const createDialog = (config, customDialogOptions = {}) => { return new Promise((resolve) => { - const dialogPath = "lib/dialog/view.html"; - const preloadPath = "lib/dialog/controller.js"; - // linux 和 win32 都使用 win32 的样式 const platform = os.platform() === "darwin" ? "darwin" : "win32"; @@ -26,18 +42,8 @@ const createDialog = (config, customDialogOptions = {}) => { title: config.title || "对话框", width: dialogWidth, height: 80, - resizable: false, - minimizable: false, - maximizable: false, - fullscreenable: false, - skipTaskbar: true, - alwaysOnTop: true, - frame: false, opacity: 0, - webPreferences: { - preload: preloadPath, - devTools: utools.isDev(), - }, + ...commonBrowserWindowOptions, ...customDialogOptions, // 合并自定义选项 }; @@ -309,7 +315,6 @@ let lastProcessBar = null; */ const showProcessBar = async (options = {}) => { const { - title = "进度", text = "处理中...", value = 0, position = "bottom-right", @@ -335,26 +340,15 @@ const showProcessBar = async (options = {}) => { return new Promise((resolve) => { const UBrowser = createBrowserWindow( - "lib/dialog/view.html", + dialogPath, { - title, width: windowWidth, height: windowHeight, x, y, - resizable: false, - minimizable: false, - maximizable: false, - fullscreenable: false, - skipTaskbar: true, - alwaysOnTop: true, - frame: false, opacity: 0, - movable: true, - webPreferences: { - preload: "lib/dialog/controller.js", - devTools: utools.isDev(), - }, + focusable: false, + ...commonBrowserWindowOptions, }, () => { const windowId = UBrowser.webContents.id; @@ -362,7 +356,6 @@ const showProcessBar = async (options = {}) => { // 发送配置到子窗口 ipcRenderer.sendTo(windowId, "dialog-config", { type: "process", - title, text, value, isDark: utools.isDarkColors(), @@ -443,10 +436,103 @@ const updateProcessBar = (options = {}, processBar = null) => { ipcRenderer.sendTo(processBar.id, "update-process", { value, text }); if (complete) { - processBar.close(); + setTimeout(() => { + processBar.close(); + }, 600); } }; +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, @@ -457,4 +543,6 @@ module.exports = { showSystemWaitButton, showProcessBar, updateProcessBar, + showLoadingBar, + closeLoadingBar, }; diff --git a/plugin/lib/dialog/style.css b/plugin/lib/dialog/style.css index ad2619a..fc1ca30 100644 --- a/plugin/lib/dialog/style.css +++ b/plugin/lib/dialog/style.css @@ -614,6 +614,7 @@ textarea:focus { background: rgba(255, 255, 255, 0.3); border-radius: 2px; overflow: hidden; + position: relative; } .process-bar-inner { @@ -624,6 +625,25 @@ textarea:focus { transition: width 0.3s ease; } +@keyframes loading-bar-animation { + 0% { + left: -80px; + } + + 100% { + left: calc(100% + 80px); + } +} + +.process-bar-loading { + position: absolute; + width: 80px; + height: 100%; + background: white; + border-radius: 2px; + animation: loading-bar-animation 3s infinite linear; +} + /* 进度条按钮容器 */ .process-buttons { position: absolute; diff --git a/plugin/lib/dialog/view.html b/plugin/lib/dialog/view.html index 3bd9c3e..b4c8ad3 100644 --- a/plugin/lib/dialog/view.html +++ b/plugin/lib/dialog/view.html @@ -60,7 +60,7 @@
diff --git a/plugin/lib/quickcomposer/ai/chat.js b/plugin/lib/quickcomposer/ai/chat.js index ee89525..f2d18cc 100644 --- a/plugin/lib/quickcomposer/ai/chat.js +++ b/plugin/lib/quickcomposer/ai/chat.js @@ -105,8 +105,27 @@ async function chat(apiConfig, content) { throw new Error("不支持的模型类型"); } + const loadingBar = await quickcommand.showLoadingBar({ + text: "AI思考中...", + onClose: () => { + // 取消请求 + if (source) { + source.cancel("操作已取消"); + } + }, + }); + + // 创建取消令牌 + const CancelToken = axios.CancelToken; + const source = CancelToken.source(); + // 发送请求 - const response = await axios.post(url, requestData, config); + const response = await axios.post(url, requestData, { + ...config, + cancelToken: source.token, + }); + + loadingBar.close(); // 解析不同模型的响应 let result; @@ -129,6 +148,14 @@ async function chat(apiConfig, content) { result, }; } catch (error) { + // 如果是用户取消的请求,返回特定的错误信息 + if (axios.isCancel(error)) { + return { + success: false, + error: "请求已取消", + cancelled: true, + }; + } return { success: false, error: error.response?.data?.error?.message || error.message, diff --git a/plugin/lib/quickcomposer/video/ffmpeg.js b/plugin/lib/quickcomposer/video/ffmpeg.js index 2402d91..efb3ef7 100644 --- a/plugin/lib/quickcomposer/video/ffmpeg.js +++ b/plugin/lib/quickcomposer/video/ffmpeg.js @@ -18,7 +18,6 @@ async function runFFmpeg(args, options = {}) { return; } const { - title = "FFmpeg处理", text = "处理中...", position = "bottom-right", onPause, @@ -31,7 +30,6 @@ async function runFFmpeg(args, options = {}) { // 显示进度条 const processBar = isShowProcessBar ? await quickcommand.showProcessBar({ - title, text, value: 0, position, diff --git a/plugin/lib/quickcomposer/windows/monitor.js b/plugin/lib/quickcomposer/windows/monitor.js index 9718904..f896a6c 100644 --- a/plugin/lib/quickcomposer/windows/monitor.js +++ b/plugin/lib/quickcomposer/windows/monitor.js @@ -1,13 +1,29 @@ const { runCsharpFeature } = require("../../csharp"); +const child_process = require("child_process"); + +const stopMonitor = () => { + child_process.exec("taskkill /f /im monitor.exe"); +}; // 监控剪贴板变化 const watchClipboard = async function () { const args = ["-type", "clipboard", "-once"]; - const result = await runCsharpFeature("monitor", args); - if (result && result.startsWith("Error:")) { - throw new Error(result.substring(7)); + const loadingBar = await quickcommand.showLoadingBar({ + text: "等待剪贴板变化...", + onClose: () => { + stopMonitor(); + }, + }); + try { + const result = await runCsharpFeature("monitor", args); + loadingBar.close(); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return JSON.parse(result); + } catch (error) { + return {}; } - return JSON.parse(result); }; // 监控文件系统变化 @@ -28,11 +44,23 @@ const watchFileSystem = async function (watchPath, options = {}) { args.push("-recursive", "false"); } - const result = await runCsharpFeature("monitor", args); - if (result && result.startsWith("Error:")) { - throw new Error(result.substring(7)); + const loadingBar = await quickcommand.showLoadingBar({ + text: "等待文件变化...", + onClose: () => { + stopMonitor(); + }, + }); + + try { + const result = await runCsharpFeature("monitor", args); + loadingBar.close(); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return JSON.parse(result); + } catch (error) { + return {}; } - return JSON.parse(result); }; module.exports = { diff --git a/src/App.vue b/src/App.vue index 947306e..35272e2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -26,7 +26,6 @@ export default defineComponent({ commandManager: useCommandManager(), setCssVar: setCssVar, programs: programmings, - isRunningCommand: false, profile: defaultProfile.common, nativeProfile: defaultProfile.native, utools: utoolsFull, diff --git a/src/components/CommandRunResult.vue b/src/components/CommandRunResult.vue index 7756373..2cc4199 100644 --- a/src/components/CommandRunResult.vue +++ b/src/components/CommandRunResult.vue @@ -126,7 +126,6 @@ export default { if (command.program === "quickcomposer") { command.cmd = generateFlowsCode(command.flows); } - this.$root.isRunningCommand = true; this.needTempPayload && (await this.getTempPayload(command)); // 如果命令包含子输入框,则设置子输入框 if (command.cmd.includes("{{subinput")) return this.setSubInput(command); @@ -292,7 +291,6 @@ export default { }; }, handleResult(stdout, stderr, options) { - this.$root.isRunningCommand = false; if (stderr) { return options.earlyExit ? alert(stderr) @@ -302,7 +300,6 @@ export default { }, // 显示运行结果 async showRunResult(content, isSuccess) { - this.$root.isRunningCommand = false; content = await this.handleContent(content); this.runResultStatus = isSuccess; this.runResult = this.runResult.concat(content); diff --git a/src/components/composer/CommandComposer.vue b/src/components/composer/CommandComposer.vue index 148c36b..da1ff65 100644 --- a/src/components/composer/CommandComposer.vue +++ b/src/components/composer/CommandComposer.vue @@ -17,9 +17,6 @@ /> -