新增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":
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;

View File

@ -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,
};

View File

@ -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;

View File

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

View File

@ -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,

View File

@ -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,

View File

@ -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 = {

View File

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

View File

@ -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);

View File

@ -17,9 +17,6 @@
/>
</div>
</div>
<q-inner-loading :showing="hasCommandNeedLoading && $root.isRunningCommand">
<q-spinner-cube size="50px" color="primary" />
</q-inner-loading>
</div>
</template>
@ -38,7 +35,6 @@ export default defineComponent({
data() {
return {
availableCommands,
hasCommandNeedLoading: false,
};
},
props: {
@ -57,16 +53,6 @@ export default defineComponent({
//
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>

View File

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

View File

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

View File

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

View File

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