新增quickcommand.showLoadingBar,支持显示加载条

This commit is contained in:
fofolee 2025-02-17 22:38:19 +08:00
parent d7508c36a7
commit 3eca3b448e
14 changed files with 339 additions and 84 deletions

View File

@ -361,12 +361,19 @@ document.addEventListener("DOMContentLoaded", () => {
case "process": case "process":
document.getElementById("process").style.display = "block"; document.getElementById("process").style.display = "block";
document.body.classList.add("dialog-process"); 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-text").textContent = config.text;
document.getElementById(
"process-bar-inner"
).style.width = `${config.value}%`;
// 如果需要显示暂停按钮 // 如果需要显示暂停按钮
if (config.showPause) { if (config.showPause) {
@ -393,8 +400,13 @@ document.addEventListener("DOMContentLoaded", () => {
// 监听进度条更新事件 // 监听进度条更新事件
ipcRenderer.on("update-process", (event, data) => { ipcRenderer.on("update-process", (event, data) => {
const { value, text } = data; const { value, text } = data;
if (typeof value === "number") { const processBarInner = document.getElementById("process-bar-inner");
document.getElementById("process-bar-inner").style.width = `${value}%`; if (
processBarInner &&
processBarInner.className === "process-bar-inner" &&
typeof value === "number"
) {
processBarInner.style.width = `${value}%`;
} }
if (text) { if (text) {
document.getElementById("process-text").textContent = text; document.getElementById("process-text").textContent = text;

View File

@ -1,6 +1,25 @@
const { ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
const os = require("os"); const os = require("os");
const { createBrowserWindow } = utools; 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 - 对话框配置 * @param {object} config - 对话框配置
@ -9,9 +28,6 @@ const { createBrowserWindow } = utools;
*/ */
const createDialog = (config, customDialogOptions = {}) => { const createDialog = (config, customDialogOptions = {}) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const dialogPath = "lib/dialog/view.html";
const preloadPath = "lib/dialog/controller.js";
// linux 和 win32 都使用 win32 的样式 // linux 和 win32 都使用 win32 的样式
const platform = os.platform() === "darwin" ? "darwin" : "win32"; const platform = os.platform() === "darwin" ? "darwin" : "win32";
@ -26,18 +42,8 @@ const createDialog = (config, customDialogOptions = {}) => {
title: config.title || "对话框", title: config.title || "对话框",
width: dialogWidth, width: dialogWidth,
height: 80, height: 80,
resizable: false,
minimizable: false,
maximizable: false,
fullscreenable: false,
skipTaskbar: true,
alwaysOnTop: true,
frame: false,
opacity: 0, opacity: 0,
webPreferences: { ...commonBrowserWindowOptions,
preload: preloadPath,
devTools: utools.isDev(),
},
...customDialogOptions, // 合并自定义选项 ...customDialogOptions, // 合并自定义选项
}; };
@ -309,7 +315,6 @@ let lastProcessBar = null;
*/ */
const showProcessBar = async (options = {}) => { const showProcessBar = async (options = {}) => {
const { const {
title = "进度",
text = "处理中...", text = "处理中...",
value = 0, value = 0,
position = "bottom-right", position = "bottom-right",
@ -335,26 +340,15 @@ const showProcessBar = async (options = {}) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const UBrowser = createBrowserWindow( const UBrowser = createBrowserWindow(
"lib/dialog/view.html", dialogPath,
{ {
title,
width: windowWidth, width: windowWidth,
height: windowHeight, height: windowHeight,
x, x,
y, y,
resizable: false,
minimizable: false,
maximizable: false,
fullscreenable: false,
skipTaskbar: true,
alwaysOnTop: true,
frame: false,
opacity: 0, opacity: 0,
movable: true, focusable: false,
webPreferences: { ...commonBrowserWindowOptions,
preload: "lib/dialog/controller.js",
devTools: utools.isDev(),
},
}, },
() => { () => {
const windowId = UBrowser.webContents.id; const windowId = UBrowser.webContents.id;
@ -362,7 +356,6 @@ const showProcessBar = async (options = {}) => {
// 发送配置到子窗口 // 发送配置到子窗口
ipcRenderer.sendTo(windowId, "dialog-config", { ipcRenderer.sendTo(windowId, "dialog-config", {
type: "process", type: "process",
title,
text, text,
value, value,
isDark: utools.isDarkColors(), isDark: utools.isDarkColors(),
@ -443,10 +436,103 @@ const updateProcessBar = (options = {}, processBar = null) => {
ipcRenderer.sendTo(processBar.id, "update-process", { value, text }); ipcRenderer.sendTo(processBar.id, "update-process", { value, text });
if (complete) { 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 = { module.exports = {
showSystemMessageBox, showSystemMessageBox,
showSystemInputBox, showSystemInputBox,
@ -457,4 +543,6 @@ module.exports = {
showSystemWaitButton, showSystemWaitButton,
showProcessBar, showProcessBar,
updateProcessBar, updateProcessBar,
showLoadingBar,
closeLoadingBar,
}; };

View File

@ -614,6 +614,7 @@ textarea:focus {
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
border-radius: 2px; border-radius: 2px;
overflow: hidden; overflow: hidden;
position: relative;
} }
.process-bar-inner { .process-bar-inner {
@ -624,6 +625,25 @@ textarea:focus {
transition: width 0.3s ease; 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 { .process-buttons {
position: absolute; position: absolute;

View File

@ -60,7 +60,7 @@
</div> </div>
<div class="process-text" id="process-text"></div> <div class="process-text" id="process-text"></div>
<div class="process-bar"> <div class="process-bar">
<div class="process-bar-inner" id="process-bar-inner"></div> <div id="process-bar-inner"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -105,8 +105,27 @@ async function chat(apiConfig, content) {
throw new Error("不支持的模型类型"); 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; let result;
@ -129,6 +148,14 @@ async function chat(apiConfig, content) {
result, result,
}; };
} catch (error) { } catch (error) {
// 如果是用户取消的请求,返回特定的错误信息
if (axios.isCancel(error)) {
return {
success: false,
error: "请求已取消",
cancelled: true,
};
}
return { return {
success: false, success: false,
error: error.response?.data?.error?.message || error.message, error: error.response?.data?.error?.message || error.message,

View File

@ -18,7 +18,6 @@ async function runFFmpeg(args, options = {}) {
return; return;
} }
const { const {
title = "FFmpeg处理",
text = "处理中...", text = "处理中...",
position = "bottom-right", position = "bottom-right",
onPause, onPause,
@ -31,7 +30,6 @@ async function runFFmpeg(args, options = {}) {
// 显示进度条 // 显示进度条
const processBar = isShowProcessBar const processBar = isShowProcessBar
? await quickcommand.showProcessBar({ ? await quickcommand.showProcessBar({
title,
text, text,
value: 0, value: 0,
position, position,

View File

@ -1,13 +1,29 @@
const { runCsharpFeature } = require("../../csharp"); 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 watchClipboard = async function () {
const args = ["-type", "clipboard", "-once"]; const args = ["-type", "clipboard", "-once"];
const result = await runCsharpFeature("monitor", args); const loadingBar = await quickcommand.showLoadingBar({
if (result && result.startsWith("Error:")) { text: "等待剪贴板变化...",
throw new Error(result.substring(7)); 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"); args.push("-recursive", "false");
} }
const result = await runCsharpFeature("monitor", args); const loadingBar = await quickcommand.showLoadingBar({
if (result && result.startsWith("Error:")) { text: "等待文件变化...",
throw new Error(result.substring(7)); 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 = { module.exports = {

View File

@ -26,7 +26,6 @@ export default defineComponent({
commandManager: useCommandManager(), commandManager: useCommandManager(),
setCssVar: setCssVar, setCssVar: setCssVar,
programs: programmings, programs: programmings,
isRunningCommand: false,
profile: defaultProfile.common, profile: defaultProfile.common,
nativeProfile: defaultProfile.native, nativeProfile: defaultProfile.native,
utools: utoolsFull, utools: utoolsFull,

View File

@ -126,7 +126,6 @@ export default {
if (command.program === "quickcomposer") { if (command.program === "quickcomposer") {
command.cmd = generateFlowsCode(command.flows); command.cmd = generateFlowsCode(command.flows);
} }
this.$root.isRunningCommand = true;
this.needTempPayload && (await this.getTempPayload(command)); this.needTempPayload && (await this.getTempPayload(command));
// //
if (command.cmd.includes("{{subinput")) return this.setSubInput(command); if (command.cmd.includes("{{subinput")) return this.setSubInput(command);
@ -292,7 +291,6 @@ export default {
}; };
}, },
handleResult(stdout, stderr, options) { handleResult(stdout, stderr, options) {
this.$root.isRunningCommand = false;
if (stderr) { if (stderr) {
return options.earlyExit return options.earlyExit
? alert(stderr) ? alert(stderr)
@ -302,7 +300,6 @@ export default {
}, },
// //
async showRunResult(content, isSuccess) { async showRunResult(content, isSuccess) {
this.$root.isRunningCommand = false;
content = await this.handleContent(content); content = await this.handleContent(content);
this.runResultStatus = isSuccess; this.runResultStatus = isSuccess;
this.runResult = this.runResult.concat(content); this.runResult = this.runResult.concat(content);

View File

@ -17,9 +17,6 @@
/> />
</div> </div>
</div> </div>
<q-inner-loading :showing="hasCommandNeedLoading && $root.isRunningCommand">
<q-spinner-cube size="50px" color="primary" />
</q-inner-loading>
</div> </div>
</template> </template>
@ -38,7 +35,6 @@ export default defineComponent({
data() { data() {
return { return {
availableCommands, availableCommands,
hasCommandNeedLoading: false,
}; };
}, },
props: { props: {
@ -57,16 +53,6 @@ export default defineComponent({
// //
this.$emit("action", actionType, actionData); this.$emit("action", actionType, actionData);
}, },
findCommandNeedLoading(flow) {
//
// showLoading
if (!flow) return;
return flow.some(
(cmd) =>
cmd.showLoading ||
cmd.subCommands?.find((c) => c.value === cmd.value)?.showLoading
);
},
}, },
}); });
</script> </script>

View File

@ -11,7 +11,6 @@ export const aiCommands = {
desc: "获取API支持的模型列表", desc: "获取API支持的模型列表",
asyncMode: "await", asyncMode: "await",
icon: "list", icon: "list",
showLoading: true,
config: [ config: [
{ {
label: "API配置", label: "API配置",
@ -74,7 +73,6 @@ export const aiCommands = {
desc: "与AI助手进行对话", desc: "与AI助手进行对话",
asyncMode: "await", asyncMode: "await",
icon: "chat", icon: "chat",
showLoading: true,
config: [ config: [
{ {
label: "API配置", label: "API配置",

View File

@ -440,26 +440,21 @@ export const uiCommands = {
{ {
component: "OptionEditor", component: "OptionEditor",
options: { options: {
title: {
label: "标题",
component: "VariableInput",
width: 6,
},
text: { text: {
label: "文本", label: "文本",
component: "VariableInput", component: "VariableInput",
width: 6, width: 4,
}, },
value: { value: {
label: "初始进度值", label: "初始进度值",
component: "VariableInput", component: "VariableInput",
disableToggleType: true, disableToggleType: true,
width: 3, width: 4,
}, },
position: { position: {
label: "位置", label: "位置",
component: "QSelect", component: "QSelect",
width: 3, width: 4,
options: [ options: [
{ label: "屏幕左上角", value: "top-left" }, { label: "屏幕左上角", value: "top-left" },
{ label: "屏幕右上角", value: "top-right" }, { label: "屏幕右上角", value: "top-right" },
@ -471,20 +466,20 @@ export const uiCommands = {
label: "关闭按钮回调函数", label: "关闭按钮回调函数",
component: "VariableInput", component: "VariableInput",
disableToggleType: true, disableToggleType: true,
width: 6, width: 4,
}, },
onPause: { onPause: {
label: "暂停按钮回调函数", label: "暂停按钮回调函数",
component: "VariableInput", component: "VariableInput",
disableToggleType: true, disableToggleType: true,
width: 6, width: 4,
placeholder: "必须和恢复回调一起配置", placeholder: "必须和恢复回调一起配置",
}, },
onResume: { onResume: {
label: "恢复按钮回调函数", label: "恢复按钮回调函数",
component: "VariableInput", component: "VariableInput",
disableToggleType: true, disableToggleType: true,
width: 6, width: 4,
placeholder: "必须和暂停回调一起配置", placeholder: "必须和暂停回调一起配置",
}, },
}, },
@ -542,6 +537,65 @@ 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", value: "utools.showOpenDialog",
label: "文件选择框", label: "文件选择框",

View File

@ -933,7 +933,6 @@ export const windowsCommands = {
label: "剪贴板/文件监控", label: "剪贴板/文件监控",
icon: "monitor_heart", icon: "monitor_heart",
asyncMode: "await", asyncMode: "await",
showLoading: true,
subCommands: [ subCommands: [
{ {
value: "quickcomposer.windows.monitor.watchClipboard", value: "quickcomposer.windows.monitor.watchClipboard",

View File

@ -724,7 +724,6 @@ interface quickcommandApi {
/** /**
* *
* @param {object} options - * @param {object} options -
* @param {string} [options.title="进度"] -
* @param {string} [options.text="处理中..."] - * @param {string} [options.text="处理中..."] -
* @param {number} [options.value=0] - (0-100) * @param {number} [options.value=0] - (0-100)
* @param {string} [options.position="bottom-right"] - top-left, top-right, bottom-left, bottom-right * @param {string} [options.position="bottom-right"] - top-left, top-right, bottom-left, bottom-right
@ -736,7 +735,6 @@ interface quickcommandApi {
* ```js * ```js
* // 基本使用 * // 基本使用
* const processBar = await quickcommand.showProcessBar({ * const processBar = await quickcommand.showProcessBar({
* title: "下载进度",
* text: "正在下载文件...", * text: "正在下载文件...",
* value: 0, * value: 0,
* position: "bottom-right" * position: "bottom-right"
@ -745,7 +743,6 @@ interface quickcommandApi {
* // 带暂停/恢复,关闭回调功能 * // 带暂停/恢复,关闭回调功能
* let isPaused = false; * let isPaused = false;
* const processBar = await quickcommand.showProcessBar({ * const processBar = await quickcommand.showProcessBar({
* title: "下载进度",
* text: "正在下载文件...", * text: "正在下载文件...",
* value: 0, * value: 0,
* onPause: () => { * onPause: () => {
@ -839,6 +836,58 @@ interface quickcommandApi {
close: () => void; close: () => void;
} }
): void; ): 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;
} }
declare var quickcommand: quickcommandApi; declare var quickcommand: quickcommandApi;