Merge branch '108' of https://github.com/HBAI-Ltd/Toonflow-app into 108
This commit is contained in:
commit
8b5a8ff9c2
BIN
data/latest.zip
Normal file
BIN
data/latest.zip
Normal file
Binary file not shown.
27
data/update.json
Normal file
27
data/update.json
Normal file
@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 169 KiB |
2282
data/web/index.html
2282
data/web/index.html
File diff suppressed because one or more lines are too long
BIN
docs/g-star.png
Normal file
BIN
docs/g-star.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 201 KiB |
@ -13,19 +13,16 @@ declare const __APP_VERSION__: string | undefined;
|
|||||||
* 将 extraResources 中的 data 目录复制到用户数据目录(跳过已存在的文件,保留用户修改)
|
* 将 extraResources 中的 data 目录复制到用户数据目录(跳过已存在的文件,保留用户修改)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getVersionFromFile(filePath: string): string | null {
|
function getVersionFromUpdateJson(filePath: string): string | null {
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
return fs.readFileSync(filePath, "utf8").trim();
|
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||||
|
return data.version ?? null;
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeVersionToFile(filePath: string, version: string): void {
|
|
||||||
fs.writeFileSync(filePath, version, { encoding: "utf8" });
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyDirForce(src: string, dest: string): void {
|
function copyDirForce(src: string, dest: string): void {
|
||||||
if (!fs.existsSync(src)) return;
|
if (!fs.existsSync(src)) return;
|
||||||
if (fs.existsSync(dest)) {
|
if (fs.existsSync(dest)) {
|
||||||
@ -37,14 +34,13 @@ function copyDirForce(src: string, dest: string): void {
|
|||||||
function initializeData(): void {
|
function initializeData(): void {
|
||||||
const srcDir = path.join(process.resourcesPath, "data");
|
const srcDir = path.join(process.resourcesPath, "data");
|
||||||
const destDir = path.join(app.getPath("userData"), "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 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) {
|
if (!fs.existsSync(destDir) || !userVersion) {
|
||||||
copyDirRecursive(srcDir, destDir);
|
copyDirRecursive(srcDir, destDir);
|
||||||
writeVersionToFile(versionFile, currentVersion);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +48,6 @@ function initializeData(): void {
|
|||||||
if (userVersion !== currentVersion) {
|
if (userVersion !== currentVersion) {
|
||||||
copyDirForce(path.join(srcDir, "serve"), path.join(destDir, "serve"));
|
copyDirForce(path.join(srcDir, "serve"), path.join(destDir, "serve"));
|
||||||
copyDirForce(path.join(srcDir, "web"), path.join(destDir, "web"));
|
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 });
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
||||||
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
||||||
// 跳过 oss 文件夹和 db2.sqlite 文件
|
// 跳过 oss 文件夹和 db2.sqlite 文件
|
||||||
|
if (entry.isDirectory() && entry.name === "logs") continue;
|
||||||
if (entry.isDirectory() && entry.name === "oss") continue;
|
if (entry.isDirectory() && entry.name === "oss") continue;
|
||||||
if (!entry.isDirectory() && entry.name === "db2.sqlite") continue;
|
if (!entry.isDirectory() && entry.name === "db2.sqlite") continue;
|
||||||
const srcPath = path.join(src, entry.name);
|
const srcPath = path.join(src, entry.name);
|
||||||
|
|||||||
@ -43,12 +43,23 @@ export async function decisionAI(ctx: AgentContext) {
|
|||||||
const skill = path.join(u.getPath("skills"), "production_agent_decision.md");
|
const skill = path.join(u.getPath("skills"), "production_agent_decision.md");
|
||||||
const prompt = await fs.promises.readFile(skill, "utf-8");
|
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 mem = buildMemPrompt(await memory.get(text));
|
||||||
|
|
||||||
const { textStream } = await u.Ai.Text("productionAgent").stream({
|
const { textStream } = await u.Ai.Text("productionAgent").stream({
|
||||||
messages: [
|
messages: [
|
||||||
{ role: "system", content: prompt },
|
{ role: "system", content: prompt },
|
||||||
{ role: "assistant", content: mem },
|
{ role: "assistant", content: mem + "\n" + modelInfo },
|
||||||
{ role: "user", content: text },
|
{ role: "user", content: text },
|
||||||
],
|
],
|
||||||
abortSignal,
|
abortSignal,
|
||||||
@ -138,13 +149,20 @@ function createSubAgent(parentCtx: AgentContext) {
|
|||||||
"分镜面板:<storyboardItem videoDesc='视频描述' prompt=提示词内容 track='分组' duration='视频推荐时间' associateAssetsIds='[该分镜所需的资产ID列表]'></storyboardItem>",
|
"分镜面板:<storyboardItem videoDesc='视频描述' prompt=提示词内容 track='分组' duration='视频推荐时间' associateAssetsIds='[该分镜所需的资产ID列表]'></storyboardItem>",
|
||||||
"```",
|
"```",
|
||||||
].join("\n");
|
].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();
|
const projectInfo = await u.db("o_project").where("id", resTool.data.projectId).first();
|
||||||
if (!projectInfo) throw new Error(`项目不存在,ID: ${resTool.data.projectId}`);
|
if (!projectInfo) throw new Error(`项目不存在,ID: ${resTool.data.projectId}`);
|
||||||
const artSkills = await createArtSkills(projectInfo?.artStyle!);
|
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({
|
return runAgent({
|
||||||
prompt,
|
prompt,
|
||||||
system: systemPrompt + addPrompt,
|
system: systemPrompt + addPrompt,
|
||||||
@ -194,7 +212,6 @@ async function createArtSkills(artName: string) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function removeAllXmlTags(text: string): string {
|
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+[^>]*)?>([\s\S]*?)<\/\1>/g, "");
|
||||||
text = text.replace(/<([a-zA-Z][\w-]*)(\s+[^>]*)?\/>/g, "");
|
text = text.replace(/<([a-zA-Z][\w-]*)(\s+[^>]*)?\/>/g, "");
|
||||||
|
|||||||
@ -95,8 +95,12 @@ export default router.post(
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const { text } = await u.Ai.Text("universalAi").invoke({
|
const { text } = await u.Ai.Text("universalAi").invoke({
|
||||||
system: `${videoPrompt?.data}\n${visualManual}\n${directorManual}`,
|
system: videoPrompt?.data!,
|
||||||
messages: [
|
messages: [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: `${visualManual}\n${directorManual}`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
content: content,
|
content: content,
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import express from "express";
|
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();
|
const router = express.Router();
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@ -17,21 +19,49 @@ const APP_VERSION: string = (() => {
|
|||||||
return pkg.version;
|
return pkg.version;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export default router.post("/", async (req, res) => {
|
export default router.post(
|
||||||
const tagger = "1.1.0";
|
"/",
|
||||||
const taggerList = tagger.split(".").map(Number);
|
validateFields({
|
||||||
const currentVersionList = APP_VERSION.split(".").map(Number);
|
source: z.enum(["toonflow", "github", "gitee", "atomgit"]),
|
||||||
//对比Major
|
}),
|
||||||
if (taggerList[0] > currentVersionList[0]) {
|
async (req, res) => {
|
||||||
return res.status(200).send(success({ needUpdate: true, latestVersion: tagger, reinstall: false }));
|
const { source } = req.body;
|
||||||
}
|
|
||||||
//对比Minor
|
const getUrl = "https://toonflow.oss-cn-beijing.aliyuncs.com/update.json";
|
||||||
if (taggerList[1] > currentVersionList[1]) {
|
|
||||||
return res.status(200).send(success({ needUpdate: true, latestVersion: tagger, reinstall: false }));
|
const versionInfo = await fetch(getUrl).then((res) => res.json());
|
||||||
}
|
if (!versionInfo) return res.status(400).send(error("无法获取版本信息"));
|
||||||
//Patch
|
const { version: tagger, time, data } = versionInfo;
|
||||||
if (taggerList[2] > currentVersionList[2]) {
|
|
||||||
return res.status(200).send(success({ needUpdate: true, latestVersion: tagger, reinstall: false }));
|
const sourceData = data[source];
|
||||||
}
|
if (!sourceData) return res.status(400).send(error("无法获取该源的下载信息"));
|
||||||
return res.status(200).send(success({ needUpdate: false, latestVersion: tagger, reinstall: false }));
|
|
||||||
});
|
const platformType: Record<string, string> = {
|
||||||
|
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 }));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -1,238 +1,64 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import { success, error } from "@/lib/responseFormat";
|
|
||||||
import getPath from "@/utils/getPath";
|
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
import { validateFields } from "@/middleware/middleware";
|
||||||
|
import u from "@/utils";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import compressing from "compressing";
|
import compressing from "compressing";
|
||||||
import { validateFields } from "@/middleware/middleware";
|
import path from "path";
|
||||||
import { spawn } from "child_process";
|
import { success, error } from "@/lib/responseFormat";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
/** 仓库源配置 */
|
const runInstaller = (installerPath: string) => {
|
||||||
const REPO_SOURCES = {
|
const { exec } = require("child_process");
|
||||||
github: {
|
if (process.platform === "darwin") {
|
||||||
repo: "HBAI-Ltd/Toonflow-app",
|
exec(`open "${installerPath}"`);
|
||||||
api: "https://api.github.com/repos/HBAI-Ltd/Toonflow-app/releases/latest",
|
} else {
|
||||||
headers: { Accept: "application/vnd.github.v3+json" },
|
if (process.platform !== "win32") fs.chmodSync(installerPath, 0o755);
|
||||||
},
|
exec(`"${installerPath}"`);
|
||||||
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,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
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<string, string[]> = {
|
|
||||||
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<void> {
|
|
||||||
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(
|
export default router.post(
|
||||||
"/",
|
"/",
|
||||||
validateFields({
|
validateFields({
|
||||||
source: z.enum(["github", "gitee"]),
|
url: z.url(),
|
||||||
reinstall: z.boolean(),
|
reinstall: z.boolean(),
|
||||||
latestVersion: z.string(),
|
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
try {
|
const { reinstall, url } = req.body;
|
||||||
const { reinstall, latestVersion, source } = req.body as {
|
const rootDir = u.getPath(["temp"]);
|
||||||
reinstall: boolean;
|
fs.mkdirSync(rootDir, { recursive: true });
|
||||||
latestVersion: string;
|
if (reinstall) {
|
||||||
source: string;
|
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");
|
||||||
if (!latestVersion) {
|
const installerPath = path.join(rootDir, `latest${ext}`);
|
||||||
return res.status(400).send(error("缺少目标版本号 latestVersion"));
|
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 webPath = u.getPath(["temp", "web"]);
|
||||||
const sourceConfig = REPO_SOURCES[source as SourceType] ?? REPO_SOURCES.github;
|
if (fs.existsSync(webPath)) {
|
||||||
|
fs.cpSync(webPath, u.getPath(["web"]), { recursive: true });
|
||||||
// ─── 获取 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 tempSkillsPath = u.getPath(["temp", "skills"]);
|
||||||
const release = releaseRes.data;
|
if (fs.existsSync(tempSkillsPath)) {
|
||||||
|
fs.cpSync(tempSkillsPath, u.getPath(["skills"]), { recursive: true, force: false });
|
||||||
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<string>();
|
|
||||||
await new Promise<void>((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,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
const tempModelsPath = u.getPath(["temp", "models"]);
|
||||||
console.error("[downloadApp] 更新失败:", err);
|
if (fs.existsSync(tempModelsPath)) {
|
||||||
const message = err?.response?.status === 404 ? "未找到更新资源,请检查版本号或稍后重试" : (err?.message ?? "更新失败,请稍后重试");
|
fs.cpSync(tempModelsPath, u.getPath(["models"]), { recursive: true, force: false });
|
||||||
return res.status(500).send(error(message));
|
}
|
||||||
|
fs.rmSync(rootDir, { recursive: true, force: true });
|
||||||
|
res.status(200).send(success("更新成功,5秒后重启"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -14,7 +14,5 @@ export default router.post("/", async (req, res) => {
|
|||||||
onlyFiles: true,
|
onlyFiles: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("%c Line:15 🍺 entries", "background:#e41a6a", entries);
|
|
||||||
|
|
||||||
res.status(200).send(success(entries));
|
res.status(200).send(success(entries));
|
||||||
});
|
});
|
||||||
|
|||||||
67
src/types/database.d.ts
vendored
67
src/types/database.d.ts
vendored
@ -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 {
|
export interface memories {
|
||||||
'content': string;
|
'content': string;
|
||||||
'createTime': number;
|
'createTime': number;
|
||||||
@ -130,7 +80,6 @@ export interface o_image {
|
|||||||
'filePath'?: string | null;
|
'filePath'?: string | null;
|
||||||
'id'?: number;
|
'id'?: number;
|
||||||
'model'?: string | null;
|
'model'?: string | null;
|
||||||
'reason'?: string | null;
|
|
||||||
'resolution'?: string | null;
|
'resolution'?: string | null;
|
||||||
'state'?: string | null;
|
'state'?: string | null;
|
||||||
'type'?: string | null;
|
'type'?: string | null;
|
||||||
@ -165,6 +114,7 @@ export interface o_outlineNovel {
|
|||||||
export interface o_project {
|
export interface o_project {
|
||||||
'artStyle'?: string | null;
|
'artStyle'?: string | null;
|
||||||
'createTime'?: number | null;
|
'createTime'?: number | null;
|
||||||
|
'directorManual'?: string | null;
|
||||||
'id'?: number | null;
|
'id'?: number | null;
|
||||||
'imageModel'?: string | null;
|
'imageModel'?: string | null;
|
||||||
'imageQuality'?: string | null;
|
'imageQuality'?: string | null;
|
||||||
@ -255,7 +205,6 @@ export interface o_vendorConfig {
|
|||||||
'createTime'?: number | null;
|
'createTime'?: number | null;
|
||||||
'description'?: string | null;
|
'description'?: string | null;
|
||||||
'enable'?: number | null;
|
'enable'?: number | null;
|
||||||
'enableEnglish'?: number | null;
|
|
||||||
'icon'?: string | null;
|
'icon'?: string | null;
|
||||||
'id'?: string;
|
'id'?: string;
|
||||||
'inputs'?: string | null;
|
'inputs'?: string | null;
|
||||||
@ -285,10 +234,6 @@ export interface o_videoTrack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DB {
|
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;
|
"memories": memories;
|
||||||
"o_agentDeploy": o_agentDeploy;
|
"o_agentDeploy": o_agentDeploy;
|
||||||
"o_agentWorkData": o_agentWorkData;
|
"o_agentWorkData": o_agentWorkData;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user