Compare commits

..

No commits in common. "master" and "5.0.2" have entirely different histories.

44 changed files with 726 additions and 881 deletions

View File

@ -1,4 +0,0 @@
#!/bin/sh
git pull
cd plugin && npm i && cd .. && npm i
quasar build

4
dev.sh
View File

@ -1,4 +0,0 @@
#!/bin/sh
git pull
cd plugin && npm i && cd .. && npm i
quasar dev

View File

@ -17,7 +17,6 @@ window.aiResponseParser = (content) => {
const API_TYPES = {
OPENAI: "openai",
OLLAMA: "ollama",
UTOOLS: "utools",
};
// 角色提示词
@ -123,7 +122,7 @@ function buildRequestData(content, apiConfig) {
const roleMessage = rolePrompt
? [
{
role: "system",
role: "user",
content: rolePrompt,
},
]
@ -152,6 +151,21 @@ function buildRequestData(content, apiConfig) {
};
}
// 处理普通响应
function parseResponse(response, apiType) {
if (apiType === API_TYPES.OPENAI) {
if (!response.data.choices || !response.data.choices[0]) {
throw new Error("OpenAI 响应格式错误");
}
return response.data.choices[0].message.content;
} else {
if (!response.data.message) {
throw new Error("Ollama 响应格式错误");
}
return response.data.message.content;
}
}
// 处理模型列表响应
function parseModelsResponse(response, apiType) {
if (apiType === API_TYPES.OPENAI) {
@ -167,24 +181,6 @@ function parseModelsResponse(response, apiType) {
}
}
let reasoning_content_start = false;
function processContentWithReason(response, onStream) {
if (response.reasoning_content) {
if (!reasoning_content_start) {
reasoning_content_start = true;
onStream("<think>", false);
}
onStream(response.reasoning_content, false);
}
if (response.content) {
if (reasoning_content_start) {
reasoning_content_start = false;
onStream("</think>", false);
}
onStream(response.content, false);
}
}
// 处理 OpenAI 流式响应
async function handleOpenAIStreamResponse(line, onStream) {
if (line.startsWith("data:")) {
@ -194,9 +190,9 @@ async function handleOpenAIStreamResponse(line, onStream) {
return;
}
const json = JSON.parse(jsonStr);
const response = json.choices[0]?.delta;
if (response) {
processContentWithReason(response, onStream);
const content = json.choices[0]?.delta?.content;
if (content) {
onStream(content, false);
}
}
}
@ -208,30 +204,13 @@ async function handleOllamaStreamResponse(line, onStream) {
onStream("", true);
return;
}
const response = json.message;
if (response) {
processContentWithReason(response, onStream);
if (json.message?.content) {
onStream(json.message.content, false);
}
}
// 处理 uTools AI 流式响应
async function handleUToolsAIStreamResponse(response, onStream) {
processContentWithReason(response, onStream);
}
// 处理流式响应
async function handleStreamResponse(response, apiConfig, onStream) {
// 处理 uTools AI 响应
if (apiConfig.apiType === API_TYPES.UTOOLS) {
try {
await handleUToolsAIStreamResponse(response, onStream);
return { success: true };
} catch (error) {
throw error;
}
}
// 处理其他 API 的流式响应
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
@ -304,27 +283,24 @@ async function chat(content, apiConfig, options = {}) {
} = options;
// 验证必要参数
if (apiConfig.apiType === API_TYPES.UTOOLS) {
if (!content.prompt || !apiConfig.model) {
throw new Error("模型名称和提示词不能为空");
}
} else {
if (!apiConfig.apiUrl) {
throw new Error("API地址不能为空");
}
if (!apiConfig.apiUrl || !content.prompt || !apiConfig.model) {
throw new Error("API地址、模型名称和提示词不能为空");
}
if (!apiConfig.apiUrl || !content.prompt || !apiConfig.model) {
throw new Error("API地址、模型名称和提示词不能为空");
}
let controller;
// 构建请求URL和配置
const url = buildApiUrl(
apiConfig.apiUrl,
API_ENDPOINTS[apiConfig.apiType].chat
);
const config = buildRequestConfig(apiConfig);
const requestData = buildRequestData(content, apiConfig);
// 显示进度条
const processBar = showProcessBar
? await quickcommand.showProcessBar({
text: "AI思考中...",
onClose: () => {
if (typeof controller !== "undefined") {
if (controller) {
controller.abort();
}
},
@ -351,65 +327,11 @@ async function chat(content, apiConfig, options = {}) {
onStream(chunk, isDone);
};
// 处理 uTools AI 请求
if (apiConfig.apiType === API_TYPES.UTOOLS) {
try {
const messages = buildRequestData(content, apiConfig).messages;
controller = utools.ai(
{
model: apiConfig.model,
messages: messages,
},
(chunk) => {
handleUToolsAIStreamResponse(chunk, streamHandler);
}
);
onFetch(controller);
await controller;
// 在流式响应完全结束后,发送一个空字符串表示结束
streamHandler("", true);
// 完成时更新进度条并关闭
if (processBar) {
quickcommand.updateProcessBar(
{
text: "AI响应完成",
complete: true,
},
processBar
);
}
return {
success: true,
result: fullResponse,
};
} catch (error) {
if (error.name === "AbortError") {
return {
success: false,
error: "请求已取消",
cancelled: true,
};
}
throw error;
}
}
// 统一使用 fetch 处理其他 API 请求
controller = new AbortController();
// 统一使用 fetch 处理请求
const controller = new AbortController();
onFetch(controller);
const url = buildApiUrl(
apiConfig.apiUrl,
API_ENDPOINTS[apiConfig.apiType].chat
);
const config = buildRequestConfig(apiConfig);
const requestData = buildRequestData(content, apiConfig);
const response = await fetch(url, {
method: "POST",
headers: config.headers,

View File

@ -14,10 +14,7 @@ window.getModelsFromAiApi = getModels;
const systemDialog = require("./dialog/service");
const {
getQuickcommandTempFile,
getQuickcommandFolderFile,
} = require("./getQuickcommandFile");
const { getQuickcommandTempFile } = require("./getQuickcommandFile");
const createTerminalCommand = require("./createTerminalCommand");
@ -156,26 +153,18 @@ const quickcommand = {
},
// 载入在线资源
loadRemoteScript: async function (url, options) {
const urlReg =
/^((ht|f)tps?):\/\/([\w\-]+(\.[\w\-]+)*\/)*[\w\-]+(\.[\w\-]+)*\/?(\?([\w\-\.,@?^=%&:\/~\+#]*)+)?/;
if (!urlReg.test(url)) throw "url 不合法";
const { useCache = false } = options;
if (useCache) {
const urlHash = quickcomposer.coding.md5Hash(url);
const fileName = path.basename(url, ".js") + "." + urlHash.slice(8, -8);
const local = getQuickcommandFolderFile(fileName, "js");
if (!fs.existsSync(local)) {
await this.downloadFile(url, local);
}
return require(local);
} else {
const local = getQuickcommandTempFile("js");
await this.downloadFile(url, local);
let source = require(local);
fs.unlinkSync(local);
return source;
}
loadRemoteScript: async function (url) {
if (
!/^((ht|f)tps?):\/\/([\w\-]+(\.[\w\-]+)*\/)*[\w\-]+(\.[\w\-]+)*\/?(\?([\w\-\.,@?^=%&:\/~\+#]*)+)?/.test(
url
)
)
throw "url 不合法";
let local = getQuickcommandTempFile("js");
await this.downloadFile(url, local);
let source = require(local);
fs.unlinkSync(local);
return source;
},
// 唤醒 uTools

View File

@ -1,5 +1,4 @@
const fs = require("fs").promises;
const fsSync = require("fs");
const path = require("path");
/**
@ -165,240 +164,6 @@ async function permission(config) {
}
}
async function mkdir(targetDir) {
// 在 Windows 上,在根目录上使用 fs.mkdir() (即使使用递归参数)也会导致错误
// 所以还是要先检查目录是否存在
if (fsSync.existsSync(targetDir)) return;
await fs.mkdir(targetDir, { recursive: true });
}
/**
* 格式化速度显示
* @param {number} bytesPerSecond 每秒字节数
* @returns {string} 格式化后的速度字符串
*/
function formatSpeed(bytesPerSecond) {
if (bytesPerSecond >= 1024 * 1024) {
return `${(bytesPerSecond / (1024 * 1024)).toFixed(2)} MB/s`;
} else if (bytesPerSecond >= 1024) {
return `${(bytesPerSecond / 1024).toFixed(2)} KB/s`;
}
return `${bytesPerSecond.toFixed(2)} B/s`;
}
/**
* 获取目录下所有文件的总大小和文件数
* @param {string} dir 目录路径
* @returns {Promise<{totalSize: number, fileCount: number}>}
*/
async function getDirStats(dir) {
let totalSize = 0;
let fileCount = 0;
async function walk(currentDir) {
const entries = await fs.readdir(currentDir);
for (const entry of entries) {
const fullPath = path.join(currentDir, entry);
const stat = await fs.lstat(fullPath);
if (stat.isDirectory()) {
await walk(fullPath);
} else {
totalSize += stat.size;
fileCount++;
}
}
}
await walk(dir);
return { totalSize, fileCount };
}
/**
* 使用流复制文件支持进度显示
* @param {string} src 源文件路径
* @param {string} dest 目标文件路径
* @param {Object} progressInfo 进度信息
* @returns {Promise<void>}
*/
async function copyFileWithProgress(src, dest, progressInfo) {
const {
processBar,
totalSize,
fileCount,
processedFiles,
startTime,
signal, // AbortController 的 signal
} = progressInfo;
return new Promise(async (resolve, reject) => {
try {
let currentCopiedSize = 0;
let lastUpdate = Date.now();
let lastCopiedSize = 0;
const readStream = fsSync.createReadStream(src);
const writeStream = fsSync.createWriteStream(dest);
// 监听中止信号
signal.addEventListener("abort", () => {
readStream.destroy();
writeStream.destroy();
fs.unlink(dest).catch(() => {});
reject(new Error("操作已取消"));
});
readStream.on("data", (chunk) => {
currentCopiedSize += chunk.length;
progressInfo.copiedSize += chunk.length;
const now = Date.now();
if (now - lastUpdate >= 100) {
const progress = Math.round(
(progressInfo.copiedSize / totalSize) * 100
);
const timeSpent = (now - lastUpdate) / 1000;
const bytesCopied = currentCopiedSize - lastCopiedSize;
const speed = bytesCopied / timeSpent;
quickcommand.updateProcessBar(
{
value: progress,
text:
`[${processedFiles}/${fileCount}][${formatBytes(
progressInfo.copiedSize
)}/${formatBytes(totalSize)}] ${formatSpeed(speed)}<br/>` +
`${path.basename(src)}`,
},
processBar
);
lastUpdate = now;
lastCopiedSize = currentCopiedSize;
}
});
writeStream.on("finish", () => {
progressInfo.processedFiles++;
resolve();
});
writeStream.on("error", reject);
readStream.on("error", reject);
readStream.pipe(writeStream);
} catch (error) {
reject(error);
}
});
}
async function copyDirWithProcess(src, dest, progressInfo) {
await mkdir(dest);
const entries = await fs.readdir(src);
for (const entry of entries) {
// 检查是否已中止
if (progressInfo.signal.aborted) {
throw new Error("操作已取消");
}
const srcPath = path.join(src, entry);
const destPath = path.join(dest, entry);
const entryStat = await fs.lstat(srcPath);
if (entryStat.isDirectory()) {
await copyDirWithProcess(srcPath, destPath, progressInfo);
} else {
await copyFileWithProgress(srcPath, destPath, progressInfo);
}
}
}
async function copy(filePath, newPath) {
const controller = new AbortController();
let isCompleted = false; // 添加完成标志
const processBar = await quickcommand.showProcessBar({
text: "正在计算文件大小...",
value: 0,
onClose: () => {
// 只有在未完成时才触发取消
if (!isCompleted) {
controller.abort();
}
},
});
try {
let totalSize = 0;
let fileCount = 0;
const stat = await fs.stat(filePath);
if (stat.isDirectory()) {
const stats = await getDirStats(filePath);
totalSize = stats.totalSize;
fileCount = stats.fileCount;
} else {
totalSize = stat.size;
fileCount = 1;
}
const progressInfo = {
processBar,
totalSize,
copiedSize: 0,
fileCount,
processedFiles: 0,
startTime: Date.now(),
signal: controller.signal,
};
if (stat.isDirectory()) {
await copyDirWithProcess(filePath, newPath, progressInfo);
} else {
await copyFileWithProgress(filePath, newPath, progressInfo);
}
const totalTime = (Date.now() - progressInfo.startTime) / 1000;
const averageSpeed = totalSize / totalTime;
isCompleted = true; // 标记为已完成
quickcommand.updateProcessBar(
{
value: 100,
text:
`总大小: ${formatBytes(totalSize)} - 文件数: ${fileCount} <br/>` +
`平均速度: ${formatSpeed(averageSpeed)} - 用时: ${totalTime.toFixed(
1
)}s`,
complete: true,
},
processBar
);
} catch (error) {
if (error.message === "操作已取消") {
// 清理目标文件/目录
fs.rm(newPath, { recursive: true }).catch(() => {});
}
throw error;
}
}
async function move(filePath, newPath) {
try {
// rename 不支持跨驱动器
await fs.rename(filePath, newPath);
} catch (error) {
try {
await copy(filePath, newPath);
if (!fsSync.existsSync(newPath)) return;
await fs.rm(filePath, { recursive: true });
} catch (error) {
throw error;
}
}
}
/**
* 文件复制移动操作
*/
@ -406,15 +171,53 @@ async function transfer(config) {
const { filePath, transferOperation, newPath } = config;
// 检查文件是否存在
if (!fsSync.existsSync(filePath)) throw "文件或目录不存在!";
// 确保目标目录存在
await mkdir(path.dirname(newPath));
if (transferOperation === "copy") {
await copy(filePath, newPath);
} else if (transferOperation === "rename") {
await move(filePath, newPath);
} else {
throw new Error(`不支持的操作类型: ${transferOperation}`);
try {
const stats = await fs.lstat(filePath);
// 确保目标目录存在
await fs.mkdir(path.dirname(newPath), { recursive: true });
if (transferOperation === "copy") {
const processBar = await quickcommand.showProcessBar({
text: "复制中...",
});
if (stats.isDirectory()) {
// 复制目录
const copyDir = async (src, dest) => {
await fs.mkdir(dest, { recursive: true });
const entries = await fs.readdir(src);
for (const entry of entries) {
const srcPath = path.join(src, entry);
const destPath = path.join(dest, entry);
const entryStat = await fs.lstat(srcPath);
if (entryStat.isDirectory()) {
await copyDir(srcPath, destPath);
} else {
await fs.copyFile(srcPath, destPath);
}
quickcommand.updateProcessBar({ text: entry }, processBar);
}
};
await copyDir(filePath, newPath);
} else {
// 复制文件
await fs.copyFile(filePath, newPath);
}
processBar.close();
} else if (transferOperation === "rename") {
const processBar = await quickcommand.showProcessBar({
text: "处理中...",
});
await fs.rename(filePath, newPath);
processBar.close();
} else {
throw new Error(`不支持的操作类型: ${transferOperation}`);
}
} catch (error) {
processBar?.close();
if (error.code === "ENOENT") {
throw new Error("文件或目录不存在");
}
throw error;
}
}

View File

@ -5,7 +5,7 @@
"packages": {
"": {
"dependencies": {
"axios": "^1.8.2",
"axios": "^1.7.9",
"chrome-remote-interface": "^0.33.2",
"crypto-js": "^4.2.0",
"dompurify": "^3.2.4",
@ -15,7 +15,7 @@
"node-forge": "^1.3.1",
"pinyin-match": "^1.2.6",
"png2icons": "^2.0.1",
"ses": "^1.12.0",
"ses": "^1.10.0",
"sm-crypto": "^0.3.13",
"tree-kill": "^1.2.2"
}
@ -40,9 +40,9 @@
"license": "MIT"
},
"node_modules/axios": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@ -229,9 +229,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/ses": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/ses/-/ses-1.12.0.tgz",
"integrity": "sha512-jvmwXE2lFxIIY1j76hFjewIIhYMR9Slo3ynWZGtGl5M7VUCw3EA0wetS+JCIbl2UcSQjAT0yGAHkyxPJreuC9w==",
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/ses/-/ses-1.10.0.tgz",
"integrity": "sha512-HXmJbNEgY/4hsQfaz5dna39vVKNyvlElRmJYk+bjTqSXSElT0Hr6NKwWVg4j0TxP6IuHp/PNMoWJKIRXzmLbAQ==",
"license": "Apache-2.0",
"dependencies": {
"@endo/env-options": "^1.1.8"
@ -293,9 +293,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"axios": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"requires": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
@ -419,9 +419,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"ses": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/ses/-/ses-1.12.0.tgz",
"integrity": "sha512-jvmwXE2lFxIIY1j76hFjewIIhYMR9Slo3ynWZGtGl5M7VUCw3EA0wetS+JCIbl2UcSQjAT0yGAHkyxPJreuC9w==",
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/ses/-/ses-1.10.0.tgz",
"integrity": "sha512-HXmJbNEgY/4hsQfaz5dna39vVKNyvlElRmJYk+bjTqSXSElT0Hr6NKwWVg4j0TxP6IuHp/PNMoWJKIRXzmLbAQ==",
"requires": {
"@endo/env-options": "^1.1.8"
}

View File

@ -1,6 +1,6 @@
{
"dependencies": {
"axios": "^1.8.2",
"axios": "^1.7.9",
"chrome-remote-interface": "^0.33.2",
"crypto-js": "^4.2.0",
"dompurify": "^3.2.4",
@ -10,7 +10,7 @@
"node-forge": "^1.3.1",
"pinyin-match": "^1.2.6",
"png2icons": "^2.0.1",
"ses": "^1.12.0",
"ses": "^1.10.0",
"sm-crypto": "^0.3.13",
"tree-kill": "^1.2.2"
}

View File

@ -16,8 +16,6 @@ import programmings from "./js/options/programs.js";
import defaultProfile from "./js/options/defaultProfile.js";
import Cron from "croner";
import QuickCommand from "components/quickcommandUI/QuickCommand";
import { generateFlowsCode } from "js/composer/generateCode";
// import autoDetach from "./js/autoDetach.js";
export default defineComponent({
@ -159,13 +157,10 @@ export default defineComponent({
reslove([`超过${timeout}ms未响应`]);
}, timeout);
let command = dbManager.getDB("qc_" + featureCode);
let commandCode =
command.program === "quickcomposer"
? generateFlowsCode(command.flows)
: command.cmd;
let commandCode = command.cmd;
if (mainInput)
commandCode = commandCode.replace(/\{\{input\}\}/g, mainInput);
if (["quickcommand", "quickcomposer"].includes(command.program)) {
if (command.program === "quickcommand") {
window.runCodeInSandbox(commandCode, (stdout, stderr) => {
stderr && reslove([stderr.toString()]);
reslove(stdout);
@ -173,10 +168,10 @@ export default defineComponent({
} else {
let option =
command.program === "custom"
? command.customOptions || {}
: this.programs[command.program] || {};
option.scptarg = command.scptarg || "";
option.charset = command.charset || {};
? command.customOptions
: this.programs[command.program];
option.scptarg = command.scptarg;
option.charset = command.charset;
window.runCodeFile(
commandCode,
option,

View File

@ -170,8 +170,8 @@ export default {
getCommandOpt(command) {
let option =
command.program === "custom"
? command.customOptions || {}
: programs[command.program] || {};
? command.customOptions
: programs[command.program];
option.scptarg = command.scptarg || "";
option.charset = command.charset || {};
option.envPath = this.$root.nativeProfile.envPath.trim() || "";

View File

@ -213,7 +213,7 @@ export default defineComponent({
const response = await window.quickcommand.askAI(
{
prompt: promptText,
context: [presetContext, ...this.chatHistory.slice(0, -2)],
context: [...presetContext, ...this.chatHistory.slice(0, -2)],
},
this.selectedApi,
{
@ -291,7 +291,7 @@ export default defineComponent({
shell: "liunx shell脚本",
};
const languageName = languageMap[language] || language;
const commonInstructions = `接下来所有的对话中的需求都请通过编写${languageName}代码来实现,并请遵循以下原则:
const commonInstructions = `接下来所有的对话中的需求都请通过编写${languageName}代码来实现,并请遵循以下原则:
- 编写简洁可读性强的代码
- 遵循${languageName}最佳实践和设计模式
- 使用恰当的命名规范和代码组织
@ -312,7 +312,7 @@ export default defineComponent({
const specificInstructions = languageSpecific[language] || "";
const lastInstructions =
"\n请直接提供MARKDOWN格式的代码以```脚本语言开头,以```结尾),任何情况下都不需要做解释和说明";
"\n请直接给我MARKDOWN格式的代码,任何情况下都不需要做解释和说明";
return commonInstructions + specificInstructions + lastInstructions;
},
@ -330,26 +330,48 @@ export default defineComponent({
];
},
getPresetContext() {
let finnalPrompt = ""
const languagePrompt = this.getLanguagePrompt(this.language);
finnalPrompt += languagePrompt;
let presetContext = [
{
role: "user",
content: languagePrompt,
},
{
role: "assistant",
content: "好的,我会严格按照你的要求编写代码。",
},
];
if (this.submitDocs && this.language === "quickcommand") {
const docs = this.getLanguageDocs(this.language);
finnalPrompt += `\n你现在使用的是一种特殊的环境支持uTools和quickcommand两种特殊的接口请优先使用uTools和quickcommand接口解决需求然后再使用当前语言通用的解决方案`;
presetContext.push(
{
role: "user",
content: `你现在使用的是一种特殊的环境支持uTools和quickcommand两种特殊的接口请优先使用uTools和quickcommand接口解决需求然后再使用当前语言通用的解决方案`,
},
{
role: "assistant",
content: "好的,我会注意。",
}
);
docs.forEach((doc) => {
finnalPrompt += `\n这是${doc.name}的API文档\n${doc.api}`;
presetContext.push(
{
role: "user",
content: `这是${doc.name}的API文档\n${doc.api}`,
},
{
role: "assistant",
content: "好的,我会认真学习并记住这些接口。",
}
);
});
}
return {
role: "system",
content: finnalPrompt,
};
return presetContext;
},
openAIAssistantHelp() {
window.showUb.help("#KUCwm");

View File

@ -2,20 +2,25 @@
<q-card style="width: 800px" class="q-pa-sm">
<div class="text-h5 q-my-md q-px-sm">API配置</div>
<div>
<div class="q-pa-sm row q-gutter-sm">
<q-btn
v-for="option in aiOptions"
:key="option.value"
icon="add_link"
dense
color="primary"
:label="option.label"
@click="addModel(option.value)"
<div class="flex q-mb-md q-px-sm" style="height: 26px">
<ButtonGroup
v-model="apiToAdd"
class="col"
:options="[
{ label: 'OPENAI', value: 'openai' },
{ label: 'OLLAMA', value: 'ollama' },
]"
height="26px"
/>
<q-icon
name="add_box"
@click="addModel"
color="primary"
size="26px"
class="cursor-pointer q-ml-sm"
/>
</div>
<q-scroll-area
ref="scrollArea"
:style="`height: ${getConfigListHeight()}px;`"
class="q-px-sm"
:vertical-thumb-style="{
@ -75,7 +80,6 @@
? '例https://api.openai.com'
: '例http://localhost:11434'
"
v-show="aiConfig.apiType !== 'utools'"
>
<template v-slot:prepend>
<q-badge
@ -126,7 +130,7 @@
dense
v-model="aiConfig.apiToken"
v-if="aiConfig.apiType === 'openai'"
:type="tokenInputTypes[index] || 'password'"
type="password"
class="col-7"
>
<template v-slot:prepend>
@ -137,22 +141,6 @@
class="q-pa-xs"
/>
</template>
<template v-slot:append>
<q-icon
name="visibility_off"
@click="tokenInputTypes[index] = 'password'"
size="16px"
class="cursor-pointer"
v-if="tokenInputTypes[index] === 'text'"
/>
<q-icon
name="visibility"
@click="tokenInputTypes[index] = 'text'"
size="16px"
class="cursor-pointer"
v-else
/>
</template>
</q-input>
</div>
</div>
@ -175,54 +163,28 @@
</template>
<script>
import { defineComponent, ref } from "vue";
import { defineComponent } from "vue";
import { dbManager } from "js/utools.js";
import ButtonGroup from "components/composer/common/ButtonGroup.vue";
import draggable from "vuedraggable";
import { getUniqueId } from "js/common/uuid.js";
export default defineComponent({
name: "AIConfig",
components: {
ButtonGroup,
draggable,
},
setup() {
const initAiOptions = utools.allAiModels
? [{ label: "uTools内置AI", value: "utools" }]
: [];
const aiOptions = ref([
...initAiOptions,
{ label: "OPENAI接口(需Key)", value: "openai" },
{ label: "OLLAMA接口", value: "ollama" },
]);
return {
aiOptions,
};
},
data() {
return {
apiToAdd: "openai",
aiConfigs: [],
models: [],
tokenInputTypes: [],
};
},
emits: ["save"],
methods: {
async getModels(aiConfig) {
if (aiConfig.apiType === "utools") {
try {
const models = await utools.allAiModels();
this.models = models.map((model) => model.id);
} catch (error) {
quickcommand.showMessageBox(
"获取 uTools AI 模型失败: " + error.message,
"error"
);
this.models = [];
}
return;
}
const { success, result, error } = await window.getModelsFromAiApi(
aiConfig
);
@ -243,22 +205,15 @@ export default defineComponent({
deleteModel(index) {
this.aiConfigs.splice(index, 1);
},
addModel(apiType) {
const defaultConfig = {
addModel() {
this.aiConfigs.push({
id: getUniqueId(),
apiType: apiType,
apiType: this.apiToAdd,
apiUrl: "",
apiToken: "",
model: "",
name: "",
};
if (apiType === "utools") {
defaultConfig.apiUrl = "";
}
this.aiConfigs.unshift(defaultConfig);
});
},
getConfigListHeight() {
const counts = Math.min(this.aiConfigs.length, 3);

View File

@ -2,7 +2,7 @@
<div class="composer-flow">
<ChainStyles ref="chainStyles" :commands="commands" />
<q-scroll-area class="command-scroll" ref="scrollArea">
<q-scroll-area class="command-scroll">
<div
class="command-flow-container"
@dragover.prevent="onDragOver"
@ -541,12 +541,6 @@ export default defineComponent({
this.$emit("update:modelValue", newCommands);
},
},
mounted() {
// 1000
this.$nextTick(() => {
this.$refs.scrollArea.setScrollPosition("vertical", 0);
});
},
});
</script>

View File

@ -117,7 +117,9 @@ export default defineComponent({
...category,
commands: this.commands
.filter(
(cmd) => cmd.label && window.pinyinMatch.match(cmd.label, query)
(cmd) =>
(cmd.label && window.pinyinMatch.match(cmd.label, query)) ||
(cmd.value && window.pinyinMatch.match(cmd.value, query))
)
.filter((cmd) => cmd.type === category.label),
}))

View File

@ -323,7 +323,7 @@ export default defineComponent({
},
computed: {
showCommandConfig() {
return !this.isRunComposerPage && !!this.commandConfig.features;
return !this.isRunComposerPage && this.commandConfig.features;
},
},
methods: {
@ -590,23 +590,14 @@ export default defineComponent({
.flow-container {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.command-config-panel {
flex-shrink: 0;
padding: 8px;
z-index: 1;
}
.flow-wrapper {
flex: 1;
min-height: 0;
position: relative;
overflow: hidden;
height: 100%;
}
.variable-panel {
@ -633,4 +624,8 @@ export default defineComponent({
border-bottom: 2px solid var(--q-primary);
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.command-config-panel {
padding: 8px;
}
</style>

View File

@ -14,7 +14,7 @@
import { defineComponent } from "vue";
import OperationCard from "components/composer/common/OperationCard.vue";
import ParamInput from "components/composer/param/ParamInput.vue";
import { stringifyArgv } from "js/composer/formatString";
import { stringifyArgv, parseFunction } from "js/composer/formatString";
import {
newVarInputVal,
isVarInputVal,
@ -68,7 +68,7 @@ export default defineComponent({
},
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
hasSubCommands() {
@ -143,6 +143,42 @@ export default defineComponent({
return `${subCommand}(${finalArgvs.join(",")})`;
},
parseCodeToArgvs(code) {
let argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
if (this.localCommand.isExpression) {
return [code];
}
const variableFormatPaths = [];
const addVariableFormatPath = (prefix, config) => {
if (config.component === "VariableInput") {
variableFormatPaths.push(prefix);
} else if (config.component === "ArrayEditor") {
variableFormatPaths.push(`${prefix}[*].**`, `${prefix}[*]`);
} else if (config.component === "DictEditor") {
variableFormatPaths.push(`${prefix}.**`);
}
};
this.localConfig.forEach((item, index) => {
if (item.component === "OptionEditor") {
Object.entries(item.options).forEach(([key, config]) => {
addVariableFormatPath(`arg${index}.${key}`, config);
});
} else {
addVariableFormatPath(`arg${index}`, item);
}
});
try {
argvs = parseFunction(code, { variableFormatPaths }).argvs;
} catch (e) {
console.log("解析参数失败:", e);
}
return argvs;
},
getAllInputValues(argvs) {
const flatArgvs = [];
if (!argvs) return flatArgvs;
@ -201,8 +237,9 @@ export default defineComponent({
},
},
mounted() {
if (Array.isArray(this.argvs)) {
this.updateModelValue(this.subCommand, this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code && Array.isArray(argvs)) {
this.updateModelValue(this.subCommand, argvs);
}
},
});

View File

@ -29,7 +29,7 @@ import { defineComponent } from "vue";
import ButtonGroup from "components/composer/common/ButtonGroup.vue";
import { newVarInputVal } from "js/composer/varInputValManager";
import VariableInput from "components/composer/common/VariableInput.vue";
import { stringifyArgv } from "js/composer/formatString";
import { parseFunction, stringifyArgv } from "js/composer/formatString";
import AISelector from "components/ai/AISelector.vue";
export default defineComponent({
name: "AskAIEditor",
@ -69,18 +69,30 @@ export default defineComponent({
computed: {
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
},
methods: {
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
try {
const variableFormatPaths = ["arg0.prompt"];
const params = parseFunction(code, { variableFormatPaths });
return params;
} catch (e) {
console.error("解析参数失败:", e);
}
return argvs;
},
generateCode(argvs = this.argvs) {
return `${this.modelValue.value}(${stringifyArgv(
argvs.content
)}, ${JSON.stringify(argvs.apiConfig)})`;
},
getSummary(argvs) {
return "问AI" + stringifyArgv(argvs.content.prompt);
return "问AI" + argvs.content.prompt;
},
updateArgvs(keyPath, newValue) {
const newArgvs = { ...this.argvs };

View File

@ -16,16 +16,16 @@
<q-icon name="linear_scale" size="xs" />
</div>
<div v-else class="chain-icon">
<q-icon :name="getChianIcon()" size="xs" />
<q-icon name="fork_left" size="xs" />
</div>
<!-- 标题 -->
<div class="command-label" v-if="!isControlFlow">
<div class="command-label">
<div class="drag-handle text-subtitle2 command-label-text">
{{ command.label }}
</div>
<div
v-if="isCollapsed"
v-if="isCollapsed && !isControlFlow"
class="summary-container"
@click.stop="isEditingSummary = true"
>
@ -78,8 +78,6 @@
@update:outputVariable="$emit('update:outputVariable', $event)"
@run="$emit('run')"
@remove="$emit('remove')"
@add-print="$emit('add-print')"
@copy="$emit('copy')"
/>
</div>
</template>
@ -109,8 +107,6 @@ export default {
"run",
"remove",
"toggle-collapse",
"copy",
"add-print",
],
computed: {
contentClass() {
@ -139,15 +135,6 @@ export default {
return this.command.userComments || this.command.summary;
},
},
methods: {
getChianIcon() {
return (
this.command?.subCommands?.find(
(command) => command.value === this.command.commandType
)?.icon || "fork_left"
);
},
},
};
</script>
@ -156,7 +143,6 @@ export default {
.chain-icon {
display: flex;
align-items: center;
margin-right: 2px;
transition: all 0.2s ease;
}
@ -176,18 +162,15 @@ export default {
.command-label {
user-select: none;
pointer-events: all;
cursor: grab;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
margin-right: 8px;
}
.command-label-text {
cursor: grab;
transition: all 0.3s ease;
}
.command-label-text:hover {
.command-label:hover {
color: var(--q-primary);
transition: all 0.3s ease;
}

View File

@ -189,7 +189,7 @@
<script>
import { defineComponent } from "vue";
import VariableInput from "components/composer/common/VariableInput.vue";
import { stringifyArgv } from "js/composer/formatString";
import { stringifyArgv, parseFunction } from "js/composer/formatString";
import { newVarInputVal } from "js/composer/varInputValManager";
export default defineComponent({
name: "AsymmetricCryptoEditor",
@ -224,7 +224,7 @@ export default defineComponent({
computed: {
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
algorithms() {
@ -261,6 +261,18 @@ export default defineComponent({
},
},
methods: {
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
try {
const variableFormatPaths = ["arg0.text"];
const params = parseFunction(code, { variableFormatPaths });
return params.argvs[0];
} catch (e) {
console.error("解析加密参数失败:", e);
}
return argvs;
},
generateCode(argvs = this.argvs) {
return `${this.modelValue.value}(${stringifyArgv({
text: argvs.text,
@ -309,7 +321,10 @@ export default defineComponent({
},
},
mounted() {
this.updateModelValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateModelValue(argvs);
}
},
});
</script>

View File

@ -190,7 +190,7 @@
<script>
import { defineComponent } from "vue";
import VariableInput from "components/composer/common/VariableInput.vue";
import { stringifyArgv } from "js/composer/formatString";
import { stringifyArgv, parseFunction } from "js/composer/formatString";
import { newVarInputVal } from "js/composer/varInputValManager";
export default defineComponent({
@ -226,7 +226,7 @@ export default defineComponent({
computed: {
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
keyCodecs() {
@ -324,6 +324,18 @@ export default defineComponent({
this.updateModelValue(argvs);
},
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
try {
const variableFormatPaths = ["arg0.text"];
const params = parseFunction(code, { variableFormatPaths });
return params.argvs[0];
} catch (e) {
console.error("解析加密参数失败:", e);
}
return argvs;
},
getSummary(argvs) {
const text = window.lodashM.truncate(argvs.text.value, {
length: 30,
@ -343,7 +355,10 @@ export default defineComponent({
},
},
mounted() {
this.updateModelValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateModelValue(argvs);
}
},
});
</script>

View File

@ -2,7 +2,7 @@
<div class="control-command-wrapper">
<div class="control-command">
<!-- 类型标签 -->
<div class="control-type-label drag-handle">
<div class="control-type-label">
{{ currentFunction?.label || modelValue.commandType }}
</div>
@ -25,7 +25,7 @@
no-icon-animation
class="control-btn"
>
<q-list dense>
<q-list>
<q-item
v-for="func in branchOptions"
:key="func.value"
@ -87,7 +87,7 @@ export default defineComponent({
},
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
},
@ -123,7 +123,44 @@ export default defineComponent({
return code;
},
parseCodeToArgvs(code) {
if (!code) return this.defaultArgvs;
if (!this.currentFunction?.codeTemplate) return {};
const template = this.currentFunction.codeTemplate;
const argvs = {};
//
if (!template.includes("${")) return {};
//
const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
let pattern = escapeRegExp(template);
//
const variables = [];
const variablePattern = /\${(\w+)}/g;
let match;
while ((match = variablePattern.exec(template)) !== null) {
variables.push(match[1]);
pattern = pattern.replace(escapeRegExp(`\${${match[1]}}`), "([^}]*?)");
}
//
const regex = new RegExp(`^${pattern}$`);
const matches = code.match(regex);
if (matches) {
//
variables.forEach((variable, index) => {
argvs[variable] = matches[index + 1] || "";
});
return argvs;
}
return this.defaultArgvs;
},
updateModelValue(argvs) {
const code = this.generateCode(argvs);
@ -143,7 +180,9 @@ export default defineComponent({
}
},
mounted() {
this.updateModelValue(this.argvs);
if (!this.modelValue.code) {
this.updateModelValue(this.defaultArgvs);
}
},
});
</script>
@ -168,12 +207,6 @@ export default defineComponent({
opacity: 0.9;
user-select: none;
flex-shrink: 0;
transition: all 0.3s ease;
}
.control-type-label:hover {
color: var(--q-primary) !important;
transition: all 0.3s ease;
}
.control-settings {

View File

@ -128,7 +128,7 @@
<script>
import { defineComponent } from "vue";
import { stringifyArgv } from "js/composer/formatString";
import { stringifyArgv, parseFunction } from "js/composer/formatString";
import VariableInput from "components/composer/common/VariableInput.vue";
import NumberInput from "components/composer/common/NumberInput.vue";
import OperationCard from "components/composer/common/OperationCard.vue";
@ -208,7 +208,10 @@ export default defineComponent({
argvs: {
get() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs ||
this.parseCodeToArgvs(this.modelValue.code) || {
...this.defaultArgvs,
}
);
},
set(value) {
@ -231,6 +234,49 @@ export default defineComponent({
argvs.data
)}, "${argvs.method}", ${stringifyArgv(options)})`;
},
parseCodeToArgvs(code) {
if (!code) return null;
try {
// 使variable
const variableFormatPaths = [
"arg0", //
];
// 使 parseFunction
const result = parseFunction(code, { variableFormatPaths });
if (!result) return this.defaultArgvs;
const operation = result.name.split(".").pop();
const [data, method, options] = result.argvs;
const newArgvs = {
...this.defaultArgvs,
operation,
data,
method: method?.value || "gzip",
};
if (options) {
if (method?.value === "brotli") {
newArgvs.options = {
params: options.params || this.defaultArgvs.options.params,
};
} else {
newArgvs.options = {
level: options.level ?? this.defaultArgvs.options.level,
memLevel: options.memLevel ?? this.defaultArgvs.options.memLevel,
strategy: options.strategy ?? this.defaultArgvs.options.strategy,
};
}
}
return newArgvs;
} catch (e) {
console.error("解析Zlib参数失败:", e);
return this.defaultArgvs;
}
},
updateArgvs(key, value) {
this.argvs = {
...this.argvs,
@ -254,7 +300,10 @@ export default defineComponent({
},
},
mounted() {
this.updateModelValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateModelValue(argvs);
}
},
});
</script>

View File

@ -97,6 +97,7 @@ import VariableInput from "components/composer/common/VariableInput.vue";
import RegexInput from "./RegexInput.vue";
import RegexBuilder from "./RegexBuilder.vue";
import RegexTester from "./RegexTester.vue";
import { parseToHasType } from "js/composer/formatString";
import { newVarInputVal } from "js/composer/varInputValManager";
export default defineComponent({
@ -140,7 +141,7 @@ export default defineComponent({
},
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
},
@ -229,6 +230,31 @@ export default defineComponent({
this.updateArgvs("regexValue", newContent);
}
},
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
const match = code.match(/^.*?\((.*)\)$/);
if (!match) return argvs;
const params = match[1];
const parts = params.split(",");
const text = parts[0];
const regexPart = parts[1];
const replace = parts[2];
if (regexPart) {
const [_, pattern, flags] = regexPart.match(/\/(.*?)\/(.*)/) || [];
return {
textValue: parseToHasType(text),
regexValue: pattern,
replaceValue: parseToHasType(replace),
flags: {
ignoreCase: flags.includes("i"),
multiline: flags.includes("m"),
global: flags.includes("g"),
},
isReplace: !!replace,
};
}
},
getSummary(argvs) {
return argvs.isReplace
? argvs.textValue.value +
@ -248,7 +274,10 @@ export default defineComponent({
},
},
mounted() {
this.updateModelValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateModelValue(argvs);
}
},
});
</script>

View File

@ -240,16 +240,8 @@
<VariableInput
:model-value="argvs.newPath"
@update:model-value="updateArgvs('newPath', $event)"
label="目标路径(含被复制/移动的文件名)"
label="目标路径"
icon="drive_file_rename_outline"
:options="{
dialog: {
options: {
title: '选择文件',
properties: ['openFile', 'showHiddenFiles'],
},
},
}"
class="col-6"
/>
</div>

View File

@ -14,7 +14,7 @@
import { defineComponent } from "vue";
import { newVarInputVal } from "js/composer/varInputValManager";
import VariableInput from "components/composer/common/VariableInput.vue";
import { stringifyArgv } from "js/composer/formatString";
import { parseFunction, stringifyArgv } from "js/composer/formatString";
export default defineComponent({
name: "ReturnEditor",
@ -34,15 +34,32 @@ export default defineComponent({
},
computed: {
argvs() {
return this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs);
return (
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
},
methods: {
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
code = code.trim().replace(/^return\s(.*)/, "tempFunc($1)");
try {
const variableFormatPaths = ["arg0"];
const params = parseFunction(code, { variableFormatPaths });
return {
returnValue: params.argvs[0],
};
} catch (e) {
console.error("解析参数失败:", e);
}
return argvs;
},
generateCode(argvs = this.argvs) {
return `${this.modelValue.value} ${stringifyArgv(argvs.returnValue)}`;
},
getSummary(argvs) {
return "返回" + " " + stringifyArgv(argvs.returnValue);
return "返回" + " " + argvs.returnValue.value;
},
updateArgvs(key, newValue) {
this.argvs[key] = newValue;
@ -58,7 +75,10 @@ export default defineComponent({
},
},
mounted() {
this.updateModelValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateModelValue(argvs);
}
},
});
</script>

View File

@ -156,7 +156,7 @@ import VariableInput from "components/composer/common/VariableInput.vue";
import ArrayEditor from "components/composer/common/ArrayEditor.vue";
import BorderLabel from "components/composer/common/BorderLabel.vue";
import CheckButton from "components/composer/common/CheckButton.vue";
import { stringifyArgv } from "js/composer/formatString";
import { parseFunction, stringifyArgv } from "js/composer/formatString";
import programs from "js/options/programs";
import VariableList from "components/composer/common/varinput/VariableList.vue";
@ -197,7 +197,9 @@ export default defineComponent({
computed: {
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs ||
this.parseCodeToArgvs(this.modelValue.code) ||
this.defaultArgvs
);
},
isCodeSnippet() {
@ -209,6 +211,29 @@ export default defineComponent({
},
},
methods: {
parseCodeToArgvs(code) {
if (!code) return this.defaultArgvs;
if (this.isCodeSnippet) {
const result = parseFunction(code);
return {
code: quickcomposer.coding.base64Decode(result.argvs?.[0]),
};
}
try {
const variableFormatPaths = ["arg1.args[*]"];
const result = parseFunction(code, { variableFormatPaths });
if (!result) return this.defaultArgvs;
const [scriptCode, options] = result.argvs;
return {
code: scriptCode,
...options,
};
} catch (e) {
console.error("解析参数失败:", e);
return this.defaultArgvs;
}
},
generateCode(argvs = this.argvs) {
const variables = argvs.code.match(/"?___([^_]+?)___"?/g);
const replaceStr =
@ -292,7 +317,10 @@ export default defineComponent({
},
},
mounted() {
this.updateModelValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateModelValue(argvs);
}
},
});
</script>

View File

@ -132,7 +132,7 @@ export default defineComponent({
computed: {
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
},
@ -208,6 +208,35 @@ export default defineComponent({
return `${this.modelValue.value}("data:image/png;base64,${config.imageData}", { threshold: ${config.threshold}, mouseAction: "${config.mouseAction}" })`;
},
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
//
try {
const imageDataMatch = code.match(/"data:image\/png;base64,([^"]+)"/);
const thresholdMatch = code.match(/threshold:\s*([\d.]+)/);
const mouseActionMatch = code.match(/mouseAction:\s*"([^"]+)"/);
let { imagePreview, threshold, mouseAction } = argvs;
if (imageDataMatch) {
imagePreview = `data:image/png;base64,${imageDataMatch[1]}`;
}
if (thresholdMatch) {
threshold = parseFloat(thresholdMatch[1]);
}
if (mouseActionMatch) {
mouseAction = mouseActionMatch[1];
}
return {
imagePreview,
threshold,
mouseAction,
};
} catch (e) {
return argvs;
}
},
updateModelValue(argvs) {
this.$emit("update:modelValue", {
...this.modelValue,
@ -217,7 +246,10 @@ export default defineComponent({
},
},
mounted() {
this.updateModelValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateModelValue(argvs);
}
},
});
</script>

View File

@ -150,6 +150,7 @@
<script>
import { defineComponent } from "vue";
import NumberInput from "components/composer/common/NumberInput.vue";
import { parseFunction } from "js/composer/formatString";
//
const isMac = window.utools.isMacOs();
@ -437,7 +438,7 @@ export default defineComponent({
computed: {
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
mainKeyDisplay() {
@ -621,7 +622,44 @@ export default defineComponent({
...this.argvs,
...argv,
};
this.updateModelValue(newArgvs);
this.$emit("update:modelValue", {
...this.modelValue,
argvs: newArgvs,
code: this.generateCode(newArgvs),
});
},
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
try {
const result = parseFunction(code);
if (!result || !result.argvs || !result.argvs[0]) return argvs;
const keys = result.argvs[0];
const options = result.argvs[1] || {};
if (keys.length > 0) {
argvs.mainKey = keys[0];
Object.keys(argvs.modifiers).forEach((key) => {
// Mac meta command
const modKey = !isMac && key === "command" ? "meta" : key;
argvs.modifiers[key] = keys.slice(1).includes(modKey);
});
}
//
if (options) {
if (options.repeatCount) argvs.repeatCount = options.repeatCount;
if (options.repeatInterval)
argvs.repeatInterval = options.repeatInterval;
if (options.keyDelay) argvs.keyDelay = options.keyDelay;
}
return argvs;
} catch (e) {
console.error("Failed to parse key string:", e);
return argvs;
}
},
handleKeyInput(val) {
let newMainKey;
@ -665,24 +703,16 @@ export default defineComponent({
.join(" + ");
return `${modifiers} + ${shortcut.mainKey}`;
},
getSummary(argvs) {
const modifiers = Object.entries(argvs.modifiers)
.filter(([_, active]) => active)
.map(([key]) => this.modifierLabels[key])
.join(" + ");
return modifiers ? `${modifiers} + ${argvs.mainKey}` : argvs.mainKey;
},
updateModelValue(argvs) {
this.$emit("update:modelValue", {
...this.modelValue,
argvs: argvs,
code: this.generateCode(argvs),
summary: this.getSummary(argvs),
});
},
},
mounted() {
this.updateModelValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.$emit("update:modelValue", {
...this.modelValue,
argvs,
code: this.generateCode(argvs),
});
}
},
});
</script>

View File

@ -459,7 +459,7 @@ export default defineComponent({
computed: {
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
},
@ -608,26 +608,15 @@ export default defineComponent({
}
return `${this.modelValue.value}(${JSON.stringify(keySequence)})`;
},
updateValue(newArgvs = this.argvs) {
updateValue(newArgvs) {
const updatedModelValue = {
...this.modelValue,
argvs: newArgvs,
code: this.generateCode(newArgvs),
summary: this.getSummary(newArgvs),
argvs: newArgvs || this.argvs,
code: this.generateCode(newArgvs || this.argvs),
};
this.$emit("update:modelValue", updatedModelValue);
},
getSingleSummary(item) {
const modifiers = Object.entries(item.modifiers)
.filter(([_, active]) => active)
.map(([key]) => this.modifierLabels[key])
.join(" + ");
return modifiers ? `${modifiers} + ${item.mainKey}` : item.mainKey;
},
getSummary(argvs) {
return argvs.sequence.map(this.getSingleSummary).join("; ");
},
appendSequence(newSequence) {
const startId = Date.now();
this.argvs.sequence.push(
@ -638,6 +627,37 @@ export default defineComponent({
);
this.updateValue();
},
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
try {
const match = code.match(/\((.*)\)/s);
if (match) {
const args = eval(`[${match[1]}]`);
if (Array.isArray(args[0])) {
argvs.sequence = args[0].map((keys) => {
const mainKey = keys[0];
const modifiers = {
control: keys.includes("control"),
alt: keys.includes("alt"),
shift: keys.includes("shift"),
command:
!isMac && keys.includes("meta")
? true
: keys.includes("command"),
};
return { mainKey, modifiers, id: Date.now() + Math.random() };
});
if (args[1] && args[1].interval) {
argvs.interval = args[1].interval;
}
}
}
} catch (e) {
console.error("Failed to parse existing code:", e);
}
return argvs;
},
toggleModifier(index, key) {
// argvs
const newArgvs = {
@ -658,7 +678,10 @@ export default defineComponent({
},
},
mounted() {
this.updateValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateValue(argvs);
}
},
});
</script>

View File

@ -132,7 +132,7 @@
<script>
import { defineComponent } from "vue";
import { stringifyArgv } from "js/composer/formatString";
import { parseFunction, stringifyArgv } from "js/composer/formatString";
import { newVarInputVal } from "js/composer/varInputValManager";
import VariableInput from "components/composer/common/VariableInput.vue";
import NumberInput from "components/composer/common/NumberInput.vue";
@ -182,7 +182,9 @@ export default defineComponent({
argvs: {
get() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs ||
this.parseCodeToArgvs(this.modelValue.code) ||
this.defaultArgvs
);
},
set(value) {
@ -191,6 +193,32 @@ export default defineComponent({
},
},
methods: {
parseCodeToArgvs(code) {
if (!code) return null;
// 使variable
const variableFormatPaths = [
"arg0", //
"arg1.cwd", //
"arg1.env.**", //
];
//
const result = parseFunction(code, { variableFormatPaths });
if (!result) return this.defaultArgvs;
//
const [command, options = {}] = result.argvs;
return {
command: command || this.defaultArgvs.command,
options: {
...this.defaultArgvs.options,
...options,
cwd: options.cwd || this.defaultArgvs.options.cwd,
env: options.env || this.defaultArgvs.options.env,
},
};
},
generateCode(argvs) {
const args = [];
@ -232,7 +260,7 @@ export default defineComponent({
}
},
getSummary(argvs) {
return stringifyArgv(argvs.command);
return argvs.command.value;
},
updateModelValue(argvs) {
this.$emit("update:modelValue", {
@ -244,7 +272,10 @@ export default defineComponent({
},
},
mounted() {
this.updateModelValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateModelValue(argvs);
}
},
});
</script>

View File

@ -50,7 +50,6 @@ import UBrowserBasic from "components/composer/ubrowser/UBrowserBasic.vue";
import UBrowserOperations from "components/composer/ubrowser/UBrowserOperations.vue";
import UBrowserRun from "components/composer/ubrowser/UBrowserRun.vue";
import { generateUBrowserCode } from "js/composer/generateUBrowserCode";
import { stringifyArgv } from "src/js/composer/formatString";
export default {
name: "UBrowserEditor",
@ -84,7 +83,7 @@ export default {
return this.modelValue.argvs || this.defaultArgvs;
},
summary() {
const goto = stringifyArgv(this.argvs.goto?.url || "");
const goto = this.argvs.goto?.url || "";
return `访问 ${goto}`;
},
},
@ -113,6 +112,9 @@ export default {
code: this.generateCode(),
});
},
parseCodeToArgvs(code) {
// TODO:
},
},
};
</script>

View File

@ -14,15 +14,12 @@
bordered
class="action-card cursor-pointer"
:class="{
'action-selected': actionCount[actionKey] > 0,
'action-selected': selectedActionKeys.includes(actionKey),
}"
@click="addAction(actionKey)"
@click="toggleAction(actionKey)"
>
<div class="q-pa-xs text-caption text-wrap text-center">
{{ action.label }}
<q-badge v-if="actionCount[actionKey]" color="primary" floating>
{{ actionCount[actionKey] }}
</q-badge>
</div>
</q-card>
</div>
@ -41,7 +38,7 @@
<q-chip
square
removable
@remove="removeAction(index)"
@remove="toggleAction(selectedActionKey)"
class="text-caption q-mb-sm"
:style="{
paddingLeft: '7px',
@ -128,13 +125,6 @@ export default {
selectedActionArgs() {
return this.modelValue.map((x) => x.args);
},
actionCount() {
const count = {};
this.selectedActionKeys.forEach((key) => {
count[key] = (count[key] || 0) + 1;
});
return count;
},
},
methods: {
moveAction(index, direction) {
@ -148,25 +138,26 @@ export default {
this.$emit("update:model-value", newOperation);
}
},
addAction(actionKey) {
toggleAction(actionKey) {
const index = this.selectedActionKeys.indexOf(actionKey);
let newOperation = [...this.modelValue];
//
const { config, value } = this.operationsMap[actionKey];
const args = config?.length
? config.map((field) => field.defaultValue)
: [];
if (index !== -1) {
//
newOperation.splice(index, 1);
} else {
//
const { config, value } = this.operationsMap[actionKey];
const args = config?.length
? config.map((field) => field.defaultValue)
: [];
const newOperationItem = { value, args };
if (actionKey !== value) {
newOperationItem.key = actionKey;
const newOperationItem = { value, args };
if (actionKey !== value) {
newOperationItem.key = actionKey;
}
newOperation.push(newOperationItem);
}
newOperation.push(newOperationItem);
this.$emit("update:model-value", newOperation);
},
removeAction(index) {
let newOperation = [...this.modelValue];
newOperation.splice(index, 1);
this.$emit("update:model-value", newOperation);
},
updateActionArgs(argvIndex, argvVal, actionIndex) {
@ -259,11 +250,4 @@ export default {
.row.q-col-gutter-xs > * {
padding: 2px;
}
.q-badge {
font-size: 10px;
padding: 2px 4px;
right: -4px;
top: -4px;
}
</style>

View File

@ -152,8 +152,8 @@ import VariableInput from "components/composer/common/VariableInput.vue";
import ArrayEditor from "components/composer/common/ArrayEditor.vue";
import OperationCard from "components/composer/common/OperationCard.vue";
import BorderLabel from "components/composer/common/BorderLabel.vue";
import { stringifyArgv } from "js/composer/formatString";
import { newVarInputVal } from "js/composer/varInputValManager";
import { parseFunction, stringifyArgv } from "js/composer/formatString";
import { newVarInputVal, isVarInputVal } from "js/composer/varInputValManager";
const jsonDefaultSelects = new Array(3).fill().map((_, index) => ({
id: newVarInputVal("var", index),
@ -235,7 +235,7 @@ export default defineComponent({
computed: {
argvs() {
return (
this.modelValue.argvs || window.lodashM.cloneDeep(this.defaultArgvs)
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
optionTypes() {
@ -310,6 +310,32 @@ export default defineComponent({
Object.keys(options).length ? `, ${stringifyArgv(options)}` : ""
})`;
},
parseCodeToArgvs(code) {
if (!code) return this.defaultArgvs;
try {
const result = parseFunction(code, {
variableFormatPaths: ["arg0", "arg0[*]", "arg1.placeholder"],
});
if (!result) return this.defaultArgvs;
const subCommand = result.name;
const [selects, options = {}] = result.argvs;
const inputMode = isVarInputVal(selects) ? "variable" : "manual";
return {
...this.defaultArgvs,
inputMode,
selects,
subCommand,
...options,
};
} catch (e) {
console.warn("选择列表参数解析失败:" + e, code);
return this.defaultArgvs;
}
},
getSummary(argvs) {
const count = Array.isArray(argvs.selects) ? argvs.selects.length : "?";
return `显示${count}${
@ -364,7 +390,10 @@ export default defineComponent({
},
},
mounted() {
this.updateModelValue(this.argvs);
const argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateModelValue(argvs);
}
},
});
</script>

View File

@ -87,11 +87,9 @@ export default {
const savedTagOrder = dbManager.getDB(TAG_ORDER_KEY);
if (savedTagOrder.length) {
this.savedTagOrder = savedTagOrder;
}
if (!this.commandManager.state.currentTag) {
this.commandManager.changeCurrentTag(
this.savedTagOrder[0] || this.allQuickCommandTags[0]
);
this.commandManager.changeCurrentTag(this.savedTagOrder[0]);
} else {
this.commandManager.changeCurrentTag(this.allQuickCommandTags[0]);
}
});
},

View File

@ -28,7 +28,7 @@
size="xs"
label="插件会员"
@click="showPayPage = true"
><q-tooltip>{{ memberPrice }}元解锁本插件所有会员特权</q-tooltip></q-btn
><q-tooltip>2元解锁本插件所有会员特权</q-tooltip></q-btn
>
</div>
<q-separator vertical inset class="q-mx-lg" />

View File

@ -141,7 +141,7 @@ export default {
* @param position 消息显示位置默认为"top"
*/
showMessageBox: (message, icon = "success", time, position = "top") => {
message = window.lodashM.truncate(message.toString(), { length: 1200 });
message = window.lodashM.truncate(message, { length: 1200 });
if (icon === "success") icon = "positive";
if (icon === "error") icon = "negative";
if (typeof time === "undefined")

View File

@ -114,13 +114,9 @@ export default {
return this.items.filter((x) => {
if (this.is.json) {
const titleMatch = window.pinyinMatch.match(
x.title,
this.searchWords
);
const titleMatch = window.pinyinMatch.match(x.title, this.searchWords);
const descMatch =
x.description &&
window.pinyinMatch.match(x.description, this.searchWords);
x.description && window.pinyinMatch.match(x.description, this.searchWords);
return titleMatch || descMatch;
} else {
return window.pinyinMatch.match(x, this.searchWords);
@ -216,7 +212,6 @@ export default {
this.searchWords = text;
if (this.matchedItems.length < this.currentIndex + 1)
this.currentIndex = 0;
this.setUtoolsHeight(this.itemHeight * this.matchedItemsSize);
}, this.options.options.placeholder);
},

View File

@ -23,10 +23,16 @@ export const controlCommands = {
},
],
},
{
label: "否则",
value: "else",
icon: "fork_left",
codeTemplate: "} else {",
},
{
label: "否则满足",
value: "else if",
icon: "fork_right",
icon: "fork_left",
codeTemplate: "} else if (${condition}) {",
config: [
{
@ -37,16 +43,10 @@ export const controlCommands = {
},
],
},
{
label: "否则",
value: "else",
icon: "airline_stops",
codeTemplate: "} else {",
},
{
label: "结束",
value: "end",
codeTemplate: "};",
codeTemplate: "}",
},
],
},
@ -112,7 +112,7 @@ export const controlCommands = {
{
label: "结束",
value: "end",
codeTemplate: "};",
codeTemplate: "}",
},
],
},
@ -169,7 +169,7 @@ export const controlCommands = {
{
label: "结束",
value: "end",
codeTemplate: "};",
codeTemplate: "}",
},
],
},
@ -225,7 +225,7 @@ export const controlCommands = {
{
label: "结束",
value: "end",
codeTemplate: "};",
codeTemplate: "}",
},
],
},
@ -266,7 +266,7 @@ export const controlCommands = {
{
label: "结束",
value: "end",
codeTemplate: "};",
codeTemplate: "}",
},
],
},
@ -295,7 +295,7 @@ export const controlCommands = {
{
label: "匹配分支",
value: "case",
icon: "fork_right",
icon: "check",
codeTemplate: "case ${value}:",
config: [
{
@ -305,22 +305,16 @@ export const controlCommands = {
},
],
},
{
label: "中断",
value: "break",
icon: "stop",
codeTemplate: "break;",
},
{
label: "默认分支",
value: "default",
icon: "airline_stops",
icon: "last_page",
codeTemplate: "default:",
},
{
label: "结束",
value: "end",
codeTemplate: "};",
codeTemplate: "}",
},
],
},
@ -340,7 +334,7 @@ export const controlCommands = {
{
label: "捕获异常",
value: "catch",
icon: "priority_high",
icon: "error",
codeTemplate: "} catch (${errorVar}) {",
config: [
{
@ -360,7 +354,7 @@ export const controlCommands = {
{
label: "结束",
value: "end",
codeTemplate: "};",
codeTemplate: "}",
},
],
},

View File

@ -28,7 +28,6 @@ export const networkCommands = {
label: "HTTP请求结果",
suggestName: "responseResult",
structure: {
data: { label: "响应数据", suggestName: "responseData" },
status: { label: "HTTP状态码", suggestName: "responseStatus" },
statusText: {
label: "HTTP状态信息",
@ -37,6 +36,7 @@ export const networkCommands = {
headers: { label: "服务器响应头", suggestName: "responseHeaders" },
config: { label: "请求配置信息", suggestName: "requestConfig" },
request: { label: "发送的请求", suggestName: "request" },
data: { label: "响应数据", suggestName: "responseData" },
},
},
},

View File

@ -4,13 +4,13 @@ export function generateCode(flow) {
// 获取变量赋值代码,如果变量已经存在,则直接赋值,否则声明并赋值
const getVarAssignCode = (varName, varValue) => {
if (usedVarNames.includes(varName)) {
return `${varName} = ${varValue}`;
return `${varName} = ${varValue};`;
}
usedVarNames.push(varName);
if (!varValue) {
return `let ${varName}`;
return `let ${varName};`;
}
return `let ${varName} = ${varValue}`;
return `let ${varName} = ${varValue};`;
};
const getVarByPath = (name, path) => {
@ -36,11 +36,10 @@ export function generateCode(flow) {
);
const indent = " ";
const comma = ";";
// 局部变量赋值
manualVars.forEach((v) => {
code.push(indent + getVarAssignCode(v.name, v.value) + comma);
code.push(indent + getVarAssignCode(v.name, v.value));
});
commands.forEach((cmd) => {
@ -66,7 +65,7 @@ export function generateCode(flow) {
const extractVarCode = Object.entries(details)
.map(
([path, varName]) =>
`let ${varName} = ${getVarByPath(promiseName, path)};`
`let ${varName} = ${getVarByPath(promiseName, path)}`
)
.join("\n");
@ -81,22 +80,20 @@ export function generateCode(flow) {
})`;
}
}
code.push(indent + cmdCode + comma);
code.push(indent + cmdCode);
} else if (cmd.asyncMode === "await") {
// 使用 await 模式
const promiseName = name || "__result";
cmdCode = getVarAssignCode(promiseName, `await ${cmdCode}`);
code.push(indent + cmdCode + comma);
code.push(indent + cmdCode);
// 处理详细变量
if (details) {
Object.entries(details).forEach(([path, varName]) => {
code.push(
indent +
`${getVarAssignCode(
varName,
getVarByPath(promiseName, path)
)}` +
comma
`${indent}${getVarAssignCode(
varName,
getVarByPath(promiseName, path)
)}`
);
});
}
@ -104,17 +101,15 @@ export function generateCode(flow) {
// 非Async命令
const resultVarName = name || "__result";
cmdCode = getVarAssignCode(resultVarName, `${cmdCode}`);
code.push(indent + cmdCode + comma);
code.push(indent + cmdCode);
// 处理详细变量
if (details) {
Object.entries(details).forEach(([path, varName]) => {
code.push(
indent +
`${getVarAssignCode(
varName,
getVarByPath(resultVarName, path)
)}` +
comma
`${indent}${getVarAssignCode(
varName,
getVarByPath(resultVarName, path)
)}`
);
});
}
@ -124,11 +119,11 @@ export function generateCode(flow) {
if (cmd.asyncMode === "await") {
cmdCode = `await ${cmdCode}`;
}
code.push(indent + cmdCode + (cmd.isControlFlow ? "" : comma));
code.push(indent + cmdCode);
}
});
code.push("};"); // Close the function
code.push("}"); // Close the function
// 如果是主函数,则自动执行
if (funcName === "main") {

View File

@ -70,6 +70,8 @@ export function generateUBrowserCode(argvs) {
// 添加其他操作
if (argvs.operations?.length) {
argvs.operations.forEach(({ value, args }) => {
if (!args?.length) return;
const stringifiedArgs = args
.map((arg) => stringifyArgv(arg))
.filter(Boolean);

View File

@ -323,19 +323,6 @@ export const ubrowserOperationConfigs = {
},
],
},
markdown: {
value: "markdown",
label: "转markdown",
icon: "get_app",
config: [
{
label: "CSS 或 XPath 选择器,不传递则转换整个网页内容",
icon: "find_in_page",
component: "VariableInput",
width: 12,
},
],
},
setCookies: {
value: "setCookies",
label: "设置Cookie",
@ -491,13 +478,13 @@ export const ubrowserOperationConfigs = {
],
},
setValue: {
value: "value",
value: "setValue",
label: "设置值",
icon: "check_box",
config: [
{
label: "元素选择器",
icon: "find_in_page",
icon: "varInput",
component: "VariableInput",
width: 6,
},

View File

@ -167,8 +167,6 @@ export default {
},
//
editCommand(commandOrCode) {
//
if (this.isEditorShow) return;
// code command
const command =
typeof commandOrCode === "string"
@ -184,8 +182,6 @@ export default {
},
//
addNewCommand(program = "quickcommand") {
//
if (this.isEditorShow) return;
this.editorComponent =
program === "quickcomposer" ? "ComposerEditor" : "CommandEditor";
this.commandManager.state.currentCommand =

View File

@ -346,10 +346,8 @@ interface quickcommandApi {
* ```
*
* @param url
* @param options
* @param options.useCache 使utools.getPath("userData")/quickcommand目录
*/
loadRemoteScript(url: string, options?: { useCache?: boolean }): Promise<object>;
loadRemoteScript(url: string): Promise<object>;
/**
* signal pid ,

View File

@ -138,11 +138,6 @@ interface UBrowser {
* when 使
*/
end(): this;
/**
* markdown
* @param selector CSS XPath
*/
markdown(selector: string): this;
/**
*
*/
@ -667,134 +662,6 @@ interface UToolsApi {
isLinux(): boolean;
ubrowser: UBrowser;
/**
* AI Function Calling
* @param option AI
* @param streamCallback ()
* @returns PromiseLike
*/
ai(option: AiOption): PromiseLike<Message>;
ai(
option: AiOption,
streamCallback: (chunk: Message) => void
): PromiseLike<void>;
/**
* AI
* @returns AI
*/
allAiModels(): Promise<AiModel[]>;
}
/**
* AI
*/
interface AiOption {
/**
* AI , 使 deepseek-v3
*/
model?: string;
/**
*
*/
messages: Message[];
/**
*
*/
tools?: Tool[];
}
/**
* AI
*/
interface Message {
/**
*
* system
* user
* assistantAI
*/
role: "system" | "user" | "assistant";
/**
*
*/
content?: string;
/**
*
*/
reasoning_content?: string;
}
/**
* AI
*/
interface Tool {
/**
*
* function
*/
type: "function";
/**
*
*/
function?: {
/**
*
*/
name: string;
/**
*
*/
description: string;
/**
*
*/
parameters: {
type: "object";
properties: Record<string, any>;
};
/**
*
*/
required?: string[];
};
}
/**
* AI
*/
interface AiModel {
/**
* AI ID utools.ai model
*/
id: string;
/**
* AI
*/
label: string;
/**
* AI
*/
description: string;
/**
* AI
*/
icon: string;
/**
* AI
*/
cost: number;
}
/**
* Promise abort()
*/
interface PromiseLike<T> extends Promise<T> {
/**
* AI
*/
abort(): void;
}
declare var utools: UToolsApi;