diff --git a/data/latest.zip b/data/latest.zip new file mode 100644 index 0000000..0a84d71 Binary files /dev/null and b/data/latest.zip differ diff --git a/data/update.json b/data/update.json new file mode 100644 index 0000000..a332301 --- /dev/null +++ b/data/update.json @@ -0,0 +1,27 @@ +{ + "version": "1.0.8", + "time": 1775118545494, + "data": { + "toonflow": [ + { + "type": "zip", + "url": "https://toonflow.oss-cn-beijing.aliyuncs.com/latest/latest.zip" + }, + { + "type": "windows", + "url": "https://toonflow.oss-cn-beijing.aliyuncs.com/latest/latest.exe" + }, + { + "type": "linux", + "url": "https://toonflow.oss-cn-beijing.aliyuncs.com/latest/latest.AppImage" + }, + { + "type": "macos", + "url": "https://toonflow.oss-cn-beijing.aliyuncs.com/latest/latest.dmg" + } + ], + "github": [], + "atomgit": [], + "gitee": [] + } +} \ No newline at end of file diff --git a/data/web/favicon.ico b/data/web/favicon.ico index c6473c5..2402d02 100644 Binary files a/data/web/favicon.ico and b/data/web/favicon.ico differ diff --git a/data/web/index.html b/data/web/index.html index 383b9b1..23eae4b 100644 --- a/data/web/index.html +++ b/data/web/index.html @@ -5,812 +5,812 @@ Toonflow - - + `),lFs=/enable|requires|diagnostic/,avn=new RegExp("[_\\p{XID_Start}]\\p{XID_Continue}*","u"),aF="variable.predefined",cFs={tokenPostfix:".wgsl",defaultToken:"invalid",unicode:!0,atoms:JPs,keywords:eFs,reserved:tFs,predeclared_enums:nFs,predeclared_types:iFs,predeclared_type_generators:rFs,predeclared_type_aliases:oFs,predeclared_intrinsics:sFs,operators:aFs,symbols:/[!%&*+\-\.\/:;<=>^|_~,]+/,tokenizer:{root:[[lFs,"keyword","@directive"],[avn,{cases:{"@atoms":aF,"@keywords":"keyword","@reserved":"invalid","@predeclared_enums":aF,"@predeclared_types":aF,"@predeclared_type_generators":aF,"@predeclared_type_aliases":aF,"@predeclared_intrinsics":aF,"@default":"identifier"}}],{include:"@commentOrSpace"},{include:"@numbers"},[/[{}()\[\]]/,"@brackets"],["@","annotation","@attribute"],[/@symbols/,{cases:{"@operators":"operator","@default":"delimiter"}}],[/./,"invalid"]],commentOrSpace:[[/\s+/,"white"],[/\/\*/,"comment","@blockComment"],[/\/\/.*$/,"comment"]],blockComment:[[/[^\/*]+/,"comment"],[/\/\*/,"comment","@push"],[/\*\//,"comment","@pop"],[/[\/*]/,"comment"]],attribute:[{include:"@commentOrSpace"},[/\w+/,"annotation","@pop"]],directive:[{include:"@commentOrSpace"},[/[()]/,"@brackets"],[/,/,"delimiter"],[avn,"meta.content"],[/;/,"delimiter","@pop"]],numbers:[[/0[fh]/,"number.float"],[/[1-9][0-9]*[fh]/,"number.float"],[/[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?[fh]?/,"number.float"],[/[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?[fh]?/,"number.float"],[/[0-9]+[eE][+-]?[0-9]+[fh]?/,"number.float"],[/0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+(?:[pP][+-]?[0-9]+[fh]?)?/,"number.hex"],[/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*(?:[pP][+-]?[0-9]+[fh]?)?/,"number.hex"],[/0[xX][0-9a-fA-F]+[pP][+-]?[0-9]+[fh]?/,"number.hex"],[/0[xX][0-9a-fA-F]+[iu]?/,"number.hex"],[/[1-9][0-9]*[iu]?/,"number"],[/0[iu]?/,"number"]]}},dFs=Object.freeze(Object.defineProperty({__proto__:null,conf:KPs,language:cFs},Symbol.toStringTag,{value:"Module"})),uFs={comments:{blockComment:["\x3C!--","-->"]},brackets:[["<",">"]],autoClosingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],onEnterRules:[{beforeText:new RegExp("<([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$","i"),afterText:/^<\/([_:\w][_:\w-.\d]*)\s*>$/i,action:{indentAction:Tt.IndentAction.IndentOutdent}},{beforeText:new RegExp("<(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$","i"),action:{indentAction:Tt.IndentAction.Indent}}]},hFs={defaultToken:"",tokenPostfix:".xml",ignoreCase:!0,qualifiedName:/(?:[\w\.\-]+:)?[\w\.\-]+/,tokenizer:{root:[[/[^<&]+/,""],{include:"@whitespace"},[/(<)(@qualifiedName)/,[{token:"delimiter"},{token:"tag",next:"@tag"}]],[/(<\/)(@qualifiedName)(\s*)(>)/,[{token:"delimiter"},{token:"tag"},"",{token:"delimiter"}]],[/(<\?)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/(<\!)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/<\!\[CDATA\[/,{token:"delimiter.cdata",next:"@cdata"}],[/&\w+;/,"string.escape"]],cdata:[[/[^\]]+/,""],[/\]\]>/,{token:"delimiter.cdata",next:"@pop"}],[/\]/,""]],tag:[[/[ \t\r\n]+/,""],[/(@qualifiedName)(\s*=\s*)("[^"]*"|'[^']*')/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">?\/]*|'[^'>?\/]*)(?=[\?\/]\>)/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">]*|'[^'>]*)/,["attribute.name","","attribute.value"]],[/@qualifiedName/,"attribute.name"],[/\?>/,{token:"delimiter",next:"@pop"}],[/(\/)(>)/,[{token:"tag"},{token:"delimiter",next:"@pop"}]],[/>/,{token:"delimiter",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,""],[/\x3C!--/,{token:"comment",next:"@comment"}]],comment:[[/[^<\-]+/,"comment.content"],[/-->/,{token:"comment",next:"@pop"}],[/\x3C!--/,"comment.content.invalid"],[/[<\-]/,"comment.content"]]}},fFs=Object.freeze(Object.defineProperty({__proto__:null,conf:uFs,language:hFs},Symbol.toStringTag,{value:"Module"})),gFs={comments:{lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],folding:{offSide:!0},onEnterRules:[{beforeText:/:\s*$/,action:{indentAction:Tt.IndentAction.Indent}}]},mFs={tokenPostfix:".yaml",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.square",open:"[",close:"]"}],keywords:["true","True","TRUE","false","False","FALSE","null","Null","Null","~"],numberInteger:/(?:0|[+-]?[0-9]+)/,numberFloat:/(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,numberOctal:/0o[0-7]+/,numberHex:/0x[0-9a-fA-F]+/,numberInfinity:/[+-]?\.(?:inf|Inf|INF)/,numberNaN:/\.(?:nan|Nan|NAN)/,numberDate:/\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,escapes:/\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,tokenizer:{root:[{include:"@whitespace"},{include:"@comment"},[/%[^ ]+.*$/,"meta.directive"],[/---/,"operators.directivesEnd"],[/\.{3}/,"operators.documentEnd"],[/[-?:](?= )/,"operators"],{include:"@anchor"},{include:"@tagHandle"},{include:"@flowCollections"},{include:"@blockStyle"},[/@numberInteger(?![ \t]*\S+)/,"number"],[/@numberFloat(?![ \t]*\S+)/,"number.float"],[/@numberOctal(?![ \t]*\S+)/,"number.octal"],[/@numberHex(?![ \t]*\S+)/,"number.hex"],[/@numberInfinity(?![ \t]*\S+)/,"number.infinity"],[/@numberNaN(?![ \t]*\S+)/,"number.nan"],[/@numberDate(?![ \t]*\S+)/,"number.date"],[/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/,["type","white","operators","white"]],{include:"@flowScalars"},[/.+?(?=(\s+#|$))/,{cases:{"@keywords":"keyword","@default":"string"}}]],object:[{include:"@whitespace"},{include:"@comment"},[/\}/,"@brackets","@pop"],[/,/,"delimiter.comma"],[/:(?= )/,"operators"],[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/,"type"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\},]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],array:[{include:"@whitespace"},{include:"@comment"},[/\]/,"@brackets","@pop"],[/,/,"delimiter.comma"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\],]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],multiString:[[/^( +).+$/,"string","@multiStringContinued.$1"]],multiStringContinued:[[/^( *).+$/,{cases:{"$1==$S2":"string","@default":{token:"@rematch",next:"@popall"}}}]],whitespace:[[/[ \t\r\n]+/,"white"]],comment:[[/#.*$/,"comment"]],flowCollections:[[/\[/,"@brackets","@array"],[/\{/,"@brackets","@object"]],flowScalars:[[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'[^']*'/,"string"],[/"/,"string","@doubleQuotedString"]],doubleQuotedString:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],blockStyle:[[/[>|][0-9]*[+-]?$/,"operators","@multiString"]],flowNumber:[[/@numberInteger(?=[ \t]*[,\]\}])/,"number"],[/@numberFloat(?=[ \t]*[,\]\}])/,"number.float"],[/@numberOctal(?=[ \t]*[,\]\}])/,"number.octal"],[/@numberHex(?=[ \t]*[,\]\}])/,"number.hex"],[/@numberInfinity(?=[ \t]*[,\]\}])/,"number.infinity"],[/@numberNaN(?=[ \t]*[,\]\}])/,"number.nan"],[/@numberDate(?=[ \t]*[,\]\}])/,"number.date"]],tagHandle:[[/\![^ ]*/,"tag"]],anchor:[[/[&*][^ ]+/,"namespace"]]}},kFs=Object.freeze(Object.defineProperty({__proto__:null,conf:gFs,language:mFs},Symbol.toStringTag,{value:"Module"})); +
diff --git a/docs/g-star.png b/docs/g-star.png new file mode 100644 index 0000000..dfa9c65 Binary files /dev/null and b/docs/g-star.png differ diff --git a/scripts/main.ts b/scripts/main.ts index eef2e45..d4f4f65 100644 --- a/scripts/main.ts +++ b/scripts/main.ts @@ -13,19 +13,16 @@ declare const __APP_VERSION__: string | undefined; * 将 extraResources 中的 data 目录复制到用户数据目录(跳过已存在的文件,保留用户修改) */ -function getVersionFromFile(filePath: string): string | null { +function getVersionFromUpdateJson(filePath: string): string | null { try { if (fs.existsSync(filePath)) { - return fs.readFileSync(filePath, "utf8").trim(); + const data = JSON.parse(fs.readFileSync(filePath, "utf8")); + return data.version ?? null; } } catch {} return null; } -function writeVersionToFile(filePath: string, version: string): void { - fs.writeFileSync(filePath, version, { encoding: "utf8" }); -} - function copyDirForce(src: string, dest: string): void { if (!fs.existsSync(src)) return; if (fs.existsSync(dest)) { @@ -37,14 +34,13 @@ function copyDirForce(src: string, dest: string): void { function initializeData(): void { const srcDir = path.join(process.resourcesPath, "data"); const destDir = path.join(app.getPath("userData"), "data"); - const versionFile = path.join(destDir, "version.txt"); + const updateJsonFile = path.join(destDir, "update.json"); const currentVersion = typeof __APP_VERSION__ !== "undefined" ? __APP_VERSION__ : "0.0.0"; - const userVersion = getVersionFromFile(versionFile); + const userVersion = getVersionFromUpdateJson(updateJsonFile); - // 首次安装或无version.txt,直接全量拷贝 + // 首次安装或无update.json,直接全量拷贝 if (!fs.existsSync(destDir) || !userVersion) { copyDirRecursive(srcDir, destDir); - writeVersionToFile(versionFile, currentVersion); return; } @@ -52,7 +48,6 @@ function initializeData(): void { if (userVersion !== currentVersion) { copyDirForce(path.join(srcDir, "serve"), path.join(destDir, "serve")); copyDirForce(path.join(srcDir, "web"), path.join(destDir, "web")); - writeVersionToFile(versionFile, currentVersion); } } @@ -61,6 +56,7 @@ function copyDirRecursive(src: string, dest: string): void { if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true }); for (const entry of fs.readdirSync(src, { withFileTypes: true })) { // 跳过 oss 文件夹和 db2.sqlite 文件 + if (entry.isDirectory() && entry.name === "logs") continue; if (entry.isDirectory() && entry.name === "oss") continue; if (!entry.isDirectory() && entry.name === "db2.sqlite") continue; const srcPath = path.join(src, entry.name); diff --git a/src/agents/productionAgent/index.ts b/src/agents/productionAgent/index.ts index 441d3e7..3228770 100644 --- a/src/agents/productionAgent/index.ts +++ b/src/agents/productionAgent/index.ts @@ -43,12 +43,23 @@ export async function decisionAI(ctx: AgentContext) { const skill = path.join(u.getPath("skills"), "production_agent_decision.md"); const prompt = await fs.promises.readFile(skill, "utf-8"); + const projectInfo = await u.db("o_project").where("id", ctx.resTool.data.projectId).first(); + if (!projectInfo) throw new Error(`项目不存在,ID: ${ctx.resTool.data.projectId}`); + const [_, imageModelName] = projectInfo.imageModel!.split(":"); + const [id, videoModelName] = projectInfo.videoModel!.split(":"); + const data = await u.db("o_vendorConfig").where("id", id).select("models").first(); + const models = JSON.parse(data!.models!); + const findData = models.find((i: any) => i.modelName == name); + const isRef = findData.mode.every((i: any) => Array.isArray(i)); + const modelInfo = `项目使用的模型如下:\n图像模型:${imageModelName}\n视频模型:${videoModelName}\n多参:${isRef ? "是" : "否"}`; + + const mem = buildMemPrompt(await memory.get(text)); const { textStream } = await u.Ai.Text("productionAgent").stream({ messages: [ { role: "system", content: prompt }, - { role: "assistant", content: mem }, + { role: "assistant", content: mem + "\n" + modelInfo }, { role: "user", content: text }, ], abortSignal, @@ -138,13 +149,20 @@ function createSubAgent(parentCtx: AgentContext) { "分镜面板:", "```", ].join("\n"); - const projectData = await u.db("o_project").where("id", resTool.data.projectId).first(); - const modelInfo = `项目使用的模型如下:\n图像模型:${projectData?.imageModel}\n视频模型:${projectData?.videoModel}`; + const projectInfo = await u.db("o_project").where("id", resTool.data.projectId).first(); if (!projectInfo) throw new Error(`项目不存在,ID: ${resTool.data.projectId}`); const artSkills = await createArtSkills(projectInfo?.artStyle!); + const [_, imageModelName] = projectInfo.imageModel!.split(":"); + const [id, videoModelName] = projectInfo.videoModel!.split(":"); + const data = await u.db("o_vendorConfig").where("id", id).select("models").first(); + const models = JSON.parse(data!.models!); + const findData = models.find((i: any) => i.modelName == name); + const isRef = findData.mode.every((i: any) => Array.isArray(i)); + const modelInfo = `项目使用的模型如下:\n图像模型:${imageModelName}\n视频模型:${videoModelName}\n多参:${isRef ? "是" : "否"}`; + return runAgent({ prompt, system: systemPrompt + addPrompt, @@ -194,7 +212,6 @@ async function createArtSkills(artName: string) { return res; } - function removeAllXmlTags(text: string): string { text = text.replace(/<([a-zA-Z][\w-]*)(\s+[^>]*)?>([\s\S]*?)<\/\1>/g, ""); text = text.replace(/<([a-zA-Z][\w-]*)(\s+[^>]*)?\/>/g, ""); diff --git a/src/routes/production/workbench/generateVideoPrompt.ts b/src/routes/production/workbench/generateVideoPrompt.ts index 9ec15cb..d3fd0f2 100644 --- a/src/routes/production/workbench/generateVideoPrompt.ts +++ b/src/routes/production/workbench/generateVideoPrompt.ts @@ -95,8 +95,12 @@ export default router.post( `; const { text } = await u.Ai.Text("universalAi").invoke({ - system: `${videoPrompt?.data}\n${visualManual}\n${directorManual}`, + system: videoPrompt?.data!, messages: [ + { + role: "assistant", + content: `${visualManual}\n${directorManual}`, + }, { role: "user", content: content, diff --git a/src/routes/setting/about/checkUpdate.ts b/src/routes/setting/about/checkUpdate.ts index b229f1c..750b2e4 100644 --- a/src/routes/setting/about/checkUpdate.ts +++ b/src/routes/setting/about/checkUpdate.ts @@ -1,5 +1,7 @@ import express from "express"; -import { success } from "@/lib/responseFormat"; +import { success, error } from "@/lib/responseFormat"; +import { validateFields } from "@/middleware/middleware"; +import { z } from "zod"; const router = express.Router(); import fs from "fs"; @@ -17,21 +19,49 @@ const APP_VERSION: string = (() => { return pkg.version; })(); -export default router.post("/", async (req, res) => { - const tagger = "1.1.0"; - const taggerList = tagger.split(".").map(Number); - const currentVersionList = APP_VERSION.split(".").map(Number); - //对比Major - if (taggerList[0] > currentVersionList[0]) { - return res.status(200).send(success({ needUpdate: true, latestVersion: tagger, reinstall: false })); - } - //对比Minor - if (taggerList[1] > currentVersionList[1]) { - return res.status(200).send(success({ needUpdate: true, latestVersion: tagger, reinstall: false })); - } - //Patch - if (taggerList[2] > currentVersionList[2]) { - return res.status(200).send(success({ needUpdate: true, latestVersion: tagger, reinstall: false })); - } - return res.status(200).send(success({ needUpdate: false, latestVersion: tagger, reinstall: false })); -}); +export default router.post( + "/", + validateFields({ + source: z.enum(["toonflow", "github", "gitee", "atomgit"]), + }), + async (req, res) => { + const { source } = req.body; + + const getUrl = "https://toonflow.oss-cn-beijing.aliyuncs.com/update.json"; + + const versionInfo = await fetch(getUrl).then((res) => res.json()); + if (!versionInfo) return res.status(400).send(error("无法获取版本信息")); + const { version: tagger, time, data } = versionInfo; + + const sourceData = data[source]; + if (!sourceData) return res.status(400).send(error("无法获取该源的下载信息")); + + const platformType: Record = { + win32: "windows", + darwin: "macos", + linux: "linux", + }; + + const zipItem = sourceData.find((d: any) => d.type === "zip"); + const installerItem = sourceData.find((d: any) => d.type === platformType[process.platform]); + + const taggerList = tagger.split(".").map(Number); + const currentVersionList = APP_VERSION.split(".").map(Number); + //对比Major + if (taggerList[0] > currentVersionList[0]) { + if (!installerItem) return res.status(400).send(error("该源暂无适用于当前系统的安装包")); + return res.status(200).send(success({ needUpdate: true, latestVersion: tagger, reinstall: true, time, url: installerItem.url })); + } + //对比Minor + if (taggerList[1] > currentVersionList[1]) { + if (!installerItem) return res.status(400).send(error("该源暂无适用于当前系统的安装包")); + return res.status(200).send(success({ needUpdate: true, latestVersion: tagger, reinstall: true, time, url: installerItem.url })); + } + //Patch + if (taggerList[2] > currentVersionList[2]) { + if (!zipItem) return res.status(400).send(error("该源暂无增量更新包")); + return res.status(200).send(success({ needUpdate: true, latestVersion: tagger, reinstall: false, time, url: zipItem.url })); + } + return res.status(200).send(success({ needUpdate: false, latestVersion: tagger, reinstall: false, time })); + }, +); diff --git a/src/routes/setting/about/downloadApp.ts b/src/routes/setting/about/downloadApp.ts index 5b146c1..1a01072 100644 --- a/src/routes/setting/about/downloadApp.ts +++ b/src/routes/setting/about/downloadApp.ts @@ -1,238 +1,64 @@ import express from "express"; -import { success, error } from "@/lib/responseFormat"; -import getPath from "@/utils/getPath"; import z from "zod"; +import { validateFields } from "@/middleware/middleware"; +import u from "@/utils"; import fs from "fs"; -import path from "path"; import axios from "axios"; import compressing from "compressing"; -import { validateFields } from "@/middleware/middleware"; -import { spawn } from "child_process"; - +import path from "path"; +import { success, error } from "@/lib/responseFormat"; const router = express.Router(); -/** 仓库源配置 */ -const REPO_SOURCES = { - github: { - repo: "HBAI-Ltd/Toonflow-app", - api: "https://api.github.com/repos/HBAI-Ltd/Toonflow-app/releases/latest", - headers: { Accept: "application/vnd.github.v3+json" }, - }, - gitee: { - repo: "HBAI-Ltd/Toonflow-app", - api: "https://gitee.com/api/v5/repos/HBAI-Ltd/Toonflow-app/releases/latest", - headers: {}, - }, -} as const; - -type SourceType = keyof typeof REPO_SOURCES; - -function normalizeAssets(source: SourceType, release: any): { name: string; browser_download_url: string }[] { - if (source === "github") { - return (release.assets ?? []).map((a: any) => ({ - name: a.name, - browser_download_url: a.browser_download_url, - })); +const runInstaller = (installerPath: string) => { + const { exec } = require("child_process"); + if (process.platform === "darwin") { + exec(`open "${installerPath}"`); + } else { + if (process.platform !== "win32") fs.chmodSync(installerPath, 0o755); + exec(`"${installerPath}"`); } - return (release.assets ?? []).map((a: any) => ({ - name: a.name, - browser_download_url: a.browser_download_url, - })); -} +}; -/** 获取当前系统平台和架构标识,用于匹配安装包文件名 */ -function getPlatformArch(): { platform: string; arch: string } { - const platform = process.platform === "win32" ? "win" : process.platform === "darwin" ? "mac" : "linux"; - const arch = process.arch === "arm64" ? "arm64" : "x64"; - return { platform, arch }; -} - -/** 匹配安装包资产(.exe / .dmg / .AppImage / .portable.exe) */ -function findInstallerAsset(assets: any[]): any | null { - const { platform, arch } = getPlatformArch(); - const installerExtensions: Record = { - win: [".exe"], - mac: [".dmg"], - linux: [".AppImage"], - }; - const exts = installerExtensions[platform] || [".exe"]; - // 优先找 nsis 安装包(排除 portable),如果没有再找 portable - return ( - assets.find( - (a: any) => - exts.some((ext) => a.name.endsWith(ext)) && - a.name.includes(arch) && - !a.name.toLowerCase().includes("portable") && - !a.name.endsWith(".blockmap"), - ) ?? - assets.find((a: any) => exts.some((ext) => a.name.endsWith(ext)) && a.name.includes(arch) && !a.name.endsWith(".blockmap")) ?? - null - ); -} - -/** - * 下载文件到指定路径(支持流式写入与进度) - */ -async function downloadFile(url: string, destPath: string): Promise { - const dir = path.dirname(destPath); - if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - - const response = await axios.get(url, { - responseType: "stream", - headers: { Accept: "application/octet-stream" }, - timeout: 600_000, // 10 分钟超时 - }); - - const writer = fs.createWriteStream(destPath); - response.data.pipe(writer); - - return new Promise((resolve, reject) => { - writer.on("finish", resolve); - writer.on("error", reject); - }); -} export default router.post( "/", validateFields({ - source: z.enum(["github", "gitee"]), + url: z.url(), reinstall: z.boolean(), - latestVersion: z.string(), }), async (req, res) => { - try { - const { reinstall, latestVersion, source } = req.body as { - reinstall: boolean; - latestVersion: string; - source: string; - }; - - if (!latestVersion) { - return res.status(400).send(error("缺少目标版本号 latestVersion")); + const { reinstall, url } = req.body; + const rootDir = u.getPath(["temp"]); + fs.mkdirSync(rootDir, { recursive: true }); + if (reinstall) { + const response = await axios.get(url, { responseType: "arraybuffer" }); + const ext = + path.extname(new URL(url).pathname) || (process.platform === "win32" ? ".exe" : process.platform === "darwin" ? ".dmg" : ".AppImage"); + const installerPath = path.join(rootDir, `latest${ext}`); + fs.writeFileSync(installerPath, response.data); + runInstaller(installerPath); + res.status(200).send(success("安装包已下载并启动")); + } else { + const zip = await axios.get(url, { responseType: "arraybuffer" }).then((res) => res.data); + fs.writeFileSync(`${rootDir}/latest.zip`, zip); + await compressing.zip.uncompress(`${rootDir}/latest.zip`, rootDir); + const tempServerPath = u.getPath(["temp", "serve"]); + if (fs.existsSync(tempServerPath)) { + fs.cpSync(tempServerPath, u.getPath(["serve"]), { recursive: true }); } - - const sourceConfig = REPO_SOURCES[source as SourceType] ?? REPO_SOURCES.github; - - // ─── 获取 Release 信息(支持 GitHub / Gitee) ────────────────────── - let releaseRes; - try { - releaseRes = await axios.get(sourceConfig.api, { - headers: sourceConfig.headers, - timeout: 30_000, - }); - } catch (e) { - return res.status(500).send(error(`获取 ${source} Release 信息失败`)); + const webPath = u.getPath(["temp", "web"]); + if (fs.existsSync(webPath)) { + fs.cpSync(webPath, u.getPath(["web"]), { recursive: true }); } - - const release = releaseRes.data; - - const assets = normalizeAssets(source as SourceType, release); - - if (reinstall) { - // ═══════════════ 模式 A:下载完整安装包 ═══════════════ - const installerAsset = findInstallerAsset(assets); - - if (!installerAsset) { - return res.status(404).send(error("未找到当前平台的安装包,请前往 GitHub Releases 手动下载")); - } - - const tempDir = getPath(["temp"]); - - if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true }); - const installerPath = path.join(tempDir, installerAsset.name); - - // 如果已经下载过相同文件,跳过下载 - if (!fs.existsSync(installerPath)) { - await downloadFile(installerAsset.browser_download_url, installerPath); - } - - // 使用 shell 打开安装程序 - const sub = spawn("cmd", ["/c", `${installerPath}`], { - cwd: tempDir, - detached: true, - stdio: "ignore", - windowsHide: false, - }); - - sub.unref(); - - return res.status(200).send( - success({ - type: "reinstall", - version: latestVersion, - filePath: installerPath, - message: "安装包已下载并打开,请按照安装向导完成更新", - }), - ); - } else { - // ═══════════════ 模式 B:data 补丁热更新 ═══════════════ - const patchAsset = assets.find((a: any) => a.name.startsWith(latestVersion) && a.name.endsWith(".zip")) ?? null; - - if (!patchAsset) { - return res.status(404).send(error("未找到 data 补丁包,请前往 GitHub Releases 手动下载")); - } - // - - const tempDir = getPath(["temp"]); - if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true }); - const patchZipPath = path.join(tempDir, `${latestVersion}.zip`); - - // 下载补丁 zip - await downloadFile(patchAsset.browser_download_url, patchZipPath); - - // 解压覆盖到 data 目录(同名文件夹先删除再解压,确保完全替换) - const dataDir = getPath(); - - // 先读取 zip 内的顶层文件夹/文件列表,删除 data 目录下的同名项 - const zipStream = new compressing.zip.UncompressStream({ source: patchZipPath, zipFileNameEncoding: "utf8" }); - const topLevelEntries = new Set(); - await new Promise((resolve, reject) => { - zipStream.on("entry", (_header: any, stream: any, next: () => void) => { - const entryName: string = _header.name || ""; - // 取顶层名称(第一个 / 之前的部分) - const topName = entryName.split("/")[0]; - if (topName) topLevelEntries.add(topName); - stream.resume(); - next(); - }); - zipStream.on("finish", resolve); - zipStream.on("error", reject); - }); - - // 删除 data 目录下与 zip 顶层同名的文件夹/文件 - for (const name of topLevelEntries) { - const targetPath = path.join(dataDir, name); - if (fs.existsSync(targetPath)) { - const stat = fs.statSync(targetPath); - if (stat.isDirectory()) { - fs.rmSync(targetPath, { recursive: true, force: true }); - } else { - fs.unlinkSync(targetPath); - } - } - } - - await compressing.zip.uncompress(patchZipPath, dataDir, { zipFileNameEncoding: "utf8" }); - - // 清理临时文件 - try { - fs.unlinkSync(patchZipPath); - } catch { - // 忽略清理失败 - } - - return res.status(200).send( - success({ - type: "patch", - version: latestVersion, - message: "补丁更新完成,请重启应用以使更新生效", - restartRequired: true, - }), - ); + const tempSkillsPath = u.getPath(["temp", "skills"]); + if (fs.existsSync(tempSkillsPath)) { + fs.cpSync(tempSkillsPath, u.getPath(["skills"]), { recursive: true, force: false }); } - } catch (err: any) { - console.error("[downloadApp] 更新失败:", err); - const message = err?.response?.status === 404 ? "未找到更新资源,请检查版本号或稍后重试" : (err?.message ?? "更新失败,请稍后重试"); - return res.status(500).send(error(message)); + const tempModelsPath = u.getPath(["temp", "models"]); + if (fs.existsSync(tempModelsPath)) { + fs.cpSync(tempModelsPath, u.getPath(["models"]), { recursive: true, force: false }); + } + fs.rmSync(rootDir, { recursive: true, force: true }); + res.status(200).send(success("更新成功,5秒后重启")); } }, ); diff --git a/src/routes/setting/skillManagement/getSkillList.ts b/src/routes/setting/skillManagement/getSkillList.ts index 39c0371..99c33c4 100644 --- a/src/routes/setting/skillManagement/getSkillList.ts +++ b/src/routes/setting/skillManagement/getSkillList.ts @@ -14,7 +14,5 @@ export default router.post("/", async (req, res) => { onlyFiles: true, }); - console.log("%c Line:15 🍺 entries", "background:#e41a6a", entries); - res.status(200).send(success(entries)); }); diff --git a/src/types/database.d.ts b/src/types/database.d.ts index a8c2754..988bea3 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,60 +1,10 @@ -// @db-hash 7af86e2bafe5cab7d175eb68cf76ed7a +<<<<<<< HEAD +// @db-hash 6fa5017e455bc367c9c902ba574d11b4 +======= +// @db-hash 35cf00f711e9d4df398703de70511684 +>>>>>>> c7be353ef92bb888df3af432bb21220b2fd35d7d //该文件由脚本自动生成,请勿手动修改 -export interface _o_storyboard_old_20260402 { - 'createTime'?: number | null; - 'duration'?: string | null; - 'filePath'?: string | null; - 'flowId'?: number | null; - 'id'?: number; - 'index'?: number | null; - 'projectId'?: number | null; - 'prompt'?: string | null; - 'reason'?: string | null; - 'scriptId'?: number | null; - 'state'?: string | null; - 'trackId'?: number | null; -} -export interface _o_storyboard_old_20260402_1 { - 'createTime'?: number | null; - 'duration'?: string | null; - 'filePath'?: string | null; - 'flowId'?: number | null; - 'id'?: number; - 'index'?: number | null; - 'projectId'?: number | null; - 'prompt'?: string | null; - 'reason'?: string | null; - 'scriptId'?: number | null; - 'shouldGenerateImage'?: number | null; - 'state'?: string | null; - 'track'?: string | null; - 'trackId'?: number | null; - 'videoPrompt'?: string | null; -} -export interface _o_vendorConfig_old_20260401 { - 'author'?: string | null; - 'code'?: string | null; - 'createTime'?: number | null; - 'description'?: string | null; - 'enableEnglish'?: number | null; - 'icon'?: string | null; - 'id'?: string; - 'inputs'?: string | null; - 'inputValues'?: string | null; - 'models'?: string | null; - 'name'?: string | null; -} -export interface _o_videoTrack_old_20260402 { - 'id'?: number; - 'projectId'?: number | null; - 'prompt'?: string | null; - 'reason'?: string | null; - 'scriptId'?: number | null; - 'selectVideoId'?: number | null; - 'state'?: string | null; - 'videoId'?: number | null; -} export interface memories { 'content': string; 'createTime': number; @@ -130,7 +80,6 @@ export interface o_image { 'filePath'?: string | null; 'id'?: number; 'model'?: string | null; - 'reason'?: string | null; 'resolution'?: string | null; 'state'?: string | null; 'type'?: string | null; @@ -165,6 +114,7 @@ export interface o_outlineNovel { export interface o_project { 'artStyle'?: string | null; 'createTime'?: number | null; + 'directorManual'?: string | null; 'id'?: number | null; 'imageModel'?: string | null; 'imageQuality'?: string | null; @@ -255,7 +205,6 @@ export interface o_vendorConfig { 'createTime'?: number | null; 'description'?: string | null; 'enable'?: number | null; - 'enableEnglish'?: number | null; 'icon'?: string | null; 'id'?: string; 'inputs'?: string | null; @@ -285,10 +234,6 @@ export interface o_videoTrack { } export interface DB { - "_o_storyboard_old_20260402": _o_storyboard_old_20260402; - "_o_storyboard_old_20260402_1": _o_storyboard_old_20260402_1; - "_o_vendorConfig_old_20260401": _o_vendorConfig_old_20260401; - "_o_videoTrack_old_20260402": _o_videoTrack_old_20260402; "memories": memories; "o_agentDeploy": o_agentDeploy; "o_agentWorkData": o_agentWorkData;