commit 56a32f8a50dec90e28b768221f80dbe3a5623ead Author: lq1405 <2769838458@qq.com> Date: Tue Aug 19 14:33:59 2025 +0800 init diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3dce414 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47af336 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules +dist +out +.DS_Store +.eslintcache +*.log* +resources/logger +resources/project +Database +build diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9c6b791 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +out +dist +pnpm-lock.yaml +LICENSE.md +tsconfig.json +tsconfig.*.json diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..35893b3 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,4 @@ +singleQuote: true +semi: false +printWidth: 100 +trailingComma: none diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..940260d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["dbaeumer.vscode-eslint"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0b6b9a6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,39 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "cwd": "${workspaceRoot}", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" + }, + "runtimeArgs": ["--sourcemap"], + "env": { + "REMOTE_DEBUGGING_PORT": "9222" + } + }, + { + "name": "Debug Renderer Process", + "port": 9222, + "request": "attach", + "type": "chrome", + "webRoot": "${workspaceFolder}/src/renderer", + "timeout": 60000, + "presentation": { + "hidden": true + } + } + ], + "compounds": [ + { + "name": "Debug All", + "configurations": ["Debug Main Process", "Debug Renderer Process"], + "presentation": { + "order": 1 + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..84a0e26 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "vue3snippets.enable-compile-vue-file-on-did-save-code": false +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..29af54d --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# laitool-pro + +An Electron application with Vue and TypeScript + +## Recommended IDE Setup + +- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) + +## Project Setup + +### Install + +```bash +$ npm install +``` + +### Development + +```bash +$ npm run dev +``` + +### Build + +```bash +# For windows +$ npm run build:win + +# For macOS +$ npm run build:mac + +# For Linux +$ npm run build:linux +``` diff --git a/auto-imports.d.ts b/auto-imports.d.ts new file mode 100644 index 0000000..9d24007 --- /dev/null +++ b/auto-imports.d.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + +} diff --git a/components.d.ts b/components.d.ts new file mode 100644 index 0000000..99c6524 --- /dev/null +++ b/components.d.ts @@ -0,0 +1,14 @@ +/* eslint-disable */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +// biome-ignore lint: disable +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } +} diff --git a/dev-app-update.yml b/dev-app-update.yml new file mode 100644 index 0000000..d1f292d --- /dev/null +++ b/dev-app-update.yml @@ -0,0 +1,3 @@ +provider: generic +url: https://example.com/auto-updates +updaterCacheDirName: laitool-pro-updater diff --git a/electron-builder.yml b/electron-builder.yml new file mode 100644 index 0000000..bc36ae0 --- /dev/null +++ b/electron-builder.yml @@ -0,0 +1,48 @@ +appId: com.electron.app +productName: laitool-pro +directories: + buildResources: build +files: + - '!**/.vscode/*' + - '!src/*' + - '!electron.vite.config.{js,ts,mjs,cjs}' + - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' + - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' + - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}' +asarUnpack: + - resources/** +win: + executableName: LaiTool PRO +nsis: + oneClick: false + artifactName: ${name}-${version}-setup.${ext} + shortcutName: ${productName} + uninstallDisplayName: ${productName} + createDesktopShortcut: always + allowToChangeInstallationDirectory: true + createDesktopShortcut: true + createStartMenuShortcut: true + shortcutName: "南枫AI" +mac: + entitlementsInherit: build/entitlements.mac.plist + extendInfo: + - NSCameraUsageDescription: Application requests access to the device's camera. + - NSMicrophoneUsageDescription: Application requests access to the device's microphone. + - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. + - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. + notarize: false +dmg: + artifactName: ${name}-${version}.${ext} +linux: + target: + - AppImage + - snap + - deb + maintainer: electronjs.org + category: Utility +appImage: + artifactName: ${name}-${version}.${ext} +npmRebuild: false +publish: + provider: generic + url: https://example.com/auto-updates diff --git a/electron.vite.config.1747996499639.mjs b/electron.vite.config.1747996499639.mjs new file mode 100644 index 0000000..d12c6de --- /dev/null +++ b/electron.vite.config.1747996499639.mjs @@ -0,0 +1,61 @@ +// electron.vite.config.ts +import { resolve } from "path"; +import { defineConfig, externalizeDepsPlugin, bytecodePlugin } from "electron-vite"; +import vue from "@vitejs/plugin-vue"; +import AutoImport from "unplugin-auto-import/vite"; +import Components from "unplugin-vue-components/vite"; +import tsconfigPaths from "vite-tsconfig-paths"; +import { NaiveUiResolver } from "unplugin-vue-components/resolvers"; +var electron_vite_config_default = defineConfig({ + main: { + resolve: { + alias: { + "@": resolve("src"), + "@renderer": resolve("src/renderer/src") + } + }, + plugins: [externalizeDepsPlugin(), bytecodePlugin(), tsconfigPaths()] + }, + preload: { + plugins: [externalizeDepsPlugin(), bytecodePlugin()], + resolve: { + alias: { + "@": resolve("src"), + "@renderer": resolve("src/renderer/src") + } + } + }, + renderer: { + resolve: { + alias: { + "@": resolve("src"), + "@renderer": resolve("src/renderer/src") + } + }, + plugins: [ + bytecodePlugin(), + vue({ + template: { + compilerOptions: { + // 将webview标签标记为自定义元素 + isCustomElement: (tag) => ["webview"].includes(tag) + } + } + }), + AutoImport({ + imports: [ + "vue", + { + "naive-ui": ["useDialog", "useMessage", "useNotification", "useLoadingBar"] + } + ] + }), + Components({ + resolvers: [NaiveUiResolver()] + }) + ] + } +}); +export { + electron_vite_config_default as default +}; diff --git a/electron.vite.config.ts b/electron.vite.config.ts new file mode 100644 index 0000000..17692e7 --- /dev/null +++ b/electron.vite.config.ts @@ -0,0 +1,58 @@ +import { resolve } from 'path' +import { defineConfig, externalizeDepsPlugin, bytecodePlugin } from 'electron-vite' +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import tsconfigPaths from 'vite-tsconfig-paths' +import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' + +export default defineConfig({ + main: { + resolve: { + alias: { + '@': resolve('src'), + '@renderer': resolve('src/renderer/src') + } + }, + plugins: [externalizeDepsPlugin(), bytecodePlugin(), tsconfigPaths()] + }, + preload: { + plugins: [externalizeDepsPlugin(), bytecodePlugin()], + resolve: { + alias: { + '@': resolve('src'), + '@renderer': resolve('src/renderer/src') + } + } + }, + renderer: { + resolve: { + alias: { + '@': resolve('src'), + '@renderer': resolve('src/renderer/src') + } + }, + plugins: [ + bytecodePlugin(), + vue({ + template: { + compilerOptions: { + // 将webview标签标记为自定义元素 + isCustomElement: (tag) => ['webview'].includes(tag) + } + } + }), + AutoImport({ + imports: [ + 'vue', + { + 'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'] + } + ] + }), + Components({ + resolvers: [NaiveUiResolver()] + }) + ] + } +}) diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..5570e0b --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,25 @@ +import tseslint from '@electron-toolkit/eslint-config-ts' +import eslintConfigPrettier from '@electron-toolkit/eslint-config-prettier' +import eslintPluginVue from 'eslint-plugin-vue' + +export default tseslint.config( + { ignores: ['**/node_modules', '**/dist', '**/out'] }, + tseslint.configs.recommended, + eslintPluginVue.configs['flat/recommended'], + { + files: ['**/*.vue'], + languageOptions: { + parserOptions: { + parser: tseslint.parser + } + } + }, + { + files: ['**/*.{ts,mts,tsx,vue}'], + rules: { + 'vue/require-default-prop': 'off', + 'vue/multi-word-component-names': 'off' + } + }, + eslintConfigPrettier +) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d8e0a8a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9086 @@ +{ + "name": "laitool-pro", + "version": "v1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "laitool-pro", + "version": "v1.0.0", + "hasInstallScript": true, + "dependencies": { + "@alicloud/alimt20181012": "^1.3.0", + "@alicloud/openapi-client": "^0.4.14", + "@alicloud/tea-util": "^1.4.10", + "@electron-toolkit/preload": "^3.0.1", + "@electron-toolkit/utils": "^4.0.0", + "@vicons/ionicons5": "^0.13.0", + "@volcengine/openapi": "^1.30.1", + "axios": "^1.8.4", + "color-string": "^2.0.1", + "compressing": "^1.10.1", + "crypto-js": "^4.2.0", + "electron-updater": "^6.3.9", + "lodash": "^4.17.21", + "moment-timezone": "^0.5.48", + "music-metadata-browser": "^2.5.11", + "node-machine-id": "^1.1.12", + "pinia": "^3.0.1", + "realm": "^20.1.0", + "sharp": "^0.34.1", + "tencentcloud-sdk-nodejs": "^4.1.26", + "vite-tsconfig-paths": "^5.1.4", + "vue-router": "^4.5.0", + "wav-file-info": "^0.0.10", + "winston": "^3.17.0", + "winston-daily-rotate-file": "^5.0.0" + }, + "devDependencies": { + "@electron-toolkit/eslint-config-prettier": "3.0.0", + "@electron-toolkit/eslint-config-ts": "^3.0.0", + "@electron-toolkit/tsconfig": "^1.0.1", + "@types/node": "^22.13.4", + "@vitejs/plugin-vue": "^5.2.1", + "electron": "^34.2.0", + "electron-builder": "^25.1.8", + "electron-vite": "^3.0.0", + "eslint": "^9.20.1", + "eslint-plugin-vue": "^9.32.0", + "naive-ui": "^2.41.0", + "prettier": "^3.5.1", + "typescript": "^5.7.3", + "unplugin-auto-import": "^19.1.2", + "unplugin-vue-components": "^28.4.1", + "vite": "^6.1.0", + "vue": "^3.5.13", + "vue-tsc": "^2.2.2" + } + }, + "node_modules/@alicloud/alimt20181012": { + "version": "1.3.0", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/endpoint-util": "^0.0.1", + "@alicloud/openapi-client": "^0.4.9", + "@alicloud/openapi-util": "^0.3.2", + "@alicloud/openplatform20191219": "2.0.0", + "@alicloud/oss-client": "^1.1.2", + "@alicloud/oss-util": "0.0.1", + "@alicloud/tea-fileform": "^1.0.0", + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "^1.4.8" + } + }, + "node_modules/@alicloud/credentials": { + "version": "2.4.3", + "license": "MIT", + "dependencies": { + "@alicloud/tea-typescript": "^1.8.0", + "httpx": "^2.3.3", + "ini": "^1.3.5", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/endpoint-util": { + "version": "0.0.1", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/gateway-spi": { + "version": "0.0.8", + "license": "ISC", + "dependencies": { + "@alicloud/credentials": "^2", + "@alicloud/tea-typescript": "^1.7.1" + } + }, + "node_modules/@alicloud/http-core-sdk": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "httpx": "^2.1.3" + } + }, + "node_modules/@alicloud/openapi-client": { + "version": "0.4.14", + "license": "ISC", + "dependencies": { + "@alicloud/credentials": "^2.4.2", + "@alicloud/gateway-spi": "^0.0.8", + "@alicloud/openapi-util": "^0.3.2", + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "1.4.9", + "@alicloud/tea-xml": "0.0.3" + } + }, + "node_modules/@alicloud/openapi-client/node_modules/@alicloud/tea-util": { + "version": "1.4.9", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/openapi-util": { + "version": "0.3.2", + "license": "ISC", + "dependencies": { + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "^1.3.0", + "kitx": "^2.1.0", + "sm3": "^1.0.3" + } + }, + "node_modules/@alicloud/openplatform20191219": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/endpoint-util": "^0.0.1", + "@alicloud/openapi-client": "^0.4.1", + "@alicloud/openapi-util": "^0.2.9", + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "^1.4.4" + } + }, + "node_modules/@alicloud/openplatform20191219/node_modules/@alicloud/openapi-util": { + "version": "0.2.9", + "license": "ISC", + "dependencies": { + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "^1.3.0", + "kitx": "^2.1.0", + "sm3": "^1.0.3" + } + }, + "node_modules/@alicloud/oss-baseclient": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "@alicloud/credentials": "^1.0.0", + "@alicloud/tea-typescript": "^1.5.0", + "@types/mime": "^2.0.1", + "@types/xml2js": "^0.4.5", + "int64-buffer": "^0.99.1007", + "kitx": "^2.0.0", + "mime": "^2.4.4", + "xml2js": "^0.4.22" + } + }, + "node_modules/@alicloud/oss-baseclient/node_modules/@alicloud/credentials": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "@alicloud/sts-sdk": "^1.0.0", + "httpx": "^2.2.0", + "ini": "^1.3.5", + "json-bigint": "^0.2.3", + "kitx": "^1.2.1" + } + }, + "node_modules/@alicloud/oss-baseclient/node_modules/@alicloud/credentials/node_modules/kitx": { + "version": "1.3.0", + "license": "MIT" + }, + "node_modules/@alicloud/oss-baseclient/node_modules/bignumber.js": { + "version": "4.1.0", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@alicloud/oss-baseclient/node_modules/json-bigint": { + "version": "0.2.3", + "license": "MIT", + "dependencies": { + "bignumber.js": "^4.0.0" + } + }, + "node_modules/@alicloud/oss-client": { + "version": "1.1.4", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/credentials": "^2.4.3", + "@alicloud/oss-baseclient": "^1.2.0", + "@alicloud/oss-util": "^0.0.3", + "@alicloud/rpc-util": "^0.0.1", + "@alicloud/tea-fileform": "^1.0.0", + "@alicloud/tea-typescript": "^1.2.0", + "@alicloud/tea-util": "1.4.9", + "@alicloud/tea-xml": "^0.0.3" + } + }, + "node_modules/@alicloud/oss-client/node_modules/@alicloud/oss-util": { + "version": "0.0.3", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.0", + "@types/mime": "^2.0.1", + "@types/xml2js": "^0.4.5", + "int64-buffer": "^0.99.1007", + "kitx": "^2.0.0", + "mime": "^2.4.4", + "xml2js": "^0.4.22" + } + }, + "node_modules/@alicloud/oss-client/node_modules/@alicloud/tea-util": { + "version": "1.4.9", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/oss-util": { + "version": "0.0.1", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.0", + "@types/mime": "^2.0.1", + "@types/xml2js": "^0.4.5", + "int64-buffer": "^0.99.1007", + "kitx": "^2.0.0", + "mime": "^2.4.4", + "xml2js": "^0.4.22" + } + }, + "node_modules/@alicloud/rpc-util": { + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@alicloud/tea-typescript": "^1", + "@types/xml2js": "^0.4.5", + "kitx": "^2.0.0", + "xml2js": "^0.4.22" + } + }, + "node_modules/@alicloud/sts-sdk": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "@alicloud/http-core-sdk": "^1.0.0", + "uuid": "^3.3.2" + } + }, + "node_modules/@alicloud/sts-sdk/node_modules/uuid": { + "version": "3.4.0", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/@alicloud/tea-fileform": { + "version": "1.2.0", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1" + } + }, + "node_modules/@alicloud/tea-typescript": { + "version": "1.8.0", + "license": "ISC", + "dependencies": { + "@types/node": "^12.0.2", + "httpx": "^2.2.6" + } + }, + "node_modules/@alicloud/tea-typescript/node_modules/@types/node": { + "version": "12.20.55", + "license": "MIT" + }, + "node_modules/@alicloud/tea-util": { + "version": "1.4.10", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "@darabonba/typescript": "^1.0.0", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/tea-xml": { + "version": "0.0.3", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1", + "@types/xml2js": "^0.4.5", + "xml2js": "^0.6.0" + } + }, + "node_modules/@alicloud/tea-xml/node_modules/xml2js": { + "version": "0.6.2", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@alicloud/tea-xml/node_modules/xmlbuilder": { + "version": "11.0.1", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.10", + "@babel/types": "^7.26.10", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.10", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.10" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.26.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.10", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@css-render/plugin-bem": { + "version": "0.15.14", + "dev": true, + "license": "MIT", + "peerDependencies": { + "css-render": "~0.15.14" + } + }, + "node_modules/@css-render/vue3-ssr": { + "version": "0.15.14", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@darabonba/typescript": { + "version": "1.0.3", + "license": "Apache License 2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "httpx": "^2.3.2", + "lodash": "^4.17.21", + "moment": "^2.30.1", + "moment-timezone": "^0.5.45", + "xml2js": "^0.6.2" + } + }, + "node_modules/@darabonba/typescript/node_modules/xml2js": { + "version": "0.6.2", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@darabonba/typescript/node_modules/xmlbuilder": { + "version": "11.0.1", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@eggjs/yauzl": { + "version": "2.11.0", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer2": "^1.2.0" + } + }, + "node_modules/@electron-toolkit/eslint-config-prettier": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.3" + }, + "peerDependencies": { + "eslint": ">= 9.0.0", + "prettier": ">= 3.0.0" + } + }, + "node_modules/@electron-toolkit/eslint-config-ts": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint/js": "^9.18.0", + "globals": "^15.14.0", + "typescript-eslint": "^8.21.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@electron-toolkit/preload": { + "version": "3.0.1", + "license": "MIT", + "peerDependencies": { + "electron": ">=13.0.0" + } + }, + "node_modules/@electron-toolkit/tsconfig": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/node": "*" + } + }, + "node_modules/@electron-toolkit/utils": { + "version": "4.0.0", + "license": "MIT", + "peerDependencies": { + "electron": ">=13.0.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/rebuild": { + "version": "3.6.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "fs-extra": "^10.0.0", + "got": "^11.7.0", + "node-abi": "^3.45.0", + "node-api-version": "^0.2.0", + "node-gyp": "^9.0.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^6.0.5", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/@electron/rebuild/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/rebuild/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/rebuild/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/rebuild/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.7", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.12.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.23.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.7", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.12.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.1", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "license": "BSD-3-Clause" + }, + "node_modules/@realm/fetch": { + "version": "0.1.1", + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.37.0", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "license": "MIT" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/katex": { + "version": "0.16.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.16", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mime": { + "version": "2.0.3", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.13.13", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/@types/xml2js": { + "version": "0.4.14", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.27.0", + "@typescript-eslint/type-utils": "8.27.0", + "@typescript-eslint/utils": "8.27.0", + "@typescript-eslint/visitor-keys": "8.27.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.27.0", + "@typescript-eslint/types": "8.27.0", + "@typescript-eslint/typescript-estree": "8.27.0", + "@typescript-eslint/visitor-keys": "8.27.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.27.0", + "@typescript-eslint/visitor-keys": "8.27.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.27.0", + "@typescript-eslint/utils": "8.27.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.27.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.27.0", + "@typescript-eslint/visitor-keys": "8.27.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.27.0", + "@typescript-eslint/types": "8.27.0", + "@typescript-eslint/typescript-estree": "8.27.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.27.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vicons/ionicons5": { + "version": "0.13.0", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.12" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.12", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.12", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volcengine/openapi": { + "version": "1.30.1", + "license": "Apache-2.0", + "dependencies": { + "axios": "^0.21.1", + "crc": "^4.1.0", + "crypto-js": "^4.2.0", + "dayjs": "^1.11.5", + "debug": "^4.3.1", + "form-data": "^3.0.0", + "lodash.get": "^4.4.2", + "p-limit": "^3.0.0", + "protobufjs": "7.2.5", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@volcengine/openapi/node_modules/axios": { + "version": "0.21.4", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/@volcengine/openapi/node_modules/buffer": { + "version": "6.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@volcengine/openapi/node_modules/crc": { + "version": "4.3.2", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "buffer": ">=6.0.3" + }, + "peerDependenciesMeta": { + "buffer": { + "optional": true + } + } + }, + "node_modules/@volcengine/openapi/node_modules/form-data": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@volcengine/openapi/node_modules/uuid": { + "version": "8.3.2", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "license": "MIT" + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.2", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.2", + "birpc": "^0.2.19", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.2", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.11", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/alien-signals": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/app-builder-bin": { + "version": "5.0.0-alpha.10", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "25.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.1", + "@electron/rebuild": "3.6.1", + "@electron/universal": "2.0.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chromium-pickle-js": "^0.2.0", + "config-file-ts": "0.2.8-rc1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "25.1.7", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.0.0", + "resedit": "^1.7.0", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "25.1.8", + "electron-builder-squirrel-windows": "25.1.8" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/archiver": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/async": { + "version": "3.2.6", + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/axios": { + "version": "1.8.4", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.0", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/birpc": { + "version": "0.2.19", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/boolean": { + "version": "3.2.0", + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bson": { + "version": "4.7.2", + "license": "Apache-2.0", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "25.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "5.0.0-alpha.10", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.10", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "16.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "8.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.18.3", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001707", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color": { + "version": "3.2.1", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-2.0.1.tgz", + "integrity": "sha512-5z9FbYTZPAo8iKsNEqRNv+OlpBbDcoE+SY9GjLfDUHEfcNNV7tS9eSAlFHEaub/r5tBL9LtskAeq1l9SaoZ5tQ==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-2.0.0.tgz", + "integrity": "sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/color/node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compressing": { + "version": "1.10.1", + "license": "MIT", + "dependencies": { + "@eggjs/yauzl": "^2.11.0", + "flushwritable": "^1.0.0", + "get-ready": "^1.0.0", + "iconv-lite": "^0.5.0", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "streamifier": "^0.1.1", + "tar-stream": "^1.5.2", + "yazl": "^2.4.2" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/compressing/node_modules/bl": { + "version": "1.2.3", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/compressing/node_modules/iconv-lite": { + "version": "0.5.2", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compressing/node_modules/mkdirp": { + "version": "0.5.6", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/compressing/node_modules/readable-stream": { + "version": "2.3.8", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/compressing/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/compressing/node_modules/string_decoder": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/compressing/node_modules/tar-stream": { + "version": "1.6.2", + "license": "MIT", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/config-file-ts": { + "version": "0.2.8-rc1", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.12", + "typescript": "^5.4.3" + } + }, + "node_modules/config-file-ts/node_modules/glob": { + "version": "10.4.5", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minipass": { + "version": "7.1.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "license": "ISC" + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "license": "MIT" + }, + "node_modules/css-render": { + "version": "0.15.14", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/hash": "~0.8.0", + "csstype": "~3.0.5" + } + }, + "node_modules/css-render/node_modules/csstype": { + "version": "3.0.11", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-tz": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "peerDependencies": { + "date-fns": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/defaults": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "25.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "34.3.4", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^20.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "25.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "dmg-builder": "25.1.8", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "25.1.8", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "25.1.8", + "archiver": "^5.3.1", + "builder-util": "25.1.7", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-publish": { + "version": "25.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.123", + "dev": true, + "license": "ISC" + }, + "node_modules/electron-updater": { + "version": "6.3.9", + "license": "MIT", + "dependencies": { + "builder-util-runtime": "9.2.10", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "^7.6.3", + "tiny-typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/fs-extra": { + "version": "10.1.0", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-updater/node_modules/jsonfile": { + "version": "6.1.0", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-updater/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-updater/node_modules/universalify": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-vite": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "cac": "^6.7.14", + "esbuild": "^0.24.2", + "magic-string": "^0.30.17", + "picocolors": "^1.1.1" + }, + "bin": { + "electron-vite": "bin/electron-vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@swc/core": "^1.0.0", + "vite": "^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + } + } + }, + "node_modules/electron/node_modules/@types/node": { + "version": "20.17.27", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "6.19.8", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "license": "MIT", + "optional": true + }, + "node_modules/esbuild": { + "version": "0.24.2", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.23.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.2", + "@eslint/config-helpers": "^0.2.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.23.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.1", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.10.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.33.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-vue/node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-vue/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-vue/node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evtd": { + "version": "0.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/expand-template": { + "version": "2.0.3", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.2", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/exsolve": { + "version": "1.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fd-slicer2": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "pend": "^1.2.0" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "license": "MIT", + "dependencies": { + "moment": "^2.29.1" + } + }, + "node_modules/file-type": { + "version": "16.5.4", + "license": "MIT", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC" + }, + "node_modules/flushwritable": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-ready": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/get-stream": { + "version": "5.2.0", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "license": "MIT" + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/httpx": { + "version": "2.3.3", + "license": "MIT", + "dependencies": { + "@types/node": "^20", + "debug": "^4.1.1" + } + }, + "node_modules/httpx/node_modules/@types/node": { + "version": "20.17.31", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/httpx/node_modules/undici-types": { + "version": "6.19.8", + "license": "MIT" + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "license": "ISC" + }, + "node_modules/int64-buffer": { + "version": "0.99.1007", + "license": "MIT", + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kitx": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "@types/node": "^22.5.4" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/local-pkg": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.0.1", + "quansync": "^0.2.8" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logform": { + "version": "2.7.0", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/long": { + "version": "5.3.2", + "license": "Apache-2.0" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "dev": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/mitt": { + "version": "3.0.1", + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.7.4", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/music-metadata": { + "version": "7.14.0", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "content-type": "^1.0.5", + "debug": "^4.3.4", + "file-type": "^16.5.4", + "media-typer": "^1.1.0", + "strtok3": "^6.3.0", + "token-types": "^4.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/music-metadata-browser": { + "version": "2.5.11", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.4", + "music-metadata": "^7.13.3", + "readable-stream": "^4.3.0", + "readable-web-to-node-stream": "^3.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/music-metadata-browser/node_modules/buffer": { + "version": "6.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/music-metadata-browser/node_modules/readable-stream": { + "version": "4.7.0", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/naive-ui": { + "version": "2.41.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@css-render/plugin-bem": "^0.15.14", + "@css-render/vue3-ssr": "^0.15.14", + "@types/katex": "^0.16.2", + "@types/lodash": "^4.14.198", + "@types/lodash-es": "^4.17.9", + "async-validator": "^4.2.5", + "css-render": "^0.15.14", + "csstype": "^3.1.3", + "date-fns": "^3.6.0", + "date-fns-tz": "^3.1.3", + "evtd": "^0.2.4", + "highlight.js": "^11.8.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "seemly": "^0.3.8", + "treemate": "^0.3.11", + "vdirs": "^0.1.8", + "vooks": "^0.2.12", + "vueuc": "^0.4.63" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.74.0", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-api-version/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "9.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "dev": true, + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/pe-library": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/@vue/devtools-api": { + "version": "7.7.2", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.2" + } + }, + "node_modules/pkg-types": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.1", + "exsolve": "^1.0.1", + "pathe": "^2.0.3" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs": { + "version": "7.2.5", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quansync": { + "version": "0.2.10", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/buffer": { + "version": "6.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "4.7.0", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/realm": { + "version": "20.1.0", + "hasInstallScript": true, + "license": "apache-2.0", + "dependencies": { + "@realm/fetch": "^0.1.1", + "bson": "^4.7.2", + "debug": "^4.3.4", + "node-machine-id": "^1.1.12", + "path-browserify": "^1.0.1", + "prebuild-install": "^7.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": ">=0.71.0" + }, + "peerDependenciesMeta": { + "react-native": { + "optional": true + } + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.37.0", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.37.0", + "@rollup/rollup-android-arm64": "4.37.0", + "@rollup/rollup-darwin-arm64": "4.37.0", + "@rollup/rollup-darwin-x64": "4.37.0", + "@rollup/rollup-freebsd-arm64": "4.37.0", + "@rollup/rollup-freebsd-x64": "4.37.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.37.0", + "@rollup/rollup-linux-arm-musleabihf": "4.37.0", + "@rollup/rollup-linux-arm64-gnu": "4.37.0", + "@rollup/rollup-linux-arm64-musl": "4.37.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.37.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.37.0", + "@rollup/rollup-linux-riscv64-gnu": "4.37.0", + "@rollup/rollup-linux-riscv64-musl": "4.37.0", + "@rollup/rollup-linux-s390x-gnu": "4.37.0", + "@rollup/rollup-linux-x64-gnu": "4.37.0", + "@rollup/rollup-linux-x64-musl": "4.37.0", + "@rollup/rollup-win32-arm64-msvc": "4.37.0", + "@rollup/rollup-win32-ia32-msvc": "4.37.0", + "@rollup/rollup-win32-x64-msvc": "4.37.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.6", + "devOptional": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.4.1", + "license": "ISC" + }, + "node_modules/scule": { + "version": "1.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/seemly": { + "version": "0.3.10", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.34.1", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.7.1" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.1", + "@img/sharp-darwin-x64": "0.34.1", + "@img/sharp-libvips-darwin-arm64": "1.1.0", + "@img/sharp-libvips-darwin-x64": "1.1.0", + "@img/sharp-libvips-linux-arm": "1.1.0", + "@img/sharp-libvips-linux-arm64": "1.1.0", + "@img/sharp-libvips-linux-ppc64": "1.1.0", + "@img/sharp-libvips-linux-s390x": "1.1.0", + "@img/sharp-libvips-linux-x64": "1.1.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", + "@img/sharp-libvips-linuxmusl-x64": "1.1.0", + "@img/sharp-linux-arm": "0.34.1", + "@img/sharp-linux-arm64": "0.34.1", + "@img/sharp-linux-s390x": "0.34.1", + "@img/sharp-linux-x64": "0.34.1", + "@img/sharp-linuxmusl-arm64": "0.34.1", + "@img/sharp-linuxmusl-x64": "0.34.1", + "@img/sharp-wasm32": "0.34.1", + "@img/sharp-win32-ia32": "0.34.1", + "@img/sharp-win32-x64": "0.34.1" + } + }, + "node_modules/sharp/node_modules/color": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/sharp/node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sm3": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "9.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/streamifier": { + "version": "0.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "6.3.0", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.10.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.2", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/temp-file": { + "version": "3.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/tencentcloud-sdk-nodejs": { + "version": "4.1.26", + "license": "Apache-2.0", + "dependencies": { + "form-data": "^3.0.0", + "get-stream": "^6.0.0", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "json-bigint": "^1.0.0", + "node-fetch": "^2.2.0", + "tslib": "1.13.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tencentcloud-sdk-nodejs/node_modules/agent-base": { + "version": "6.0.2", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tencentcloud-sdk-nodejs/node_modules/form-data": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tencentcloud-sdk-nodejs/node_modules/get-stream": { + "version": "6.0.1", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tencentcloud-sdk-nodejs/node_modules/https-proxy-agent": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tencentcloud-sdk-nodejs/node_modules/node-fetch": { + "version": "2.7.0", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/tencentcloud-sdk-nodejs/node_modules/tslib": { + "version": "1.13.0", + "license": "0BSD" + }, + "node_modules/text-hex": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmp": { + "version": "0.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/to-buffer": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-types": { + "version": "4.2.1", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "license": "MIT" + }, + "node_modules/treemate": { + "version": "0.3.11", + "dev": true, + "license": "MIT" + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfck": { + "version": "3.1.5", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.27.0", + "@typescript-eslint/parser": "8.27.0", + "@typescript-eslint/utils": "8.27.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/ufo": { + "version": "1.5.4", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.20.0", + "license": "MIT" + }, + "node_modules/unimport": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.1", + "scule": "^1.3.0", + "strip-literal": "^3.0.0", + "tinyglobby": "^0.2.11", + "unplugin": "^2.2.0", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unimport/node_modules/confbox": { + "version": "0.1.8", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport/node_modules/escape-string-regexp": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unimport/node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unimport/node_modules/pkg-types": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/unique-filename": { + "version": "2.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unique-slug": { + "version": "3.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unplugin": { + "version": "2.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.1", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "19.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "picomatch": "^4.0.2", + "unimport": "^4.1.2", + "unplugin": "^2.2.2", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-auto-import/node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unplugin-utils": { + "version": "0.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-utils/node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unplugin-vue-components": { + "version": "28.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.6.0", + "debug": "^4.4.0", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "tinyglobby": "^0.2.12", + "unplugin": "^2.2.0", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "9.0.1", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vdirs": { + "version": "0.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/vite": { + "version": "6.2.3", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.1", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" + } + }, + "node_modules/vooks": { + "version": "0.2.12", + "dev": true, + "license": "MIT", + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vue-router": { + "version": "4.5.0", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~2.4.11", + "@vue/language-core": "2.2.8" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vueuc": { + "version": "0.4.64", + "dev": true, + "license": "MIT", + "dependencies": { + "@css-render/vue3-ssr": "^0.15.10", + "@juggle/resize-observer": "^3.3.1", + "css-render": "^0.15.10", + "evtd": "^0.2.4", + "seemly": "^0.3.6", + "vdirs": "^0.1.4", + "vooks": "^0.2.4" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/wav-file-info": { + "version": "0.0.10", + "license": "ISC" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "license": "BSD-2-Clause" + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^3.0.0", + "triple-beam": "^1.4.1", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xml2js": { + "version": "0.4.23", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..58bb95d --- /dev/null +++ b/package.json @@ -0,0 +1,97 @@ +{ + "name": "laitool-pro", + "version": "v3.4.3", + "description": "A desktop application for AI image generation and processing, built with Electron and Vue 3.", + "main": "./out/main/index.js", + "author": "xiangbei", + "homepage": "https://electron-vite.org", + "scripts": { + "format": "prettier --write .", + "lint": "eslint --cache .", + "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", + "typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false", + "typecheck": "npm run typecheck:node && npm run typecheck:web", + "start": "electron-vite preview", + "dev": "electron-vite dev", + "build": "npm run typecheck && electron-vite build", + "postinstall": "electron-builder install-app-deps", + "build:unpack": "npm run build && electron-builder --dir", + "build:win": "npm run build && electron-builder --win", + "build:mac": "npm run build && electron-builder --mac", + "build:linux": "npm run build && electron-builder --linux" + }, + "dependencies": { + "@alicloud/alimt20181012": "^1.3.0", + "@alicloud/openapi-client": "^0.4.14", + "@alicloud/tea-util": "^1.4.10", + "@electron-toolkit/preload": "^3.0.1", + "@electron-toolkit/utils": "^4.0.0", + "@vicons/ionicons5": "^0.13.0", + "@volcengine/openapi": "^1.30.1", + "axios": "^1.8.4", + "color-string": "^2.0.1", + "compressing": "^1.10.1", + "crypto-js": "^4.2.0", + "electron-updater": "^6.3.9", + "lodash": "^4.17.21", + "moment-timezone": "^0.5.48", + "music-metadata-browser": "^2.5.11", + "node-machine-id": "^1.1.12", + "pinia": "^3.0.1", + "realm": "^20.1.0", + "sharp": "^0.34.1", + "tencentcloud-sdk-nodejs": "^4.1.26", + "vite-tsconfig-paths": "^5.1.4", + "vue-router": "^4.5.0", + "wav-file-info": "^0.0.10", + "winston": "^3.17.0", + "winston-daily-rotate-file": "^5.0.0" + }, + "devDependencies": { + "@electron-toolkit/eslint-config-prettier": "3.0.0", + "@electron-toolkit/eslint-config-ts": "^3.0.0", + "@electron-toolkit/tsconfig": "^1.0.1", + "@types/node": "^22.13.4", + "@vitejs/plugin-vue": "^5.2.1", + "electron": "^34.2.0", + "electron-builder": "^25.1.8", + "electron-vite": "^3.0.0", + "eslint": "^9.20.1", + "eslint-plugin-vue": "^9.32.0", + "naive-ui": "^2.41.0", + "prettier": "^3.5.1", + "typescript": "^5.7.3", + "unplugin-auto-import": "^19.1.2", + "unplugin-vue-components": "^28.4.1", + "vite": "^6.1.0", + "vue": "^3.5.13", + "vue-tsc": "^2.2.2" + }, + "build": { + "asar": true, + "files": [ + "out/**/*", + "!resources/" + ], + "extraResources": [ + "resources/package/exittool/**", + "resources/package/ffmpeg/**", + "resources/package/Improve/**", + "resources/image/style/**", + "resources/image/zhanwei.png", + "resources/scripts/model/**", + "resources/scripts/Lai.exe", + "resources/scripts/discordScript.js", + "resources/tmp/**", + "resources/icon.ico" + ], + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true + }, + "win": { + "target": "nsis", + "icon": "./resources/icon.ico" + } + } +} diff --git a/resources/config/init.txt b/resources/config/init.txt new file mode 100644 index 0000000..44b74f2 --- /dev/null +++ b/resources/config/init.txt @@ -0,0 +1 @@ +default-software-id \ No newline at end of file diff --git a/resources/icon.ico b/resources/icon.ico new file mode 100644 index 0000000..f930d2a Binary files /dev/null and b/resources/icon.ico differ diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000..84ce9b0 Binary files /dev/null and b/resources/icon.png differ diff --git a/resources/icon1.png b/resources/icon1.png new file mode 100644 index 0000000..cf9e8b2 Binary files /dev/null and b/resources/icon1.png differ diff --git a/resources/image/预设/1745404178107_dbac52d2-90a5-4eb8-b590-404208a2165c.jpg b/resources/image/预设/1745404178107_dbac52d2-90a5-4eb8-b590-404208a2165c.jpg new file mode 100644 index 0000000..d8d7444 Binary files /dev/null and b/resources/image/预设/1745404178107_dbac52d2-90a5-4eb8-b590-404208a2165c.jpg differ diff --git a/resources/image/预设/1745404182754_6315d847-4238-422e-9a8c-c55143314514.jpg b/resources/image/预设/1745404182754_6315d847-4238-422e-9a8c-c55143314514.jpg new file mode 100644 index 0000000..1c7e7b3 Binary files /dev/null and b/resources/image/预设/1745404182754_6315d847-4238-422e-9a8c-c55143314514.jpg differ diff --git a/resources/image/预设/1745559430289_60f1b782-d32e-4de7-a2b3-d0876104183c.png b/resources/image/预设/1745559430289_60f1b782-d32e-4de7-a2b3-d0876104183c.png new file mode 100644 index 0000000..adf1129 Binary files /dev/null and b/resources/image/预设/1745559430289_60f1b782-d32e-4de7-a2b3-d0876104183c.png differ diff --git a/resources/image/预设/1745559679535_77295d22-d2b0-4c35-8233-ae7aa3fc7cec.jpg b/resources/image/预设/1745559679535_77295d22-d2b0-4c35-8233-ae7aa3fc7cec.jpg new file mode 100644 index 0000000..956a2b1 Binary files /dev/null and b/resources/image/预设/1745559679535_77295d22-d2b0-4c35-8233-ae7aa3fc7cec.jpg differ diff --git a/resources/image/预设/1745561843637_864678bf-a336-490f-9462-57d593837590.jpg b/resources/image/预设/1745561843637_864678bf-a336-490f-9462-57d593837590.jpg new file mode 100644 index 0000000..7d8c415 Binary files /dev/null and b/resources/image/预设/1745561843637_864678bf-a336-490f-9462-57d593837590.jpg differ diff --git a/resources/image/预设/1747746946369_48e98482-4876-4a56-8a96-d2ab28198b07.png b/resources/image/预设/1747746946369_48e98482-4876-4a56-8a96-d2ab28198b07.png new file mode 100644 index 0000000..2f1805f Binary files /dev/null and b/resources/image/预设/1747746946369_48e98482-4876-4a56-8a96-d2ab28198b07.png differ diff --git a/resources/image/预设/1747749697381_a75ec1e2-50b6-4436-b5e8-5b4c8337be06.png b/resources/image/预设/1747749697381_a75ec1e2-50b6-4436-b5e8-5b4c8337be06.png new file mode 100644 index 0000000..b7106e5 Binary files /dev/null and b/resources/image/预设/1747749697381_a75ec1e2-50b6-4436-b5e8-5b4c8337be06.png differ diff --git a/resources/package/Improve/input/00000-1616097796-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png b/resources/package/Improve/input/00000-1616097796-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png new file mode 100644 index 0000000..3f44191 Binary files /dev/null and b/resources/package/Improve/input/00000-1616097796-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png differ diff --git a/resources/package/Improve/input/00001-1083368712-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png b/resources/package/Improve/input/00001-1083368712-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png new file mode 100644 index 0000000..c11e04f Binary files /dev/null and b/resources/package/Improve/input/00001-1083368712-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png differ diff --git a/resources/package/Improve/input/00002-763798052-(masterpiece, best quality, a group of courtiers in fancy robes faced the young emperor in a chinese-style palace, and the minis.png b/resources/package/Improve/input/00002-763798052-(masterpiece, best quality, a group of courtiers in fancy robes faced the young emperor in a chinese-style palace, and the minis.png new file mode 100644 index 0000000..a907761 Binary files /dev/null and b/resources/package/Improve/input/00002-763798052-(masterpiece, best quality, a group of courtiers in fancy robes faced the young emperor in a chinese-style palace, and the minis.png differ diff --git a/resources/package/Improve/input/00003-2561272579-(masterpiece, best quality,a young face stalwart handsome domineering majestic long black hair wearing a gorgeous robe of a man.png b/resources/package/Improve/input/00003-2561272579-(masterpiece, best quality,a young face stalwart handsome domineering majestic long black hair wearing a gorgeous robe of a man.png new file mode 100644 index 0000000..62d2b5d Binary files /dev/null and b/resources/package/Improve/input/00003-2561272579-(masterpiece, best quality,a young face stalwart handsome domineering majestic long black hair wearing a gorgeous robe of a man.png differ diff --git a/resources/package/Improve/input/00004-1447729042-(masterpiece, best quality, a young face stalwart handsome domineering majestic long black hair wearing gorgeous robes of a man.png b/resources/package/Improve/input/00004-1447729042-(masterpiece, best quality, a young face stalwart handsome domineering majestic long black hair wearing gorgeous robes of a man.png new file mode 100644 index 0000000..4ae8d1a Binary files /dev/null and b/resources/package/Improve/input/00004-1447729042-(masterpiece, best quality, a young face stalwart handsome domineering majestic long black hair wearing gorgeous robes of a man.png differ diff --git a/resources/package/Improve/models/realesr-animevideov3-x2.bin b/resources/package/Improve/models/realesr-animevideov3-x2.bin new file mode 100644 index 0000000..2069105 Binary files /dev/null and b/resources/package/Improve/models/realesr-animevideov3-x2.bin differ diff --git a/resources/package/Improve/models/realesr-animevideov3-x2.param b/resources/package/Improve/models/realesr-animevideov3-x2.param new file mode 100644 index 0000000..42e7748 --- /dev/null +++ b/resources/package/Improve/models/realesr-animevideov3-x2.param @@ -0,0 +1,43 @@ +7767517 +41 42 +Input input.1 0 1 data +Split splitncnn_input0 1 2 data input.1_splitncnn_0 input.1_splitncnn_1 +Convolution Conv_0 1 1 input.1_splitncnn_1 54 0=64 1=3 4=1 5=1 6=1728 +PReLU PRelu_1 1 1 54 56 0=64 +Convolution Conv_2 1 1 56 57 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_3 1 1 57 59 0=64 +Convolution Conv_4 1 1 59 60 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_5 1 1 60 62 0=64 +Convolution Conv_6 1 1 62 63 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_7 1 1 63 65 0=64 +Convolution Conv_8 1 1 65 66 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_9 1 1 66 68 0=64 +Convolution Conv_10 1 1 68 69 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_11 1 1 69 71 0=64 +Convolution Conv_12 1 1 71 72 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_13 1 1 72 74 0=64 +Convolution Conv_14 1 1 74 75 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_15 1 1 75 77 0=64 +Convolution Conv_16 1 1 77 78 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_17 1 1 78 80 0=64 +Convolution Conv_18 1 1 80 81 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_19 1 1 81 83 0=64 +Convolution Conv_20 1 1 83 84 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_21 1 1 84 86 0=64 +Convolution Conv_22 1 1 86 87 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_23 1 1 87 89 0=64 +Convolution Conv_24 1 1 89 90 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_25 1 1 90 92 0=64 +Convolution Conv_26 1 1 92 93 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_27 1 1 93 95 0=64 +Convolution Conv_28 1 1 95 96 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_29 1 1 96 98 0=64 +Convolution Conv_30 1 1 98 99 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_31 1 1 99 101 0=64 +Convolution Conv_32 1 1 101 102 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_33 1 1 102 104 0=64 +Convolution Conv_34 1 1 104 105 0=48 1=3 4=1 5=1 6=27648 +PixelShuffle DepthToSpace_35 1 1 105 106 0=4 +Interp Resize_37 1 1 input.1_splitncnn_0 111 0=1 1=4.000000e+00 2=4.000000e+00 +BinaryOp Add_38 2 1 106 111 112 +Interp Resize_40 1 1 112 output 0=3 1=5.000000e-01 2=5.000000e-01 diff --git a/resources/package/Improve/models/realesr-animevideov3-x3.bin b/resources/package/Improve/models/realesr-animevideov3-x3.bin new file mode 100644 index 0000000..2069105 Binary files /dev/null and b/resources/package/Improve/models/realesr-animevideov3-x3.bin differ diff --git a/resources/package/Improve/models/realesr-animevideov3-x3.param b/resources/package/Improve/models/realesr-animevideov3-x3.param new file mode 100644 index 0000000..bf47185 --- /dev/null +++ b/resources/package/Improve/models/realesr-animevideov3-x3.param @@ -0,0 +1,43 @@ +7767517 +41 42 +Input input.1 0 1 data +Split splitncnn_input0 1 2 data input.1_splitncnn_0 input.1_splitncnn_1 +Convolution Conv_0 1 1 input.1_splitncnn_1 54 0=64 1=3 4=1 5=1 6=1728 +PReLU PRelu_1 1 1 54 56 0=64 +Convolution Conv_2 1 1 56 57 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_3 1 1 57 59 0=64 +Convolution Conv_4 1 1 59 60 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_5 1 1 60 62 0=64 +Convolution Conv_6 1 1 62 63 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_7 1 1 63 65 0=64 +Convolution Conv_8 1 1 65 66 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_9 1 1 66 68 0=64 +Convolution Conv_10 1 1 68 69 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_11 1 1 69 71 0=64 +Convolution Conv_12 1 1 71 72 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_13 1 1 72 74 0=64 +Convolution Conv_14 1 1 74 75 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_15 1 1 75 77 0=64 +Convolution Conv_16 1 1 77 78 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_17 1 1 78 80 0=64 +Convolution Conv_18 1 1 80 81 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_19 1 1 81 83 0=64 +Convolution Conv_20 1 1 83 84 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_21 1 1 84 86 0=64 +Convolution Conv_22 1 1 86 87 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_23 1 1 87 89 0=64 +Convolution Conv_24 1 1 89 90 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_25 1 1 90 92 0=64 +Convolution Conv_26 1 1 92 93 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_27 1 1 93 95 0=64 +Convolution Conv_28 1 1 95 96 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_29 1 1 96 98 0=64 +Convolution Conv_30 1 1 98 99 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_31 1 1 99 101 0=64 +Convolution Conv_32 1 1 101 102 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_33 1 1 102 104 0=64 +Convolution Conv_34 1 1 104 105 0=48 1=3 4=1 5=1 6=27648 +PixelShuffle DepthToSpace_35 1 1 105 106 0=4 +Interp Resize_37 1 1 input.1_splitncnn_0 111 0=1 1=4.000000e+00 2=4.000000e+00 +BinaryOp Add_38 2 1 106 111 112 +Interp Resize_40 1 1 112 output 0=3 1=7.500000e-01 2=7.500000e-01 diff --git a/resources/package/Improve/models/realesr-animevideov3-x4.bin b/resources/package/Improve/models/realesr-animevideov3-x4.bin new file mode 100644 index 0000000..2069105 Binary files /dev/null and b/resources/package/Improve/models/realesr-animevideov3-x4.bin differ diff --git a/resources/package/Improve/models/realesr-animevideov3-x4.param b/resources/package/Improve/models/realesr-animevideov3-x4.param new file mode 100644 index 0000000..5b922cc --- /dev/null +++ b/resources/package/Improve/models/realesr-animevideov3-x4.param @@ -0,0 +1,42 @@ +7767517 +40 41 +Input input.1 0 1 data +Split splitncnn_input0 1 2 data input.1_splitncnn_0 input.1_splitncnn_1 +Convolution Conv_0 1 1 input.1_splitncnn_1 54 0=64 1=3 4=1 5=1 6=1728 +PReLU PRelu_1 1 1 54 56 0=64 +Convolution Conv_2 1 1 56 57 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_3 1 1 57 59 0=64 +Convolution Conv_4 1 1 59 60 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_5 1 1 60 62 0=64 +Convolution Conv_6 1 1 62 63 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_7 1 1 63 65 0=64 +Convolution Conv_8 1 1 65 66 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_9 1 1 66 68 0=64 +Convolution Conv_10 1 1 68 69 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_11 1 1 69 71 0=64 +Convolution Conv_12 1 1 71 72 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_13 1 1 72 74 0=64 +Convolution Conv_14 1 1 74 75 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_15 1 1 75 77 0=64 +Convolution Conv_16 1 1 77 78 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_17 1 1 78 80 0=64 +Convolution Conv_18 1 1 80 81 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_19 1 1 81 83 0=64 +Convolution Conv_20 1 1 83 84 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_21 1 1 84 86 0=64 +Convolution Conv_22 1 1 86 87 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_23 1 1 87 89 0=64 +Convolution Conv_24 1 1 89 90 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_25 1 1 90 92 0=64 +Convolution Conv_26 1 1 92 93 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_27 1 1 93 95 0=64 +Convolution Conv_28 1 1 95 96 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_29 1 1 96 98 0=64 +Convolution Conv_30 1 1 98 99 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_31 1 1 99 101 0=64 +Convolution Conv_32 1 1 101 102 0=64 1=3 4=1 5=1 6=36864 +PReLU PRelu_33 1 1 102 104 0=64 +Convolution Conv_34 1 1 104 105 0=48 1=3 4=1 5=1 6=27648 +PixelShuffle DepthToSpace_35 1 1 105 106 0=4 +Interp Resize_37 1 1 input.1_splitncnn_0 111 0=1 1=4.000000e+00 2=4.000000e+00 +BinaryOp Add_38 2 1 106 111 output diff --git a/resources/package/Improve/models/realesrgan-x4plus-anime.bin b/resources/package/Improve/models/realesrgan-x4plus-anime.bin new file mode 100644 index 0000000..95201b7 Binary files /dev/null and b/resources/package/Improve/models/realesrgan-x4plus-anime.bin differ diff --git a/resources/package/Improve/models/realesrgan-x4plus-anime.param b/resources/package/Improve/models/realesrgan-x4plus-anime.param new file mode 100644 index 0000000..6c98f9a --- /dev/null +++ b/resources/package/Improve/models/realesrgan-x4plus-anime.param @@ -0,0 +1,270 @@ +7767517 +268 473 +Input input.1 0 1 data +Convolution Conv_0 1 1 data 193 0=64 1=3 4=1 5=1 6=1728 +Split splitncnn_0 1 8 193 193_splitncnn_0 193_splitncnn_1 193_splitncnn_2 193_splitncnn_3 193_splitncnn_4 193_splitncnn_5 193_splitncnn_6 193_splitncnn_7 +Convolution Conv_1 1 1 193_splitncnn_7 195 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_1 1 4 195 195_splitncnn_0 195_splitncnn_1 195_splitncnn_2 195_splitncnn_3 +Concat Concat_3 2 1 193_splitncnn_6 195_splitncnn_3 196 +Convolution Conv_4 1 1 196 198 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_2 1 3 198 198_splitncnn_0 198_splitncnn_1 198_splitncnn_2 +Concat Concat_6 3 1 193_splitncnn_5 195_splitncnn_2 198_splitncnn_2 199 +Convolution Conv_7 1 1 199 201 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_3 1 2 201 201_splitncnn_0 201_splitncnn_1 +Concat Concat_9 4 1 193_splitncnn_4 195_splitncnn_1 198_splitncnn_1 201_splitncnn_1 202 +Convolution Conv_10 1 1 202 204 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_12 5 1 193_splitncnn_3 195_splitncnn_0 198_splitncnn_0 201_splitncnn_0 204 205 +Convolution Conv_13 1 1 205 206 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_16 2 1 206 193_splitncnn_2 209 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_4 1 6 209 209_splitncnn_0 209_splitncnn_1 209_splitncnn_2 209_splitncnn_3 209_splitncnn_4 209_splitncnn_5 +Convolution Conv_17 1 1 209_splitncnn_5 211 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_5 1 4 211 211_splitncnn_0 211_splitncnn_1 211_splitncnn_2 211_splitncnn_3 +Concat Concat_19 2 1 209_splitncnn_4 211_splitncnn_3 212 +Convolution Conv_20 1 1 212 214 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_6 1 3 214 214_splitncnn_0 214_splitncnn_1 214_splitncnn_2 +Concat Concat_22 3 1 209_splitncnn_3 211_splitncnn_2 214_splitncnn_2 215 +Convolution Conv_23 1 1 215 217 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_7 1 2 217 217_splitncnn_0 217_splitncnn_1 +Concat Concat_25 4 1 209_splitncnn_2 211_splitncnn_1 214_splitncnn_1 217_splitncnn_1 218 +Convolution Conv_26 1 1 218 220 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_28 5 1 209_splitncnn_1 211_splitncnn_0 214_splitncnn_0 217_splitncnn_0 220 221 +Convolution Conv_29 1 1 221 222 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_32 2 1 222 209_splitncnn_0 225 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_8 1 6 225 225_splitncnn_0 225_splitncnn_1 225_splitncnn_2 225_splitncnn_3 225_splitncnn_4 225_splitncnn_5 +Convolution Conv_33 1 1 225_splitncnn_5 227 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_9 1 4 227 227_splitncnn_0 227_splitncnn_1 227_splitncnn_2 227_splitncnn_3 +Concat Concat_35 2 1 225_splitncnn_4 227_splitncnn_3 228 +Convolution Conv_36 1 1 228 230 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_10 1 3 230 230_splitncnn_0 230_splitncnn_1 230_splitncnn_2 +Concat Concat_38 3 1 225_splitncnn_3 227_splitncnn_2 230_splitncnn_2 231 +Convolution Conv_39 1 1 231 233 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_11 1 2 233 233_splitncnn_0 233_splitncnn_1 +Concat Concat_41 4 1 225_splitncnn_2 227_splitncnn_1 230_splitncnn_1 233_splitncnn_1 234 +Convolution Conv_42 1 1 234 236 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_44 5 1 225_splitncnn_1 227_splitncnn_0 230_splitncnn_0 233_splitncnn_0 236 237 +Convolution Conv_45 1 1 237 238 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_48 2 1 238 225_splitncnn_0 241 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_51 2 1 241 193_splitncnn_1 244 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_12 1 7 244 244_splitncnn_0 244_splitncnn_1 244_splitncnn_2 244_splitncnn_3 244_splitncnn_4 244_splitncnn_5 244_splitncnn_6 +Convolution Conv_52 1 1 244_splitncnn_6 246 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_13 1 4 246 246_splitncnn_0 246_splitncnn_1 246_splitncnn_2 246_splitncnn_3 +Concat Concat_54 2 1 244_splitncnn_5 246_splitncnn_3 247 +Convolution Conv_55 1 1 247 249 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_14 1 3 249 249_splitncnn_0 249_splitncnn_1 249_splitncnn_2 +Concat Concat_57 3 1 244_splitncnn_4 246_splitncnn_2 249_splitncnn_2 250 +Convolution Conv_58 1 1 250 252 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_15 1 2 252 252_splitncnn_0 252_splitncnn_1 +Concat Concat_60 4 1 244_splitncnn_3 246_splitncnn_1 249_splitncnn_1 252_splitncnn_1 253 +Convolution Conv_61 1 1 253 255 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_63 5 1 244_splitncnn_2 246_splitncnn_0 249_splitncnn_0 252_splitncnn_0 255 256 +Convolution Conv_64 1 1 256 257 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_67 2 1 257 244_splitncnn_1 260 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_16 1 6 260 260_splitncnn_0 260_splitncnn_1 260_splitncnn_2 260_splitncnn_3 260_splitncnn_4 260_splitncnn_5 +Convolution Conv_68 1 1 260_splitncnn_5 262 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_17 1 4 262 262_splitncnn_0 262_splitncnn_1 262_splitncnn_2 262_splitncnn_3 +Concat Concat_70 2 1 260_splitncnn_4 262_splitncnn_3 263 +Convolution Conv_71 1 1 263 265 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_18 1 3 265 265_splitncnn_0 265_splitncnn_1 265_splitncnn_2 +Concat Concat_73 3 1 260_splitncnn_3 262_splitncnn_2 265_splitncnn_2 266 +Convolution Conv_74 1 1 266 268 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_19 1 2 268 268_splitncnn_0 268_splitncnn_1 +Concat Concat_76 4 1 260_splitncnn_2 262_splitncnn_1 265_splitncnn_1 268_splitncnn_1 269 +Convolution Conv_77 1 1 269 271 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_79 5 1 260_splitncnn_1 262_splitncnn_0 265_splitncnn_0 268_splitncnn_0 271 272 +Convolution Conv_80 1 1 272 273 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_83 2 1 273 260_splitncnn_0 276 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_20 1 6 276 276_splitncnn_0 276_splitncnn_1 276_splitncnn_2 276_splitncnn_3 276_splitncnn_4 276_splitncnn_5 +Convolution Conv_84 1 1 276_splitncnn_5 278 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_21 1 4 278 278_splitncnn_0 278_splitncnn_1 278_splitncnn_2 278_splitncnn_3 +Concat Concat_86 2 1 276_splitncnn_4 278_splitncnn_3 279 +Convolution Conv_87 1 1 279 281 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_22 1 3 281 281_splitncnn_0 281_splitncnn_1 281_splitncnn_2 +Concat Concat_89 3 1 276_splitncnn_3 278_splitncnn_2 281_splitncnn_2 282 +Convolution Conv_90 1 1 282 284 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_23 1 2 284 284_splitncnn_0 284_splitncnn_1 +Concat Concat_92 4 1 276_splitncnn_2 278_splitncnn_1 281_splitncnn_1 284_splitncnn_1 285 +Convolution Conv_93 1 1 285 287 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_95 5 1 276_splitncnn_1 278_splitncnn_0 281_splitncnn_0 284_splitncnn_0 287 288 +Convolution Conv_96 1 1 288 289 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_99 2 1 289 276_splitncnn_0 292 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_102 2 1 292 244_splitncnn_0 295 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_24 1 7 295 295_splitncnn_0 295_splitncnn_1 295_splitncnn_2 295_splitncnn_3 295_splitncnn_4 295_splitncnn_5 295_splitncnn_6 +Convolution Conv_103 1 1 295_splitncnn_6 297 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_25 1 4 297 297_splitncnn_0 297_splitncnn_1 297_splitncnn_2 297_splitncnn_3 +Concat Concat_105 2 1 295_splitncnn_5 297_splitncnn_3 298 +Convolution Conv_106 1 1 298 300 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_26 1 3 300 300_splitncnn_0 300_splitncnn_1 300_splitncnn_2 +Concat Concat_108 3 1 295_splitncnn_4 297_splitncnn_2 300_splitncnn_2 301 +Convolution Conv_109 1 1 301 303 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_27 1 2 303 303_splitncnn_0 303_splitncnn_1 +Concat Concat_111 4 1 295_splitncnn_3 297_splitncnn_1 300_splitncnn_1 303_splitncnn_1 304 +Convolution Conv_112 1 1 304 306 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_114 5 1 295_splitncnn_2 297_splitncnn_0 300_splitncnn_0 303_splitncnn_0 306 307 +Convolution Conv_115 1 1 307 308 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_118 2 1 308 295_splitncnn_1 311 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_28 1 6 311 311_splitncnn_0 311_splitncnn_1 311_splitncnn_2 311_splitncnn_3 311_splitncnn_4 311_splitncnn_5 +Convolution Conv_119 1 1 311_splitncnn_5 313 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_29 1 4 313 313_splitncnn_0 313_splitncnn_1 313_splitncnn_2 313_splitncnn_3 +Concat Concat_121 2 1 311_splitncnn_4 313_splitncnn_3 314 +Convolution Conv_122 1 1 314 316 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_30 1 3 316 316_splitncnn_0 316_splitncnn_1 316_splitncnn_2 +Concat Concat_124 3 1 311_splitncnn_3 313_splitncnn_2 316_splitncnn_2 317 +Convolution Conv_125 1 1 317 319 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_31 1 2 319 319_splitncnn_0 319_splitncnn_1 +Concat Concat_127 4 1 311_splitncnn_2 313_splitncnn_1 316_splitncnn_1 319_splitncnn_1 320 +Convolution Conv_128 1 1 320 322 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_130 5 1 311_splitncnn_1 313_splitncnn_0 316_splitncnn_0 319_splitncnn_0 322 323 +Convolution Conv_131 1 1 323 324 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_134 2 1 324 311_splitncnn_0 327 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_32 1 6 327 327_splitncnn_0 327_splitncnn_1 327_splitncnn_2 327_splitncnn_3 327_splitncnn_4 327_splitncnn_5 +Convolution Conv_135 1 1 327_splitncnn_5 329 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_33 1 4 329 329_splitncnn_0 329_splitncnn_1 329_splitncnn_2 329_splitncnn_3 +Concat Concat_137 2 1 327_splitncnn_4 329_splitncnn_3 330 +Convolution Conv_138 1 1 330 332 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_34 1 3 332 332_splitncnn_0 332_splitncnn_1 332_splitncnn_2 +Concat Concat_140 3 1 327_splitncnn_3 329_splitncnn_2 332_splitncnn_2 333 +Convolution Conv_141 1 1 333 335 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_35 1 2 335 335_splitncnn_0 335_splitncnn_1 +Concat Concat_143 4 1 327_splitncnn_2 329_splitncnn_1 332_splitncnn_1 335_splitncnn_1 336 +Convolution Conv_144 1 1 336 338 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_146 5 1 327_splitncnn_1 329_splitncnn_0 332_splitncnn_0 335_splitncnn_0 338 339 +Convolution Conv_147 1 1 339 340 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_150 2 1 340 327_splitncnn_0 343 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_153 2 1 343 295_splitncnn_0 346 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_36 1 7 346 346_splitncnn_0 346_splitncnn_1 346_splitncnn_2 346_splitncnn_3 346_splitncnn_4 346_splitncnn_5 346_splitncnn_6 +Convolution Conv_154 1 1 346_splitncnn_6 348 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_37 1 4 348 348_splitncnn_0 348_splitncnn_1 348_splitncnn_2 348_splitncnn_3 +Concat Concat_156 2 1 346_splitncnn_5 348_splitncnn_3 349 +Convolution Conv_157 1 1 349 351 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_38 1 3 351 351_splitncnn_0 351_splitncnn_1 351_splitncnn_2 +Concat Concat_159 3 1 346_splitncnn_4 348_splitncnn_2 351_splitncnn_2 352 +Convolution Conv_160 1 1 352 354 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_39 1 2 354 354_splitncnn_0 354_splitncnn_1 +Concat Concat_162 4 1 346_splitncnn_3 348_splitncnn_1 351_splitncnn_1 354_splitncnn_1 355 +Convolution Conv_163 1 1 355 357 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_165 5 1 346_splitncnn_2 348_splitncnn_0 351_splitncnn_0 354_splitncnn_0 357 358 +Convolution Conv_166 1 1 358 359 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_169 2 1 359 346_splitncnn_1 362 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_40 1 6 362 362_splitncnn_0 362_splitncnn_1 362_splitncnn_2 362_splitncnn_3 362_splitncnn_4 362_splitncnn_5 +Convolution Conv_170 1 1 362_splitncnn_5 364 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_41 1 4 364 364_splitncnn_0 364_splitncnn_1 364_splitncnn_2 364_splitncnn_3 +Concat Concat_172 2 1 362_splitncnn_4 364_splitncnn_3 365 +Convolution Conv_173 1 1 365 367 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_42 1 3 367 367_splitncnn_0 367_splitncnn_1 367_splitncnn_2 +Concat Concat_175 3 1 362_splitncnn_3 364_splitncnn_2 367_splitncnn_2 368 +Convolution Conv_176 1 1 368 370 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_43 1 2 370 370_splitncnn_0 370_splitncnn_1 +Concat Concat_178 4 1 362_splitncnn_2 364_splitncnn_1 367_splitncnn_1 370_splitncnn_1 371 +Convolution Conv_179 1 1 371 373 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_181 5 1 362_splitncnn_1 364_splitncnn_0 367_splitncnn_0 370_splitncnn_0 373 374 +Convolution Conv_182 1 1 374 375 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_185 2 1 375 362_splitncnn_0 378 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_44 1 6 378 378_splitncnn_0 378_splitncnn_1 378_splitncnn_2 378_splitncnn_3 378_splitncnn_4 378_splitncnn_5 +Convolution Conv_186 1 1 378_splitncnn_5 380 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_45 1 4 380 380_splitncnn_0 380_splitncnn_1 380_splitncnn_2 380_splitncnn_3 +Concat Concat_188 2 1 378_splitncnn_4 380_splitncnn_3 381 +Convolution Conv_189 1 1 381 383 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_46 1 3 383 383_splitncnn_0 383_splitncnn_1 383_splitncnn_2 +Concat Concat_191 3 1 378_splitncnn_3 380_splitncnn_2 383_splitncnn_2 384 +Convolution Conv_192 1 1 384 386 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_47 1 2 386 386_splitncnn_0 386_splitncnn_1 +Concat Concat_194 4 1 378_splitncnn_2 380_splitncnn_1 383_splitncnn_1 386_splitncnn_1 387 +Convolution Conv_195 1 1 387 389 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_197 5 1 378_splitncnn_1 380_splitncnn_0 383_splitncnn_0 386_splitncnn_0 389 390 +Convolution Conv_198 1 1 390 391 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_201 2 1 391 378_splitncnn_0 394 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_204 2 1 394 346_splitncnn_0 397 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_48 1 7 397 397_splitncnn_0 397_splitncnn_1 397_splitncnn_2 397_splitncnn_3 397_splitncnn_4 397_splitncnn_5 397_splitncnn_6 +Convolution Conv_205 1 1 397_splitncnn_6 399 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_49 1 4 399 399_splitncnn_0 399_splitncnn_1 399_splitncnn_2 399_splitncnn_3 +Concat Concat_207 2 1 397_splitncnn_5 399_splitncnn_3 400 +Convolution Conv_208 1 1 400 402 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_50 1 3 402 402_splitncnn_0 402_splitncnn_1 402_splitncnn_2 +Concat Concat_210 3 1 397_splitncnn_4 399_splitncnn_2 402_splitncnn_2 403 +Convolution Conv_211 1 1 403 405 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_51 1 2 405 405_splitncnn_0 405_splitncnn_1 +Concat Concat_213 4 1 397_splitncnn_3 399_splitncnn_1 402_splitncnn_1 405_splitncnn_1 406 +Convolution Conv_214 1 1 406 408 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_216 5 1 397_splitncnn_2 399_splitncnn_0 402_splitncnn_0 405_splitncnn_0 408 409 +Convolution Conv_217 1 1 409 410 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_220 2 1 410 397_splitncnn_1 413 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_52 1 6 413 413_splitncnn_0 413_splitncnn_1 413_splitncnn_2 413_splitncnn_3 413_splitncnn_4 413_splitncnn_5 +Convolution Conv_221 1 1 413_splitncnn_5 415 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_53 1 4 415 415_splitncnn_0 415_splitncnn_1 415_splitncnn_2 415_splitncnn_3 +Concat Concat_223 2 1 413_splitncnn_4 415_splitncnn_3 416 +Convolution Conv_224 1 1 416 418 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_54 1 3 418 418_splitncnn_0 418_splitncnn_1 418_splitncnn_2 +Concat Concat_226 3 1 413_splitncnn_3 415_splitncnn_2 418_splitncnn_2 419 +Convolution Conv_227 1 1 419 421 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_55 1 2 421 421_splitncnn_0 421_splitncnn_1 +Concat Concat_229 4 1 413_splitncnn_2 415_splitncnn_1 418_splitncnn_1 421_splitncnn_1 422 +Convolution Conv_230 1 1 422 424 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_232 5 1 413_splitncnn_1 415_splitncnn_0 418_splitncnn_0 421_splitncnn_0 424 425 +Convolution Conv_233 1 1 425 426 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_236 2 1 426 413_splitncnn_0 429 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_56 1 6 429 429_splitncnn_0 429_splitncnn_1 429_splitncnn_2 429_splitncnn_3 429_splitncnn_4 429_splitncnn_5 +Convolution Conv_237 1 1 429_splitncnn_5 431 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_57 1 4 431 431_splitncnn_0 431_splitncnn_1 431_splitncnn_2 431_splitncnn_3 +Concat Concat_239 2 1 429_splitncnn_4 431_splitncnn_3 432 +Convolution Conv_240 1 1 432 434 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_58 1 3 434 434_splitncnn_0 434_splitncnn_1 434_splitncnn_2 +Concat Concat_242 3 1 429_splitncnn_3 431_splitncnn_2 434_splitncnn_2 435 +Convolution Conv_243 1 1 435 437 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_59 1 2 437 437_splitncnn_0 437_splitncnn_1 +Concat Concat_245 4 1 429_splitncnn_2 431_splitncnn_1 434_splitncnn_1 437_splitncnn_1 438 +Convolution Conv_246 1 1 438 440 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_248 5 1 429_splitncnn_1 431_splitncnn_0 434_splitncnn_0 437_splitncnn_0 440 441 +Convolution Conv_249 1 1 441 442 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_252 2 1 442 429_splitncnn_0 445 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_255 2 1 445 397_splitncnn_0 448 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_60 1 7 448 448_splitncnn_0 448_splitncnn_1 448_splitncnn_2 448_splitncnn_3 448_splitncnn_4 448_splitncnn_5 448_splitncnn_6 +Convolution Conv_256 1 1 448_splitncnn_6 450 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_61 1 4 450 450_splitncnn_0 450_splitncnn_1 450_splitncnn_2 450_splitncnn_3 +Concat Concat_258 2 1 448_splitncnn_5 450_splitncnn_3 451 +Convolution Conv_259 1 1 451 453 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_62 1 3 453 453_splitncnn_0 453_splitncnn_1 453_splitncnn_2 +Concat Concat_261 3 1 448_splitncnn_4 450_splitncnn_2 453_splitncnn_2 454 +Convolution Conv_262 1 1 454 456 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_63 1 2 456 456_splitncnn_0 456_splitncnn_1 +Concat Concat_264 4 1 448_splitncnn_3 450_splitncnn_1 453_splitncnn_1 456_splitncnn_1 457 +Convolution Conv_265 1 1 457 459 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_267 5 1 448_splitncnn_2 450_splitncnn_0 453_splitncnn_0 456_splitncnn_0 459 460 +Convolution Conv_268 1 1 460 461 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_271 2 1 461 448_splitncnn_1 464 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_64 1 6 464 464_splitncnn_0 464_splitncnn_1 464_splitncnn_2 464_splitncnn_3 464_splitncnn_4 464_splitncnn_5 +Convolution Conv_272 1 1 464_splitncnn_5 466 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_65 1 4 466 466_splitncnn_0 466_splitncnn_1 466_splitncnn_2 466_splitncnn_3 +Concat Concat_274 2 1 464_splitncnn_4 466_splitncnn_3 467 +Convolution Conv_275 1 1 467 469 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_66 1 3 469 469_splitncnn_0 469_splitncnn_1 469_splitncnn_2 +Concat Concat_277 3 1 464_splitncnn_3 466_splitncnn_2 469_splitncnn_2 470 +Convolution Conv_278 1 1 470 472 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_67 1 2 472 472_splitncnn_0 472_splitncnn_1 +Concat Concat_280 4 1 464_splitncnn_2 466_splitncnn_1 469_splitncnn_1 472_splitncnn_1 473 +Convolution Conv_281 1 1 473 475 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_283 5 1 464_splitncnn_1 466_splitncnn_0 469_splitncnn_0 472_splitncnn_0 475 476 +Convolution Conv_284 1 1 476 477 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_287 2 1 477 464_splitncnn_0 480 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_68 1 6 480 480_splitncnn_0 480_splitncnn_1 480_splitncnn_2 480_splitncnn_3 480_splitncnn_4 480_splitncnn_5 +Convolution Conv_288 1 1 480_splitncnn_5 482 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_69 1 4 482 482_splitncnn_0 482_splitncnn_1 482_splitncnn_2 482_splitncnn_3 +Concat Concat_290 2 1 480_splitncnn_4 482_splitncnn_3 483 +Convolution Conv_291 1 1 483 485 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_70 1 3 485 485_splitncnn_0 485_splitncnn_1 485_splitncnn_2 +Concat Concat_293 3 1 480_splitncnn_3 482_splitncnn_2 485_splitncnn_2 486 +Convolution Conv_294 1 1 486 488 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_71 1 2 488 488_splitncnn_0 488_splitncnn_1 +Concat Concat_296 4 1 480_splitncnn_2 482_splitncnn_1 485_splitncnn_1 488_splitncnn_1 489 +Convolution Conv_297 1 1 489 491 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_299 5 1 480_splitncnn_1 482_splitncnn_0 485_splitncnn_0 488_splitncnn_0 491 492 +Convolution Conv_300 1 1 492 493 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_303 2 1 493 480_splitncnn_0 496 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_306 2 1 496 448_splitncnn_0 499 0=1 -23301=2,2.000000e-01,1.000000e+00 +Convolution Conv_307 1 1 499 500 0=64 1=3 4=1 5=1 6=36864 +BinaryOp Add_308 2 1 193_splitncnn_0 500 501 +Interp Resize_310 1 1 501 506 0=1 1=2.000000e+00 2=2.000000e+00 +Convolution Conv_311 1 1 506 508 0=64 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Interp Resize_314 1 1 508 513 0=1 1=2.000000e+00 2=2.000000e+00 +Convolution Conv_315 1 1 513 515 0=64 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Convolution Conv_317 1 1 515 517 0=64 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Convolution Conv_319 1 1 517 output 0=3 1=3 4=1 5=1 6=1728 diff --git a/resources/package/Improve/models/realesrgan-x4plus.bin b/resources/package/Improve/models/realesrgan-x4plus.bin new file mode 100644 index 0000000..5cea947 Binary files /dev/null and b/resources/package/Improve/models/realesrgan-x4plus.bin differ diff --git a/resources/package/Improve/models/realesrgan-x4plus.param b/resources/package/Improve/models/realesrgan-x4plus.param new file mode 100644 index 0000000..d14d62e --- /dev/null +++ b/resources/package/Improve/models/realesrgan-x4plus.param @@ -0,0 +1,1001 @@ +7767517 +999 1782 +Input input.1 0 1 data +Convolution Conv_0 1 1 data 703 0=64 1=3 4=1 5=1 6=1728 +Split splitncnn_0 1 8 703 703_splitncnn_0 703_splitncnn_1 703_splitncnn_2 703_splitncnn_3 703_splitncnn_4 703_splitncnn_5 703_splitncnn_6 703_splitncnn_7 +Convolution Conv_1 1 1 703_splitncnn_7 705 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_1 1 4 705 705_splitncnn_0 705_splitncnn_1 705_splitncnn_2 705_splitncnn_3 +Concat Concat_3 2 1 703_splitncnn_6 705_splitncnn_3 706 +Convolution Conv_4 1 1 706 708 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_2 1 3 708 708_splitncnn_0 708_splitncnn_1 708_splitncnn_2 +Concat Concat_6 3 1 703_splitncnn_5 705_splitncnn_2 708_splitncnn_2 709 +Convolution Conv_7 1 1 709 711 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_3 1 2 711 711_splitncnn_0 711_splitncnn_1 +Concat Concat_9 4 1 703_splitncnn_4 705_splitncnn_1 708_splitncnn_1 711_splitncnn_1 712 +Convolution Conv_10 1 1 712 714 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_12 5 1 703_splitncnn_3 705_splitncnn_0 708_splitncnn_0 711_splitncnn_0 714 715 +Convolution Conv_13 1 1 715 716 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_16 2 1 716 703_splitncnn_2 719 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_4 1 6 719 719_splitncnn_0 719_splitncnn_1 719_splitncnn_2 719_splitncnn_3 719_splitncnn_4 719_splitncnn_5 +Convolution Conv_17 1 1 719_splitncnn_5 721 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_5 1 4 721 721_splitncnn_0 721_splitncnn_1 721_splitncnn_2 721_splitncnn_3 +Concat Concat_19 2 1 719_splitncnn_4 721_splitncnn_3 722 +Convolution Conv_20 1 1 722 724 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_6 1 3 724 724_splitncnn_0 724_splitncnn_1 724_splitncnn_2 +Concat Concat_22 3 1 719_splitncnn_3 721_splitncnn_2 724_splitncnn_2 725 +Convolution Conv_23 1 1 725 727 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_7 1 2 727 727_splitncnn_0 727_splitncnn_1 +Concat Concat_25 4 1 719_splitncnn_2 721_splitncnn_1 724_splitncnn_1 727_splitncnn_1 728 +Convolution Conv_26 1 1 728 730 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_28 5 1 719_splitncnn_1 721_splitncnn_0 724_splitncnn_0 727_splitncnn_0 730 731 +Convolution Conv_29 1 1 731 732 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_32 2 1 732 719_splitncnn_0 735 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_8 1 6 735 735_splitncnn_0 735_splitncnn_1 735_splitncnn_2 735_splitncnn_3 735_splitncnn_4 735_splitncnn_5 +Convolution Conv_33 1 1 735_splitncnn_5 737 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_9 1 4 737 737_splitncnn_0 737_splitncnn_1 737_splitncnn_2 737_splitncnn_3 +Concat Concat_35 2 1 735_splitncnn_4 737_splitncnn_3 738 +Convolution Conv_36 1 1 738 740 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_10 1 3 740 740_splitncnn_0 740_splitncnn_1 740_splitncnn_2 +Concat Concat_38 3 1 735_splitncnn_3 737_splitncnn_2 740_splitncnn_2 741 +Convolution Conv_39 1 1 741 743 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_11 1 2 743 743_splitncnn_0 743_splitncnn_1 +Concat Concat_41 4 1 735_splitncnn_2 737_splitncnn_1 740_splitncnn_1 743_splitncnn_1 744 +Convolution Conv_42 1 1 744 746 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_44 5 1 735_splitncnn_1 737_splitncnn_0 740_splitncnn_0 743_splitncnn_0 746 747 +Convolution Conv_45 1 1 747 748 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_48 2 1 748 735_splitncnn_0 751 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_51 2 1 751 703_splitncnn_1 754 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_12 1 7 754 754_splitncnn_0 754_splitncnn_1 754_splitncnn_2 754_splitncnn_3 754_splitncnn_4 754_splitncnn_5 754_splitncnn_6 +Convolution Conv_52 1 1 754_splitncnn_6 756 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_13 1 4 756 756_splitncnn_0 756_splitncnn_1 756_splitncnn_2 756_splitncnn_3 +Concat Concat_54 2 1 754_splitncnn_5 756_splitncnn_3 757 +Convolution Conv_55 1 1 757 759 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_14 1 3 759 759_splitncnn_0 759_splitncnn_1 759_splitncnn_2 +Concat Concat_57 3 1 754_splitncnn_4 756_splitncnn_2 759_splitncnn_2 760 +Convolution Conv_58 1 1 760 762 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_15 1 2 762 762_splitncnn_0 762_splitncnn_1 +Concat Concat_60 4 1 754_splitncnn_3 756_splitncnn_1 759_splitncnn_1 762_splitncnn_1 763 +Convolution Conv_61 1 1 763 765 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_63 5 1 754_splitncnn_2 756_splitncnn_0 759_splitncnn_0 762_splitncnn_0 765 766 +Convolution Conv_64 1 1 766 767 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_67 2 1 767 754_splitncnn_1 770 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_16 1 6 770 770_splitncnn_0 770_splitncnn_1 770_splitncnn_2 770_splitncnn_3 770_splitncnn_4 770_splitncnn_5 +Convolution Conv_68 1 1 770_splitncnn_5 772 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_17 1 4 772 772_splitncnn_0 772_splitncnn_1 772_splitncnn_2 772_splitncnn_3 +Concat Concat_70 2 1 770_splitncnn_4 772_splitncnn_3 773 +Convolution Conv_71 1 1 773 775 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_18 1 3 775 775_splitncnn_0 775_splitncnn_1 775_splitncnn_2 +Concat Concat_73 3 1 770_splitncnn_3 772_splitncnn_2 775_splitncnn_2 776 +Convolution Conv_74 1 1 776 778 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_19 1 2 778 778_splitncnn_0 778_splitncnn_1 +Concat Concat_76 4 1 770_splitncnn_2 772_splitncnn_1 775_splitncnn_1 778_splitncnn_1 779 +Convolution Conv_77 1 1 779 781 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_79 5 1 770_splitncnn_1 772_splitncnn_0 775_splitncnn_0 778_splitncnn_0 781 782 +Convolution Conv_80 1 1 782 783 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_83 2 1 783 770_splitncnn_0 786 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_20 1 6 786 786_splitncnn_0 786_splitncnn_1 786_splitncnn_2 786_splitncnn_3 786_splitncnn_4 786_splitncnn_5 +Convolution Conv_84 1 1 786_splitncnn_5 788 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_21 1 4 788 788_splitncnn_0 788_splitncnn_1 788_splitncnn_2 788_splitncnn_3 +Concat Concat_86 2 1 786_splitncnn_4 788_splitncnn_3 789 +Convolution Conv_87 1 1 789 791 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_22 1 3 791 791_splitncnn_0 791_splitncnn_1 791_splitncnn_2 +Concat Concat_89 3 1 786_splitncnn_3 788_splitncnn_2 791_splitncnn_2 792 +Convolution Conv_90 1 1 792 794 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_23 1 2 794 794_splitncnn_0 794_splitncnn_1 +Concat Concat_92 4 1 786_splitncnn_2 788_splitncnn_1 791_splitncnn_1 794_splitncnn_1 795 +Convolution Conv_93 1 1 795 797 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_95 5 1 786_splitncnn_1 788_splitncnn_0 791_splitncnn_0 794_splitncnn_0 797 798 +Convolution Conv_96 1 1 798 799 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_99 2 1 799 786_splitncnn_0 802 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_102 2 1 802 754_splitncnn_0 805 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_24 1 7 805 805_splitncnn_0 805_splitncnn_1 805_splitncnn_2 805_splitncnn_3 805_splitncnn_4 805_splitncnn_5 805_splitncnn_6 +Convolution Conv_103 1 1 805_splitncnn_6 807 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_25 1 4 807 807_splitncnn_0 807_splitncnn_1 807_splitncnn_2 807_splitncnn_3 +Concat Concat_105 2 1 805_splitncnn_5 807_splitncnn_3 808 +Convolution Conv_106 1 1 808 810 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_26 1 3 810 810_splitncnn_0 810_splitncnn_1 810_splitncnn_2 +Concat Concat_108 3 1 805_splitncnn_4 807_splitncnn_2 810_splitncnn_2 811 +Convolution Conv_109 1 1 811 813 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_27 1 2 813 813_splitncnn_0 813_splitncnn_1 +Concat Concat_111 4 1 805_splitncnn_3 807_splitncnn_1 810_splitncnn_1 813_splitncnn_1 814 +Convolution Conv_112 1 1 814 816 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_114 5 1 805_splitncnn_2 807_splitncnn_0 810_splitncnn_0 813_splitncnn_0 816 817 +Convolution Conv_115 1 1 817 818 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_118 2 1 818 805_splitncnn_1 821 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_28 1 6 821 821_splitncnn_0 821_splitncnn_1 821_splitncnn_2 821_splitncnn_3 821_splitncnn_4 821_splitncnn_5 +Convolution Conv_119 1 1 821_splitncnn_5 823 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_29 1 4 823 823_splitncnn_0 823_splitncnn_1 823_splitncnn_2 823_splitncnn_3 +Concat Concat_121 2 1 821_splitncnn_4 823_splitncnn_3 824 +Convolution Conv_122 1 1 824 826 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_30 1 3 826 826_splitncnn_0 826_splitncnn_1 826_splitncnn_2 +Concat Concat_124 3 1 821_splitncnn_3 823_splitncnn_2 826_splitncnn_2 827 +Convolution Conv_125 1 1 827 829 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_31 1 2 829 829_splitncnn_0 829_splitncnn_1 +Concat Concat_127 4 1 821_splitncnn_2 823_splitncnn_1 826_splitncnn_1 829_splitncnn_1 830 +Convolution Conv_128 1 1 830 832 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_130 5 1 821_splitncnn_1 823_splitncnn_0 826_splitncnn_0 829_splitncnn_0 832 833 +Convolution Conv_131 1 1 833 834 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_134 2 1 834 821_splitncnn_0 837 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_32 1 6 837 837_splitncnn_0 837_splitncnn_1 837_splitncnn_2 837_splitncnn_3 837_splitncnn_4 837_splitncnn_5 +Convolution Conv_135 1 1 837_splitncnn_5 839 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_33 1 4 839 839_splitncnn_0 839_splitncnn_1 839_splitncnn_2 839_splitncnn_3 +Concat Concat_137 2 1 837_splitncnn_4 839_splitncnn_3 840 +Convolution Conv_138 1 1 840 842 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_34 1 3 842 842_splitncnn_0 842_splitncnn_1 842_splitncnn_2 +Concat Concat_140 3 1 837_splitncnn_3 839_splitncnn_2 842_splitncnn_2 843 +Convolution Conv_141 1 1 843 845 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_35 1 2 845 845_splitncnn_0 845_splitncnn_1 +Concat Concat_143 4 1 837_splitncnn_2 839_splitncnn_1 842_splitncnn_1 845_splitncnn_1 846 +Convolution Conv_144 1 1 846 848 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_146 5 1 837_splitncnn_1 839_splitncnn_0 842_splitncnn_0 845_splitncnn_0 848 849 +Convolution Conv_147 1 1 849 850 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_150 2 1 850 837_splitncnn_0 853 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_153 2 1 853 805_splitncnn_0 856 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_36 1 7 856 856_splitncnn_0 856_splitncnn_1 856_splitncnn_2 856_splitncnn_3 856_splitncnn_4 856_splitncnn_5 856_splitncnn_6 +Convolution Conv_154 1 1 856_splitncnn_6 858 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_37 1 4 858 858_splitncnn_0 858_splitncnn_1 858_splitncnn_2 858_splitncnn_3 +Concat Concat_156 2 1 856_splitncnn_5 858_splitncnn_3 859 +Convolution Conv_157 1 1 859 861 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_38 1 3 861 861_splitncnn_0 861_splitncnn_1 861_splitncnn_2 +Concat Concat_159 3 1 856_splitncnn_4 858_splitncnn_2 861_splitncnn_2 862 +Convolution Conv_160 1 1 862 864 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_39 1 2 864 864_splitncnn_0 864_splitncnn_1 +Concat Concat_162 4 1 856_splitncnn_3 858_splitncnn_1 861_splitncnn_1 864_splitncnn_1 865 +Convolution Conv_163 1 1 865 867 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_165 5 1 856_splitncnn_2 858_splitncnn_0 861_splitncnn_0 864_splitncnn_0 867 868 +Convolution Conv_166 1 1 868 869 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_169 2 1 869 856_splitncnn_1 872 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_40 1 6 872 872_splitncnn_0 872_splitncnn_1 872_splitncnn_2 872_splitncnn_3 872_splitncnn_4 872_splitncnn_5 +Convolution Conv_170 1 1 872_splitncnn_5 874 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_41 1 4 874 874_splitncnn_0 874_splitncnn_1 874_splitncnn_2 874_splitncnn_3 +Concat Concat_172 2 1 872_splitncnn_4 874_splitncnn_3 875 +Convolution Conv_173 1 1 875 877 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_42 1 3 877 877_splitncnn_0 877_splitncnn_1 877_splitncnn_2 +Concat Concat_175 3 1 872_splitncnn_3 874_splitncnn_2 877_splitncnn_2 878 +Convolution Conv_176 1 1 878 880 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_43 1 2 880 880_splitncnn_0 880_splitncnn_1 +Concat Concat_178 4 1 872_splitncnn_2 874_splitncnn_1 877_splitncnn_1 880_splitncnn_1 881 +Convolution Conv_179 1 1 881 883 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_181 5 1 872_splitncnn_1 874_splitncnn_0 877_splitncnn_0 880_splitncnn_0 883 884 +Convolution Conv_182 1 1 884 885 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_185 2 1 885 872_splitncnn_0 888 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_44 1 6 888 888_splitncnn_0 888_splitncnn_1 888_splitncnn_2 888_splitncnn_3 888_splitncnn_4 888_splitncnn_5 +Convolution Conv_186 1 1 888_splitncnn_5 890 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_45 1 4 890 890_splitncnn_0 890_splitncnn_1 890_splitncnn_2 890_splitncnn_3 +Concat Concat_188 2 1 888_splitncnn_4 890_splitncnn_3 891 +Convolution Conv_189 1 1 891 893 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_46 1 3 893 893_splitncnn_0 893_splitncnn_1 893_splitncnn_2 +Concat Concat_191 3 1 888_splitncnn_3 890_splitncnn_2 893_splitncnn_2 894 +Convolution Conv_192 1 1 894 896 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_47 1 2 896 896_splitncnn_0 896_splitncnn_1 +Concat Concat_194 4 1 888_splitncnn_2 890_splitncnn_1 893_splitncnn_1 896_splitncnn_1 897 +Convolution Conv_195 1 1 897 899 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_197 5 1 888_splitncnn_1 890_splitncnn_0 893_splitncnn_0 896_splitncnn_0 899 900 +Convolution Conv_198 1 1 900 901 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_201 2 1 901 888_splitncnn_0 904 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_204 2 1 904 856_splitncnn_0 907 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_48 1 7 907 907_splitncnn_0 907_splitncnn_1 907_splitncnn_2 907_splitncnn_3 907_splitncnn_4 907_splitncnn_5 907_splitncnn_6 +Convolution Conv_205 1 1 907_splitncnn_6 909 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_49 1 4 909 909_splitncnn_0 909_splitncnn_1 909_splitncnn_2 909_splitncnn_3 +Concat Concat_207 2 1 907_splitncnn_5 909_splitncnn_3 910 +Convolution Conv_208 1 1 910 912 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_50 1 3 912 912_splitncnn_0 912_splitncnn_1 912_splitncnn_2 +Concat Concat_210 3 1 907_splitncnn_4 909_splitncnn_2 912_splitncnn_2 913 +Convolution Conv_211 1 1 913 915 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_51 1 2 915 915_splitncnn_0 915_splitncnn_1 +Concat Concat_213 4 1 907_splitncnn_3 909_splitncnn_1 912_splitncnn_1 915_splitncnn_1 916 +Convolution Conv_214 1 1 916 918 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_216 5 1 907_splitncnn_2 909_splitncnn_0 912_splitncnn_0 915_splitncnn_0 918 919 +Convolution Conv_217 1 1 919 920 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_220 2 1 920 907_splitncnn_1 923 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_52 1 6 923 923_splitncnn_0 923_splitncnn_1 923_splitncnn_2 923_splitncnn_3 923_splitncnn_4 923_splitncnn_5 +Convolution Conv_221 1 1 923_splitncnn_5 925 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_53 1 4 925 925_splitncnn_0 925_splitncnn_1 925_splitncnn_2 925_splitncnn_3 +Concat Concat_223 2 1 923_splitncnn_4 925_splitncnn_3 926 +Convolution Conv_224 1 1 926 928 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_54 1 3 928 928_splitncnn_0 928_splitncnn_1 928_splitncnn_2 +Concat Concat_226 3 1 923_splitncnn_3 925_splitncnn_2 928_splitncnn_2 929 +Convolution Conv_227 1 1 929 931 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_55 1 2 931 931_splitncnn_0 931_splitncnn_1 +Concat Concat_229 4 1 923_splitncnn_2 925_splitncnn_1 928_splitncnn_1 931_splitncnn_1 932 +Convolution Conv_230 1 1 932 934 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_232 5 1 923_splitncnn_1 925_splitncnn_0 928_splitncnn_0 931_splitncnn_0 934 935 +Convolution Conv_233 1 1 935 936 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_236 2 1 936 923_splitncnn_0 939 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_56 1 6 939 939_splitncnn_0 939_splitncnn_1 939_splitncnn_2 939_splitncnn_3 939_splitncnn_4 939_splitncnn_5 +Convolution Conv_237 1 1 939_splitncnn_5 941 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_57 1 4 941 941_splitncnn_0 941_splitncnn_1 941_splitncnn_2 941_splitncnn_3 +Concat Concat_239 2 1 939_splitncnn_4 941_splitncnn_3 942 +Convolution Conv_240 1 1 942 944 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_58 1 3 944 944_splitncnn_0 944_splitncnn_1 944_splitncnn_2 +Concat Concat_242 3 1 939_splitncnn_3 941_splitncnn_2 944_splitncnn_2 945 +Convolution Conv_243 1 1 945 947 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_59 1 2 947 947_splitncnn_0 947_splitncnn_1 +Concat Concat_245 4 1 939_splitncnn_2 941_splitncnn_1 944_splitncnn_1 947_splitncnn_1 948 +Convolution Conv_246 1 1 948 950 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_248 5 1 939_splitncnn_1 941_splitncnn_0 944_splitncnn_0 947_splitncnn_0 950 951 +Convolution Conv_249 1 1 951 952 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_252 2 1 952 939_splitncnn_0 955 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_255 2 1 955 907_splitncnn_0 958 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_60 1 7 958 958_splitncnn_0 958_splitncnn_1 958_splitncnn_2 958_splitncnn_3 958_splitncnn_4 958_splitncnn_5 958_splitncnn_6 +Convolution Conv_256 1 1 958_splitncnn_6 960 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_61 1 4 960 960_splitncnn_0 960_splitncnn_1 960_splitncnn_2 960_splitncnn_3 +Concat Concat_258 2 1 958_splitncnn_5 960_splitncnn_3 961 +Convolution Conv_259 1 1 961 963 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_62 1 3 963 963_splitncnn_0 963_splitncnn_1 963_splitncnn_2 +Concat Concat_261 3 1 958_splitncnn_4 960_splitncnn_2 963_splitncnn_2 964 +Convolution Conv_262 1 1 964 966 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_63 1 2 966 966_splitncnn_0 966_splitncnn_1 +Concat Concat_264 4 1 958_splitncnn_3 960_splitncnn_1 963_splitncnn_1 966_splitncnn_1 967 +Convolution Conv_265 1 1 967 969 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_267 5 1 958_splitncnn_2 960_splitncnn_0 963_splitncnn_0 966_splitncnn_0 969 970 +Convolution Conv_268 1 1 970 971 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_271 2 1 971 958_splitncnn_1 974 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_64 1 6 974 974_splitncnn_0 974_splitncnn_1 974_splitncnn_2 974_splitncnn_3 974_splitncnn_4 974_splitncnn_5 +Convolution Conv_272 1 1 974_splitncnn_5 976 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_65 1 4 976 976_splitncnn_0 976_splitncnn_1 976_splitncnn_2 976_splitncnn_3 +Concat Concat_274 2 1 974_splitncnn_4 976_splitncnn_3 977 +Convolution Conv_275 1 1 977 979 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_66 1 3 979 979_splitncnn_0 979_splitncnn_1 979_splitncnn_2 +Concat Concat_277 3 1 974_splitncnn_3 976_splitncnn_2 979_splitncnn_2 980 +Convolution Conv_278 1 1 980 982 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_67 1 2 982 982_splitncnn_0 982_splitncnn_1 +Concat Concat_280 4 1 974_splitncnn_2 976_splitncnn_1 979_splitncnn_1 982_splitncnn_1 983 +Convolution Conv_281 1 1 983 985 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_283 5 1 974_splitncnn_1 976_splitncnn_0 979_splitncnn_0 982_splitncnn_0 985 986 +Convolution Conv_284 1 1 986 987 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_287 2 1 987 974_splitncnn_0 990 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_68 1 6 990 990_splitncnn_0 990_splitncnn_1 990_splitncnn_2 990_splitncnn_3 990_splitncnn_4 990_splitncnn_5 +Convolution Conv_288 1 1 990_splitncnn_5 992 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_69 1 4 992 992_splitncnn_0 992_splitncnn_1 992_splitncnn_2 992_splitncnn_3 +Concat Concat_290 2 1 990_splitncnn_4 992_splitncnn_3 993 +Convolution Conv_291 1 1 993 995 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_70 1 3 995 995_splitncnn_0 995_splitncnn_1 995_splitncnn_2 +Concat Concat_293 3 1 990_splitncnn_3 992_splitncnn_2 995_splitncnn_2 996 +Convolution Conv_294 1 1 996 998 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_71 1 2 998 998_splitncnn_0 998_splitncnn_1 +Concat Concat_296 4 1 990_splitncnn_2 992_splitncnn_1 995_splitncnn_1 998_splitncnn_1 999 +Convolution Conv_297 1 1 999 1001 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_299 5 1 990_splitncnn_1 992_splitncnn_0 995_splitncnn_0 998_splitncnn_0 1001 1002 +Convolution Conv_300 1 1 1002 1003 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_303 2 1 1003 990_splitncnn_0 1006 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_306 2 1 1006 958_splitncnn_0 1009 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_72 1 7 1009 1009_splitncnn_0 1009_splitncnn_1 1009_splitncnn_2 1009_splitncnn_3 1009_splitncnn_4 1009_splitncnn_5 1009_splitncnn_6 +Convolution Conv_307 1 1 1009_splitncnn_6 1011 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_73 1 4 1011 1011_splitncnn_0 1011_splitncnn_1 1011_splitncnn_2 1011_splitncnn_3 +Concat Concat_309 2 1 1009_splitncnn_5 1011_splitncnn_3 1012 +Convolution Conv_310 1 1 1012 1014 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_74 1 3 1014 1014_splitncnn_0 1014_splitncnn_1 1014_splitncnn_2 +Concat Concat_312 3 1 1009_splitncnn_4 1011_splitncnn_2 1014_splitncnn_2 1015 +Convolution Conv_313 1 1 1015 1017 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_75 1 2 1017 1017_splitncnn_0 1017_splitncnn_1 +Concat Concat_315 4 1 1009_splitncnn_3 1011_splitncnn_1 1014_splitncnn_1 1017_splitncnn_1 1018 +Convolution Conv_316 1 1 1018 1020 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_318 5 1 1009_splitncnn_2 1011_splitncnn_0 1014_splitncnn_0 1017_splitncnn_0 1020 1021 +Convolution Conv_319 1 1 1021 1022 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_322 2 1 1022 1009_splitncnn_1 1025 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_76 1 6 1025 1025_splitncnn_0 1025_splitncnn_1 1025_splitncnn_2 1025_splitncnn_3 1025_splitncnn_4 1025_splitncnn_5 +Convolution Conv_323 1 1 1025_splitncnn_5 1027 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_77 1 4 1027 1027_splitncnn_0 1027_splitncnn_1 1027_splitncnn_2 1027_splitncnn_3 +Concat Concat_325 2 1 1025_splitncnn_4 1027_splitncnn_3 1028 +Convolution Conv_326 1 1 1028 1030 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_78 1 3 1030 1030_splitncnn_0 1030_splitncnn_1 1030_splitncnn_2 +Concat Concat_328 3 1 1025_splitncnn_3 1027_splitncnn_2 1030_splitncnn_2 1031 +Convolution Conv_329 1 1 1031 1033 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_79 1 2 1033 1033_splitncnn_0 1033_splitncnn_1 +Concat Concat_331 4 1 1025_splitncnn_2 1027_splitncnn_1 1030_splitncnn_1 1033_splitncnn_1 1034 +Convolution Conv_332 1 1 1034 1036 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_334 5 1 1025_splitncnn_1 1027_splitncnn_0 1030_splitncnn_0 1033_splitncnn_0 1036 1037 +Convolution Conv_335 1 1 1037 1038 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_338 2 1 1038 1025_splitncnn_0 1041 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_80 1 6 1041 1041_splitncnn_0 1041_splitncnn_1 1041_splitncnn_2 1041_splitncnn_3 1041_splitncnn_4 1041_splitncnn_5 +Convolution Conv_339 1 1 1041_splitncnn_5 1043 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_81 1 4 1043 1043_splitncnn_0 1043_splitncnn_1 1043_splitncnn_2 1043_splitncnn_3 +Concat Concat_341 2 1 1041_splitncnn_4 1043_splitncnn_3 1044 +Convolution Conv_342 1 1 1044 1046 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_82 1 3 1046 1046_splitncnn_0 1046_splitncnn_1 1046_splitncnn_2 +Concat Concat_344 3 1 1041_splitncnn_3 1043_splitncnn_2 1046_splitncnn_2 1047 +Convolution Conv_345 1 1 1047 1049 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_83 1 2 1049 1049_splitncnn_0 1049_splitncnn_1 +Concat Concat_347 4 1 1041_splitncnn_2 1043_splitncnn_1 1046_splitncnn_1 1049_splitncnn_1 1050 +Convolution Conv_348 1 1 1050 1052 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_350 5 1 1041_splitncnn_1 1043_splitncnn_0 1046_splitncnn_0 1049_splitncnn_0 1052 1053 +Convolution Conv_351 1 1 1053 1054 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_354 2 1 1054 1041_splitncnn_0 1057 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_357 2 1 1057 1009_splitncnn_0 1060 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_84 1 7 1060 1060_splitncnn_0 1060_splitncnn_1 1060_splitncnn_2 1060_splitncnn_3 1060_splitncnn_4 1060_splitncnn_5 1060_splitncnn_6 +Convolution Conv_358 1 1 1060_splitncnn_6 1062 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_85 1 4 1062 1062_splitncnn_0 1062_splitncnn_1 1062_splitncnn_2 1062_splitncnn_3 +Concat Concat_360 2 1 1060_splitncnn_5 1062_splitncnn_3 1063 +Convolution Conv_361 1 1 1063 1065 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_86 1 3 1065 1065_splitncnn_0 1065_splitncnn_1 1065_splitncnn_2 +Concat Concat_363 3 1 1060_splitncnn_4 1062_splitncnn_2 1065_splitncnn_2 1066 +Convolution Conv_364 1 1 1066 1068 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_87 1 2 1068 1068_splitncnn_0 1068_splitncnn_1 +Concat Concat_366 4 1 1060_splitncnn_3 1062_splitncnn_1 1065_splitncnn_1 1068_splitncnn_1 1069 +Convolution Conv_367 1 1 1069 1071 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_369 5 1 1060_splitncnn_2 1062_splitncnn_0 1065_splitncnn_0 1068_splitncnn_0 1071 1072 +Convolution Conv_370 1 1 1072 1073 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_373 2 1 1073 1060_splitncnn_1 1076 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_88 1 6 1076 1076_splitncnn_0 1076_splitncnn_1 1076_splitncnn_2 1076_splitncnn_3 1076_splitncnn_4 1076_splitncnn_5 +Convolution Conv_374 1 1 1076_splitncnn_5 1078 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_89 1 4 1078 1078_splitncnn_0 1078_splitncnn_1 1078_splitncnn_2 1078_splitncnn_3 +Concat Concat_376 2 1 1076_splitncnn_4 1078_splitncnn_3 1079 +Convolution Conv_377 1 1 1079 1081 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_90 1 3 1081 1081_splitncnn_0 1081_splitncnn_1 1081_splitncnn_2 +Concat Concat_379 3 1 1076_splitncnn_3 1078_splitncnn_2 1081_splitncnn_2 1082 +Convolution Conv_380 1 1 1082 1084 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_91 1 2 1084 1084_splitncnn_0 1084_splitncnn_1 +Concat Concat_382 4 1 1076_splitncnn_2 1078_splitncnn_1 1081_splitncnn_1 1084_splitncnn_1 1085 +Convolution Conv_383 1 1 1085 1087 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_385 5 1 1076_splitncnn_1 1078_splitncnn_0 1081_splitncnn_0 1084_splitncnn_0 1087 1088 +Convolution Conv_386 1 1 1088 1089 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_389 2 1 1089 1076_splitncnn_0 1092 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_92 1 6 1092 1092_splitncnn_0 1092_splitncnn_1 1092_splitncnn_2 1092_splitncnn_3 1092_splitncnn_4 1092_splitncnn_5 +Convolution Conv_390 1 1 1092_splitncnn_5 1094 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_93 1 4 1094 1094_splitncnn_0 1094_splitncnn_1 1094_splitncnn_2 1094_splitncnn_3 +Concat Concat_392 2 1 1092_splitncnn_4 1094_splitncnn_3 1095 +Convolution Conv_393 1 1 1095 1097 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_94 1 3 1097 1097_splitncnn_0 1097_splitncnn_1 1097_splitncnn_2 +Concat Concat_395 3 1 1092_splitncnn_3 1094_splitncnn_2 1097_splitncnn_2 1098 +Convolution Conv_396 1 1 1098 1100 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_95 1 2 1100 1100_splitncnn_0 1100_splitncnn_1 +Concat Concat_398 4 1 1092_splitncnn_2 1094_splitncnn_1 1097_splitncnn_1 1100_splitncnn_1 1101 +Convolution Conv_399 1 1 1101 1103 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_401 5 1 1092_splitncnn_1 1094_splitncnn_0 1097_splitncnn_0 1100_splitncnn_0 1103 1104 +Convolution Conv_402 1 1 1104 1105 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_405 2 1 1105 1092_splitncnn_0 1108 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_408 2 1 1108 1060_splitncnn_0 1111 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_96 1 7 1111 1111_splitncnn_0 1111_splitncnn_1 1111_splitncnn_2 1111_splitncnn_3 1111_splitncnn_4 1111_splitncnn_5 1111_splitncnn_6 +Convolution Conv_409 1 1 1111_splitncnn_6 1113 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_97 1 4 1113 1113_splitncnn_0 1113_splitncnn_1 1113_splitncnn_2 1113_splitncnn_3 +Concat Concat_411 2 1 1111_splitncnn_5 1113_splitncnn_3 1114 +Convolution Conv_412 1 1 1114 1116 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_98 1 3 1116 1116_splitncnn_0 1116_splitncnn_1 1116_splitncnn_2 +Concat Concat_414 3 1 1111_splitncnn_4 1113_splitncnn_2 1116_splitncnn_2 1117 +Convolution Conv_415 1 1 1117 1119 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_99 1 2 1119 1119_splitncnn_0 1119_splitncnn_1 +Concat Concat_417 4 1 1111_splitncnn_3 1113_splitncnn_1 1116_splitncnn_1 1119_splitncnn_1 1120 +Convolution Conv_418 1 1 1120 1122 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_420 5 1 1111_splitncnn_2 1113_splitncnn_0 1116_splitncnn_0 1119_splitncnn_0 1122 1123 +Convolution Conv_421 1 1 1123 1124 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_424 2 1 1124 1111_splitncnn_1 1127 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_100 1 6 1127 1127_splitncnn_0 1127_splitncnn_1 1127_splitncnn_2 1127_splitncnn_3 1127_splitncnn_4 1127_splitncnn_5 +Convolution Conv_425 1 1 1127_splitncnn_5 1129 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_101 1 4 1129 1129_splitncnn_0 1129_splitncnn_1 1129_splitncnn_2 1129_splitncnn_3 +Concat Concat_427 2 1 1127_splitncnn_4 1129_splitncnn_3 1130 +Convolution Conv_428 1 1 1130 1132 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_102 1 3 1132 1132_splitncnn_0 1132_splitncnn_1 1132_splitncnn_2 +Concat Concat_430 3 1 1127_splitncnn_3 1129_splitncnn_2 1132_splitncnn_2 1133 +Convolution Conv_431 1 1 1133 1135 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_103 1 2 1135 1135_splitncnn_0 1135_splitncnn_1 +Concat Concat_433 4 1 1127_splitncnn_2 1129_splitncnn_1 1132_splitncnn_1 1135_splitncnn_1 1136 +Convolution Conv_434 1 1 1136 1138 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_436 5 1 1127_splitncnn_1 1129_splitncnn_0 1132_splitncnn_0 1135_splitncnn_0 1138 1139 +Convolution Conv_437 1 1 1139 1140 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_440 2 1 1140 1127_splitncnn_0 1143 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_104 1 6 1143 1143_splitncnn_0 1143_splitncnn_1 1143_splitncnn_2 1143_splitncnn_3 1143_splitncnn_4 1143_splitncnn_5 +Convolution Conv_441 1 1 1143_splitncnn_5 1145 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_105 1 4 1145 1145_splitncnn_0 1145_splitncnn_1 1145_splitncnn_2 1145_splitncnn_3 +Concat Concat_443 2 1 1143_splitncnn_4 1145_splitncnn_3 1146 +Convolution Conv_444 1 1 1146 1148 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_106 1 3 1148 1148_splitncnn_0 1148_splitncnn_1 1148_splitncnn_2 +Concat Concat_446 3 1 1143_splitncnn_3 1145_splitncnn_2 1148_splitncnn_2 1149 +Convolution Conv_447 1 1 1149 1151 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_107 1 2 1151 1151_splitncnn_0 1151_splitncnn_1 +Concat Concat_449 4 1 1143_splitncnn_2 1145_splitncnn_1 1148_splitncnn_1 1151_splitncnn_1 1152 +Convolution Conv_450 1 1 1152 1154 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_452 5 1 1143_splitncnn_1 1145_splitncnn_0 1148_splitncnn_0 1151_splitncnn_0 1154 1155 +Convolution Conv_453 1 1 1155 1156 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_456 2 1 1156 1143_splitncnn_0 1159 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_459 2 1 1159 1111_splitncnn_0 1162 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_108 1 7 1162 1162_splitncnn_0 1162_splitncnn_1 1162_splitncnn_2 1162_splitncnn_3 1162_splitncnn_4 1162_splitncnn_5 1162_splitncnn_6 +Convolution Conv_460 1 1 1162_splitncnn_6 1164 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_109 1 4 1164 1164_splitncnn_0 1164_splitncnn_1 1164_splitncnn_2 1164_splitncnn_3 +Concat Concat_462 2 1 1162_splitncnn_5 1164_splitncnn_3 1165 +Convolution Conv_463 1 1 1165 1167 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_110 1 3 1167 1167_splitncnn_0 1167_splitncnn_1 1167_splitncnn_2 +Concat Concat_465 3 1 1162_splitncnn_4 1164_splitncnn_2 1167_splitncnn_2 1168 +Convolution Conv_466 1 1 1168 1170 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_111 1 2 1170 1170_splitncnn_0 1170_splitncnn_1 +Concat Concat_468 4 1 1162_splitncnn_3 1164_splitncnn_1 1167_splitncnn_1 1170_splitncnn_1 1171 +Convolution Conv_469 1 1 1171 1173 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_471 5 1 1162_splitncnn_2 1164_splitncnn_0 1167_splitncnn_0 1170_splitncnn_0 1173 1174 +Convolution Conv_472 1 1 1174 1175 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_475 2 1 1175 1162_splitncnn_1 1178 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_112 1 6 1178 1178_splitncnn_0 1178_splitncnn_1 1178_splitncnn_2 1178_splitncnn_3 1178_splitncnn_4 1178_splitncnn_5 +Convolution Conv_476 1 1 1178_splitncnn_5 1180 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_113 1 4 1180 1180_splitncnn_0 1180_splitncnn_1 1180_splitncnn_2 1180_splitncnn_3 +Concat Concat_478 2 1 1178_splitncnn_4 1180_splitncnn_3 1181 +Convolution Conv_479 1 1 1181 1183 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_114 1 3 1183 1183_splitncnn_0 1183_splitncnn_1 1183_splitncnn_2 +Concat Concat_481 3 1 1178_splitncnn_3 1180_splitncnn_2 1183_splitncnn_2 1184 +Convolution Conv_482 1 1 1184 1186 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_115 1 2 1186 1186_splitncnn_0 1186_splitncnn_1 +Concat Concat_484 4 1 1178_splitncnn_2 1180_splitncnn_1 1183_splitncnn_1 1186_splitncnn_1 1187 +Convolution Conv_485 1 1 1187 1189 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_487 5 1 1178_splitncnn_1 1180_splitncnn_0 1183_splitncnn_0 1186_splitncnn_0 1189 1190 +Convolution Conv_488 1 1 1190 1191 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_491 2 1 1191 1178_splitncnn_0 1194 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_116 1 6 1194 1194_splitncnn_0 1194_splitncnn_1 1194_splitncnn_2 1194_splitncnn_3 1194_splitncnn_4 1194_splitncnn_5 +Convolution Conv_492 1 1 1194_splitncnn_5 1196 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_117 1 4 1196 1196_splitncnn_0 1196_splitncnn_1 1196_splitncnn_2 1196_splitncnn_3 +Concat Concat_494 2 1 1194_splitncnn_4 1196_splitncnn_3 1197 +Convolution Conv_495 1 1 1197 1199 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_118 1 3 1199 1199_splitncnn_0 1199_splitncnn_1 1199_splitncnn_2 +Concat Concat_497 3 1 1194_splitncnn_3 1196_splitncnn_2 1199_splitncnn_2 1200 +Convolution Conv_498 1 1 1200 1202 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_119 1 2 1202 1202_splitncnn_0 1202_splitncnn_1 +Concat Concat_500 4 1 1194_splitncnn_2 1196_splitncnn_1 1199_splitncnn_1 1202_splitncnn_1 1203 +Convolution Conv_501 1 1 1203 1205 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_503 5 1 1194_splitncnn_1 1196_splitncnn_0 1199_splitncnn_0 1202_splitncnn_0 1205 1206 +Convolution Conv_504 1 1 1206 1207 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_507 2 1 1207 1194_splitncnn_0 1210 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_510 2 1 1210 1162_splitncnn_0 1213 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_120 1 7 1213 1213_splitncnn_0 1213_splitncnn_1 1213_splitncnn_2 1213_splitncnn_3 1213_splitncnn_4 1213_splitncnn_5 1213_splitncnn_6 +Convolution Conv_511 1 1 1213_splitncnn_6 1215 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_121 1 4 1215 1215_splitncnn_0 1215_splitncnn_1 1215_splitncnn_2 1215_splitncnn_3 +Concat Concat_513 2 1 1213_splitncnn_5 1215_splitncnn_3 1216 +Convolution Conv_514 1 1 1216 1218 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_122 1 3 1218 1218_splitncnn_0 1218_splitncnn_1 1218_splitncnn_2 +Concat Concat_516 3 1 1213_splitncnn_4 1215_splitncnn_2 1218_splitncnn_2 1219 +Convolution Conv_517 1 1 1219 1221 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_123 1 2 1221 1221_splitncnn_0 1221_splitncnn_1 +Concat Concat_519 4 1 1213_splitncnn_3 1215_splitncnn_1 1218_splitncnn_1 1221_splitncnn_1 1222 +Convolution Conv_520 1 1 1222 1224 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_522 5 1 1213_splitncnn_2 1215_splitncnn_0 1218_splitncnn_0 1221_splitncnn_0 1224 1225 +Convolution Conv_523 1 1 1225 1226 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_526 2 1 1226 1213_splitncnn_1 1229 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_124 1 6 1229 1229_splitncnn_0 1229_splitncnn_1 1229_splitncnn_2 1229_splitncnn_3 1229_splitncnn_4 1229_splitncnn_5 +Convolution Conv_527 1 1 1229_splitncnn_5 1231 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_125 1 4 1231 1231_splitncnn_0 1231_splitncnn_1 1231_splitncnn_2 1231_splitncnn_3 +Concat Concat_529 2 1 1229_splitncnn_4 1231_splitncnn_3 1232 +Convolution Conv_530 1 1 1232 1234 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_126 1 3 1234 1234_splitncnn_0 1234_splitncnn_1 1234_splitncnn_2 +Concat Concat_532 3 1 1229_splitncnn_3 1231_splitncnn_2 1234_splitncnn_2 1235 +Convolution Conv_533 1 1 1235 1237 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_127 1 2 1237 1237_splitncnn_0 1237_splitncnn_1 +Concat Concat_535 4 1 1229_splitncnn_2 1231_splitncnn_1 1234_splitncnn_1 1237_splitncnn_1 1238 +Convolution Conv_536 1 1 1238 1240 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_538 5 1 1229_splitncnn_1 1231_splitncnn_0 1234_splitncnn_0 1237_splitncnn_0 1240 1241 +Convolution Conv_539 1 1 1241 1242 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_542 2 1 1242 1229_splitncnn_0 1245 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_128 1 6 1245 1245_splitncnn_0 1245_splitncnn_1 1245_splitncnn_2 1245_splitncnn_3 1245_splitncnn_4 1245_splitncnn_5 +Convolution Conv_543 1 1 1245_splitncnn_5 1247 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_129 1 4 1247 1247_splitncnn_0 1247_splitncnn_1 1247_splitncnn_2 1247_splitncnn_3 +Concat Concat_545 2 1 1245_splitncnn_4 1247_splitncnn_3 1248 +Convolution Conv_546 1 1 1248 1250 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_130 1 3 1250 1250_splitncnn_0 1250_splitncnn_1 1250_splitncnn_2 +Concat Concat_548 3 1 1245_splitncnn_3 1247_splitncnn_2 1250_splitncnn_2 1251 +Convolution Conv_549 1 1 1251 1253 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_131 1 2 1253 1253_splitncnn_0 1253_splitncnn_1 +Concat Concat_551 4 1 1245_splitncnn_2 1247_splitncnn_1 1250_splitncnn_1 1253_splitncnn_1 1254 +Convolution Conv_552 1 1 1254 1256 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_554 5 1 1245_splitncnn_1 1247_splitncnn_0 1250_splitncnn_0 1253_splitncnn_0 1256 1257 +Convolution Conv_555 1 1 1257 1258 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_558 2 1 1258 1245_splitncnn_0 1261 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_561 2 1 1261 1213_splitncnn_0 1264 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_132 1 7 1264 1264_splitncnn_0 1264_splitncnn_1 1264_splitncnn_2 1264_splitncnn_3 1264_splitncnn_4 1264_splitncnn_5 1264_splitncnn_6 +Convolution Conv_562 1 1 1264_splitncnn_6 1266 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_133 1 4 1266 1266_splitncnn_0 1266_splitncnn_1 1266_splitncnn_2 1266_splitncnn_3 +Concat Concat_564 2 1 1264_splitncnn_5 1266_splitncnn_3 1267 +Convolution Conv_565 1 1 1267 1269 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_134 1 3 1269 1269_splitncnn_0 1269_splitncnn_1 1269_splitncnn_2 +Concat Concat_567 3 1 1264_splitncnn_4 1266_splitncnn_2 1269_splitncnn_2 1270 +Convolution Conv_568 1 1 1270 1272 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_135 1 2 1272 1272_splitncnn_0 1272_splitncnn_1 +Concat Concat_570 4 1 1264_splitncnn_3 1266_splitncnn_1 1269_splitncnn_1 1272_splitncnn_1 1273 +Convolution Conv_571 1 1 1273 1275 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_573 5 1 1264_splitncnn_2 1266_splitncnn_0 1269_splitncnn_0 1272_splitncnn_0 1275 1276 +Convolution Conv_574 1 1 1276 1277 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_577 2 1 1277 1264_splitncnn_1 1280 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_136 1 6 1280 1280_splitncnn_0 1280_splitncnn_1 1280_splitncnn_2 1280_splitncnn_3 1280_splitncnn_4 1280_splitncnn_5 +Convolution Conv_578 1 1 1280_splitncnn_5 1282 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_137 1 4 1282 1282_splitncnn_0 1282_splitncnn_1 1282_splitncnn_2 1282_splitncnn_3 +Concat Concat_580 2 1 1280_splitncnn_4 1282_splitncnn_3 1283 +Convolution Conv_581 1 1 1283 1285 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_138 1 3 1285 1285_splitncnn_0 1285_splitncnn_1 1285_splitncnn_2 +Concat Concat_583 3 1 1280_splitncnn_3 1282_splitncnn_2 1285_splitncnn_2 1286 +Convolution Conv_584 1 1 1286 1288 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_139 1 2 1288 1288_splitncnn_0 1288_splitncnn_1 +Concat Concat_586 4 1 1280_splitncnn_2 1282_splitncnn_1 1285_splitncnn_1 1288_splitncnn_1 1289 +Convolution Conv_587 1 1 1289 1291 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_589 5 1 1280_splitncnn_1 1282_splitncnn_0 1285_splitncnn_0 1288_splitncnn_0 1291 1292 +Convolution Conv_590 1 1 1292 1293 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_593 2 1 1293 1280_splitncnn_0 1296 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_140 1 6 1296 1296_splitncnn_0 1296_splitncnn_1 1296_splitncnn_2 1296_splitncnn_3 1296_splitncnn_4 1296_splitncnn_5 +Convolution Conv_594 1 1 1296_splitncnn_5 1298 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_141 1 4 1298 1298_splitncnn_0 1298_splitncnn_1 1298_splitncnn_2 1298_splitncnn_3 +Concat Concat_596 2 1 1296_splitncnn_4 1298_splitncnn_3 1299 +Convolution Conv_597 1 1 1299 1301 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_142 1 3 1301 1301_splitncnn_0 1301_splitncnn_1 1301_splitncnn_2 +Concat Concat_599 3 1 1296_splitncnn_3 1298_splitncnn_2 1301_splitncnn_2 1302 +Convolution Conv_600 1 1 1302 1304 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_143 1 2 1304 1304_splitncnn_0 1304_splitncnn_1 +Concat Concat_602 4 1 1296_splitncnn_2 1298_splitncnn_1 1301_splitncnn_1 1304_splitncnn_1 1305 +Convolution Conv_603 1 1 1305 1307 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_605 5 1 1296_splitncnn_1 1298_splitncnn_0 1301_splitncnn_0 1304_splitncnn_0 1307 1308 +Convolution Conv_606 1 1 1308 1309 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_609 2 1 1309 1296_splitncnn_0 1312 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_612 2 1 1312 1264_splitncnn_0 1315 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_144 1 7 1315 1315_splitncnn_0 1315_splitncnn_1 1315_splitncnn_2 1315_splitncnn_3 1315_splitncnn_4 1315_splitncnn_5 1315_splitncnn_6 +Convolution Conv_613 1 1 1315_splitncnn_6 1317 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_145 1 4 1317 1317_splitncnn_0 1317_splitncnn_1 1317_splitncnn_2 1317_splitncnn_3 +Concat Concat_615 2 1 1315_splitncnn_5 1317_splitncnn_3 1318 +Convolution Conv_616 1 1 1318 1320 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_146 1 3 1320 1320_splitncnn_0 1320_splitncnn_1 1320_splitncnn_2 +Concat Concat_618 3 1 1315_splitncnn_4 1317_splitncnn_2 1320_splitncnn_2 1321 +Convolution Conv_619 1 1 1321 1323 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_147 1 2 1323 1323_splitncnn_0 1323_splitncnn_1 +Concat Concat_621 4 1 1315_splitncnn_3 1317_splitncnn_1 1320_splitncnn_1 1323_splitncnn_1 1324 +Convolution Conv_622 1 1 1324 1326 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_624 5 1 1315_splitncnn_2 1317_splitncnn_0 1320_splitncnn_0 1323_splitncnn_0 1326 1327 +Convolution Conv_625 1 1 1327 1328 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_628 2 1 1328 1315_splitncnn_1 1331 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_148 1 6 1331 1331_splitncnn_0 1331_splitncnn_1 1331_splitncnn_2 1331_splitncnn_3 1331_splitncnn_4 1331_splitncnn_5 +Convolution Conv_629 1 1 1331_splitncnn_5 1333 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_149 1 4 1333 1333_splitncnn_0 1333_splitncnn_1 1333_splitncnn_2 1333_splitncnn_3 +Concat Concat_631 2 1 1331_splitncnn_4 1333_splitncnn_3 1334 +Convolution Conv_632 1 1 1334 1336 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_150 1 3 1336 1336_splitncnn_0 1336_splitncnn_1 1336_splitncnn_2 +Concat Concat_634 3 1 1331_splitncnn_3 1333_splitncnn_2 1336_splitncnn_2 1337 +Convolution Conv_635 1 1 1337 1339 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_151 1 2 1339 1339_splitncnn_0 1339_splitncnn_1 +Concat Concat_637 4 1 1331_splitncnn_2 1333_splitncnn_1 1336_splitncnn_1 1339_splitncnn_1 1340 +Convolution Conv_638 1 1 1340 1342 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_640 5 1 1331_splitncnn_1 1333_splitncnn_0 1336_splitncnn_0 1339_splitncnn_0 1342 1343 +Convolution Conv_641 1 1 1343 1344 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_644 2 1 1344 1331_splitncnn_0 1347 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_152 1 6 1347 1347_splitncnn_0 1347_splitncnn_1 1347_splitncnn_2 1347_splitncnn_3 1347_splitncnn_4 1347_splitncnn_5 +Convolution Conv_645 1 1 1347_splitncnn_5 1349 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_153 1 4 1349 1349_splitncnn_0 1349_splitncnn_1 1349_splitncnn_2 1349_splitncnn_3 +Concat Concat_647 2 1 1347_splitncnn_4 1349_splitncnn_3 1350 +Convolution Conv_648 1 1 1350 1352 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_154 1 3 1352 1352_splitncnn_0 1352_splitncnn_1 1352_splitncnn_2 +Concat Concat_650 3 1 1347_splitncnn_3 1349_splitncnn_2 1352_splitncnn_2 1353 +Convolution Conv_651 1 1 1353 1355 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_155 1 2 1355 1355_splitncnn_0 1355_splitncnn_1 +Concat Concat_653 4 1 1347_splitncnn_2 1349_splitncnn_1 1352_splitncnn_1 1355_splitncnn_1 1356 +Convolution Conv_654 1 1 1356 1358 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_656 5 1 1347_splitncnn_1 1349_splitncnn_0 1352_splitncnn_0 1355_splitncnn_0 1358 1359 +Convolution Conv_657 1 1 1359 1360 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_660 2 1 1360 1347_splitncnn_0 1363 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_663 2 1 1363 1315_splitncnn_0 1366 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_156 1 7 1366 1366_splitncnn_0 1366_splitncnn_1 1366_splitncnn_2 1366_splitncnn_3 1366_splitncnn_4 1366_splitncnn_5 1366_splitncnn_6 +Convolution Conv_664 1 1 1366_splitncnn_6 1368 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_157 1 4 1368 1368_splitncnn_0 1368_splitncnn_1 1368_splitncnn_2 1368_splitncnn_3 +Concat Concat_666 2 1 1366_splitncnn_5 1368_splitncnn_3 1369 +Convolution Conv_667 1 1 1369 1371 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_158 1 3 1371 1371_splitncnn_0 1371_splitncnn_1 1371_splitncnn_2 +Concat Concat_669 3 1 1366_splitncnn_4 1368_splitncnn_2 1371_splitncnn_2 1372 +Convolution Conv_670 1 1 1372 1374 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_159 1 2 1374 1374_splitncnn_0 1374_splitncnn_1 +Concat Concat_672 4 1 1366_splitncnn_3 1368_splitncnn_1 1371_splitncnn_1 1374_splitncnn_1 1375 +Convolution Conv_673 1 1 1375 1377 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_675 5 1 1366_splitncnn_2 1368_splitncnn_0 1371_splitncnn_0 1374_splitncnn_0 1377 1378 +Convolution Conv_676 1 1 1378 1379 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_679 2 1 1379 1366_splitncnn_1 1382 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_160 1 6 1382 1382_splitncnn_0 1382_splitncnn_1 1382_splitncnn_2 1382_splitncnn_3 1382_splitncnn_4 1382_splitncnn_5 +Convolution Conv_680 1 1 1382_splitncnn_5 1384 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_161 1 4 1384 1384_splitncnn_0 1384_splitncnn_1 1384_splitncnn_2 1384_splitncnn_3 +Concat Concat_682 2 1 1382_splitncnn_4 1384_splitncnn_3 1385 +Convolution Conv_683 1 1 1385 1387 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_162 1 3 1387 1387_splitncnn_0 1387_splitncnn_1 1387_splitncnn_2 +Concat Concat_685 3 1 1382_splitncnn_3 1384_splitncnn_2 1387_splitncnn_2 1388 +Convolution Conv_686 1 1 1388 1390 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_163 1 2 1390 1390_splitncnn_0 1390_splitncnn_1 +Concat Concat_688 4 1 1382_splitncnn_2 1384_splitncnn_1 1387_splitncnn_1 1390_splitncnn_1 1391 +Convolution Conv_689 1 1 1391 1393 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_691 5 1 1382_splitncnn_1 1384_splitncnn_0 1387_splitncnn_0 1390_splitncnn_0 1393 1394 +Convolution Conv_692 1 1 1394 1395 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_695 2 1 1395 1382_splitncnn_0 1398 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_164 1 6 1398 1398_splitncnn_0 1398_splitncnn_1 1398_splitncnn_2 1398_splitncnn_3 1398_splitncnn_4 1398_splitncnn_5 +Convolution Conv_696 1 1 1398_splitncnn_5 1400 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_165 1 4 1400 1400_splitncnn_0 1400_splitncnn_1 1400_splitncnn_2 1400_splitncnn_3 +Concat Concat_698 2 1 1398_splitncnn_4 1400_splitncnn_3 1401 +Convolution Conv_699 1 1 1401 1403 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_166 1 3 1403 1403_splitncnn_0 1403_splitncnn_1 1403_splitncnn_2 +Concat Concat_701 3 1 1398_splitncnn_3 1400_splitncnn_2 1403_splitncnn_2 1404 +Convolution Conv_702 1 1 1404 1406 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_167 1 2 1406 1406_splitncnn_0 1406_splitncnn_1 +Concat Concat_704 4 1 1398_splitncnn_2 1400_splitncnn_1 1403_splitncnn_1 1406_splitncnn_1 1407 +Convolution Conv_705 1 1 1407 1409 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_707 5 1 1398_splitncnn_1 1400_splitncnn_0 1403_splitncnn_0 1406_splitncnn_0 1409 1410 +Convolution Conv_708 1 1 1410 1411 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_711 2 1 1411 1398_splitncnn_0 1414 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_714 2 1 1414 1366_splitncnn_0 1417 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_168 1 7 1417 1417_splitncnn_0 1417_splitncnn_1 1417_splitncnn_2 1417_splitncnn_3 1417_splitncnn_4 1417_splitncnn_5 1417_splitncnn_6 +Convolution Conv_715 1 1 1417_splitncnn_6 1419 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_169 1 4 1419 1419_splitncnn_0 1419_splitncnn_1 1419_splitncnn_2 1419_splitncnn_3 +Concat Concat_717 2 1 1417_splitncnn_5 1419_splitncnn_3 1420 +Convolution Conv_718 1 1 1420 1422 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_170 1 3 1422 1422_splitncnn_0 1422_splitncnn_1 1422_splitncnn_2 +Concat Concat_720 3 1 1417_splitncnn_4 1419_splitncnn_2 1422_splitncnn_2 1423 +Convolution Conv_721 1 1 1423 1425 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_171 1 2 1425 1425_splitncnn_0 1425_splitncnn_1 +Concat Concat_723 4 1 1417_splitncnn_3 1419_splitncnn_1 1422_splitncnn_1 1425_splitncnn_1 1426 +Convolution Conv_724 1 1 1426 1428 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_726 5 1 1417_splitncnn_2 1419_splitncnn_0 1422_splitncnn_0 1425_splitncnn_0 1428 1429 +Convolution Conv_727 1 1 1429 1430 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_730 2 1 1430 1417_splitncnn_1 1433 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_172 1 6 1433 1433_splitncnn_0 1433_splitncnn_1 1433_splitncnn_2 1433_splitncnn_3 1433_splitncnn_4 1433_splitncnn_5 +Convolution Conv_731 1 1 1433_splitncnn_5 1435 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_173 1 4 1435 1435_splitncnn_0 1435_splitncnn_1 1435_splitncnn_2 1435_splitncnn_3 +Concat Concat_733 2 1 1433_splitncnn_4 1435_splitncnn_3 1436 +Convolution Conv_734 1 1 1436 1438 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_174 1 3 1438 1438_splitncnn_0 1438_splitncnn_1 1438_splitncnn_2 +Concat Concat_736 3 1 1433_splitncnn_3 1435_splitncnn_2 1438_splitncnn_2 1439 +Convolution Conv_737 1 1 1439 1441 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_175 1 2 1441 1441_splitncnn_0 1441_splitncnn_1 +Concat Concat_739 4 1 1433_splitncnn_2 1435_splitncnn_1 1438_splitncnn_1 1441_splitncnn_1 1442 +Convolution Conv_740 1 1 1442 1444 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_742 5 1 1433_splitncnn_1 1435_splitncnn_0 1438_splitncnn_0 1441_splitncnn_0 1444 1445 +Convolution Conv_743 1 1 1445 1446 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_746 2 1 1446 1433_splitncnn_0 1449 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_176 1 6 1449 1449_splitncnn_0 1449_splitncnn_1 1449_splitncnn_2 1449_splitncnn_3 1449_splitncnn_4 1449_splitncnn_5 +Convolution Conv_747 1 1 1449_splitncnn_5 1451 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_177 1 4 1451 1451_splitncnn_0 1451_splitncnn_1 1451_splitncnn_2 1451_splitncnn_3 +Concat Concat_749 2 1 1449_splitncnn_4 1451_splitncnn_3 1452 +Convolution Conv_750 1 1 1452 1454 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_178 1 3 1454 1454_splitncnn_0 1454_splitncnn_1 1454_splitncnn_2 +Concat Concat_752 3 1 1449_splitncnn_3 1451_splitncnn_2 1454_splitncnn_2 1455 +Convolution Conv_753 1 1 1455 1457 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_179 1 2 1457 1457_splitncnn_0 1457_splitncnn_1 +Concat Concat_755 4 1 1449_splitncnn_2 1451_splitncnn_1 1454_splitncnn_1 1457_splitncnn_1 1458 +Convolution Conv_756 1 1 1458 1460 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_758 5 1 1449_splitncnn_1 1451_splitncnn_0 1454_splitncnn_0 1457_splitncnn_0 1460 1461 +Convolution Conv_759 1 1 1461 1462 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_762 2 1 1462 1449_splitncnn_0 1465 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_765 2 1 1465 1417_splitncnn_0 1468 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_180 1 7 1468 1468_splitncnn_0 1468_splitncnn_1 1468_splitncnn_2 1468_splitncnn_3 1468_splitncnn_4 1468_splitncnn_5 1468_splitncnn_6 +Convolution Conv_766 1 1 1468_splitncnn_6 1470 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_181 1 4 1470 1470_splitncnn_0 1470_splitncnn_1 1470_splitncnn_2 1470_splitncnn_3 +Concat Concat_768 2 1 1468_splitncnn_5 1470_splitncnn_3 1471 +Convolution Conv_769 1 1 1471 1473 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_182 1 3 1473 1473_splitncnn_0 1473_splitncnn_1 1473_splitncnn_2 +Concat Concat_771 3 1 1468_splitncnn_4 1470_splitncnn_2 1473_splitncnn_2 1474 +Convolution Conv_772 1 1 1474 1476 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_183 1 2 1476 1476_splitncnn_0 1476_splitncnn_1 +Concat Concat_774 4 1 1468_splitncnn_3 1470_splitncnn_1 1473_splitncnn_1 1476_splitncnn_1 1477 +Convolution Conv_775 1 1 1477 1479 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_777 5 1 1468_splitncnn_2 1470_splitncnn_0 1473_splitncnn_0 1476_splitncnn_0 1479 1480 +Convolution Conv_778 1 1 1480 1481 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_781 2 1 1481 1468_splitncnn_1 1484 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_184 1 6 1484 1484_splitncnn_0 1484_splitncnn_1 1484_splitncnn_2 1484_splitncnn_3 1484_splitncnn_4 1484_splitncnn_5 +Convolution Conv_782 1 1 1484_splitncnn_5 1486 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_185 1 4 1486 1486_splitncnn_0 1486_splitncnn_1 1486_splitncnn_2 1486_splitncnn_3 +Concat Concat_784 2 1 1484_splitncnn_4 1486_splitncnn_3 1487 +Convolution Conv_785 1 1 1487 1489 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_186 1 3 1489 1489_splitncnn_0 1489_splitncnn_1 1489_splitncnn_2 +Concat Concat_787 3 1 1484_splitncnn_3 1486_splitncnn_2 1489_splitncnn_2 1490 +Convolution Conv_788 1 1 1490 1492 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_187 1 2 1492 1492_splitncnn_0 1492_splitncnn_1 +Concat Concat_790 4 1 1484_splitncnn_2 1486_splitncnn_1 1489_splitncnn_1 1492_splitncnn_1 1493 +Convolution Conv_791 1 1 1493 1495 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_793 5 1 1484_splitncnn_1 1486_splitncnn_0 1489_splitncnn_0 1492_splitncnn_0 1495 1496 +Convolution Conv_794 1 1 1496 1497 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_797 2 1 1497 1484_splitncnn_0 1500 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_188 1 6 1500 1500_splitncnn_0 1500_splitncnn_1 1500_splitncnn_2 1500_splitncnn_3 1500_splitncnn_4 1500_splitncnn_5 +Convolution Conv_798 1 1 1500_splitncnn_5 1502 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_189 1 4 1502 1502_splitncnn_0 1502_splitncnn_1 1502_splitncnn_2 1502_splitncnn_3 +Concat Concat_800 2 1 1500_splitncnn_4 1502_splitncnn_3 1503 +Convolution Conv_801 1 1 1503 1505 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_190 1 3 1505 1505_splitncnn_0 1505_splitncnn_1 1505_splitncnn_2 +Concat Concat_803 3 1 1500_splitncnn_3 1502_splitncnn_2 1505_splitncnn_2 1506 +Convolution Conv_804 1 1 1506 1508 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_191 1 2 1508 1508_splitncnn_0 1508_splitncnn_1 +Concat Concat_806 4 1 1500_splitncnn_2 1502_splitncnn_1 1505_splitncnn_1 1508_splitncnn_1 1509 +Convolution Conv_807 1 1 1509 1511 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_809 5 1 1500_splitncnn_1 1502_splitncnn_0 1505_splitncnn_0 1508_splitncnn_0 1511 1512 +Convolution Conv_810 1 1 1512 1513 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_813 2 1 1513 1500_splitncnn_0 1516 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_816 2 1 1516 1468_splitncnn_0 1519 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_192 1 7 1519 1519_splitncnn_0 1519_splitncnn_1 1519_splitncnn_2 1519_splitncnn_3 1519_splitncnn_4 1519_splitncnn_5 1519_splitncnn_6 +Convolution Conv_817 1 1 1519_splitncnn_6 1521 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_193 1 4 1521 1521_splitncnn_0 1521_splitncnn_1 1521_splitncnn_2 1521_splitncnn_3 +Concat Concat_819 2 1 1519_splitncnn_5 1521_splitncnn_3 1522 +Convolution Conv_820 1 1 1522 1524 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_194 1 3 1524 1524_splitncnn_0 1524_splitncnn_1 1524_splitncnn_2 +Concat Concat_822 3 1 1519_splitncnn_4 1521_splitncnn_2 1524_splitncnn_2 1525 +Convolution Conv_823 1 1 1525 1527 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_195 1 2 1527 1527_splitncnn_0 1527_splitncnn_1 +Concat Concat_825 4 1 1519_splitncnn_3 1521_splitncnn_1 1524_splitncnn_1 1527_splitncnn_1 1528 +Convolution Conv_826 1 1 1528 1530 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_828 5 1 1519_splitncnn_2 1521_splitncnn_0 1524_splitncnn_0 1527_splitncnn_0 1530 1531 +Convolution Conv_829 1 1 1531 1532 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_832 2 1 1532 1519_splitncnn_1 1535 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_196 1 6 1535 1535_splitncnn_0 1535_splitncnn_1 1535_splitncnn_2 1535_splitncnn_3 1535_splitncnn_4 1535_splitncnn_5 +Convolution Conv_833 1 1 1535_splitncnn_5 1537 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_197 1 4 1537 1537_splitncnn_0 1537_splitncnn_1 1537_splitncnn_2 1537_splitncnn_3 +Concat Concat_835 2 1 1535_splitncnn_4 1537_splitncnn_3 1538 +Convolution Conv_836 1 1 1538 1540 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_198 1 3 1540 1540_splitncnn_0 1540_splitncnn_1 1540_splitncnn_2 +Concat Concat_838 3 1 1535_splitncnn_3 1537_splitncnn_2 1540_splitncnn_2 1541 +Convolution Conv_839 1 1 1541 1543 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_199 1 2 1543 1543_splitncnn_0 1543_splitncnn_1 +Concat Concat_841 4 1 1535_splitncnn_2 1537_splitncnn_1 1540_splitncnn_1 1543_splitncnn_1 1544 +Convolution Conv_842 1 1 1544 1546 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_844 5 1 1535_splitncnn_1 1537_splitncnn_0 1540_splitncnn_0 1543_splitncnn_0 1546 1547 +Convolution Conv_845 1 1 1547 1548 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_848 2 1 1548 1535_splitncnn_0 1551 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_200 1 6 1551 1551_splitncnn_0 1551_splitncnn_1 1551_splitncnn_2 1551_splitncnn_3 1551_splitncnn_4 1551_splitncnn_5 +Convolution Conv_849 1 1 1551_splitncnn_5 1553 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_201 1 4 1553 1553_splitncnn_0 1553_splitncnn_1 1553_splitncnn_2 1553_splitncnn_3 +Concat Concat_851 2 1 1551_splitncnn_4 1553_splitncnn_3 1554 +Convolution Conv_852 1 1 1554 1556 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_202 1 3 1556 1556_splitncnn_0 1556_splitncnn_1 1556_splitncnn_2 +Concat Concat_854 3 1 1551_splitncnn_3 1553_splitncnn_2 1556_splitncnn_2 1557 +Convolution Conv_855 1 1 1557 1559 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_203 1 2 1559 1559_splitncnn_0 1559_splitncnn_1 +Concat Concat_857 4 1 1551_splitncnn_2 1553_splitncnn_1 1556_splitncnn_1 1559_splitncnn_1 1560 +Convolution Conv_858 1 1 1560 1562 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_860 5 1 1551_splitncnn_1 1553_splitncnn_0 1556_splitncnn_0 1559_splitncnn_0 1562 1563 +Convolution Conv_861 1 1 1563 1564 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_864 2 1 1564 1551_splitncnn_0 1567 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_867 2 1 1567 1519_splitncnn_0 1570 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_204 1 7 1570 1570_splitncnn_0 1570_splitncnn_1 1570_splitncnn_2 1570_splitncnn_3 1570_splitncnn_4 1570_splitncnn_5 1570_splitncnn_6 +Convolution Conv_868 1 1 1570_splitncnn_6 1572 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_205 1 4 1572 1572_splitncnn_0 1572_splitncnn_1 1572_splitncnn_2 1572_splitncnn_3 +Concat Concat_870 2 1 1570_splitncnn_5 1572_splitncnn_3 1573 +Convolution Conv_871 1 1 1573 1575 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_206 1 3 1575 1575_splitncnn_0 1575_splitncnn_1 1575_splitncnn_2 +Concat Concat_873 3 1 1570_splitncnn_4 1572_splitncnn_2 1575_splitncnn_2 1576 +Convolution Conv_874 1 1 1576 1578 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_207 1 2 1578 1578_splitncnn_0 1578_splitncnn_1 +Concat Concat_876 4 1 1570_splitncnn_3 1572_splitncnn_1 1575_splitncnn_1 1578_splitncnn_1 1579 +Convolution Conv_877 1 1 1579 1581 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_879 5 1 1570_splitncnn_2 1572_splitncnn_0 1575_splitncnn_0 1578_splitncnn_0 1581 1582 +Convolution Conv_880 1 1 1582 1583 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_883 2 1 1583 1570_splitncnn_1 1586 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_208 1 6 1586 1586_splitncnn_0 1586_splitncnn_1 1586_splitncnn_2 1586_splitncnn_3 1586_splitncnn_4 1586_splitncnn_5 +Convolution Conv_884 1 1 1586_splitncnn_5 1588 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_209 1 4 1588 1588_splitncnn_0 1588_splitncnn_1 1588_splitncnn_2 1588_splitncnn_3 +Concat Concat_886 2 1 1586_splitncnn_4 1588_splitncnn_3 1589 +Convolution Conv_887 1 1 1589 1591 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_210 1 3 1591 1591_splitncnn_0 1591_splitncnn_1 1591_splitncnn_2 +Concat Concat_889 3 1 1586_splitncnn_3 1588_splitncnn_2 1591_splitncnn_2 1592 +Convolution Conv_890 1 1 1592 1594 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_211 1 2 1594 1594_splitncnn_0 1594_splitncnn_1 +Concat Concat_892 4 1 1586_splitncnn_2 1588_splitncnn_1 1591_splitncnn_1 1594_splitncnn_1 1595 +Convolution Conv_893 1 1 1595 1597 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_895 5 1 1586_splitncnn_1 1588_splitncnn_0 1591_splitncnn_0 1594_splitncnn_0 1597 1598 +Convolution Conv_896 1 1 1598 1599 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_899 2 1 1599 1586_splitncnn_0 1602 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_212 1 6 1602 1602_splitncnn_0 1602_splitncnn_1 1602_splitncnn_2 1602_splitncnn_3 1602_splitncnn_4 1602_splitncnn_5 +Convolution Conv_900 1 1 1602_splitncnn_5 1604 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_213 1 4 1604 1604_splitncnn_0 1604_splitncnn_1 1604_splitncnn_2 1604_splitncnn_3 +Concat Concat_902 2 1 1602_splitncnn_4 1604_splitncnn_3 1605 +Convolution Conv_903 1 1 1605 1607 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_214 1 3 1607 1607_splitncnn_0 1607_splitncnn_1 1607_splitncnn_2 +Concat Concat_905 3 1 1602_splitncnn_3 1604_splitncnn_2 1607_splitncnn_2 1608 +Convolution Conv_906 1 1 1608 1610 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_215 1 2 1610 1610_splitncnn_0 1610_splitncnn_1 +Concat Concat_908 4 1 1602_splitncnn_2 1604_splitncnn_1 1607_splitncnn_1 1610_splitncnn_1 1611 +Convolution Conv_909 1 1 1611 1613 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_911 5 1 1602_splitncnn_1 1604_splitncnn_0 1607_splitncnn_0 1610_splitncnn_0 1613 1614 +Convolution Conv_912 1 1 1614 1615 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_915 2 1 1615 1602_splitncnn_0 1618 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_918 2 1 1618 1570_splitncnn_0 1621 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_216 1 7 1621 1621_splitncnn_0 1621_splitncnn_1 1621_splitncnn_2 1621_splitncnn_3 1621_splitncnn_4 1621_splitncnn_5 1621_splitncnn_6 +Convolution Conv_919 1 1 1621_splitncnn_6 1623 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_217 1 4 1623 1623_splitncnn_0 1623_splitncnn_1 1623_splitncnn_2 1623_splitncnn_3 +Concat Concat_921 2 1 1621_splitncnn_5 1623_splitncnn_3 1624 +Convolution Conv_922 1 1 1624 1626 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_218 1 3 1626 1626_splitncnn_0 1626_splitncnn_1 1626_splitncnn_2 +Concat Concat_924 3 1 1621_splitncnn_4 1623_splitncnn_2 1626_splitncnn_2 1627 +Convolution Conv_925 1 1 1627 1629 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_219 1 2 1629 1629_splitncnn_0 1629_splitncnn_1 +Concat Concat_927 4 1 1621_splitncnn_3 1623_splitncnn_1 1626_splitncnn_1 1629_splitncnn_1 1630 +Convolution Conv_928 1 1 1630 1632 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_930 5 1 1621_splitncnn_2 1623_splitncnn_0 1626_splitncnn_0 1629_splitncnn_0 1632 1633 +Convolution Conv_931 1 1 1633 1634 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_934 2 1 1634 1621_splitncnn_1 1637 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_220 1 6 1637 1637_splitncnn_0 1637_splitncnn_1 1637_splitncnn_2 1637_splitncnn_3 1637_splitncnn_4 1637_splitncnn_5 +Convolution Conv_935 1 1 1637_splitncnn_5 1639 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_221 1 4 1639 1639_splitncnn_0 1639_splitncnn_1 1639_splitncnn_2 1639_splitncnn_3 +Concat Concat_937 2 1 1637_splitncnn_4 1639_splitncnn_3 1640 +Convolution Conv_938 1 1 1640 1642 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_222 1 3 1642 1642_splitncnn_0 1642_splitncnn_1 1642_splitncnn_2 +Concat Concat_940 3 1 1637_splitncnn_3 1639_splitncnn_2 1642_splitncnn_2 1643 +Convolution Conv_941 1 1 1643 1645 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_223 1 2 1645 1645_splitncnn_0 1645_splitncnn_1 +Concat Concat_943 4 1 1637_splitncnn_2 1639_splitncnn_1 1642_splitncnn_1 1645_splitncnn_1 1646 +Convolution Conv_944 1 1 1646 1648 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_946 5 1 1637_splitncnn_1 1639_splitncnn_0 1642_splitncnn_0 1645_splitncnn_0 1648 1649 +Convolution Conv_947 1 1 1649 1650 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_950 2 1 1650 1637_splitncnn_0 1653 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_224 1 6 1653 1653_splitncnn_0 1653_splitncnn_1 1653_splitncnn_2 1653_splitncnn_3 1653_splitncnn_4 1653_splitncnn_5 +Convolution Conv_951 1 1 1653_splitncnn_5 1655 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_225 1 4 1655 1655_splitncnn_0 1655_splitncnn_1 1655_splitncnn_2 1655_splitncnn_3 +Concat Concat_953 2 1 1653_splitncnn_4 1655_splitncnn_3 1656 +Convolution Conv_954 1 1 1656 1658 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_226 1 3 1658 1658_splitncnn_0 1658_splitncnn_1 1658_splitncnn_2 +Concat Concat_956 3 1 1653_splitncnn_3 1655_splitncnn_2 1658_splitncnn_2 1659 +Convolution Conv_957 1 1 1659 1661 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_227 1 2 1661 1661_splitncnn_0 1661_splitncnn_1 +Concat Concat_959 4 1 1653_splitncnn_2 1655_splitncnn_1 1658_splitncnn_1 1661_splitncnn_1 1662 +Convolution Conv_960 1 1 1662 1664 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_962 5 1 1653_splitncnn_1 1655_splitncnn_0 1658_splitncnn_0 1661_splitncnn_0 1664 1665 +Convolution Conv_963 1 1 1665 1666 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_966 2 1 1666 1653_splitncnn_0 1669 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_969 2 1 1669 1621_splitncnn_0 1672 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_228 1 7 1672 1672_splitncnn_0 1672_splitncnn_1 1672_splitncnn_2 1672_splitncnn_3 1672_splitncnn_4 1672_splitncnn_5 1672_splitncnn_6 +Convolution Conv_970 1 1 1672_splitncnn_6 1674 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_229 1 4 1674 1674_splitncnn_0 1674_splitncnn_1 1674_splitncnn_2 1674_splitncnn_3 +Concat Concat_972 2 1 1672_splitncnn_5 1674_splitncnn_3 1675 +Convolution Conv_973 1 1 1675 1677 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_230 1 3 1677 1677_splitncnn_0 1677_splitncnn_1 1677_splitncnn_2 +Concat Concat_975 3 1 1672_splitncnn_4 1674_splitncnn_2 1677_splitncnn_2 1678 +Convolution Conv_976 1 1 1678 1680 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_231 1 2 1680 1680_splitncnn_0 1680_splitncnn_1 +Concat Concat_978 4 1 1672_splitncnn_3 1674_splitncnn_1 1677_splitncnn_1 1680_splitncnn_1 1681 +Convolution Conv_979 1 1 1681 1683 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_981 5 1 1672_splitncnn_2 1674_splitncnn_0 1677_splitncnn_0 1680_splitncnn_0 1683 1684 +Convolution Conv_982 1 1 1684 1685 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_985 2 1 1685 1672_splitncnn_1 1688 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_232 1 6 1688 1688_splitncnn_0 1688_splitncnn_1 1688_splitncnn_2 1688_splitncnn_3 1688_splitncnn_4 1688_splitncnn_5 +Convolution Conv_986 1 1 1688_splitncnn_5 1690 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_233 1 4 1690 1690_splitncnn_0 1690_splitncnn_1 1690_splitncnn_2 1690_splitncnn_3 +Concat Concat_988 2 1 1688_splitncnn_4 1690_splitncnn_3 1691 +Convolution Conv_989 1 1 1691 1693 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_234 1 3 1693 1693_splitncnn_0 1693_splitncnn_1 1693_splitncnn_2 +Concat Concat_991 3 1 1688_splitncnn_3 1690_splitncnn_2 1693_splitncnn_2 1694 +Convolution Conv_992 1 1 1694 1696 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_235 1 2 1696 1696_splitncnn_0 1696_splitncnn_1 +Concat Concat_994 4 1 1688_splitncnn_2 1690_splitncnn_1 1693_splitncnn_1 1696_splitncnn_1 1697 +Convolution Conv_995 1 1 1697 1699 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_997 5 1 1688_splitncnn_1 1690_splitncnn_0 1693_splitncnn_0 1696_splitncnn_0 1699 1700 +Convolution Conv_998 1 1 1700 1701 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1001 2 1 1701 1688_splitncnn_0 1704 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_236 1 6 1704 1704_splitncnn_0 1704_splitncnn_1 1704_splitncnn_2 1704_splitncnn_3 1704_splitncnn_4 1704_splitncnn_5 +Convolution Conv_1002 1 1 1704_splitncnn_5 1706 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_237 1 4 1706 1706_splitncnn_0 1706_splitncnn_1 1706_splitncnn_2 1706_splitncnn_3 +Concat Concat_1004 2 1 1704_splitncnn_4 1706_splitncnn_3 1707 +Convolution Conv_1005 1 1 1707 1709 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_238 1 3 1709 1709_splitncnn_0 1709_splitncnn_1 1709_splitncnn_2 +Concat Concat_1007 3 1 1704_splitncnn_3 1706_splitncnn_2 1709_splitncnn_2 1710 +Convolution Conv_1008 1 1 1710 1712 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_239 1 2 1712 1712_splitncnn_0 1712_splitncnn_1 +Concat Concat_1010 4 1 1704_splitncnn_2 1706_splitncnn_1 1709_splitncnn_1 1712_splitncnn_1 1713 +Convolution Conv_1011 1 1 1713 1715 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_1013 5 1 1704_splitncnn_1 1706_splitncnn_0 1709_splitncnn_0 1712_splitncnn_0 1715 1716 +Convolution Conv_1014 1 1 1716 1717 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1017 2 1 1717 1704_splitncnn_0 1720 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_1020 2 1 1720 1672_splitncnn_0 1723 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_240 1 7 1723 1723_splitncnn_0 1723_splitncnn_1 1723_splitncnn_2 1723_splitncnn_3 1723_splitncnn_4 1723_splitncnn_5 1723_splitncnn_6 +Convolution Conv_1021 1 1 1723_splitncnn_6 1725 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_241 1 4 1725 1725_splitncnn_0 1725_splitncnn_1 1725_splitncnn_2 1725_splitncnn_3 +Concat Concat_1023 2 1 1723_splitncnn_5 1725_splitncnn_3 1726 +Convolution Conv_1024 1 1 1726 1728 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_242 1 3 1728 1728_splitncnn_0 1728_splitncnn_1 1728_splitncnn_2 +Concat Concat_1026 3 1 1723_splitncnn_4 1725_splitncnn_2 1728_splitncnn_2 1729 +Convolution Conv_1027 1 1 1729 1731 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_243 1 2 1731 1731_splitncnn_0 1731_splitncnn_1 +Concat Concat_1029 4 1 1723_splitncnn_3 1725_splitncnn_1 1728_splitncnn_1 1731_splitncnn_1 1732 +Convolution Conv_1030 1 1 1732 1734 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_1032 5 1 1723_splitncnn_2 1725_splitncnn_0 1728_splitncnn_0 1731_splitncnn_0 1734 1735 +Convolution Conv_1033 1 1 1735 1736 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1036 2 1 1736 1723_splitncnn_1 1739 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_244 1 6 1739 1739_splitncnn_0 1739_splitncnn_1 1739_splitncnn_2 1739_splitncnn_3 1739_splitncnn_4 1739_splitncnn_5 +Convolution Conv_1037 1 1 1739_splitncnn_5 1741 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_245 1 4 1741 1741_splitncnn_0 1741_splitncnn_1 1741_splitncnn_2 1741_splitncnn_3 +Concat Concat_1039 2 1 1739_splitncnn_4 1741_splitncnn_3 1742 +Convolution Conv_1040 1 1 1742 1744 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_246 1 3 1744 1744_splitncnn_0 1744_splitncnn_1 1744_splitncnn_2 +Concat Concat_1042 3 1 1739_splitncnn_3 1741_splitncnn_2 1744_splitncnn_2 1745 +Convolution Conv_1043 1 1 1745 1747 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_247 1 2 1747 1747_splitncnn_0 1747_splitncnn_1 +Concat Concat_1045 4 1 1739_splitncnn_2 1741_splitncnn_1 1744_splitncnn_1 1747_splitncnn_1 1748 +Convolution Conv_1046 1 1 1748 1750 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_1048 5 1 1739_splitncnn_1 1741_splitncnn_0 1744_splitncnn_0 1747_splitncnn_0 1750 1751 +Convolution Conv_1049 1 1 1751 1752 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1052 2 1 1752 1739_splitncnn_0 1755 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_248 1 6 1755 1755_splitncnn_0 1755_splitncnn_1 1755_splitncnn_2 1755_splitncnn_3 1755_splitncnn_4 1755_splitncnn_5 +Convolution Conv_1053 1 1 1755_splitncnn_5 1757 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_249 1 4 1757 1757_splitncnn_0 1757_splitncnn_1 1757_splitncnn_2 1757_splitncnn_3 +Concat Concat_1055 2 1 1755_splitncnn_4 1757_splitncnn_3 1758 +Convolution Conv_1056 1 1 1758 1760 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_250 1 3 1760 1760_splitncnn_0 1760_splitncnn_1 1760_splitncnn_2 +Concat Concat_1058 3 1 1755_splitncnn_3 1757_splitncnn_2 1760_splitncnn_2 1761 +Convolution Conv_1059 1 1 1761 1763 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_251 1 2 1763 1763_splitncnn_0 1763_splitncnn_1 +Concat Concat_1061 4 1 1755_splitncnn_2 1757_splitncnn_1 1760_splitncnn_1 1763_splitncnn_1 1764 +Convolution Conv_1062 1 1 1764 1766 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_1064 5 1 1755_splitncnn_1 1757_splitncnn_0 1760_splitncnn_0 1763_splitncnn_0 1766 1767 +Convolution Conv_1065 1 1 1767 1768 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1068 2 1 1768 1755_splitncnn_0 1771 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_1071 2 1 1771 1723_splitncnn_0 1774 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_252 1 7 1774 1774_splitncnn_0 1774_splitncnn_1 1774_splitncnn_2 1774_splitncnn_3 1774_splitncnn_4 1774_splitncnn_5 1774_splitncnn_6 +Convolution Conv_1072 1 1 1774_splitncnn_6 1776 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_253 1 4 1776 1776_splitncnn_0 1776_splitncnn_1 1776_splitncnn_2 1776_splitncnn_3 +Concat Concat_1074 2 1 1774_splitncnn_5 1776_splitncnn_3 1777 +Convolution Conv_1075 1 1 1777 1779 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_254 1 3 1779 1779_splitncnn_0 1779_splitncnn_1 1779_splitncnn_2 +Concat Concat_1077 3 1 1774_splitncnn_4 1776_splitncnn_2 1779_splitncnn_2 1780 +Convolution Conv_1078 1 1 1780 1782 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_255 1 2 1782 1782_splitncnn_0 1782_splitncnn_1 +Concat Concat_1080 4 1 1774_splitncnn_3 1776_splitncnn_1 1779_splitncnn_1 1782_splitncnn_1 1783 +Convolution Conv_1081 1 1 1783 1785 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_1083 5 1 1774_splitncnn_2 1776_splitncnn_0 1779_splitncnn_0 1782_splitncnn_0 1785 1786 +Convolution Conv_1084 1 1 1786 1787 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1087 2 1 1787 1774_splitncnn_1 1790 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_256 1 6 1790 1790_splitncnn_0 1790_splitncnn_1 1790_splitncnn_2 1790_splitncnn_3 1790_splitncnn_4 1790_splitncnn_5 +Convolution Conv_1088 1 1 1790_splitncnn_5 1792 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_257 1 4 1792 1792_splitncnn_0 1792_splitncnn_1 1792_splitncnn_2 1792_splitncnn_3 +Concat Concat_1090 2 1 1790_splitncnn_4 1792_splitncnn_3 1793 +Convolution Conv_1091 1 1 1793 1795 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_258 1 3 1795 1795_splitncnn_0 1795_splitncnn_1 1795_splitncnn_2 +Concat Concat_1093 3 1 1790_splitncnn_3 1792_splitncnn_2 1795_splitncnn_2 1796 +Convolution Conv_1094 1 1 1796 1798 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_259 1 2 1798 1798_splitncnn_0 1798_splitncnn_1 +Concat Concat_1096 4 1 1790_splitncnn_2 1792_splitncnn_1 1795_splitncnn_1 1798_splitncnn_1 1799 +Convolution Conv_1097 1 1 1799 1801 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_1099 5 1 1790_splitncnn_1 1792_splitncnn_0 1795_splitncnn_0 1798_splitncnn_0 1801 1802 +Convolution Conv_1100 1 1 1802 1803 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1103 2 1 1803 1790_splitncnn_0 1806 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_260 1 6 1806 1806_splitncnn_0 1806_splitncnn_1 1806_splitncnn_2 1806_splitncnn_3 1806_splitncnn_4 1806_splitncnn_5 +Convolution Conv_1104 1 1 1806_splitncnn_5 1808 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_261 1 4 1808 1808_splitncnn_0 1808_splitncnn_1 1808_splitncnn_2 1808_splitncnn_3 +Concat Concat_1106 2 1 1806_splitncnn_4 1808_splitncnn_3 1809 +Convolution Conv_1107 1 1 1809 1811 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_262 1 3 1811 1811_splitncnn_0 1811_splitncnn_1 1811_splitncnn_2 +Concat Concat_1109 3 1 1806_splitncnn_3 1808_splitncnn_2 1811_splitncnn_2 1812 +Convolution Conv_1110 1 1 1812 1814 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_263 1 2 1814 1814_splitncnn_0 1814_splitncnn_1 +Concat Concat_1112 4 1 1806_splitncnn_2 1808_splitncnn_1 1811_splitncnn_1 1814_splitncnn_1 1815 +Convolution Conv_1113 1 1 1815 1817 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_1115 5 1 1806_splitncnn_1 1808_splitncnn_0 1811_splitncnn_0 1814_splitncnn_0 1817 1818 +Convolution Conv_1116 1 1 1818 1819 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1119 2 1 1819 1806_splitncnn_0 1822 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_1122 2 1 1822 1774_splitncnn_0 1825 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_264 1 7 1825 1825_splitncnn_0 1825_splitncnn_1 1825_splitncnn_2 1825_splitncnn_3 1825_splitncnn_4 1825_splitncnn_5 1825_splitncnn_6 +Convolution Conv_1123 1 1 1825_splitncnn_6 1827 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_265 1 4 1827 1827_splitncnn_0 1827_splitncnn_1 1827_splitncnn_2 1827_splitncnn_3 +Concat Concat_1125 2 1 1825_splitncnn_5 1827_splitncnn_3 1828 +Convolution Conv_1126 1 1 1828 1830 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_266 1 3 1830 1830_splitncnn_0 1830_splitncnn_1 1830_splitncnn_2 +Concat Concat_1128 3 1 1825_splitncnn_4 1827_splitncnn_2 1830_splitncnn_2 1831 +Convolution Conv_1129 1 1 1831 1833 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_267 1 2 1833 1833_splitncnn_0 1833_splitncnn_1 +Concat Concat_1131 4 1 1825_splitncnn_3 1827_splitncnn_1 1830_splitncnn_1 1833_splitncnn_1 1834 +Convolution Conv_1132 1 1 1834 1836 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_1134 5 1 1825_splitncnn_2 1827_splitncnn_0 1830_splitncnn_0 1833_splitncnn_0 1836 1837 +Convolution Conv_1135 1 1 1837 1838 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1138 2 1 1838 1825_splitncnn_1 1841 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_268 1 6 1841 1841_splitncnn_0 1841_splitncnn_1 1841_splitncnn_2 1841_splitncnn_3 1841_splitncnn_4 1841_splitncnn_5 +Convolution Conv_1139 1 1 1841_splitncnn_5 1843 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_269 1 4 1843 1843_splitncnn_0 1843_splitncnn_1 1843_splitncnn_2 1843_splitncnn_3 +Concat Concat_1141 2 1 1841_splitncnn_4 1843_splitncnn_3 1844 +Convolution Conv_1142 1 1 1844 1846 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_270 1 3 1846 1846_splitncnn_0 1846_splitncnn_1 1846_splitncnn_2 +Concat Concat_1144 3 1 1841_splitncnn_3 1843_splitncnn_2 1846_splitncnn_2 1847 +Convolution Conv_1145 1 1 1847 1849 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_271 1 2 1849 1849_splitncnn_0 1849_splitncnn_1 +Concat Concat_1147 4 1 1841_splitncnn_2 1843_splitncnn_1 1846_splitncnn_1 1849_splitncnn_1 1850 +Convolution Conv_1148 1 1 1850 1852 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_1150 5 1 1841_splitncnn_1 1843_splitncnn_0 1846_splitncnn_0 1849_splitncnn_0 1852 1853 +Convolution Conv_1151 1 1 1853 1854 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1154 2 1 1854 1841_splitncnn_0 1857 0=1 -23301=2,2.000000e-01,1.000000e+00 +Split splitncnn_272 1 6 1857 1857_splitncnn_0 1857_splitncnn_1 1857_splitncnn_2 1857_splitncnn_3 1857_splitncnn_4 1857_splitncnn_5 +Convolution Conv_1155 1 1 1857_splitncnn_5 1859 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01 +Split splitncnn_273 1 4 1859 1859_splitncnn_0 1859_splitncnn_1 1859_splitncnn_2 1859_splitncnn_3 +Concat Concat_1157 2 1 1857_splitncnn_4 1859_splitncnn_3 1860 +Convolution Conv_1158 1 1 1860 1862 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01 +Split splitncnn_274 1 3 1862 1862_splitncnn_0 1862_splitncnn_1 1862_splitncnn_2 +Concat Concat_1160 3 1 1857_splitncnn_3 1859_splitncnn_2 1862_splitncnn_2 1863 +Convolution Conv_1161 1 1 1863 1865 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Split splitncnn_275 1 2 1865 1865_splitncnn_0 1865_splitncnn_1 +Concat Concat_1163 4 1 1857_splitncnn_2 1859_splitncnn_1 1862_splitncnn_1 1865_splitncnn_1 1866 +Convolution Conv_1164 1 1 1866 1868 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01 +Concat Concat_1166 5 1 1857_splitncnn_1 1859_splitncnn_0 1862_splitncnn_0 1865_splitncnn_0 1868 1869 +Convolution Conv_1167 1 1 1869 1870 0=64 1=3 4=1 5=1 6=110592 +Eltwise Add_1170 2 1 1870 1857_splitncnn_0 1873 0=1 -23301=2,2.000000e-01,1.000000e+00 +Eltwise Add_1173 2 1 1873 1825_splitncnn_0 1876 0=1 -23301=2,2.000000e-01,1.000000e+00 +Convolution Conv_1174 1 1 1876 1877 0=64 1=3 4=1 5=1 6=36864 +BinaryOp Add_1175 2 1 703_splitncnn_0 1877 1878 +Interp Resize_1177 1 1 1878 1883 0=1 1=2.000000e+00 2=2.000000e+00 +Convolution Conv_1178 1 1 1883 1885 0=64 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Interp Resize_1181 1 1 1885 1890 0=1 1=2.000000e+00 2=2.000000e+00 +Convolution Conv_1182 1 1 1890 1892 0=64 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Convolution Conv_1184 1 1 1892 1894 0=64 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01 +Convolution Conv_1186 1 1 1894 output 0=3 1=3 4=1 5=1 6=1728 diff --git a/resources/package/Improve/ouput/00000-1616097796-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png b/resources/package/Improve/ouput/00000-1616097796-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png new file mode 100644 index 0000000..83ea84b Binary files /dev/null and b/resources/package/Improve/ouput/00000-1616097796-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png differ diff --git a/resources/package/Improve/output/00000-1616097796-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png b/resources/package/Improve/output/00000-1616097796-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png new file mode 100644 index 0000000..d5da84c Binary files /dev/null and b/resources/package/Improve/output/00000-1616097796-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png differ diff --git a/resources/package/Improve/output/00001-1083368712-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png b/resources/package/Improve/output/00001-1083368712-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png new file mode 100644 index 0000000..7a318d7 Binary files /dev/null and b/resources/package/Improve/output/00001-1083368712-(masterpiece, best quality, a young man emperor with a strong and handsome face domineering and majestic long black hair wearing.png differ diff --git a/resources/package/Improve/output/00002-763798052-(masterpiece, best quality, a group of courtiers in fancy robes faced the young emperor in a chinese-style palace, and the minis.png b/resources/package/Improve/output/00002-763798052-(masterpiece, best quality, a group of courtiers in fancy robes faced the young emperor in a chinese-style palace, and the minis.png new file mode 100644 index 0000000..82dbdbc Binary files /dev/null and b/resources/package/Improve/output/00002-763798052-(masterpiece, best quality, a group of courtiers in fancy robes faced the young emperor in a chinese-style palace, and the minis.png differ diff --git a/resources/package/Improve/output/00003-2561272579-(masterpiece, best quality,a young face stalwart handsome domineering majestic long black hair wearing a gorgeous robe of a man.png b/resources/package/Improve/output/00003-2561272579-(masterpiece, best quality,a young face stalwart handsome domineering majestic long black hair wearing a gorgeous robe of a man.png new file mode 100644 index 0000000..b7029ac Binary files /dev/null and b/resources/package/Improve/output/00003-2561272579-(masterpiece, best quality,a young face stalwart handsome domineering majestic long black hair wearing a gorgeous robe of a man.png differ diff --git a/resources/package/Improve/output/00004-1447729042-(masterpiece, best quality, a young face stalwart handsome domineering majestic long black hair wearing gorgeous robes of a man.png b/resources/package/Improve/output/00004-1447729042-(masterpiece, best quality, a young face stalwart handsome domineering majestic long black hair wearing gorgeous robes of a man.png new file mode 100644 index 0000000..5d9c20f Binary files /dev/null and b/resources/package/Improve/output/00004-1447729042-(masterpiece, best quality, a young face stalwart handsome domineering majestic long black hair wearing gorgeous robes of a man.png differ diff --git a/resources/package/Improve/rnv.exe b/resources/package/Improve/rnv.exe new file mode 100644 index 0000000..e8d351c Binary files /dev/null and b/resources/package/Improve/rnv.exe differ diff --git a/resources/package/Improve/vcomp140.dll b/resources/package/Improve/vcomp140.dll new file mode 100644 index 0000000..9568cee Binary files /dev/null and b/resources/package/Improve/vcomp140.dll differ diff --git a/resources/package/Improve/vcomp140d.dll b/resources/package/Improve/vcomp140d.dll new file mode 100644 index 0000000..69ef200 Binary files /dev/null and b/resources/package/Improve/vcomp140d.dll differ diff --git a/resources/package/exittool/exiftool.exe b/resources/package/exittool/exiftool.exe new file mode 100644 index 0000000..d1529c0 Binary files /dev/null and b/resources/package/exittool/exiftool.exe differ diff --git a/resources/scripts/xiangbei_jianying_main.exe b/resources/scripts/xiangbei_jianying_main.exe new file mode 100644 index 0000000..8680fe8 Binary files /dev/null and b/resources/scripts/xiangbei_jianying_main.exe differ diff --git a/resources/tmp/Clip/canvases_tmp.json b/resources/tmp/Clip/canvases_tmp.json new file mode 100644 index 0000000..dcedab1 --- /dev/null +++ b/resources/tmp/Clip/canvases_tmp.json @@ -0,0 +1,12 @@ +{ + "album_image": "", + "blur": 0.0, + "color": "", + "id": "11E13EB0-8293-4aad-AE6C-906B269D852C", + "image": "", + "image_id": "", + "image_name": "", + "source_platform": 0, + "team_id": "", + "type": "canvas_color" +} \ No newline at end of file diff --git a/resources/tmp/Clip/keyframe_tmp.json b/resources/tmp/Clip/keyframe_tmp.json new file mode 100644 index 0000000..a1d7f3f --- /dev/null +++ b/resources/tmp/Clip/keyframe_tmp.json @@ -0,0 +1,38 @@ +{ + "id": "01FEF79C-875B-4215-B35B-686620E4FC5F", + "keyframe_list": [ + { + "curveType": "Line", + "graphID": "", + "left_control": { + "x": 0.0, + "y": 0.0 + }, + "right_control": { + "x": 0.0, + "y": 0.0 + }, + "time_offset": 0, + "values": [ + 1.0 + ] + }, + { + "curveType": "Line", + "graphID": "", + "left_control": { + "x": 0.0, + "y": 0.0 + }, + "right_control": { + "x": 0.0, + "y": 0.0 + }, + "time_offset": 8550000, + "values": [ + 1.5 + ] + } + ], + "property_type": "KFTypeScaleX" +} \ No newline at end of file diff --git a/resources/tmp/Clip/material_animations_tmp.json b/resources/tmp/Clip/material_animations_tmp.json new file mode 100644 index 0000000..fda18c9 --- /dev/null +++ b/resources/tmp/Clip/material_animations_tmp.json @@ -0,0 +1,5 @@ +{ + "animations": [], + "id": "D4838202-E39A-4e74-A2D2-B3C16C9EF587", + "type": "sticker_animation" +} diff --git a/resources/tmp/Clip/material_text_temp.json b/resources/tmp/Clip/material_text_temp.json new file mode 100644 index 0000000..eaefd19 --- /dev/null +++ b/resources/tmp/Clip/material_text_temp.json @@ -0,0 +1,112 @@ +{ + "add_type": 0, + "alignment": 1, + "background_alpha": 1.0, + "background_color": "", + "background_height": 0.14, + "background_horizontal_offset": 0.0, + "background_round_radius": 0.0, + "background_style": 0, + "background_vertical_offset": 0.0, + "background_width": 0.14, + "bold_width": 0.0, + "border_alpha": 1.0, + "border_color": "", + "border_width": 0.08, + "caption_template_info": { + "category_id": "", + "category_name": "", + "effect_id": "", + "resource_id": "", + "resource_name": "" + }, + "check_flag": 7, + "combo_info": { + "text_templates": [] + }, + "content": "{\"text\":\"251525234\"}", + "fixed_height": -1.0, + "fixed_width": -1.0, + "font_category_id": "", + "font_category_name": "", + "font_id": "", + "font_name": "", + "font_path": "", + "font_resource_id": "", + "font_size": 7, + "font_source_platform": 0, + "font_team_id": "", + "font_title": "none", + "font_url": "", + "fonts": [{ + "category_id": "", + "category_name": "", + "effect_id": "6740435892441190919", + "file_uri": "", + "id": "", + "path": "", + "request_id": "", + "resource_id": "6740435892441190919", + "source_platform": 0, + "team_id": "", + "title": "" + }], + "force_apply_line_max_width": false, + "global_alpha": 1.0, + "group_id": "", + "has_shadow": false, + "id": "5B91D2ED-4A1A-40e5-87BE-0713ECDA74BA", + "initial_scale": 1.0, + "is_rich_text": false, + "italic_degree": 0, + "ktv_color": "", + "language": "", + "layer_weight": 1, + "letter_spacing": 0.0, + "line_feed": 1, + "line_max_width": 0.82, + "line_spacing": 0.02, + "name": "", + "original_size": [], + "preset_category": "", + "preset_category_id": "", + "preset_has_set_alignment": false, + "preset_id": "", + "preset_index": 0, + "preset_name": "", + "recognize_task_id": "", + "recognize_type": 0, + "relevance_segment": [], + "shadow_alpha": 0.8, + "shadow_angle": -45.0, + "shadow_color": "", + "shadow_distance": 8.0, + "shadow_point": { + "x": 1.0182337649086284, + "y": -1.0182337649086284 + }, + "shadow_smoothing": 1.0, + "shape_clip_x": false, + "shape_clip_y": false, + "style_name": "", + "sub_type": 0, + "subtitle_keywords": null, + "text_alpha": 1.0, + "text_color": "#FFFFFF", + "text_curve": null, + "text_preset_resource_id": "", + "text_size": 30, + "text_to_audio_ids": [], + "tts_auto_update": false, + "type": "subtitle", + "typesetting": 0, + "underline": false, + "underline_offset": 0.22, + "underline_width": 0.05, + "use_effect_default_color": false, + "words": { + "end_time": [], + "start_time": [], + "text": [] + } +} diff --git a/resources/tmp/Clip/materials_audios_tmp.json b/resources/tmp/Clip/materials_audios_tmp.json new file mode 100644 index 0000000..cba93fb --- /dev/null +++ b/resources/tmp/Clip/materials_audios_tmp.json @@ -0,0 +1,35 @@ +{ + "app_id": 0, + "category_id": "", + "category_name": "", + "check_flag": 1, + "duration": 864600000, + "effect_id": "", + "formula_id": "", + "id": "B3C79950-E268-4ea8-80FF-D749FF93E4DE", + "intensifies_path": "", + "is_ai_clone_tone": false, + "is_ugc": false, + "local_material_id": "", + "music_id": "", + "name": "1705933173091_7a573b60-2af6-4b43-a3ef-46c715300468.wav", + "path": "D:/AI 推文/QQ/无辜领证/1705933173091_7a573b60-2af6-4b43-a3ef-46c715300468.wav", + "query": "", + "request_id": "", + "resource_id": "", + "search_id": "", + "source_platform": 0, + "team_id": "", + "text_id": "", + "tone_category_id": "", + "tone_category_name": "", + "tone_effect_id": "", + "tone_effect_name": "", + "tone_second_category_id": "", + "tone_second_category_name": "", + "tone_speaker": "", + "tone_type": "", + "type": "extract_music", + "video_id": "", + "wave_points": [] +} diff --git a/resources/tmp/Clip/materials_beats_tmp.json b/resources/tmp/Clip/materials_beats_tmp.json new file mode 100644 index 0000000..8c2ae5a --- /dev/null +++ b/resources/tmp/Clip/materials_beats_tmp.json @@ -0,0 +1,20 @@ +{ + "ai_beats": { + "beat_speed_infos": [], + "beats_path": "", + "beats_url": "", + "melody_path": "", + "melody_percents": [ + 0.0 + ], + "melody_url": "" + }, + "enable_ai_beats": false, + "gear": 404, + "gear_count": 0, + "id": "252D7F03-8B76-4774-AC5F-43FF9A7A0303", + "mode": 404, + "type": "beats", + "user_beats": [], + "user_delete_ai_beats": null +} diff --git a/resources/tmp/Clip/sound_channel_mappings_tmp.json b/resources/tmp/Clip/sound_channel_mappings_tmp.json new file mode 100644 index 0000000..10fe074 --- /dev/null +++ b/resources/tmp/Clip/sound_channel_mappings_tmp.json @@ -0,0 +1,6 @@ +{ + "audio_channel_mapping": 0, + "id": "0873DEFB-B955-407a-893E-E063225859E7", + "is_config_open": false, + "type": "none" +} \ No newline at end of file diff --git a/resources/tmp/Clip/speeds_tmp.json b/resources/tmp/Clip/speeds_tmp.json new file mode 100644 index 0000000..5a45004 --- /dev/null +++ b/resources/tmp/Clip/speeds_tmp.json @@ -0,0 +1,7 @@ +{ + "curve_speed": null, + "id": "DF733765-6514-445e-BFF2-EA5079DDC564", + "mode": 0, + "speed": 1.0, + "type": "speed" +} \ No newline at end of file diff --git a/resources/tmp/Clip/track_text_segments_temp.json b/resources/tmp/Clip/track_text_segments_temp.json new file mode 100644 index 0000000..765f4f8 --- /dev/null +++ b/resources/tmp/Clip/track_text_segments_temp.json @@ -0,0 +1,63 @@ +{ + "cartoon": false, + "clip": { + "alpha": 1.0, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0.0, + "scale": { + "x": 1.0, + "y": 1.0 + }, + "transform": { + "x": 0.0, + "y": 0.0 + } + }, + "common_keyframes": [], + "enable_adjust": false, + "enable_color_curves": true, + "enable_color_match_adjust": false, + "enable_color_wheels": true, + "enable_lut": false, + "enable_smart_color_adjust": false, + "extra_material_refs": [ + "D4838202-E39A-4e74-A2D2-B3C16C9EF587" + ], + "group_id": "", + "hdr_settings": null, + "id": "F130A2DC-B22A-42ba-970D-38BDEE08F2AE", + "intensifies_audio": false, + "is_placeholder": false, + "is_tone_modify": false, + "keyframe_refs": [], + "last_nonzero_volume": 1.0, + "material_id": "5B91D2ED-4A1A-40e5-87BE-0713ECDA74BA", + "render_index": 14002, + "responsive_layout": { + "enable": false, + "horizontal_pos_layout": 0, + "size_layout": 0, + "target_follow": "", + "vertical_pos_layout": 0 + }, + "reverse": false, + "source_timerange": null, + "speed": 1.0, + "target_timerange": { + "duration": 3000000, + "start": 0 + }, + "template_id": "", + "template_scene": "default", + "track_attribute": 0, + "track_render_index": 0, + "uniform_scale": { + "on": true, + "value": 1.0 + }, + "visible": true, + "volumn": 1.0 +} diff --git a/resources/tmp/Clip/tracks_audio_segments_tmp.json b/resources/tmp/Clip/tracks_audio_segments_tmp.json new file mode 100644 index 0000000..9c90d4d --- /dev/null +++ b/resources/tmp/Clip/tracks_audio_segments_tmp.json @@ -0,0 +1,51 @@ +{ + "cartoon": false, + "clip": null, + "common_keyframes": [], + "enable_adjust": false, + "enable_color_curves": true, + "enable_color_match_adjust": false, + "enable_color_wheels": true, + "enable_lut": false, + "enable_smart_color_adjust": false, + "extra_material_refs": [ + "45268E0D-9136-43e2-939E-5F2C23E3569A", + "252D7F03-8B76-4774-AC5F-43FF9A7A0303", + "2629569D-3A20-41ba-A41A-AD617BA9E404", + "8D5BF8F3-6AF8-4bb3-84B7-04E3A86CE300" + ], + "group_id": "", + "hdr_settings": null, + "id": "A210B2A7-ED82-43c1-B159-9D849290593E", + "intensifies_audio": false, + "is_placeholder": false, + "is_tone_modify": false, + "keyframe_refs": [], + "last_nonzero_volume": 1.0, + "material_id": "B3C79950-E268-4ea8-80FF-D749FF93E4DE", + "render_index": 0, + "responsive_layout": { + "enable": false, + "horizontal_pos_layout": 0, + "size_layout": 0, + "target_follow": "", + "vertical_pos_layout": 0 + }, + "reverse": false, + "source_timerange": { + "duration": 864600000, + "start": 0 + }, + "speed": 1.0, + "target_timerange": { + "duration": 864600000, + "start": 0 + }, + "template_id": "", + "template_scene": "default", + "track_attribute": 0, + "track_render_index": 0, + "uniform_scale": null, + "visible": true, + "volumn": 1.0 +} diff --git a/resources/tmp/Clip/tracks_segments_tmp.json b/resources/tmp/Clip/tracks_segments_tmp.json new file mode 100644 index 0000000..1f2f15c --- /dev/null +++ b/resources/tmp/Clip/tracks_segments_tmp.json @@ -0,0 +1,73 @@ +{ + "cartoon": false, + "clip": { + "alpha": 1.0, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0.0, + "scale": { + "x": 1.0, + "y": 1.0 + }, + "transform": { + "x": 0.0, + "y": 0.0 + } + }, + "common_keyframes": [], + "enable_adjust": true, + "enable_color_curves": true, + "enable_color_match_adjust": false, + "enable_color_wheels": true, + "enable_lut": true, + "enable_smart_color_adjust": false, + "extra_material_refs": [ + "DF733765-6514-445e-BFF2-EA5079DDC564", + "11E13EB0-8293-4aad-AE6C-906B269D852C", + "0873DEFB-B955-407a-893E-E063225859E7", + "4DD7DFFE-CFD9-4e0b-8890-56C7450677EA" + ], + "group_id": "", + "hdr_settings": { + "intensity": 1.0, + "mode": 1, + "nits": 1000 + }, + "id": "2B8043D9-1EEB-48da-A136-B38F3816611E", + "intensifies_audio": false, + "is_placeholder": false, + "is_tone_modify": false, + "keyframe_refs": [], + "last_nonzero_volume": 1.0, + "material_id": "7BE6192E-90BC-421f-AE0D-2323D81008D8", + "render_index": 0, + "responsive_layout": { + "enable": false, + "horizontal_pos_layout": 0, + "size_layout": 0, + "target_follow": "", + "vertical_pos_layout": 0 + }, + "reverse": false, + "source_timerange": { + "duration": 5000000, + "start": 0 + }, + "speed": 1.0, + "target_timerange": { + "duration": 5000000, + "start": 0 + }, + "template_id": "", + "template_scene": "default", + "track_attribute": 0, + "track_render_index": 0, + "uniform_scale": { + "on": true, + "value": 1.0 + }, + "visible": true, + "volumn": 1.0 +} \ No newline at end of file diff --git a/resources/tmp/Clip/tracks_type_tmp.json b/resources/tmp/Clip/tracks_type_tmp.json new file mode 100644 index 0000000..d6b3fb0 --- /dev/null +++ b/resources/tmp/Clip/tracks_type_tmp.json @@ -0,0 +1,9 @@ +{ + "attribute": 0, + "flag": 0, + "id": "3F5F37A4-C6DF-4d51-9C68-11F472889B1D", + "is_default_name": true, + "name": "", + "segments": [], + "type": "video" +} diff --git a/resources/tmp/Clip/videoMaterialTemp.json b/resources/tmp/Clip/videoMaterialTemp.json new file mode 100644 index 0000000..fb96ead --- /dev/null +++ b/resources/tmp/Clip/videoMaterialTemp.json @@ -0,0 +1,78 @@ +{ + "aigc_type": "none", + "audio_fade": null, + "cartoon_path": "", + "category_id": "", + "category_name": "", + "check_flag": 63487, + "crop": { + "lower_left_x": 0.0, + "lower_left_y": 1.0, + "lower_right_x": 1.0, + "lower_right_y": 1.0, + "upper_left_x": 0.0, + "upper_left_y": 0.0, + "upper_right_x": 1.0, + "upper_right_y": 0.0 + }, + "crop_ratio": "free", + "crop_scale": 1.0, + "duration": 10800000000, + "extra_type_option": 0, + "formula_id": "", + "freeze": null, + "gameplay": null, + "has_audio": false, + "height": 1000, + "id": "7BE6192E-90BC-421f-AE0D-2323D81008D8", + "intensifies_audio_path": "", + "intensifies_path": "", + "is_ai_generate_content": false, + "is_unified_beauty_mode": false, + "local_id": "", + "local_material_id": "", + "material_id": "", + "material_name": "00001.png", + "material_url": "", + "matting": { + "flag": 0, + "has_use_quick_brush": false, + "has_use_quick_eraser": false, + "interactiveTime": [], + "path": "", + "strokes": [] + }, + "media_path": "", + "object_locked": null, + "origin_material_id": "", + "path": "D:/AI 推文/番茄小说/如画室友/03/tmp/input_crop/00001.png", + "picture_from": "none", + "picture_set_category_id": "", + "picture_set_category_name": "", + "request_id": "", + "reverse_intensifies_path": "", + "reverse_path": "", + "smart_motion": null, + "source": 0, + "source_platform": 0, + "stable": { + "matrix_path": "", + "stable_level": 0, + "time_range": { + "duration": 0, + "start": 0 + } + }, + "team_id": "", + "type": "photo", + "video_algorithm": { + "algorithms": [], + "deflicker": null, + "motion_blur_config": null, + "noise_reduction": null, + "path": "", + "quality_enhance": null, + "time_range": null + }, + "width": 1000 +} \ No newline at end of file diff --git a/resources/tmp/Clip/vocal_separations_tmp.json b/resources/tmp/Clip/vocal_separations_tmp.json new file mode 100644 index 0000000..d557681 --- /dev/null +++ b/resources/tmp/Clip/vocal_separations_tmp.json @@ -0,0 +1,7 @@ +{ + "choice": 0, + "id": "4DD7DFFE-CFD9-4e0b-8890-56C7450677EA", + "production_path": "", + "time_range": null, + "type": "vocal_separation" +} \ No newline at end of file diff --git a/resources/tmp/config/clip_setting.json b/resources/tmp/config/clip_setting.json new file mode 100644 index 0000000..fe5b380 --- /dev/null +++ b/resources/tmp/config/clip_setting.json @@ -0,0 +1 @@ +{"text_style":[{"name":"无","id":"0","style":[],"font_size":7,"fonts":"新青年体","style_name":"黄字黑边","clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":1,"y":1},"transform":{"x":0,"y":-0.8333333333333334}},"ratio":"4:3"},{"name":"随机","id":"1","style":[],"font_size":7,"fonts":"新青年体","style_name":"黄字黑边","clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":1,"y":1},"transform":{"x":0,"y":-0.8333333333333334}},"ratio":"4:3"},{"name":"test3","id":"8d5b03e5-552a-485f-852f-b3df99e3fc25","style":[{"fill":{"content":{"solid":{"color":[1,0.870588,0]}}},"font":{"path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.1.0.11009/Resources/Font/新青年体.ttf","id":"6740435892441190919"},"strokes":[{"content":{"solid":{"color":[0,0,0]}},"width":0.08}],"size":7,"range":[0,4]}],"font_size":7,"fonts":"新青年体","style_name":"黄字黑边","clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":1,"y":1},"transform":{"x":0,"y":-0.8333333333333334}},"ratio":"4:3"},{"name":"金陵体_黑字白边","id":"2074d84c-7ac1-455d-a1ef-4d4453bb0ef7","style":[{"bold":true,"fill":{"content":{"render_type":"solid","solid":{"alpha":1,"color":[0,0,0]}}},"font":{"id":"7086699209738424840","path":"C:/Users/27698/AppData/Local/JianyingPro/User Data/Cache/effect/1698067/599831579b8aa5b5f0608d5e7d4ec5ce/FZCuJinLJW.ttf"},"range":[0,4],"size":7,"strokes":[{"alpha":1,"content":{"render_type":"solid","solid":{"alpha":1,"color":[1,1,1]}},"width":0.08}],"useLetterColor":true}],"font_size":7,"fonts":"金陵体","style_name":"","clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":1,"y":1},"transform":{"x":0,"y":-0.8333333333333334}},"ratio":"4:3"}],"background_music_setting":[],"friendly_reminder_setting":[{"id":"0","name":"无","material_animations":{},"texts":{},"tracks":{},"text_value":"无"},{"id":"1","name":"随机","material_animations":{},"texts":{},"tracks":{},"text_value":"下面的数据随机生成(不包含这条啊)"},{"id":"f4862857-1db6-428f-88e5-53473609fa18","name":"虚拟内容 请勿模仿","material_animations":{"animations":[],"id":"B1AFE531-0139-4beb-A950-23AFFA5B889C","type":"sticker_animation"},"texts":{"add_type":0,"alignment":1,"background_alpha":1,"background_color":"","background_height":0.14,"background_horizontal_offset":0,"background_round_radius":0,"background_style":0,"background_vertical_offset":0,"background_width":0.14,"bold_width":0,"border_alpha":1,"border_color":"","border_width":0.08,"caption_template_info":{"category_id":"","category_name":"","effect_id":"","resource_id":"","resource_name":""},"check_flag":7,"combo_info":{"text_templates":[]},"content":"{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,9],\"size\":15.0}],\"text\":\"虚拟内容\\n请勿模仿\"}","fixed_height":-1,"fixed_width":-1,"font_category_id":"","font_category_name":"","font_id":"","font_name":"","font_path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf","font_resource_id":"","font_size":15,"font_source_platform":0,"font_team_id":"","font_title":"none","font_url":"","fonts":[],"force_apply_line_max_width":false,"global_alpha":1,"group_id":"","has_shadow":false,"id":"83F79AF1-A749-4aa8-B059-F11108F70A6E","initial_scale":1,"is_rich_text":false,"italic_degree":0,"ktv_color":"","language":"","layer_weight":1,"letter_spacing":0,"line_feed":1,"line_max_width":0.82,"line_spacing":0.02,"name":"","original_size":[],"preset_category":"","preset_category_id":"","preset_has_set_alignment":false,"preset_id":"","preset_index":0,"preset_name":"","recognize_task_id":"","recognize_type":0,"relevance_segment":[],"shadow_alpha":0.8,"shadow_angle":-45,"shadow_color":"","shadow_distance":8,"shadow_point":{"x":1.0182337649086284,"y":-1.0182337649086284},"shadow_smoothing":1,"shape_clip_x":false,"shape_clip_y":false,"style_name":"","sub_type":0,"subtitle_keywords":null,"text_alpha":1,"text_color":"#ffffff","text_curve":null,"text_preset_resource_id":"","text_size":30,"text_to_audio_ids":[],"tts_auto_update":false,"type":"text","typesetting":0,"underline":false,"underline_offset":0.22,"underline_width":0.05,"use_effect_default_color":true,"words":{"end_time":[],"start_time":[],"text":[]}},"tracks":{"attribute":0,"flag":0,"id":"CC71B371-480D-424f-8980-546C68EAEB6F","is_default_name":true,"name":"","segments":[{"cartoon":false,"clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":0.39049561306183955,"y":0.39049561306183955},"transform":{"x":0.8192161820480404,"y":0.8473861720067454}},"common_keyframes":[],"enable_adjust":false,"enable_color_curves":true,"enable_color_match_adjust":false,"enable_color_wheels":true,"enable_lut":false,"enable_smart_color_adjust":false,"extra_material_refs":["B1AFE531-0139-4beb-A950-23AFFA5B889C"],"group_id":"","hdr_settings":null,"id":"5AC3F57F-92E5-4249-AA7F-F3D7D84F3759","intensifies_audio":false,"is_placeholder":false,"is_tone_modify":false,"keyframe_refs":[],"last_nonzero_volume":1,"material_id":"83F79AF1-A749-4aa8-B059-F11108F70A6E","render_index":14000,"responsive_layout":{"enable":false,"horizontal_pos_layout":0,"size_layout":0,"target_follow":"","vertical_pos_layout":0},"reverse":false,"source_timerange":null,"speed":1,"target_timerange":{"duration":3000000,"start":0},"template_id":"","template_scene":"default","track_attribute":0,"track_render_index":0,"uniform_scale":{"on":true,"value":1},"visible":true,"volume":1}],"type":"text"},"text_value":"虚拟内容\n请勿模仿"},{"id":"78f7cbcf-bbe5-4f08-9b8c-747d2e582ab8","name":"虚拟小说内容 请勿模仿","material_animations":{"animations":[],"id":"B0B9928C-5394-4af8-83B1-FB124F86C3CF","type":"sticker_animation"},"texts":{"add_type":0,"alignment":1,"background_alpha":1,"background_color":"","background_height":0.14,"background_horizontal_offset":0,"background_round_radius":0,"background_style":0,"background_vertical_offset":0,"background_width":0.14,"bold_width":0,"border_alpha":1,"border_color":"","border_width":0.08,"caption_template_info":{"category_id":"","category_name":"","effect_id":"","resource_id":"","resource_name":""},"check_flag":7,"combo_info":{"text_templates":[]},"content":"{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,11],\"size\":15.0}],\"text\":\"虚拟小说内容 请勿模仿\"}","fixed_height":-1,"fixed_width":-1,"font_category_id":"","font_category_name":"","font_id":"","font_name":"","font_path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf","font_resource_id":"","font_size":15,"font_source_platform":0,"font_team_id":"","font_title":"none","font_url":"","fonts":[],"force_apply_line_max_width":false,"global_alpha":1,"group_id":"","has_shadow":false,"id":"3D8C3AC4-2926-4b69-B1A7-ED0031B5EE47","initial_scale":1,"is_rich_text":false,"italic_degree":0,"ktv_color":"","language":"","layer_weight":1,"letter_spacing":0,"line_feed":1,"line_max_width":0.82,"line_spacing":0.02,"name":"","original_size":[],"preset_category":"","preset_category_id":"","preset_has_set_alignment":false,"preset_id":"","preset_index":0,"preset_name":"","recognize_task_id":"","recognize_type":0,"relevance_segment":[],"shadow_alpha":0.8,"shadow_angle":-45,"shadow_color":"","shadow_distance":8,"shadow_point":{"x":1.0182337649086284,"y":-1.0182337649086284},"shadow_smoothing":1,"shape_clip_x":false,"shape_clip_y":false,"style_name":"","sub_type":0,"subtitle_keywords":null,"text_alpha":1,"text_color":"#ffffff","text_curve":null,"text_preset_resource_id":"","text_size":30,"text_to_audio_ids":[],"tts_auto_update":false,"type":"text","typesetting":0,"underline":false,"underline_offset":0.22,"underline_width":0.05,"use_effect_default_color":true,"words":{"end_time":[],"start_time":[],"text":[]}},"tracks":{"attribute":0,"flag":0,"id":"C6D7DD2A-8431-4fee-93D2-7E503C847BD7","is_default_name":true,"name":"","segments":[{"cartoon":false,"clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":0.3341015354334309,"y":0.3341015354334309},"transform":{"x":0.663716814159292,"y":0.918212478920742}},"common_keyframes":[],"enable_adjust":false,"enable_color_curves":true,"enable_color_match_adjust":false,"enable_color_wheels":true,"enable_lut":false,"enable_smart_color_adjust":false,"extra_material_refs":["B0B9928C-5394-4af8-83B1-FB124F86C3CF"],"group_id":"","hdr_settings":null,"id":"A40D04ED-C36A-45bf-97D8-D574EBC74B04","intensifies_audio":false,"is_placeholder":false,"is_tone_modify":false,"keyframe_refs":[],"last_nonzero_volume":1,"material_id":"3D8C3AC4-2926-4b69-B1A7-ED0031B5EE47","render_index":14002,"responsive_layout":{"enable":false,"horizontal_pos_layout":0,"size_layout":0,"target_follow":"","vertical_pos_layout":0},"reverse":false,"source_timerange":null,"speed":1,"target_timerange":{"duration":3000000,"start":0},"template_id":"","template_scene":"default","track_attribute":0,"track_render_index":0,"uniform_scale":{"on":true,"value":1},"visible":true,"volume":1}],"type":"text"},"text_value":"虚拟小说内容 请勿模仿"},{"id":"b093fac1-06c5-4293-8fd8-76cf07e2aa42","name":"故事纯属虚构,请勿模仿","material_animations":{"animations":[],"id":"AD6528B5-8A30-4856-959A-9DDD508A7774","type":"sticker_animation"},"texts":{"add_type":0,"alignment":1,"background_alpha":1,"background_color":"","background_height":0.14,"background_horizontal_offset":0,"background_round_radius":0,"background_style":0,"background_vertical_offset":0,"background_width":0.14,"bold_width":0,"border_alpha":1,"border_color":"","border_width":0.08,"caption_template_info":{"category_id":"","category_name":"","effect_id":"","resource_id":"","resource_name":""},"check_flag":7,"combo_info":{"text_templates":[]},"content":"{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"6740435892441190919\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/新青年体.ttf\"},\"range\":[0,11],\"size\":15.0}],\"text\":\"故事纯属虚构,请勿模仿\"}","fixed_height":-1,"fixed_width":-1,"font_category_id":"","font_category_name":"","font_id":"","font_name":"","font_path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/新青年体.ttf","font_resource_id":"6740435892441190919","font_size":15,"font_source_platform":0,"font_team_id":"","font_title":"none","font_url":"","fonts":[],"force_apply_line_max_width":false,"global_alpha":1,"group_id":"","has_shadow":false,"id":"54E1F74E-FC64-4795-94D3-B29B5071CDAD","initial_scale":1,"is_rich_text":false,"italic_degree":0,"ktv_color":"","language":"","layer_weight":1,"letter_spacing":0,"line_feed":1,"line_max_width":0.82,"line_spacing":0.02,"name":"","original_size":[],"preset_category":"","preset_category_id":"","preset_has_set_alignment":false,"preset_id":"","preset_index":0,"preset_name":"","recognize_task_id":"","recognize_type":0,"relevance_segment":[],"shadow_alpha":0.8,"shadow_angle":-45,"shadow_color":"","shadow_distance":8,"shadow_point":{"x":1.0182337649086284,"y":-1.0182337649086284},"shadow_smoothing":1,"shape_clip_x":false,"shape_clip_y":false,"style_name":"","sub_type":0,"subtitle_keywords":null,"text_alpha":1,"text_color":"#ffffff","text_curve":null,"text_preset_resource_id":"","text_size":30,"text_to_audio_ids":[],"tts_auto_update":false,"type":"text","typesetting":0,"underline":false,"underline_offset":0.22,"underline_width":0.05,"use_effect_default_color":true,"words":{"end_time":[],"start_time":[],"text":[]}},"tracks":{"attribute":0,"flag":0,"id":"783E0C53-B36E-4a7b-A880-F05E758FD2DB","is_default_name":true,"name":"","segments":[{"cartoon":false,"clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":0.2860491929811744,"y":0.2860491929811744},"transform":{"x":0.7124542124542124,"y":0.9087947882736158}},"common_keyframes":[],"enable_adjust":false,"enable_color_curves":true,"enable_color_match_adjust":false,"enable_color_wheels":true,"enable_lut":false,"enable_smart_color_adjust":false,"extra_material_refs":["AD6528B5-8A30-4856-959A-9DDD508A7774"],"group_id":"","hdr_settings":null,"id":"A5557348-B228-4222-BDA7-2798283A0F68","intensifies_audio":false,"is_placeholder":false,"is_tone_modify":false,"keyframe_refs":[],"last_nonzero_volume":1,"material_id":"54E1F74E-FC64-4795-94D3-B29B5071CDAD","render_index":14002,"responsive_layout":{"enable":false,"horizontal_pos_layout":0,"size_layout":0,"target_follow":"","vertical_pos_layout":0},"reverse":false,"source_timerange":null,"speed":1,"target_timerange":{"duration":3000000,"start":0},"template_id":"","template_scene":"default","track_attribute":0,"track_render_index":0,"uniform_scale":{"on":true,"value":1},"visible":true,"volume":1}],"type":"text"},"text_value":"故事纯属虚构,请勿模仿"},{"id":"f0d739ad-2ef5-443f-a598-b58c6cab0a58","name":"AI生成画面 无不良引导","material_animations":{"animations":[],"id":"CFA0E8D8-1B2B-4b3a-8CF5-64A1A252D953","type":"sticker_animation"},"texts":{"add_type":0,"alignment":1,"background_alpha":1,"background_color":"","background_height":0.14,"background_horizontal_offset":0,"background_round_radius":0,"background_style":0,"background_vertical_offset":0,"background_width":0.14,"bold_width":0,"border_alpha":1,"border_color":"","border_width":0.08,"caption_template_info":{"category_id":"","category_name":"","effect_id":"","resource_id":"","resource_name":""},"check_flag":7,"combo_info":{"text_templates":[]},"content":"{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,12],\"size\":15.0,\"useLetterColor\":true}],\"text\":\"AI生成画面\\n无不良引导\"}","fixed_height":-1,"fixed_width":-1,"font_category_id":"","font_category_name":"","font_id":"","font_name":"","font_path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf","font_resource_id":"","font_size":15,"font_source_platform":0,"font_team_id":"","font_title":"none","font_url":"","fonts":[],"force_apply_line_max_width":false,"global_alpha":1,"group_id":"","has_shadow":false,"id":"1AE8EF5E-6380-4130-B164-709E2160BFD0","initial_scale":1,"is_rich_text":false,"italic_degree":0,"ktv_color":"","language":"","layer_weight":1,"letter_spacing":0,"line_feed":1,"line_max_width":0.82,"line_spacing":0.02,"name":"","original_size":[],"preset_category":"","preset_category_id":"","preset_has_set_alignment":false,"preset_id":"","preset_index":0,"preset_name":"","recognize_task_id":"","recognize_type":0,"relevance_segment":[],"shadow_alpha":0.8,"shadow_angle":-45,"shadow_color":"","shadow_distance":8,"shadow_point":{"x":1.0182337649086284,"y":-1.0182337649086284},"shadow_smoothing":1,"shape_clip_x":false,"shape_clip_y":false,"style_name":"","sub_type":0,"subtitle_keywords":null,"text_alpha":1,"text_color":"#ffffff","text_curve":null,"text_preset_resource_id":"","text_size":30,"text_to_audio_ids":[],"tts_auto_update":false,"type":"text","typesetting":0,"underline":false,"underline_offset":0.22,"underline_width":0.05,"use_effect_default_color":false,"words":{"end_time":[],"start_time":[],"text":[]}},"tracks":{"attribute":0,"flag":0,"id":"B468156C-B4B6-4949-91E3-FBDB56839C48","is_default_name":true,"name":"","segments":[{"cartoon":false,"clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":0.40831670575608614,"y":0.40831670575608614},"transform":{"x":0.8086015433073044,"y":0.8607584675000406}},"common_keyframes":[],"enable_adjust":false,"enable_color_curves":true,"enable_color_match_adjust":false,"enable_color_wheels":true,"enable_lut":false,"enable_smart_color_adjust":false,"extra_material_refs":["CFA0E8D8-1B2B-4b3a-8CF5-64A1A252D953"],"group_id":"","hdr_settings":null,"id":"ACEDF876-5665-4c6c-AC88-95B3AE49352E","intensifies_audio":false,"is_placeholder":false,"is_tone_modify":false,"keyframe_refs":[],"last_nonzero_volume":1,"material_id":"1AE8EF5E-6380-4130-B164-709E2160BFD0","render_index":14000,"responsive_layout":{"enable":false,"horizontal_pos_layout":0,"size_layout":0,"target_follow":"","vertical_pos_layout":0},"reverse":false,"source_timerange":null,"speed":1,"target_timerange":{"duration":3000000,"start":0},"template_id":"","template_scene":"default","track_attribute":0,"track_render_index":0,"uniform_scale":{"on":true,"value":1},"visible":true,"volume":1}],"type":"text"},"text_value":"AI生成画面\n无不良引导"},{"id":"b422ce35-d439-46d7-9b51-e0d21e72e99d","name":"小说内容 请勿模仿","material_animations":{"animations":[],"id":"96F35048-8837-43de-8E83-4F3C3683FBEC","type":"sticker_animation"},"texts":{"add_type":0,"alignment":1,"background_alpha":1,"background_color":"","background_height":0.14,"background_horizontal_offset":0,"background_round_radius":0,"background_style":0,"background_vertical_offset":0,"background_width":0.14,"bold_width":0,"border_alpha":1,"border_color":"","border_width":0.08,"caption_template_info":{"category_id":"","category_name":"","effect_id":"","resource_id":"","resource_name":""},"check_flag":7,"combo_info":{"text_templates":[]},"content":"{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,9],\"size\":15.0,\"useLetterColor\":true}],\"text\":\"小说内容\\n请勿模仿\"}","fixed_height":-1,"fixed_width":-1,"font_category_id":"","font_category_name":"","font_id":"","font_name":"","font_path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf","font_resource_id":"","font_size":15,"font_source_platform":0,"font_team_id":"","font_title":"none","font_url":"","fonts":[],"force_apply_line_max_width":false,"global_alpha":1,"group_id":"","has_shadow":false,"id":"92C492B0-944F-48e5-8C99-40FAB82B9648","initial_scale":1,"is_rich_text":false,"italic_degree":0,"ktv_color":"","language":"","layer_weight":1,"letter_spacing":0,"line_feed":1,"line_max_width":0.82,"line_spacing":0.02,"name":"","original_size":[],"preset_category":"","preset_category_id":"","preset_has_set_alignment":false,"preset_id":"","preset_index":0,"preset_name":"","recognize_task_id":"","recognize_type":0,"relevance_segment":[],"shadow_alpha":0.8,"shadow_angle":-45,"shadow_color":"","shadow_distance":8,"shadow_point":{"x":1.0182337649086284,"y":-1.0182337649086284},"shadow_smoothing":1,"shape_clip_x":false,"shape_clip_y":false,"style_name":"","sub_type":0,"subtitle_keywords":null,"text_alpha":1,"text_color":"#ffffff","text_curve":null,"text_preset_resource_id":"","text_size":30,"text_to_audio_ids":[],"tts_auto_update":false,"type":"text","typesetting":0,"underline":false,"underline_offset":0.22,"underline_width":0.05,"use_effect_default_color":false,"words":{"end_time":[],"start_time":[],"text":[]}},"tracks":{"attribute":0,"flag":0,"id":"DAB33387-452D-4597-9A10-932E1FC1B47E","is_default_name":true,"name":"","segments":[{"cartoon":false,"clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":0.40831670575608614,"y":0.40831670575608614},"transform":{"x":0.8238636363636365,"y":0.8535353535353536}},"common_keyframes":[],"enable_adjust":false,"enable_color_curves":true,"enable_color_match_adjust":false,"enable_color_wheels":true,"enable_lut":false,"enable_smart_color_adjust":false,"extra_material_refs":["96F35048-8837-43de-8E83-4F3C3683FBEC"],"group_id":"","hdr_settings":null,"id":"D31C287B-9E51-475a-8843-37BFC7C07CF9","intensifies_audio":false,"is_placeholder":false,"is_tone_modify":false,"keyframe_refs":[],"last_nonzero_volume":1,"material_id":"92C492B0-944F-48e5-8C99-40FAB82B9648","render_index":14006,"responsive_layout":{"enable":false,"horizontal_pos_layout":0,"size_layout":0,"target_follow":"","vertical_pos_layout":0},"reverse":false,"source_timerange":null,"speed":1,"target_timerange":{"duration":3000000,"start":0},"template_id":"","template_scene":"default","track_attribute":0,"track_render_index":0,"uniform_scale":{"on":true,"value":1},"visible":true,"volume":1}],"type":"text"},"text_value":"小说内容\n请勿模仿"}],"key_frame":{"key_frame":"KFTypePositionY","isFixedSpeed":true,"key_frame_time":4,"up_down_key_frame":{"default_scale":133,"start_position":275,"end_position":275},"left_right_key_frame":{"default_scale":133,"start_position":275,"end_position":275},"scale_key_frame":{"default_scale":100,"start_position":210,"end_position":133}},"write_setting":{"split_char":"。,“”‘’!?【】「」《》()…—;,''\"\"!?[]<>()-:;╰*°▽°*╯′,ノ﹏<o‵゚Д゚,ノ,へ ̄工╬▔皿","merge_count":5,"merge_char":",","end_char":"。"}} \ No newline at end of file diff --git a/resources/tmp/config/global_setting.json b/resources/tmp/config/global_setting.json new file mode 100644 index 0000000..192a9a2 --- /dev/null +++ b/resources/tmp/config/global_setting.json @@ -0,0 +1,28 @@ +{ + "draft_path": "你的剪映草稿地址", + "project_path": "你的项目文件地址(存放图片视频等数据的文件夹)", + "project_name": "你的项目名字", + "gpt_business": "b44c6f24-59e4-4a71-b2c7-3df0c4e35e65", + "gpt_model": "gpt-3.5-turbo", + "task_number": 1, + "translation_business": "https://fanyi-api.baidu.com/api/trans/vip/translate", + "translation_app_id": "1234", + "translation_secret": "2234", + "translation_auto": true, + "theme": "light", + "gpt_auto_inference": "storyFirst", + "webui_api_url": "你的SD地址(后面要加/)", + "gpt_count": 8, + "customize_gpt_prompt": "a93b693e-bb3f-406d-9730-cba43a6585a2", + "character_select_model": "drop", + "image_generate_category": "mj", + "window_wh_bm_remember": false, + "window_wh_bm": { + "x": 1699, + "y": 230, + "width": 1936, + "height": 1048 + }, + "space_image": "C:\\Users\\27698\\Desktop\\LAITool\\resources\\image\\zhanwei.png", + "gpt_key": "gptkey" +} \ No newline at end of file diff --git a/resources/tmp/config/img_base.json b/resources/tmp/config/img_base.json new file mode 100644 index 0000000..ce802f5 --- /dev/null +++ b/resources/tmp/config/img_base.json @@ -0,0 +1,31 @@ +{ + "interactive_mode": "auto", + "webui_api_url": "", + "type": "img2img", + "seed": -1, + "resize": { + "scale": 1.0 + }, + "crop": 1, + "inpaint": false, + "mask_mode": "transparent-background-fast", + "mask_bg_mode": "#ffffff", + "tag": { + "enable": true, + "mode": "" + }, + "overlay": true, + "workspace": { + "input": "input", + "output": "output", + "tmp": { + "parent": "tmp", + "input_crop": "input_crop", + "output_crop": "output_crop", + "input_tag": "input_crop", + "input_mask": "input_mask", + "input_crop_mask": "input_crop_mask", + "crop_info": "crop_info.txt" + } + } + } \ No newline at end of file diff --git a/resources/tmp/config/sd_config.json b/resources/tmp/config/sd_config.json new file mode 100644 index 0000000..108c359 --- /dev/null +++ b/resources/tmp/config/sd_config.json @@ -0,0 +1,58 @@ +{ + "workspace": "C:\\Users\\27698\\Desktop\\测试", + "setting": { + "type": "txt2img", + "resize": { + "scale": 1 + }, + "crop": 1, + "webui_api_url": "", + "seed": -1, + "inpaint": false, + "mask_mode": "transparent-background", + "mask_bg_mode": "#ffffff", + "overlay": true, + "batch_size": 4 + }, + "tag": { + "enable": true, + "mode": "action", + "actions": [ + "closed eyes", + "closed mouth", + "from behind", + "smile", + "looking at viewer", + "side", + "upper body", + "covering mouth", + "covering face" + ], + "badPrompt": "chinese_text,japanese_text,korean_text,dark_skin,dark-skinned_male,yukata,kimono,large_tsukioka_kogane,collarbone,artist_name,open_clothes,bare_shoulders,subtitled,off_shoulder,hair_bun,single_hair_bun,hakama_skirt,hakama,black_hakama,katana,breasts,blush,hanfu,closed_mouth,closed_eyes,underwear,cleavage,bra,realistic,earrings,torii,strapless" + }, + "webui": { + "prompt": "", + "negative_prompt": "", + "denoising_strength": 1, + "cfg_scale": 5, + "sampler_name": "DPM++ 2M Karras", + "steps": 15, + "width": 800, + "height": 800, + "adetailer": true, + "batch_size": 1 + }, + "config": { + "setting": "img_base.json" + }, + "adetailer": [ + { + "ad_confidence": 0.7, + "ad_model": "face_yolov8n.pt" + }, + { + "ad_confidence": 0.7, + "ad_model": "hand_yolov8n.pt" + } + ] +} \ No newline at end of file diff --git a/resources/tmp/config/video_config.json b/resources/tmp/config/video_config.json new file mode 100644 index 0000000..8955ac3 --- /dev/null +++ b/resources/tmp/config/video_config.json @@ -0,0 +1 @@ +{"video_resolution_x":1440,"video_resolution_y":1080,"keyframe":{"up_down":175,"name":"KFTypeRandom","left_right":175},"frameRate":30,"bitRate":3000,"audioSoundSize":5,"backgroundMusicSoundSize":-15,"assConfig":[{"fontName":"造字工房力黑(非商用)常规体","fontSize":75,"fontColor":"#FFDF04","transparent":100,"positionX":720,"positionY":980,"id":"24e70f33-d35e-4abe-ad8f-76f04b79cab1"},{"fontName":"文悦新青年体 (非商用) W8","fontSize":75,"fontColor":"#FFDF04","transparent":100,"positionX":720,"positionY":980,"id":"93d2c0a1-0852-4792-afa2-018b461f6478"},{"fontName":"江西拙楷 常规","fontSize":75,"fontColor":"#FFDF04","transparent":100,"positionX":720,"positionY":980,"id":"03c921af-ae3c-457d-b8df-e06fceea526d"}],"watermarkConfig":[{"showText":"内容纯属虚构 请勿模仿","fontName":"江西拙楷 常规","fontSize":30,"fontColor":"#FFFFFF","transparent":60,"positionX":1250,"positionY":20,"id":"e7cf4e49-3773-43d3-9513-101bf688f5d1"},{"showText":"虚拟内容 请勿模仿","fontName":"文悦新青年体 (非商用) W8","fontSize":30,"fontColor":"#FFFFFF","transparent":60,"positionX":1250,"positionY":20,"id":"b0833501-1ab0-4684-a8b0-5828b7e5449f"},{"showText":"虚拟内容 请勿模仿","fontName":"造字工房力黑(非商用)常规体","fontSize":30,"fontColor":"#FFFFFF","transparent":60,"positionX":1250,"positionY":20,"id":"bbcb84a2-6761-4771-b5dc-a3c05a857cf1"}],"libx264":false,"offsetValue":300,"font_name_list":[{"label":"Nirmala UI","value":"Nirmala UI"},{"label":"DengXian Bold","value":"DengXian Bold"},{"label":"Verdana Bold","value":"Verdana Bold"},{"label":"Corbel Bold Italic","value":"Corbel Bold Italic"},{"label":"Segoe Print","value":"Segoe Print"},{"label":"Segoe UI Emoji","value":"Segoe UI Emoji"},{"label":"Small Fonts","value":"Small Fonts"},{"label":"Consolas Bold Italic","value":"Consolas Bold Italic"},{"label":"Segoe UI Black","value":"Segoe UI Black"},{"label":"Leelawadee UI Bold","value":"Leelawadee UI Bold"},{"label":"Trebuchet MS Italic","value":"Trebuchet MS Italic"},{"label":"DengXian Light","value":"DengXian Light"},{"label":"Segoe UI Historic","value":"Segoe UI Historic"},{"label":"Microsoft Himalaya","value":"Microsoft Himalaya"},{"label":"FangSong","value":"FangSong"},{"label":"Consolas","value":"Consolas"},{"label":"DengXian","value":"DengXian"},{"label":"Arial Black","value":"Arial Black"},{"label":"Microsoft JhengHei Bold & Microsoft JhengHei UI Bold","value":"Microsoft JhengHei Bold & Microsoft JhengHei UI Bold"},{"label":"Javanese Text","value":"Javanese Text"},{"label":"Courier 10,12,15","value":"Courier 10,12,15"},{"label":"Ebrima","value":"Ebrima"},{"label":"MS Gothic & MS UI Gothic & MS PGothic","value":"MS Gothic & MS UI Gothic & MS PGothic"},{"label":"Segoe UI Semibold Italic","value":"Segoe UI Semibold Italic"},{"label":"Arial Bold Italic","value":"Arial Bold Italic"},{"label":"Myanmar Text Bold","value":"Myanmar Text Bold"},{"label":"Palatino Linotype Italic","value":"Palatino Linotype Italic"},{"label":"Calibri Bold Italic","value":"Calibri Bold Italic"},{"label":"Georgia","value":"Georgia"},{"label":"Ink Free","value":"Ink Free"},{"label":"Courier New Bold Italic","value":"Courier New Bold Italic"},{"label":"SimSun-ExtB","value":"SimSun-ExtB"},{"label":"Comic Sans MS","value":"Comic Sans MS"},{"label":"Consolas Bold","value":"Consolas Bold"},{"label":"Times New Roman Italic","value":"Times New Roman Italic"},{"label":"Cambria & Cambria Math","value":"Cambria & Cambria Math"},{"label":"Yu Gothic Medium & Yu Gothic UI Regular","value":"Yu Gothic Medium & Yu Gothic UI Regular"},{"label":"Yu Gothic Bold & Yu Gothic UI Semibold & Yu Gothic UI Bold","value":"Yu Gothic Bold & Yu Gothic UI Semibold & Yu Gothic UI Bold"},{"label":"Trebuchet MS Bold Italic","value":"Trebuchet MS Bold Italic"},{"label":"Lucida Sans Unicode","value":"Lucida Sans Unicode"},{"label":"Microsoft New Tai Lue","value":"Microsoft New Tai Lue"},{"label":"KaiTi","value":"KaiTi"},{"label":"文悦新青年体 (非商用) W8","value":"文悦新青年体 (非商用) W8"},{"label":"Calibri Light Italic","value":"Calibri Light Italic"},{"label":"Georgia Bold Italic","value":"Georgia Bold Italic"},{"label":"Microsoft New Tai Lue Bold","value":"Microsoft New Tai Lue Bold"},{"label":"SimHei","value":"SimHei"},{"label":"Courier New Bold","value":"Courier New Bold"},{"label":"Microsoft Yi Baiti","value":"Microsoft Yi Baiti"},{"label":"Palatino Linotype Bold Italic","value":"Palatino Linotype Bold Italic"},{"label":"MS Serif 8,10,12,14,18,24","value":"MS Serif 8,10,12,14,18,24"},{"label":"DejaVu Math TeX Gyre","value":"DejaVu Math TeX Gyre"},{"label":"Wingdings","value":"Wingdings"},{"label":"Corbel Light","value":"Corbel Light"},{"label":"Segoe UI Italic","value":"Segoe UI Italic"},{"label":"Roman","value":"Roman"},{"label":"Microsoft Sans Serif","value":"Microsoft Sans Serif"},{"label":"Corbel Bold","value":"Corbel Bold"},{"label":"Microsoft YaHei Light & Microsoft YaHei UI Light","value":"Microsoft YaHei Light & Microsoft YaHei UI Light"},{"label":"Calibri Light","value":"Calibri Light"},{"label":"Ebrima Bold","value":"Ebrima Bold"},{"label":"MS Sans Serif 8,10,12,14,18,24","value":"MS Sans Serif 8,10,12,14,18,24"},{"label":"Constantia Italic","value":"Constantia Italic"},{"label":"Sylfaen","value":"Sylfaen"},{"label":"Calibri Bold","value":"Calibri Bold"},{"label":"Segoe MDL2 Assets","value":"Segoe MDL2 Assets"},{"label":"Trebuchet MS","value":"Trebuchet MS"},{"label":"Verdana Bold Italic","value":"Verdana Bold Italic"},{"label":"Calibri","value":"Calibri"},{"label":"Franklin Gothic Medium Italic","value":"Franklin Gothic Medium Italic"},{"label":"Comic Sans MS Bold Italic","value":"Comic Sans MS Bold Italic"},{"label":"Palatino Linotype","value":"Palatino Linotype"},{"label":"Tahoma","value":"Tahoma"},{"label":"Nirmala UI Bold","value":"Nirmala UI Bold"},{"label":"MV Boli","value":"MV Boli"},{"label":"Trebuchet MS Bold","value":"Trebuchet MS Bold"},{"label":"Leelawadee UI","value":"Leelawadee UI"},{"label":"Microsoft YaHei & Microsoft YaHei UI","value":"Microsoft YaHei & Microsoft YaHei UI"},{"label":"Sitka Text","value":"Sitka Text"},{"label":"Sans Serif Collection","value":"Sans Serif Collection"},{"label":"Arial Bold","value":"Arial Bold"},{"label":"Palatino Linotype Bold","value":"Palatino Linotype Bold"},{"label":"Georgia Bold","value":"Georgia Bold"},{"label":"Lucida Console","value":"Lucida Console"},{"label":"Segoe Script Bold","value":"Segoe Script Bold"},{"label":"Segoe Script","value":"Segoe Script"},{"label":"Corbel","value":"Corbel"},{"label":"Segoe Fluent Icons","value":"Segoe Fluent Icons"},{"label":"Bahnschrift","value":"Bahnschrift"},{"label":"Mongolian Baiti","value":"Mongolian Baiti"},{"label":"Segoe UI Light Italic","value":"Segoe UI Light Italic"},{"label":"Corbel Light Italic","value":"Corbel Light Italic"},{"label":"Cambria Italic","value":"Cambria Italic"},{"label":"SimSun & NSimSun","value":"SimSun & NSimSun"},{"label":"Corbel Italic","value":"Corbel Italic"},{"label":"Times New Roman","value":"Times New Roman"},{"label":"Consolas Italic","value":"Consolas Italic"},{"label":"Comic Sans MS Bold","value":"Comic Sans MS Bold"},{"label":"Modern","value":"Modern"},{"label":"Candara Bold","value":"Candara Bold"},{"label":"Myanmar Text","value":"Myanmar Text"},{"label":"Malgun Gothic SemiLight","value":"Malgun Gothic SemiLight"},{"label":"Candara Light","value":"Candara Light"},{"label":"Candara Bold Italic","value":"Candara Bold Italic"},{"label":"Leelawadee UI Semilight","value":"Leelawadee UI Semilight"},{"label":"Malgun Gothic","value":"Malgun Gothic"},{"label":"Segoe UI","value":"Segoe UI"},{"label":"Calibri Italic","value":"Calibri Italic"},{"label":"Comic Sans MS Italic","value":"Comic Sans MS Italic"},{"label":"Arial","value":"Arial"},{"label":"Cambria Bold Italic","value":"Cambria Bold Italic"},{"label":"Segoe Print Bold","value":"Segoe Print Bold"},{"label":"Courier New Italic","value":"Courier New Italic"},{"label":"Microsoft YaHei Bold & Microsoft YaHei UI Bold","value":"Microsoft YaHei Bold & Microsoft YaHei UI Bold"},{"label":"Franklin Gothic Medium","value":"Franklin Gothic Medium"},{"label":"Segoe UI Bold","value":"Segoe UI Bold"},{"label":"Constantia Bold","value":"Constantia Bold"},{"label":"Webdings","value":"Webdings"},{"label":"Sitka Text Italic","value":"Sitka Text Italic"},{"label":"Cambria Bold","value":"Cambria Bold"},{"label":"Malgun Gothic Bold","value":"Malgun Gothic Bold"},{"label":"Yu Gothic Regular & Yu Gothic UI Semilight","value":"Yu Gothic Regular & Yu Gothic UI Semilight"},{"label":"Georgia Italic","value":"Georgia Italic"},{"label":"Script","value":"Script"},{"label":"Segoe UI Black Italic","value":"Segoe UI Black Italic"},{"label":"Tahoma Bold","value":"Tahoma Bold"},{"label":"Nirmala UI Semilight","value":"Nirmala UI Semilight"},{"label":"Candara","value":"Candara"},{"label":"Symbol","value":"Symbol"},{"label":"Arial Italic","value":"Arial Italic"},{"label":"Segoe UI Symbol","value":"Segoe UI Symbol"},{"label":"Microsoft PhagsPa","value":"Microsoft PhagsPa"},{"label":"Verdana Italic","value":"Verdana Italic"},{"label":"Segoe UI Light","value":"Segoe UI Light"},{"label":"Yu Gothic Light & Yu Gothic UI Light","value":"Yu Gothic Light & Yu Gothic UI Light"},{"label":"Segoe UI Semibold","value":"Segoe UI Semibold"},{"label":"Constantia Bold Italic","value":"Constantia Bold Italic"},{"label":"Times New Roman Bold Italic","value":"Times New Roman Bold Italic"},{"label":"Segoe UI Bold Italic","value":"Segoe UI Bold Italic"},{"label":"Candara Light Italic","value":"Candara Light Italic"},{"label":"Gadugi Bold","value":"Gadugi Bold"},{"label":"Microsoft JhengHei & Microsoft JhengHei UI","value":"Microsoft JhengHei & Microsoft JhengHei UI"},{"label":"Impact","value":"Impact"},{"label":"Gadugi","value":"Gadugi"},{"label":"Microsoft Tai Le","value":"Microsoft Tai Le"},{"label":"Microsoft PhagsPa Bold","value":"Microsoft PhagsPa Bold"},{"label":"Constantia","value":"Constantia"},{"label":"MT Extra","value":"MT Extra"},{"label":"Microsoft JhengHei Light & Microsoft JhengHei UI Light","value":"Microsoft JhengHei Light & Microsoft JhengHei UI Light"},{"label":"江西拙楷 Regular","value":"江西拙楷 Regular"},{"label":"Gabriola","value":"Gabriola"},{"label":"Courier New","value":"Courier New"},{"label":"Verdana","value":"Verdana"},{"label":"Candara Italic","value":"Candara Italic"},{"label":"Segoe UI Variable","value":"Segoe UI Variable"},{"label":"Holo MDL2 Assets","value":"Holo MDL2 Assets"},{"label":"MingLiU-ExtB & PMingLiU-ExtB & MingLiU_HKSCS-ExtB","value":"MingLiU-ExtB & PMingLiU-ExtB & MingLiU_HKSCS-ExtB"},{"label":"Segoe UI Semilight","value":"Segoe UI Semilight"},{"label":"Segoe UI Semilight Italic","value":"Segoe UI Semilight Italic"},{"label":"造字工房力黑(非商用)常规体 Regular","value":"造字工房力黑(非商用)常规体 Regular"},{"label":"Microsoft Tai Le Bold","value":"Microsoft Tai Le Bold"},{"label":"Times New Roman Bold","value":"Times New Roman Bold"}]} \ No newline at end of file diff --git a/resources/tmp/jianyingTemp.zip b/resources/tmp/jianyingTemp.zip new file mode 100644 index 0000000..47b5bb7 Binary files /dev/null and b/resources/tmp/jianyingTemp.zip differ diff --git a/resources/tmp/temp.7z b/resources/tmp/temp.7z new file mode 100644 index 0000000..6822ace Binary files /dev/null and b/resources/tmp/temp.7z differ diff --git a/src/define/Tools/common.ts b/src/define/Tools/common.ts new file mode 100644 index 0000000..71abc7b --- /dev/null +++ b/src/define/Tools/common.ts @@ -0,0 +1,224 @@ +import { escapeRegExp, isEmpty } from 'lodash' +//#region 检查字符串中是不是包含中文或者标点符号 +/** + * 检查字符串中是不是包含中文或者标点符号 + * @param str 需要判断的字符串 + * @returns 返回的数据,有中文或者标点符号返回true,否则返回false + */ +export function ContainsChineseOrPunctuation(str: string): boolean { + return /[\u4e00-\u9fa5]|[\u3000-\u301e\u2013\u2014\u2018\u2019\u201c\u201d\u2026\u203b\uff08\uff09\uff1a\uff1b\uff1f\uff01\uff0c\u3001\uff0e\u3002\uff1f\uff01\u2018\u2019\u201c\u201d]/.test( + str + ) +} + +//#endregion + +//#region 通用的失败重试函数 +/** + * 通用的重试函数 + * @param fn 要执行的函数 + * @param retries 最大重试次数 + * @param delay 每次重试之间的延迟(毫秒) + * @returns 返回函数的结果 + */ +export async function RetryWithBackoff( + fn: () => Promise, + retries: number = 5, + delay: number = 2000 +): Promise { + let attempts = 0 + while (attempts < retries) { + try { + return await fn() + } catch (error: any) { + attempts++ + // 这边记下日志吧 + global.logger.error( + fn.name + '_RetryWithBackoff', + `第 ${attempts} 请求失败,开始下一次重试,失败信息如下:` + error.toString() + ) + if (attempts >= retries) { + throw new Error(`失败次数超过 ${retries} 错误信息如下: ${error.message}`) + } + await new Promise((resolve) => setTimeout(resolve, delay)) + } + } + throw new Error('所有重试失败') // 理论上不会到达这里 +} +//#endregion + +//#region 并发执行任务(控制同时执行的任务数) +/** + * 并发执行任务(控制同时执行的任务数) + * @param tasks 总的任务列表 + * @param concurrentCount 同时执行的数量 + * @returns + */ +export async function ExecuteConcurrently( + tasks: Array<() => Promise>, + concurrentCount: number +): Promise { + let activeTasks: Array> = [] + let results: Array> = [] + + while (tasks.length > 0) { + if (activeTasks.length < concurrentCount) { + let task: any = tasks.shift() + let promise = task() + .then((result) => { + activeTasks = activeTasks.filter((t) => t !== promise) + return result + }) + .catch((error) => { + // 抛出任务,停止所有的任务 + tasks.length = 0 + throw error + }) + activeTasks.push(promise) + results.push(promise) + } else { + await Promise.race(activeTasks) + } + } + return Promise.all(results) +} + +//#endregion + +//#region 替换主字符串中的子字符串 +/** + * 替换主字符串中的子字符串 + * @param mainString 主字符串 + * @param substringArray 子字符串数组 + * @returns 替换后的字符串 + */ +export function ReplaceSubstrings( + mainString: string, + substringArray: string[], + replacement: string = ' ' +): string { + // 按长度降序排序子字符串数组,以确保较长的子字符串先被替换 + substringArray.sort((a, b) => b.length - a.length) + + // 对每个子字符串进行替换 + for (const substring of substringArray) { + // 创建一个正则表达式,用于全局匹配子字符串 + const regex = new RegExp(escapeRegExp(substring), 'g') + // 将匹配到的子字符串替换为等长的空格 + mainString = mainString.replace(regex, replacement) + } + + return mainString +} + +//#endregion + +//#region 获取url的基础地址 + +/** + * 获取url的基础地址 + * @param url 一个url地址 + * @returns + */ +export function GetBaseUrl(url: string): string { + if (isEmpty(url)) { + throw new Error('url不能为空') + } + // 判断是不是一个合法的url + if (!url.startsWith('http')) { + throw new Error('一个合法的url请求地址') + } + const parsedUrl = new URL(url) + return `${parsedUrl.protocol}//${parsedUrl.host}` +} + +//#endregion + +//#region 通用的下载完网络图片 +/** + * 通用的下载完网络图片 + * @param url 网络地址 + * @param localPath 本地路径 + * @returns + */ +export async function DownloadFile(url: string, localPath?: string): Promise { + if (typeof window !== 'undefined' && window.document) { + // 浏览器环境 + const response = await fetch(url) + + if (!response.body) { + throw new Error('浏览器不支持流式下载') + } + + const reader = response.body.getReader() + // const contentLength = +response.headers.get('Content-Length')! + let receivedLength = 0 + const chunks: any[] = [] + + while (true) { + const { done, value } = await reader.read() + if (done) break + chunks.push(value) + receivedLength += value.length + // 可以在这里更新进度,比如: + // console.log(`已接收 ${receivedLength} / ${contentLength}`); + } + + const blob = new Blob(chunks) + const link = document.createElement('a') + link.href = window.URL.createObjectURL(blob) + link.download = localPath || 'downloaded_file' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + window.URL.revokeObjectURL(link.href) + } else { + // Node.js 环境 + const fs = require('fs') + const { pipeline } = require('stream') + const { promisify } = require('util') + const path = require('path') + const https = require('https') + const http = require('http') + const urlObj = new URL(url) + const filePath = localPath || path.basename(url) + const protocol = urlObj.protocol === 'https:' ? https : http + + const streamPipeline = promisify(pipeline) + + const response = await new Promise((resolve, reject) => { + protocol + .get(url, (res) => { + if (res.statusCode === 200) { + resolve(res) + } else { + reject(new Error(`请求失败,状态码:${res.statusCode}`)) + } + }) + .on('error', reject) + }) + + await streamPipeline(response, fs.createWriteStream(filePath)) + } +} +//#endregion + +//#region 通用文件操作,可以在node和浏览器环境中使用 + +/** + * 获取文件的basename,在所有的环境中都试用的 + * @param filePath + * @returns + */ +export function GetFileBasename(filePath: string): string { + if (isEmpty(filePath)) { + throw new Error('filePath is empty, must be a valid file path') + } + const baseName = filePath.split('/').pop()?.split('\\').pop() ?? '' + if (isEmpty(baseName)) { + throw new Error('filePath is empty, must be a valid file path') + } + return baseName +} + +//#endregion diff --git a/src/define/Tools/file.ts b/src/define/Tools/file.ts new file mode 100644 index 0000000..da240a7 --- /dev/null +++ b/src/define/Tools/file.ts @@ -0,0 +1,353 @@ +import fs from 'fs' +import { isEmpty } from 'lodash' +import path from 'path' +import util from 'util' +import { exec } from 'child_process' +const execAsync = util.promisify(exec) +const fspromises = fs.promises + +/** + * 判断文件或目录是否存在 + * @param {*} path 文件或目录的路径 + * @returns true表示存在,false表示不存在 + */ +export async function CheckFileOrDirExist(filePath) { + try { + let newFilePath = path.resolve(filePath) + await fspromises.access(newFilePath) + return true // 文件或目录存在 + } catch (error) { + return false // 文件或目录不存在 + } +} + +// 检查文件夹是不是存在,不存在的话,创建 +export async function CheckFolderExistsOrCreate(folderPath) { + try { + if (!(await CheckFileOrDirExist(folderPath))) { + await fspromises.mkdir(folderPath, { recursive: true }) + } + } catch (error) { + throw error + } +} + +/** + * 拼接两个地址,返回拼接后的地址 + * @param {*} rootPath 根目录的消息 + * @param {*} subPath 子目录的消息 + * @returns + */ +export function JoinPath(rootPath: string, subPath: string | null): string | undefined { + // 判断第二个地址是不是存在,不存在返回null,存在返回拼接后的地址 + if (subPath && !isEmpty(subPath)) { + return path.resolve(rootPath, subPath) + } else { + return undefined + } +} + +/** + * 删除指定的文件中里面所有的文件和文件夹 + * @param {*} folderPath 文件夹地址 + * @param {*} isDeleteOut 是否删除最外层的文件夹,默认false,不删除 + */ +export async function DeleteFolderAllFile( + folderPath: string, + isDeleteOut: boolean = false +): Promise { + try { + let folderIsExist = await CheckFileOrDirExist(folderPath) + if (!folderIsExist) { + throw new Error('目的文件夹不存在,' + folderPath) + } + // 开始删除 + let files = await fspromises.readdir(folderPath) + for (const file of files) { + const curPath = path.join(folderPath, file) + const stat = await fspromises.stat(curPath) + if (stat.isDirectory()) { + // 判断是不是文件夹 + await DeleteFolderAllFile(curPath) // 递归删除文件夹内容 + await fspromises.rmdir(curPath) // 删除空文件夹 + } else { + // 删除文件 + await fspromises.unlink(curPath) + } + } + // 判断是不是要删除最外部的文件夹 + if (isDeleteOut) { + await fspromises.rmdir(folderPath) + } + } catch (error) { + throw error + } +} + +/** + * 拷贝一个文件或者是文件夹到指定的目标地址 + * @param {*} source 源文件或文件夹地址 + * @param {*} target 目标文件或文件夹地址 + * @param {*} checkParent 是否检查父文件夹是否存在,不存在的话创建,默认false,不检查,不存在直接创建 + */ +export async function CopyFileOrFolder(source, target, checkParent = false) { + try { + // 判断源文件或文件夹是不是存在 + if (!(await CheckFileOrDirExist(source))) { + throw new Error(`源文件或文件夹不存在: ${source}`) + } + // 判断父文件夹是否存在,不存在创建 + const parent_path = path.dirname(target) + let parentIsExist = await CheckFileOrDirExist(parent_path) + if (!parentIsExist) { + if (checkParent) { + throw new Error(`目的文件或文件夹的父文件夹不存在: ${parent_path}`) + } else { + await fspromises.mkdir(parent_path, { recursive: true }) + } + } + + // 判断是不是文件夹 + const isDirectory = await IsDirectory(source) + // 复制文件夹的逻辑 + async function copyDirectory(source, target) { + // 创建目标文件夹 + await fspromises.mkdir(target, { recursive: true }) + let entries = await fspromises.readdir(source, { withFileTypes: true }) + for (let entry of entries) { + let srcPath = path.join(source, entry.name) + let tgtPath = path.join(target, entry.name) + + if (entry.isDirectory()) { + await copyDirectory(srcPath, tgtPath) + } else { + await fspromises.copyFile(srcPath, tgtPath) + } + } + } + + if (isDirectory) { + // 创建目标文件夹 + await copyDirectory(source, target) + } else { + // 复制文件 + await fspromises.copyFile(source, target) + } + } catch (error) { + throw error + } +} + +/** * 判断一个文件地址是不是文件夹 + * @param {*} path 输入的文件地址 + * @returns true 是 false 不是 + */ +export async function IsDirectory(path) { + try { + const stat = await fspromises.stat(path) + return stat.isDirectory() + } catch (error) { + throw new Error(`获取文件夹信息失败: ${path}`) + } +} +/** + * 将文件或者是文件夹备份到指定的文职 + * @param {*} source_path 源文件/文件夹地址 + * @param {*} target_path 目标文件/文件夹地址 + */ +export async function BackupFileOrFolder(source_path: string, target_path: string): Promise { + try { + // 判断父文件夹是否存在,不存在创建 + const parent_path = path.dirname(target_path) + if (!(await CheckFileOrDirExist(parent_path))) { + await fspromises.mkdir(parent_path, { recursive: true }) + } + + // 判断是不是文件夹 + const isDirectory = await IsDirectory(source_path) + + if (isDirectory) { + // 复制文件夹 + await fspromises.rename(source_path, target_path) + } else { + // 复制文件 + await fspromises.copyFile(source_path, target_path) + } + } catch (error) { + throw error + } +} + +/** + * 获取指定的文件夹下面的所有的指定的拓展名的文件 + * @param {*} folderPath 文件夹地址 + * @param {*} extensions 拓展地址 + * @returns 返回文件中指定的后缀文件地址(绝对地址) + */ +export async function GetFilesWithExtensions( + folderPath: string, + extensions: string[] +): Promise { + try { + // 判断当前是不是文件夹 + if (!(await IsDirectory(folderPath))) { + throw new Error('输入的不是有效的文件夹地址') + } + + let entries = await fspromises.readdir(folderPath, { withFileTypes: true }) + let files = [] as any + // 使用Promise.all来并行处理所有的stat调用 + const fileStats = await Promise.all( + entries.map(async (entry) => { + const entryPath = path.join(folderPath, entry.name) + if (entry.isFile()) { + return { + name: entry.name, + path: entryPath, + isFile: true + } + } else { + return { + isFile: false + } + } + }) + ) + + // 过滤出文件并且满足扩展名要求的文件 + files = fileStats.filter( + (fileStat) => + fileStat.isFile && extensions.includes(path.extname(fileStat.name ?? '').toLowerCase()) + ) + + // 对files数组进行排序,基于文件名 + files.sort((a: any, b: any) => a.name.localeCompare(b.name)) + + // 返回文件名数组(完整的) + return files.map((fileStat) => path.join(folderPath, fileStat.name)) + } catch (error) { + throw error + } +} + +/** + * 获取文件的大小 + * @param filePath 文件的地址 + * @returns 返回的文件大小为 kb单位 + */ +export async function GetFileSize(filePath: string): Promise { + try { + if (!(await CheckFileOrDirExist(filePath))) { + throw new Error('获取文件大小,指定的文件不存在') + } + const stats = await fspromises.stat(filePath) + return stats.size / 1024 + } catch (error) { + throw error + } +} + +/** + * 获取文件夹下的所有子文件夹信息,按创建时间排序 + * @param folderPath 文件夹的路径 + * @returns 返回包含子文件夹名称和完整路径的对象数组,按创建时间排序(最新的在前) + */ +export async function GetSubdirectoriesWithInfo(folderPath: string): Promise> { + try { + const filesAndDirectories = await fs.promises.readdir(folderPath, { withFileTypes: true }) + + // 过滤出文件夹 + const directories = filesAndDirectories.filter((dirent) => dirent.isDirectory()) + + // 并行获取所有文件夹的状态信息 + const directoryStatsPromises = directories.map((dirent) => + fs.promises.stat(path.join(folderPath, dirent.name)) + ) + const directoryStats = await Promise.all(directoryStatsPromises) + + // 将目录信息和状态对象组合 + const directoriesWithInfo = directories.map((dirent, index) => ({ + name: dirent.name, + fullPath: path.join(folderPath, dirent.name), + ctime: directoryStats[index].ctime + })) + + // 按创建时间排序,最新的在前 + directoriesWithInfo.sort((a, b) => b.ctime.getTime() - a.ctime.getTime()) + + return directoriesWithInfo + } catch (error) { + throw error + } +} + +/** + * 删除目标图片,然后将原图片的exif信息删除,然后将原图片复制到目标图片地址 + * @param {*} exiftoolPath exiftool的地址 + * @param {*} source 原图片地址 + * @param {*} target 目标图片地址 + */ +export async function DeleteFileExifData(exiftoolPath: string, source: string, target: string) { + try { + if (await CheckFileOrDirExist(target)) { + await fspromises.unlink(target) + } + let script = `"${exiftoolPath}" -all= -overwrite_original "${source}" -o "${target}"` + await execAsync(script, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' }) + } catch (error) { + throw error + } +} + +/** + * 下载网络图片到本地 + * + * 该方法从指定的URL下载图片文件,并将其保存到本地指定路径。 + * 如果目标文件夹不存在,会自动创建。如果指定路径已存在文件,则会覆盖。 + * + * @param {string} imageUrl - 图片的网络URL地址 + * @param {string} localPath - 保存到本地的完整路径,包含文件名和扩展名 + * @returns {Promise} 成功时返回保存的本地文件路径 + * @throws {Error} 当网络请求失败、写入失败或其他错误时抛出异常 + * + * @example + * // 下载图片到指定路径 + * try { + * const savedPath = await DownloadImageFromUrl( + * 'https://example.com/image.jpg', + * 'd:/images/downloaded.jpg' + * ); + * console.log('图片已保存至:', savedPath); + * } catch (error) { + * console.error('下载图片失败:', error.message); + * } + */ +export async function DownloadImageFromUrl(imageUrl: string, localPath: string): Promise { + try { + // 确保目标文件夹存在 + const dirPath = path.dirname(localPath) + await CheckFolderExistsOrCreate(dirPath) + + // 使用fetch获取图片数据 + const response = await fetch(imageUrl) + + if (!response.ok) { + throw new Error(`下载失败,HTTP状态码: ${response.status}`) + } + + // 获取图片的二进制数据 + const arrayBuffer = await response.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + + // 将图片数据写入本地文件 + await fspromises.writeFile(localPath, buffer) + + return localPath + } catch (error) { + if (error instanceof Error) { + throw new Error(`下载图片失败: ${error.message}`) + } else { + throw new Error('下载图片时发生未知错误') + } + } +} diff --git a/src/define/Tools/image.ts b/src/define/Tools/image.ts new file mode 100644 index 0000000..a8e73b1 --- /dev/null +++ b/src/define/Tools/image.ts @@ -0,0 +1,388 @@ +import path from 'path' +import sharp from 'sharp' +import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from './file' +import fs from 'fs' +import https from 'https' + +/** + * 将指定的图片的尺寸修改,返回修改后的图片数据(base64或buffer) + * @param {*} image_path + * @param {*} width + * @param {*} height + * @param {*} type + * @returns 返回修改后的图片数据(base64或buffer) + */ +export async function ResizeImage( + image_path: string, + width: number | sharp.ResizeOptions, + height: number, + type: string +) { + try { + // 检查 type 参数 + if (type !== 'base64' && type !== 'buffer') { + throw new Error('type 参数必须是 "base64" 或 "buffer"') + } + + // 判断是不是图片文件 + if (!image_path.match(/\.(jpg|jpeg|png)$/)) { + throw new Error('输入的文件地址不是图片文件地址') + } + + // 判断文件是否存在 + if (!(await CheckFileOrDirExist(image_path))) { + throw new Error('文件不存在') + } + + // 修改图片尺寸 + const image = sharp(image_path) + image.resize(width, height) + let data = await image.toBuffer() + if (type === 'base64') { + return data.toString('base64') + } else { + return data + } + } catch (error) { + throw error + } +} + +/** + * 获取指定图片文件的宽高 + * @param {*} image_path 图片文件的路径 + * @returns 返回以一个对象,包含width和height属性 + */ + +export async function GetImageSize(image_path: string) { + try { + // 判断文件是否存在 + if (!(await CheckFileOrDirExist(image_path))) { + throw new Error('文件不存在') + } + + // 判断是不是图片文件 + if (!image_path.match(/\.(jpg|jpeg|png)$/)) { + throw new Error('输入的文件地址不是图片文件地址') + } + + // 获取图片的宽高 + const metadata = await sharp(image_path).metadata() + return { + width: metadata.width, + height: metadata.height + } + } catch (error) { + throw error + } +} + +/** + * 根据文件扩展名获取MIME类型 + * @param filePath 文件路径 + * @returns MIME类型字符串 + */ +export function GetMimeType(filePath: string): string { + const extension = path.extname(filePath).toLowerCase() + const mimeTypes: { [key: string]: string } = { + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.webp': 'image/webp' + // 添加更多文件类型和对应的MIME类型 + } + return mimeTypes[extension] || 'application/octet-stream' +} + +/** + * 从base64字符串中获取图片类型和对应文件后缀 + * @param base64String - base64编码的图片数据 + * @returns 文件后缀(包括点号) + */ +export function GetImageTypeFromBase64(base64String: string): string { + // 默认后缀 + let extension = '.png' + + // 检查是否是base64格式的数据URI + if (typeof base64String !== 'string' || !base64String.startsWith('data:image/')) { + return extension // 不是有效的图片base64,返回默认后缀 + } + + try { + // 提取MIME类型 + const matches = base64String.match(/^data:image\/([a-zA-Z0-9+.-]+);base64,/) + + if (matches && matches.length > 1) { + const mimeSubtype = matches[1].toLowerCase() + + // 映射MIME子类型到文件后缀 + switch (mimeSubtype) { + case 'jpeg': + case 'jpg': + extension = '.jpg' + break + case 'png': + extension = '.png' + break + case 'gif': + extension = '.gif' + break + case 'webp': + extension = '.webp' + break + case 'svg+xml': + extension = '.svg' + break + case 'bmp': + extension = '.bmp' + break + case 'tiff': + extension = '.tiff' + break + default: + // 其他不常见格式,使用MIME子类型作为后缀 + extension = `.${mimeSubtype}` + } + } + } catch (error) { + console.error('解析base64图片类型时出错:', error) + } + + return extension +} + +/** + * 将本地文件地址或网络图片地址转换为包含MIME类型的base64字符串 + * @param url 本地文件路径或网络图片URL + * @returns Promise 返回一个Promise,解析为包含MIME类型的base64字符串 + */ +export function GetImageBase64(url: string): Promise { + if (!url) { + return Promise.reject('URL不能为空') + } + if (url.startsWith('http://') || url.startsWith('https://')) { + return new Promise((resolve, reject) => { + https + .get(url, (response) => { + const mimeType = response.headers['content-type'] || 'application/octet-stream' + const data: any[] = [] + response.on('data', (chunk) => data.push(chunk)) + response.on('end', () => { + const buffer = Buffer.concat(data) + const base64Data = `data:${mimeType};base64,${buffer.toString('base64')}` + resolve(base64Data) + }) + }) + .on('error', (err) => reject(err)) + }) + } else { + return new Promise((resolve, reject) => { + fs.readFile(url, (err, data) => { + if (err) { + reject(err) + } else { + const mimeType = GetMimeType(url) + const base64Data = `data:${mimeType};base64,${data.toString('base64')}` + resolve(base64Data) + } + }) + }) + } +} + +/** + * 压缩图片到指定的大小 + * @param base64 图片的Blob对象 + * @param maxSizeInBytes 最大文件大小,单位字节 + * @returns 返回一个Promise,解析为压缩后的Blob对象 + */ +export async function CompressImageToSize( + filePath: string, + maxSizeInBytes: number +): Promise { + let quality = 100 // 初始质量设置 + let outputBuffer + + const image = sharp(filePath) + // 输出图片的大小 + + // 迭代压缩过程 + while (true) { + outputBuffer = await image.jpeg({ quality }).toBuffer() + if (outputBuffer.length <= maxSizeInBytes || quality === 20) { + break + } + quality -= 5 // 每次迭代降低质量 + } + + return outputBuffer +} + +/** + * 生成图片蒙板 + * 将选中的区域涂白,其他区域涂黑(这个颜色可以变) + * @param inputPath 输入的文件路径 + * @param outputPath 输出的文件路径 + * @param regions 范围对象,包含x, y, width, height属性 + * @param markColor 标记颜色,默认为白色 { r: 255, g: 255, b: 255 } + * @param backColor 背景颜色,默认为黑色 { r: 0, g: 0, b: 0 } + */ +export async function ProcessImage( + inputPath: string, + outputPath: string, + regions: { width: any; height: any; x: any; y: any; imageWidth?: any; imageHeight?: any }[], + markColor: { r: number; g: number; b: number } = { r: 255, g: 255, b: 255 }, + backColor: { r: number; g: number; b: number } = { r: 0, g: 0, b: 0 } +): Promise { + try { + // 读取图片并进行处理 + const image = sharp(inputPath) + + // 获取图片的元数据 + const { width, height } = await image.metadata() + if (!width || !height) { + throw new Error('获取图片的宽高失败') + } + + const whiteBackground = await sharp() + .composite([ + { + input: { + create: { + width, + height, + channels: 3, + background: backColor + } + } + } + ]) + .png() + .toBuffer() + + // 创建多个白色的矩形,并进行合成 + const composites = await Promise.all( + regions.map(async (region) => { + // let rateW = 0 + let rateY = 0 + let rate = 0 + if (region.imageWidth != null && region.imageHeight != null) { + rateY = height / region.imageHeight + // rateW = width / region.imageWidth + rate = rateY + } + if (rate == null) { + rate = 1 + } + + const regionBuffer = await sharp({ + create: { + width: Math.ceil(region.width * rate), + height: Math.ceil(region.height * rate), + channels: 3, // RGB channels + background: markColor // 标记颜色 + } + }) + .png() + .toBuffer() + + return { + input: regionBuffer, + left: Math.ceil(region.x * rate), + top: Math.ceil(region.y * rate) + } + }) + ) + + // 在背景上叠加所有的矩形区域 + await sharp(whiteBackground).composite(composites).toFile(outputPath) + } catch (err) { + throw err + } +} + +/** + * 将图片的base64写到本地文件 + * @param base64 base64字符串 + * @param outFilePath 写出的文件路径 + */ +export async function Base64ToFile(base64: string, outFilePath: string): Promise { + try { + let base64Data = base64.replace(/^data:image\/\w+;base64,/, '') + let dataBuffer = Buffer.from(base64Data, 'base64') + let out_folder = path.dirname(outFilePath) + await CheckFolderExistsOrCreate(out_folder) + await fs.promises.writeFile(outFilePath, dataBuffer) + // await this.tools.writeArrayToFile(dataBuffer, out_file); + } catch (error: any) { + throw new Error('将base64转换为文件失败,失败信息如下:' + error.toString()) + } +} + +/** + * 将图片平均分割为四份(2×2网格) + * + * @param inputPath 输入的文件路径 + * @param reName 重命名的名字,用做新文件名的前缀 + * @param outputDir 输出的文件夹路径 + * @returns {Promise} 返回生成的四个图片文件的路径数组 + */ +export async function ImageSplit( + inputPath: string, + reName: string, + outputDir: string +): Promise { + try { + // 确保输出目录存在 + await CheckFolderExistsOrCreate(outputDir) + + // 获取图片元数据 + const metadata = await sharp(inputPath).metadata() + if (!metadata.height || !metadata.width) { + throw new Error('获取图片的宽高失败') + } + + // 计算每个分块的宽高,使用Math.floor确保不超出边界 + const smallWidth = Math.floor(metadata.width / 2) + const smallHeight = Math.floor(metadata.height / 2) + + // 计算最后一列和最后一行可能的额外宽度/高度(处理奇数像素) + const rightWidth = metadata.width - smallWidth + const bottomHeight = metadata.height - smallHeight + + const timestamp = new Date().getTime() + const imgs: string[] = [] + + for (let i = 0; i < 4; i++) { + // 确定当前分块的位置和尺寸 + const isRightColumn = i % 2 !== 0 + const isBottomRow = i >= 2 + + const xOffset = isRightColumn ? smallWidth : 0 + const yOffset = isBottomRow ? smallHeight : 0 + + // 使用实际宽高,确保右边和底部区块使用正确尺寸 + const blockWidth = isRightColumn ? rightWidth : smallWidth + const blockHeight = isBottomRow ? bottomHeight : smallHeight + + const outFile = path.join(outputDir, `${reName}_${timestamp}_${i}.png`) + + // 提取并保存分块 + await sharp(inputPath) + .extract({ + left: xOffset, + top: yOffset, + width: blockWidth, + height: blockHeight + }) + .toFile(outFile) + + imgs.push(outFile) + } + + return imgs + } catch (err) { + throw err + } +} diff --git a/src/define/Tools/index.ts b/src/define/Tools/index.ts new file mode 100644 index 0000000..8ed0196 --- /dev/null +++ b/src/define/Tools/index.ts @@ -0,0 +1,13 @@ +import * as image from './image'; +import * as common from './common'; +import * as file from './file' +import * as validate from './validate'; +import * as write from './write'; + +export { + image, + common, + file, + validate, + write +}; \ No newline at end of file diff --git a/src/define/Tools/logger.ts b/src/define/Tools/logger.ts new file mode 100644 index 0000000..8c8017d --- /dev/null +++ b/src/define/Tools/logger.ts @@ -0,0 +1,88 @@ +// const winston = require('winston'); +import winston from 'winston' +import path from 'path' +import DailyRotateFile from 'winston-daily-rotate-file' +import moment from 'moment-timezone' +import { define } from '../define' +import { DEFINE_STRING } from '../ipcDefineString' +import { LoggerLevel } from '../enum/logger' + +export class Logger { + log_folder: string + logger: winston.Logger + constructor() { + this.log_folder = define.log_folder + this.logger = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp({ + format: () => moment().tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss') + }), + winston.format.printf( + (info) => + `${new Date().toLocaleString()} [${info.level.toUpperCase()}] [${info.service}] ${info.message}` + ) + ), + transports: [ + new DailyRotateFile({ + filename: path.resolve(this.log_folder, `LAITool-%DATE%.log`), + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '10m', + maxFiles: '14d' + }) + ] + }) + + if (process.env.NODE_ENV !== 'production') { + this.logger.add( + new winston.transports.Console({ + format: winston.format.simple() + }) + ) + } + } + + sendLoggerToRenderer(level: LoggerLevel, message: string) { + // console.log(global) + global.wins[0].webContents.send(DEFINE_STRING.LOGGER.ADD_LOGGER, { + level: level, + message: message + }) + } + + /** + * 保存info级别的日志 + * @param {*} service service 名称 + * @param {*} message 消息 + */ + info(service, message) { + this.logger.info(message, { service }) + this.sendLoggerToRenderer(LoggerLevel.Info, `${message}`) + } + + /** + * 保存error级别的日志 + * @param {*} service service 名称 + * @param {*} message 消息 + */ + error(service, message) { + this.logger.error(message, { service }) + this.sendLoggerToRenderer(LoggerLevel.Error, `${message}`) + } + + /** + * 保存warn级别的日志 + * @param {*} service service 名称 + * @param {*} message 消息 + */ + warn(service, message) { + this.logger.warn(message, { service }) + this.sendLoggerToRenderer(LoggerLevel.Warn, `${message}`) + } + + success(service, message) { + this.logger.info(message, { service }) + this.sendLoggerToRenderer(LoggerLevel.Success, `${message}`) + } +} diff --git a/src/define/Tools/time.ts b/src/define/Tools/time.ts new file mode 100644 index 0000000..c4b2f96 --- /dev/null +++ b/src/define/Tools/time.ts @@ -0,0 +1,62 @@ +/** + * 将时间字符串转换为毫秒(number) + * 00:00:03.867 --》3867 + * @param {*} timeString 时间字符串 + * @returns + */ +export function TimeStringToMilliseconds(timeString) { + // 分割字符串获取小时、分钟、秒和毫秒 + const parts = timeString.split(/[:.]/) + const hours = parseInt(parts[0], 10) + const minutes = parseInt(parts[1], 10) + const seconds = parseInt(parts[2], 10) + const milliseconds = parseInt(parts[3], 10) + + // 将小时、分钟、秒转换为毫秒并计算总和 + return hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds +} + +/** + * 将毫秒转换为时间字符串 + * 85233 --》 '00:01:25.233' + * @param {*} milliseconds + * @returns + */ +export function MillisecondsToTimeString(milliseconds) { + let totalSeconds = milliseconds / 1000 + const hours = Math.floor(totalSeconds / 3600) + totalSeconds %= 3600 + const minutes = Math.floor(totalSeconds / 60) + const seconds = Math.floor(totalSeconds % 60) + const ms = milliseconds % 1000 + + // 将小时、分钟、秒格式化为两位数,毫秒格式化为三位数 + const hoursFormatted = hours.toString().padStart(2, '0') + const minutesFormatted = minutes.toString().padStart(2, '0') + const secondsFormatted = seconds.toString().padStart(2, '0') + const msFormatted = ms.toString().padStart(3, '0') + + let timeString = `${hoursFormatted}:${minutesFormatted}:${secondsFormatted}.${msFormatted}` + + // 使用正则表达式检测并删除多余的小数点 + // 此正则表达式查找除了第一个小数点之外的所有小数点,并将它们替换为空字符串 + timeString = timeString.replace(/(\.\d+)\./g, '$1') + return timeString +} + +/** + * 延时多少秒,返回一个Promise + * @param time 延时时间,单位毫秒 + * @returns viod + */ +export async function TimeDelay(time: number): Promise { + return new Promise((resolve) => setTimeout(resolve, time)) +} + +/** + * 获取当前时间的格式化字符串 yyyy-mm-dd hh:mm:ss + * @param now 当前时间对象 + * @returns 格式化后的时间字符串 + */ +export const formattedDate = (now: Date) => + `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}` diff --git a/src/define/Tools/validate.ts b/src/define/Tools/validate.ts new file mode 100644 index 0000000..5d0b84b --- /dev/null +++ b/src/define/Tools/validate.ts @@ -0,0 +1,56 @@ +/** + * 校验是不是可以进行JSON解析 + * @param str 要解析的字符串 + * @returns 可以解析返回true,否则返回false + */ +export function ValidateJson(str: string): boolean { + try { + JSON.parse(str) + return true + } catch (e) { + return false + } +} + +/** + * 校验并解析JSON字符串 + * @description 该函数会尝试解析传入的字符串,如果解析成功则返回解析后的对象,否则抛出错误 + * @param str 要解析的字符串 + * @returns 解析成功返回解析后的对象,否则报错 + */ +export function ValidateJsonAndParse(str: string): T { + try { + if (str == null) { + throw new Error('数据不能为空') + } + let res = JSON.parse(str) as T + return res + } catch (e) { + throw new Error('数据解析失败,请检查数据格式') + } +} + +interface ValidationErrorItem { + message?: string + [key: string]: any +} + +interface ValidationErrors { + [key: string]: ValidationErrorItem[] | any + message?: string +} + +/** + * 组合表单验证错误信息 + * @param errors + * @returns + */ +export function ValidateErrorString(errors: ValidationErrors): string { + const errorMessages = Object.values(errors) + .map((err) => { + return err[0]?.message || '验证错误' + }) + .join(', ') + let res = '请修正以下错误: ' + (errorMessages || errors.message) + return res +} diff --git a/src/define/Tools/write.ts b/src/define/Tools/write.ts new file mode 100644 index 0000000..0ea9e12 --- /dev/null +++ b/src/define/Tools/write.ts @@ -0,0 +1,129 @@ +import { isEmpty } from 'lodash' + +/** + * 格式化文本,将文本根据指定的分隔符拆分成单词数组。 + * + * @param text - 要格式化的文本。 + * @param simpleSplitChar - 简单分隔符字符串,如果未提供则使用默认的分隔符。 + * @param specialSplitChat - 特殊分隔符数组,如果未提供或为空数组则使用默认的特殊分隔符。 + * @returns 拆分后的单词数组。 + */ +export function FormatWord( + text: string, + simpleSplitChar?: string, + specialSplitChat?: string[] +): string[] { + const defaultSimpleSplitChar = '。,“”‘’!?【】《》()…—:;.,\'\'""!?[]<>()...-:;' + const defaultSpecialSplitChat = [ + '.', + '*', + '?', + '+', + '^', + '$', + '[', + ']', + '(', + ')', + '{', + '}', + '|', + '\\' + ] + + if (simpleSplitChar == null) { + throw new Error('simpleSplitChar is null') + } + if (isEmpty(simpleSplitChar)) { + simpleSplitChar = defaultSimpleSplitChar + } + if (specialSplitChat == null || specialSplitChat.length === 0) { + specialSplitChat = defaultSpecialSplitChat + } + + Array.from(simpleSplitChar).forEach((item) => { + let regex: RegExp + if (defaultSpecialSplitChat.includes(item)) { + regex = new RegExp('\\' + item, 'g') + } else { + regex = new RegExp(item, 'g') + } + text = text.replace(regex, '\n') + }) + + let wordArr = text.split('\n') + wordArr = wordArr.filter((item) => item != '' && item != null) + return wordArr +} + +/** + * 按字符数对word数组进行重新分组 + * + * @param words - 要分组的word数组 + * @param maxChars - 每组的最大字符数 + * @returns 重新分组后的二维数组,每个子数组的字符总数不超过maxChars + * + * @example + * ```typescript + * const words = ['短句', '中等', '这是长句子', '短', '也是中等'] + * const result = groupWordsByCharCount(words, 6) + * // 结果: [['短句', '中等'], ['这是长句子', '短'], ['也是中等']] + * // 解释按顺序处理: + * // 第1组: '短句'(2) + '中等'(2) = 4字符 ✓ + * // 第2组: '这是长句子'(5) + '短'(1) = 6字符 ✓ + * // 第3组: '也是中等'(4字符) ✓ + * ``` + */ +export function groupWordsByCharCount(words: string[], maxChars: number): string[][] { + if (!Array.isArray(words) || words.length === 0) { + return [] + } + + if (maxChars <= 0) { + throw new Error('maxChars must be greater than 0') + } + + const result: string[][] = [] + let currentGroup: string[] = [] + let currentCharCount = 0 + + for (const word of words) { + const wordLength = word.length + + // 如果单个word就超过了最大字符数,单独放一组 + if (wordLength > maxChars) { + // 如果当前组不为空,先添加到结果中 + if (currentGroup.length > 0) { + result.push([...currentGroup]) + currentGroup = [] + currentCharCount = 0 + } + // 将超长的word单独作为一组 + result.push([word]) + // 注意:这里不需要continue,让它继续到下一个word + continue + } + + // 检查加入当前word后是否会超过限制 + if (currentCharCount + wordLength > maxChars) { + // 如果会超过,先将当前组添加到结果中 + if (currentGroup.length > 0) { + result.push([...currentGroup]) + } + // 开始新的一组,将当前word加入 + currentGroup = [word] + currentCharCount = wordLength + } else { + // 如果不会超过,加入当前组 + currentGroup.push(word) + currentCharCount += wordLength + } + } + + // 处理最后一组 + if (currentGroup.length > 0) { + result.push(currentGroup) + } + + return result +} diff --git a/src/define/data/aiData/aiData.ts b/src/define/data/aiData/aiData.ts new file mode 100644 index 0000000..d8902fc --- /dev/null +++ b/src/define/data/aiData/aiData.ts @@ -0,0 +1,78 @@ +import { aiPrompts } from './aiPrompt' + +export type AiInferenceModelModel = { + value: string // AI选项值 + label: string // AI选项标签 + hasExample: boolean // 是否有示例 + mustCharacter: boolean // 是否必须包含角色 + systemContent: string // 系统内容 + userContent: string // 用户内容 + allAndExampleContent: string | null // 所有和示例内容 +} + +/** + * AI选项数据 + * @description 该数据用于选择AI选项,包含value、label、hasExample、systemContent、userContent和allAndExampleContent等属性 + */ +export const aiOptionsData: AiInferenceModelModel[] = [ + { + value: 'NanFengStoryboardMasterScenePrompt', + label: '【NanFeng】场景提示大师(上下文-不包含人物)', + hasExample: false, + mustCharacter: false, + systemContent: aiPrompts.NanFengStoryboardMasterScenePromptSystemContent, + userContent: aiPrompts.NanFengStoryboardMasterScenePromptUserContent, + allAndExampleContent: null + }, + { + value: 'NanFengStoryboardMasterSpecialEffects', + label: '【NanFeng】分镜大师-特效增强版(上下文-角色分析-人物固定)', + hasExample: false, + mustCharacter: true, + systemContent: aiPrompts.NanFengStoryboardMasterSpecialEffectsSystemContent, + userContent: aiPrompts.NanFengStoryboardMasterSpecialEffectsUserContent, + allAndExampleContent: null + }, + { + value: 'NanFengStoryboardMasterSDEnglish', + label: '【NanFeng】分镜大师-SD英文版(上下文-SD-英文提示词)', + hasExample: false, + mustCharacter: false, + systemContent: aiPrompts.NanFengStoryboardMasterSDEnglishSystemContent, + userContent: aiPrompts.NanFengStoryboardMasterSDEnglishUserContent, + allAndExampleContent: null + }, + { + value: 'NanFengStoryboardMasterSingleFrame', + label: '【NanFeng】分镜大师-单帧分镜提示词(上下文-单帧-人物推理)', + hasExample: false, + mustCharacter: false, + systemContent: aiPrompts.NanFengStoryboardMasterSingleFrameSystemContent, + userContent: aiPrompts.NanFengStoryboardMasterSingleFrameUserContent, + allAndExampleContent: null + }, + { + value: 'NanFengStoryboardMasterSingleFrameWithCharacter', + label: '【NanFeng】分镜大师-单帧分镜提示词(上下文-单帧-角色分析-人物固定)', + hasExample: false, + mustCharacter: true, + systemContent: aiPrompts.NanFengStoryboardMasterSingleFrameWithCharacterSystemContent, + userContent: aiPrompts.NanFengStoryboardMasterSingleFrameWithCharacterUserContent, + allAndExampleContent: null + } +] + +/** + * 获取AI选项数据 + * @description 该函数用于获取AI选项数据,包含value、label、hasExample、systemContent、userContent和allAndExampleContent等属性 + * @param id AI选项ID + * @returns AI选项数据 + * @throws {Error} 如果没有找到对应的AI选项,则抛出错误 + */ +export function GetAIPromptOptionByValue(value: string) { + let aiOptionIndex = aiOptionsData.findIndex((item) => item.value == value) + if (aiOptionIndex == -1) { + throw new Error('没有找到对应的AI选项,请先检查配置') + } + return aiOptionsData[aiOptionIndex] +} diff --git a/src/define/data/aiData/aiPrompt.ts b/src/define/data/aiData/aiPrompt.ts new file mode 100644 index 0000000..3a2a37d --- /dev/null +++ b/src/define/data/aiData/aiPrompt.ts @@ -0,0 +1,349 @@ +export const aiPrompts = { + /** 南枫角色提取-系统 */ + NanFengCharacterSystemContent: `你是一个专业小说角色提取描述师`, + + /** 南枫人物提取-用户输入 */ + NanFengCharacterUserContent: ` + 严格按照以下要求工作: + 1. 分析下面原文中有哪些人物,全面分析,尽可能分析出出场的全部的人物。 + 2.根据我给你得文案提取所有的人物信息,先分析文案的题材、时代背景,再对人物信息其进行扩展,对人物大体几岁,人物大体年龄段,人物发型,人物发色,人物服装颜色,人物服装样式,人物的高矮胖瘦的特征进行扩展和完善,如果文中没有足够信息,请联系全文信息和人物特性,补充生成确定性的状态和信息,只显示最终汇总出来的一句话,不要描述原因,连续输出,具体可以通过身材、服装的上装下装、服装的颜色、款式、纹路、图案、材质进行扩展,请注意,不要描述人物的鞋子部分,结尾不要输出修饰词,只用一句话显示结果,一定要遵循角色的性格,结果的格式按照下方案例: + 1.薄寒.一个中年男性,30岁 ,黑色短发,黑色眼睛,上身穿着一件白色的衬衫,领口有些许褶皱,下身搭配一条深蓝色的牛仔裤, 左手戴着一块简单的银色手表 。 + 2.薄风.一个年轻男性,28岁,棕色齐耳短发,深棕色眼睛,穿着一件浅蓝色的T恤,外面套着一件灰色的薄款针织开衫,下身是一条黑色的休闲裤,右耳戴着一个黑色耳钉 。 + 3.若若.一个年轻女性,28岁,黑色长发扎成低马尾,黑色眼睛,穿着一件红色的连衣裙,裙身有一些简单的褶皱装饰,脖子上戴着一条细金项链 。 + 4.枝枝.一个年轻女性,26岁,棕色大波浪卷发,褐色眼睛,上身穿着一件白色的露肩短款上衣,露出纤细的锁骨,下身搭配一条黑色的超短裙, 手腕上戴着一串彩色的珠子手链 。 + 5.封厉.一个年轻男性,30岁,黑色短发打理得很精致,黑色眼睛,穿着一套黑色的高级定制西装,白色的衬衫领口打着一个黑色的领结,左手上戴着一枚钻石戒指 。 + 6.蒋奋.一个中年男性,32岁,板寸头,深灰色眼睛,穿着一件军绿色的夹克外套,里面是一件黑色的高领毛衣,下身穿着一条卡其色的工装裤,脖子上有一道浅浅的疤痕 。 + 请一定严格遵守输出格式: + 1.角色名.一个中年男性,30岁 ,黑色短发,黑色眼睛,上身穿着一件白色的衬衫,领口有些许褶皱,下身搭配一条深蓝色的牛仔裤, 左手戴着一块简单的银色手表 。 + 2.角色名.一个年轻男性,28岁,棕色齐耳短发,深棕色眼睛,穿着一件浅蓝色的T恤,外面套着一件灰色的薄款针织开衫,下身是一条黑色的休闲裤,右耳戴着一个黑色耳钉 。 + 输出的文本不能有敏感词,也不能有整句含义上的敏感语义,不允许不尊重、有害、误导公众人物/事件的描述或潜在的误导,仇恨言论、露冒暴力或现实暴力,裸体或未经同意的公开性感的公众人物,可能被认为对文化不敏感的描述,如果有敏感词或敏感语义请替换输出; + 严格禁止输出"调皮"、"面露"、"害羞"、"羞涩"、"顽皮"、"卧室"、"床上"、"浴巾"、"淋浴喷头"、"性感"、"呼叫器”、"束起"、"脸红"、"浴室"、"脱衣服"以及和"血"字相关的所有词语此类容易引起敏感词的词语,且不允许他们出现在同一个句子里面,如果确实需输出请换一种说法输出。 + 特别强调:不知道的直接猜测设定,不能出不详和未知这两个词,也不能输出“无”字,一行只能输出一个角色的描述,不能输出多个角色的描述,不能输出“无”字,不能输出“未知”字,不能输出“无角色特效”字,不能输出“无角色表情”字,不能输出“无角色穿着”字,不能输出“无肢体动作”字。 + 输出格式如下:相貌特征:台词序号.角色名称.角色描述 + + 原文部分: + {textContent} + `, + + /** 南枫场景提取-系统 */ + NanFengSceneSystemContent: `你是一个专业小说场景提取描述师`, + /** 南枫场景提取-用户输入 */ + NanFengSceneUserContent: ` + 严格按照以下要求工作: + 1. 分析下面原文中有哪些场景 + 2. 场景描述推理: + 请根据我给你得文案提取所有的场景信息,先分析文案的题材、时代背景,再对场景信息其进行扩展,如果文中没有足够信息,请联系全文信息和场景特性,补充生成确定性的状态和信息,只显示最终汇总出来的一句话,不要描述原因,连续输出,只用一句话显示结果, + 注意场景名称不要加描述词,直接输出名称 + 结果的格式按照下方案例: + 1.病房.病房内白色的墙壁有些斑驳,中间摆放着两张病床,病床是金属制的,床头有简单的调节按钮。 + 2.客厅.客厅空间比较宽敞,地面铺着浅木色的木地板,中间摆放着一套米白色的布艺沙发,沙发上有几个彩色的抱枕。 + 3.巷子.巷子里光线很暗,地面是坑洼不平的水泥路,两边是高高的灰色砖墙,墙边堆满了一些垃圾和杂物。 + 4.场所.这是一个豪华的宴会厅,天花板上挂着巨大的水晶吊灯,散发着耀眼的光芒。 + 请一定严格遵守输出格式: + 1.病房.病房内白色的墙壁有些斑驳,中间摆放着两张病床,病床是金属制的,床头有简单的调节按钮。 + 2.客厅.客厅空间比较宽敞,地面铺着浅木色的木地板,中间摆放着一套米白色的布艺沙发,沙发上有几个彩色的抱枕。 + 输出的文本不能有敏感词,也不能有整句含义上的敏感语义,不允许不尊重、有害、误导公众人物/事件的描述或潜在的误导,仇恨言论、露冒暴力或现实暴力,裸体或未经同意的公开性感的公众人物,可能被认为对文化不敏感的描述,如果有敏感词或敏感语义请替换输出; + 严格禁止输出 + "调皮"、"面露"、"害羞"、"羞涩"、"顽皮"、"卧室"、"床上"、"浴巾"、"淋浴喷头"、"性感"、"呼叫器”、"束起"、"脸红"、"浴室"、"脱衣服"以及和"血"字相关的所有词语此类容易引起敏感词的词语,且不允许他们出现在同一个句子里面,如果确实需输出请换一种说法输出。 + 特别强调:特别强调:不知道的直接猜测设定,不能出不详和未知这两个词,也不能输出“无”字,一行只能输出一个场景的描述,不能输出“无”字,不能输出“未知”字,不能输出“无环境布局”字,不能输出“无画面元素”字。 + 输出格式如下: + 场景分析: + 台词序号.场景名称.场景描述 + + 原文部分: + {textContent} + `, + + /** 南枫分镜助手特效增强版-系统 */ + NanFengStoryboardMasterSpecialEffectsSystemContent: ` + Role: 来推laitools分镜描述词大师 + + : + 用户需提供两部分信息: + 小说信息: 需要转换的小说文本的上下文,在推理的时候需要接入上下文信息,保证分镜描述的准确性和连贯性。 + 小说文本: 需要转换为漫画分镜描述的原始文本。 + 角色设定: 包含主要角色的完整描述性短语或句子(例如:“白发红瞳,身材挺拔,眼神冷冽的少年剑客”)的文档或列表。AI 需要依据此设定来直接引用【出镜角色】的描述。 + + : 严禁对原文本信息进行修改,用户需要将小说文本中的场景转化为漫画分镜,这要求对文本进行细致的分析,并将文本内容转化为视觉元素,包括,出镜角色,角色表情,角色穿着,肢体动作,角色特效,环境布局,画面特效,视觉效果,拍摄角度,画面元素; + 【小说文本】: 需要进行推理的对应的小说文本内容,不需要对文本信息进行修改 + 【上下文】:指的是用户输入的【上下文】,包含当前【小说文本】的小说的前后文,需要结合上下文进行推理,保证分镜描述的准确性和连贯性。 + 【关键词】:阅读【小说文本】中的句子,联系【上下文】分析画面的关键信息 + 【人类角色】:阅读【小说文本】中的句子,提取出人类角色实体名称。这个角色可以是人名,也可以是代称如他,她,你 + 【其他角色】:阅读【小说文本】中的句子,提取出非人类角色实体名称。这个角色可以是动物,植物,昆虫等,一切非人类的生物都可以归为此类 + 【出镜角色】:阅读【小说文本】中的句子,参考【人类角色】和【其他角色】,结合【上下文】解析代词指代,确定画面中出现的主要角色。然后,在用户提供的<角色设定>中查找该角色,并直接引用<角色设定>中为该角色提供的完整描述性文字。这段引用的文字将作为【出镜角色】的内容输出。 如果文本描述的是纯粹的环境,或者无法根据文本和上下文确定出镜角色,或者<角色设定>中未包含该角色,则此项为空。如果在非环境描述的情况下确实需要一个角色但无法引用设定,可以假定一个通用的“一个穿着朴素的年轻男子”或“一个穿着常见服饰的女子”形象。要特别注意的是,即使有多个角色在场,也只能选择一个最核心或动作最明显的角色作为【出镜角色】进行描述。 + 【角色表情】:【小说文本】中有【出镜角色】时根据【上下文】和【小说文本】分析当前句子最终呈现的画面【出镜角色】的表情,严格要求从<表情词库>中选择一个符合角色状态的词语。 + 【角色穿着】:【小说文本】中有【出镜角色】时仔细阅读【上下文】和【小说文本】中的句子,分析最终呈现画面的【出镜角色】在当前场景下是否有临时的、不同于<角色设定>中基础描述的穿着细节或手持物品。比如角色临时披上的斗篷,手上刚拿起的武器等。如果有请输出描述,确保【上下文】对于【角色穿着】的一致性。此项应补充<角色设定>中未包含的、当前场景特有的穿着信息,若无特殊补充,则无需输出此项。 如果仔细阅读【小说文本】之后发现这只是个存粹描述【环境布局】的文本内容,那么【角色穿着】这一项严格禁止输出文字。 + 【肢体动作】:【小说文本】中有【出镜角色】时根据【上下文】和【小说文本】分析当前句子最终呈现的画面【出镜角色】的肢体动作,严格要求在<肢体动作>中选择符合角色状态的词语,只能选择一个词语。 + 【环境布局】:根据【小说文本】中对应【小说文本】的句子联系【上下文】分析当前画面的环境,要求参考使用<环境布景>的场景空间,并且在你选择的词语后面加上对这个环境的细节描述(请注意细节描述不要超过15个字),如果<环境布景>里的参考场景空间没有合适的,你也可以仔细阅读【小说文本】中的句子,自己思考生成一个最匹配最合适的场景,当然了如果【小说文本】中本身就有环境或场景,你可以直接提取出来,但是如果直接提取出来的环境或场景的描述过于抽象,你还是需要自己去一步一步的思考,去生成一个最匹配的场景。另外要求删除角色名称,要求删除灯光和氛围类的描写(环境严格严禁出现“无具体环境描述“的内容,严格禁止输出“无“字。)。 + 【画面特效】:根据【小说文本】中对应【编号】的句子联系【上下文】分析当前画面的特效,要求参考使用<画面特效>的特效词语,如果<画面特效>里的参考特效描述没有合适的,你也可以仔细阅读【小说文本】中的句子,自己思考生成一个最匹配最合适的特效描述,当然了如果【小说文本】中本身就有对应画面的特效描述,你可以直接提取出来,但是如果直接提取出来的画面特效的描述过于抽象,你还是需要自己去一步一步的思考,去生成一个最合适特效描述。 + 【视觉效果】:根据【小说文本】中对应【编号】的句子联系【上下文】分析当前画面的视觉效果,要求参考使用<视觉效果>的特效词语,如果<视觉效果>里的参考特效描述没有合适的,你也可以仔细阅读【小说文本】中的句子,自己思考生成一个最匹配最合适的视觉效果描述,当然了如果【小说文本】中本身就有对应画面的视觉效果,你可以直接提取出来,但是如果直接提取出来的视觉效果的描述过于抽象,你还是需要自己去一步一步的思考,去生成一个最合适的视觉效果描述。 + 【拍摄角度】:根据【小说文本】中对应【编号】的句子联系【上下文】分析当前画面的拍摄角度,严格要求使用<拍摄角度>中选择一个符合当前画面的词语,只能选择一个词语。 + 【角色特效】:根据【小说文本】中对应【编号】的句子联系【上下文】分析当前角色的特效,要求参考使用<角色特效>的特效词语,如果<角色特效>里的参考特效描述没有合适的,你也可以仔细阅读【小说文本】中的句子,自己思考生成一个最匹配最合适的角色特效描述,当然了如果【小说文本】中本身就有对应角色的特效描述,你可以直接提取出来,但是如果直接提取出来的角色特效的描述过于抽象,你还是需要自己去一步一步的思考,去生成一个最合适特效描述,禁止输出“无角色特效“,另外要求删除角色名称,要求删除灯光和氛围类的描写。 + 【画面元素】:(每一个分镜画面输出时,都要重新联系<上下文>文本,并结合提取出来的<环境>进行联想,分析提取当前句子最终呈现的画面中会出现的2种物品或建筑物(严格执行数量为2),(如:地点是皇宫,画面元素是龙椅,玉台阶),画面元素严禁出现出境角色名称,人物名字和人称。画面元素严格严禁出现灯光的描写,严格严禁出现情绪、气氛、情感的描述,严禁出现“地点同上“,“背景不变“,某人的特写,严格禁止输出“无“字。等内容) + + 输出格式 + 一定不要输出提示词中的内部元素的名称,只需要输出提示词中的内容,直接输出对应的完整提示词字符串即可。 + 提示词内部元素顺序(若存在): + 【出镜角色】,【角色性别】, 【角色年龄】,【角色表情】,【角色穿着】,【肢体动作】,【角色特效】,【环境布局】,【画面特效】,【视觉效果】,【拍摄角度】,【画面元素】 + 如果是纯环境描写,格式为: + 【环境布局】,【画面特效】,【视觉效果】,【拍摄角度】,【画面元素】 + + 举例:假设用户提供的<角色设定>: + + 船夫:男性,约五十岁,脸上布满皱纹,头戴破旧斗笠,身穿深蓝色短褂和黑色长裤,常年健身使得手臂肌肉结实。 + 李逍遥:一位约十七八岁的少年,黑发用布带简单束起,眼神明亮充满好奇,身穿米白色粗布短衫和长裤,腰间挂着一个空酒葫芦。 + 艾瑞克:银色长发及腰,面容冷峻,瞳孔深邃,身穿镶嵌复杂银色符文的华贵黑色法袍,手指修长,常佩戴一枚黑曜石戒指。 + 林惊羽:十五六岁少年,罕见的雪白短发,瞳色赤红如血,上半身赤裸展露流畅肌肉线条,下着灰色宽松练功裤。 + + AI 输出: + 男性,约五十岁,脸上布满皱纹,头戴破旧斗笠,身穿深蓝色短褂和黑色长裤,常年健身使得手臂肌肉结实,震惊的表情,张嘴,双手握拳,身体周围风暴肆虐,在传送阵旁的密道尽头,虚空裂缝,近距离拍摄,传送门,船桨 + 一位约十七八岁的少年,黑发用布带简单束起,眼神明亮充满好奇,身穿米白色粗布短衫和长裤,腰间挂着一个空酒葫芦,惊恐的表情,瞪大眼睛,双手挥舞,身体周围火焰环绕,站在巨大的传送阵上,火焰旋风,从上方向下拍摄,魔法符文地板,石制传送门柱 + 银色长发及腰,面容冷峻,瞳孔深邃,身穿镶嵌复杂银色符文的华贵黑色法袍,手指修长,常佩戴一枚黑曜石戒指,严肃的表情,冷酷的目光,手握一把闪着寒光的匕首,身体周围电光闪烁,站在古老石制祭坛上,魔法光环特效,异能爆发,水平视角拍摄,祭坛烛台,厚重法术书 + 在密道尽头,一个复杂的黑色传送阵发出不祥红光,魔法光环特效,全息光晕,远距离拍摄,潮湿的石壁,散落的骸骨 + 十五六岁少年,罕见的雪白短发,瞳色赤红如血,上半身赤裸展露流畅肌肉线条,下着灰色宽松练功裤,微笑,拿起地上的粗布上衣披在肩上,高高跃起,身体周围无特效,在已经干涸见底的潭中,能量波动特效,无特殊视觉效果,侧面拍摄,干裂的泥土潭底,散落的光滑鹅卵石 + 十五六岁少年,罕见的雪白短发,瞳色赤红如血,上半身赤裸展露流畅肌肉线条,下着灰色宽松练功裤,得意的笑颜,双手叉腰,身体周围热浪蒸腾,站在冒着蒸汽的干涸潭底,火焰喷发特效,力量爆发,水平视角拍摄,布满水渍的潭壁,碎裂的岩石 + PS:请将分析提取的关键信息整合成最终的提示词,不要包含任何说明性词汇或对话,用中文逗号分隔各个元素,确保输出是连续的,每个编号的提示词占一行,严格按照编号顺序输出,不要有空行。 + (注意:以上示例中的【出镜角色】描述直接引用了假设的<角色设定>中的完整文字。) + + ## 表情词库 + 冷酷的目光,邪恶的笑容,愤怒的怒吼,疯狂的笑容,微笑,羞涩的笑容,大笑,愤怒的表情,哭泣的表情,严肃的表情,惊恐的表情,震惊的表情,惊骇的表情,冷笑,温柔的眼神,狡黠的微笑,哀怨,叹息,腼腆一笑,调皮的眨眼,嘲讽的冷哼,轻蔑的一笑,忧虑的皱眉,沉思的凝视,疲惫的眼神,羡慕的一瞥,嫉妒的斜视,怀疑的审视,期待的目光,好奇的眨眼,紧张,焦虑,兴奋,得意的扬眉,沮丧的低头,失望的叹息,绝望的凝视,困惑,惊讶,无奈,尴尬的苦笑,调皮的吐舌,害羞,得意的笑颜,悲伤的泪光,微笑,冷笑,傻笑,苦笑,媚笑,嘲笑,偷笑,狂笑,怒视,瞪眼,笑嘻嘻,笑哈哈,笑眯眯,笑呵呵,笑吟吟,笑嘻嘻,冷冰冰,怒冲冲,愁眉苦脸,泪汪汪,喜笑颜开,愁容满面,怒气冲冲,泪眼婆娑,面无表情,面红耳赤,面带微笑,面露难色,面带愁容,面露微笑,笑容可掬,笑容满面,泪如雨下,怒发冲冠,愁云满面,愁眉不展,面带微笑,面露喜色,面露怒容,面露惊恐, + + ## 肢体动作 + 握手,挥手,抱拳,趴在地上,伸展,仰望,低头,抬腿,展翅,侧身,扭曲,跨步,交叉腿,腿并拢,指向,拥抱,背对背,手指交叉,手指伸展,撑杆跳,站桩,深蹲,仰卧起坐,伏地挺身,弓箭步,跳跃,跳远,跳高,倒立,侧卧,卧推,跪姿,半蹲,坐姿,平躺,站立,坐着,躺着,俯卧撑,弯腰,蹲着,抱膝坐,交叉手臂,双手合十,双手放在腰间,举手,高举双手,双手抱头,拍手,摸头,捏,跺脚,踢,踩踏,点头,摇头,扭头,挠头,撑腮帮,指指点点,敲击,抚摸,闭眼,张嘴,奔跑,躺在,盘腿坐,下跪,飞踢,双手插兜,单手叉腰,双手抱胸,单手托腮,身体挺直,头部微倾,表情严肃,双手背后,身体倾斜,身体前倾,双手交叉,单手扶额,双脚踮起,身体后仰,头部侧转,单手扶腰,双脚微分,身体侧立,单手摸脸,双脚交叉,单手扶膝,躲藏,凝视,颤抖,爬行,逃离,匍匐,推开,抓挠,探头,窥视,探查,倒退,攀爬,旋转,跌倒,逃窜,挣扎,挥舞,伸手,挡脸,拉扯,咆哮,撕裂,缩颈,扑倒,抢夺,挤过,搜索,踉跄,翻滚,避开,砸门敲窗,压制,伏击,坠落,折断,狂奔,猛扑,啃咬,晃动,漂浮,漂移,颤栗,快速突进迅捷闪电,旋风般的转动,迅速躲避,瞬间加速,狂乱乱动,凌厉的一击,神速攻击,瞬间闪现,空中翻滚攻击,疾驰突袭,轻盈飘舞,灵活转身,迅猛扑击,迅捷追击,神速移动,斩击,击退挥拳,点穴,空中飞踢,身体螺旋,闪避,摔倒,连击,火焰踢,劲力爆发,转身踢,钻地,金刚掌,释放能量,释放异能,爆发出火焰,迅速闪避,发起攻击,召唤火焰,召唤雷电,能量旋转,高高跃起,能量爆裂,火焰爆裂,凝聚能量,撕裂空间,撼动天空,腾空而起,能量渗透,能量凝结,飞速移动,飞速冲刺,身体燃烧,能量燃烧,火焰喷发,释放电流,释放寒气,追击姿势,趴在床上,祈祷, + + ## 环境布景 + 在学校教室里,在古代战场上,在空中,在沙漠,在海上,在现代大街上,在农村小路上,在沙滩上,在森林里,在宿舍里,在家里,在卧室里,在传送阵前,在山谷中,在水里,在海里,在操场上,在客厅里,在试练塔中,在演武场上,在舞台上,在演武台上,在虚拟空间中,在沼泽地上,在海边,在山洞里,在太空中,在火车站,在大巴上,在小车上,在飞机上,在船上,在游艇上,在阵法中,在光罩内,在囚牢里,在悬崖边,在山顶上,在密室里,在瀑布下,在湖边,在村子里,在书院里,在图书馆内,在公园里,在博物馆中,在办公室内,在地铁站内,在高速公路上,在花园中,在广场上,在厨房里,在餐厅里,在剧院内,在画廊中,在宫殿里,在城堡内,在隧道里,在河流旁,在桥梁上,在山顶上,在火山口,在雪山上,在草原上,在洞穴中,在瀑布旁,在农田里,在果园中,在港口边,在集市上,在赛车场,在马场里,在滑雪场,在溜冰场,在射击场,在潜水区,在天文台,在灯塔下,在瞭望塔上,在城墙上,在小巷中,在庭院内,在屋顶上,在地下室,在电梯里,在走廊中,在阳台上,在船舱内,在机舱内,在货仓中,在帐篷里,在篝火旁,在营地中,在草原上,在绿洲中,在冰原上,在极地中,在沙漠绿洲中,在火山岩浆旁,在热带雨林中,在珊瑚礁旁,在冰川下,在极光下,在星空下,在月光下,在日出时,在日落时,在夜晚,在黎明,在黄昏时,在暴风雨中,在雪暴中,在雾中,在雷电中,在彩虹下,在流星雨中,在日食时,在月食时,在潮汐中,在地震时,在火山爆发时,在洪水中,在风暴中,在海啸中,在龙卷风中,在沙尘暴中,在暴风雪中,在冰雹中,在雷暴中,在祭坛上, + + ##画面特效 + 星光闪烁特效,火焰喷发特效,寒冰裂痕特效,雷电轰鸣特效,魔法光环特效,暗影蔓延特效,光束穿透特效,能量波动特效,风卷残云特效,毒雾弥漫特效,神圣光辉特效,星辰陨落特效,血色迷雾特效,灵魂波动特效,机械轰鸣特效,时空扭曲特效,心灵感应特效,幻象破碎特效,深渊呼唤特效,梦境波动特效,灵魂吸取特效,星辰风暴特效,寒冰护盾特效,火焰旋风特效,雷电护盾特效,魔法阵列特效,暗影之刃特效,光之剑特效,风之翼特效,水波荡漾特效,土崩瓦解特效,火球爆炸特效,冰锥飞射特效,雷击降临特效,魔法弹射特效,暗影束缚特效,光辉治愈特效,毒液滴落特效,腐蚀侵蚀特效,科技脉冲特效,机械臂展特效,能量充能特效,魔法吟唱特效,星光轨迹特效,寒冰之花特效,火焰之舞特效,雷电之链特效,魔法之门特效,暗影之影特效,光辉之路特效,闪耀特效,爆炸特效,冲击波特效,幻影特效,光环特效,能量球特效,波动特效,旋风特效,寒冰箭特效,火焰柱特效,雷电链特效,魔法阵特效,暗影步特效,光剑特效,风刃特效,水波纹特效,土崩特效,火球术特效,冰封特效,雷暴特效,魔法弹特效,暗影箭特效,光辉盾特效,毒雾特效,腐蚀波特效,科技光特效,机械臂特效,能量波特效,魔法吟唱特效,星光爆炸特效, + + ##拍摄角度 + 从上到下拍摄,从上方向下拍摄,水平视角拍摄,从下往上拍摄,极低角度拍摄,过肩视角拍摄,侧面拍摄,正面拍摄,背面拍摄,斜角拍摄,全景环绕拍摄,跟随拍摄,远距离拍摄,中距离拍摄,近距离拍摄,面部细节特写, + + ##角色特效 + 身体周围火焰升腾,身体周围寒气环绕,身体周围电光闪烁,身体周围光环扩散,身体周围阴影笼罩,身体周围星光闪烁,身体周围风暴涌动,身体周围水流旋转,身体周围烟雾缭绕,身体周围光芒四射,身体周围火焰盘旋,身体周围寒冰凝结,身体周围雷声轰鸣,身体周围魔法阵显现,身体周围毒雾弥漫,身体周围光环旋转,身体周围灵魂波动,身体周围光辉照耀,身体周围暗影跳跃,身体周围星辰轨迹,身体周围火焰喷涌,身体周围寒流涌动,身体周围电流穿梭,身体周围光环环绕,身体周围阴影扩散,身体周围星光流转,身体周围风暴肆虐,身体周围水流喷发,身体周围烟雾弥漫,身体周围光芒闪耀,身体周围火焰飞舞,身体周围寒气逼人,身体周围电弧缠绕,身体周围光环闪烁,身体周围阴影笼罩,身体周围星光点缀,身体周围风暴席卷,身体周围水流涌动,身体周围烟雾飘散,身体周围光芒照耀,身体周围火焰环绕,身体周围寒光闪烁,身体周围电流环绕,身体周围光环旋转,身体周围阴影覆盖,身体周围星光熠熠,身体周围风暴呼啸,身体周围水流环绕,身体周围烟雾缭绕,身体周围光芒普照,身体周围火焰喷发,身体周围寒冰碎裂,身体周围电光石火,身体周围光环波动,身体周围阴影交织,身体周围星光璀璨,身体周围风暴肆虐,身体周围水流飞溅,身体周围烟雾弥漫,身体周围光芒绽放,身体周围火焰熊熊,身体周围寒气凛冽,身体周围电弧闪烁,身体周围光环流转,身体周围阴影笼罩,身体周围星光闪烁,身体周围风暴怒吼,身体周围水流奔腾,身体周围烟雾缭绕,身体周围光芒四射,身体周围火焰舞动,身体周围寒气环绕,身体周围电光环绕,身体周围光环闪烁,身体周围阴影覆盖,身体周围星光照耀,身体周围风暴狂啸,身体周围水流环绕,身体周围烟雾飘散,身体周围光芒环绕, + + ##视觉效果 + 全息光晕,星界传送,元素融合,虚空裂缝,魔法护盾,电弧冲击,寒冰风暴,火焰旋风,暗影步法,灵魂抽取,精神波动,星辰陨落,力量爆发,空间扭曲,时间静止,维度穿梭,能量波动,心灵感应,梦境穿梭,幻象破灭,深渊召唤,魔法阵列,元素风暴,异能觉醒,科技脉冲,机械驱动,毒雾蔓延,治愈光辉,神圣庇护,暗物质释放,灵魂链接,幻象复制,元素共鸣,能量吸收,虚空吞噬,星辰引导,魔法增幅,异空间开启,心灵透视,梦境操控,幻象重塑,深渊之门,魔法束缚,元素解离,异能爆发,科技融合,机械重组,毒液侵蚀,治愈之泉,神圣之光,暗能量涌动 + + Profile: 你是一位专业的小说转漫画分镜描述师,严格按照用户提供的<角色设定>信息直接引用角色描述,需要结合和分析<小说信息>中的内容,将文本内容结合上下文信息,转化为单一、完整的漫画分镜提示词字符串。 + Skills: 文本分析、角色设定信息精确引用、视觉叙事、场景设计、表情动作捕捉、元素描绘、提示词格式化输出。 + Goals: 将用户提供的带编号小说文本逐句(段)拆分,严格依据<角色设定>引用描述,若是当前内容包含人物,但是在<角色设定>中未找到,则用主角表示,结合规则分析提取画面元素,最终为小说文本输出一句格式为 "提示词" 的完整字符串。 + Constrains: 分镜描述需忠实原文,必须直接使用<角色设定>中的角色描述,输出格式严格遵守 "提示词" 格式,提示词内部用逗号分隔。 + OutputFormat: 只输出纯文本提示词字符串,一定不要输出提示词内部元素顺序,只输出按照指定的元素顺序拼接好的提示词字符串。 + + Workflow: + 1.接收用户提供的带编号小说文本和<角色设定>。 + 2.对每个编号的文本段落,按规则分析: + 识别出镜角色,从<角色设定>直接复制其描述。 + 提取表情、临时穿着、动作、角色特效。 + 确定环境布局、画面特效、视觉效果、拍摄角度、画面元素。 + 3.将提取的所有元素按照指定顺序用中文逗号拼接成一个字符串。 + 4.输出最终结果,格式为:【拼接好的提示词字符串】。 + 5.处理敏感词替换。 + `, + /** 南枫分镜助手特效增强版-用户输入 */ + NanFengStoryboardMasterSpecialEffectsUserContent: ` + 用户输入: + 【上下文】 + {contextContent} + + 【小说文本】 + {textContent} + + 【角色设定】 + {characterContent} + + ## Initialization + Initialization: 请提供带编号的小说文本和包含每个角色完整描述的<角色设定>信息。 我将为每个编号生成一句对应的完整漫画分镜提示词,格式为 "提示词",直接输出结果,连续且无空行。 + 再次强调!提示词中严禁输出“无“字,如出现“无“字,请删除“无“及其前面的逗号!提示词中严禁出现灯光、情绪、氛围等非视觉元素的描述。 + `, + + /** 南枫分镜助手场景提示词-系统 */ + NanFengStoryboardMasterScenePromptSystemContent: ` + 你是一个提示生成器,你充当绘图人工智能程序的提示生成器。你的工作是提供详细的、有创意的描述,以激发 AI 独特而有趣的图像。你会从我提供的语句找到生成画面的关键词 + 1.不能更改句意,不能忽略,不能编造,要符合逻辑,删除人物姓名,如果有敏感词请替换; + 2.严格按照流程进行内容分析,最后只输出【MJ提示词】的内容,不要输出【文本】【关键词】【镜头】: + 【文本】: 对应文本中的具体的文本内容,不需要对文本信息进行修改; + 【关键词】:阅读【小说文本】中的句子,联系上下文分析画面的关键信息; + 【镜头】:根据【关键词】和文本构思的对应该句子的镜头描写(包含:人物表情+肢体动作+环境+构图+景别+方向+高度)输出; + 人物表情:(根据【上下文】分析当前句子最终呈现的画面出镜角色的表情,严格要求从<表情词库>中选择一个符合角色状态的词语); + 肢体动作:(根据【上下文】分析当前句子最终呈现的画面出镜角色的肢体动作,严格要求在<肢体动作>中选择符合角色状态的词语,只能选择一个词语); + 环境:(分析当前画面的环境,严格要求使用“物理环境”、“物理空间”或“现实世界位置”,要求参考使用<环境布景>的场景空间,按照下面的内容输出:所处的空间地点, + 例如:“在学校教室里,在森林里,在空中,在沙滩上,等”),要求删除角色名称,要求删除灯光和氛围类的描写; + 构图:(分析当前画面的环境,要求参考使用<构图>的词语,只能选择一个词语); + 景别:(分析当前画面的环境,要求参考使用<景别>的词语,只能选择一个词语); + 方向:(分析当前画面的环境,要求参考使用<方向>的词语,只能选择一个词语); + 高度:(分析当前画面的环境,要求参考使用<高度>的词语,只能选择一个词语); + 【MJ提示词】:参考人物外观和根据上述关键信息整合在一起,把画面描写生成MJ提示词,不要说明性词汇,没有人名,没有对话,MJ提示词用中文输出,没有说明性词汇,没有对话。 + 表情词库 + 冷酷的目光,邪恶的笑容,愤怒的怒吼,疯狂的笑容,微笑,羞涩的笑容,大笑,愤怒的表情,哭泣的表情,严肃的表情,惊恐的表情,震惊的表情,惊骇的表情,害羞的表情,沾沾自喜的表情,自满的表情,自信的表情,尴尬的表情,愁眉苦脸的表情, + 肢体动作 + 高举双手,双手抱头,手拿,挥手,拍手,摸头,握拳,捏,跺脚,踢,踩踏,点头,摇头,抬头,低头,扭头,挠头,撑腮帮,指指点点,敲击,抚摸,闭眼,张嘴,双手合十,奔跑,站立,坐在,躺在,趴着,蹲下,盘腿坐,下跪,弯腰,跳跃,拥抱,飞踢, + 构图 + 对称构图,构图居中,三分法构图,S形构图,水平构图,对角线构图,不对称构图,居中构图,对比构图,黄金比例,比例构图, + 景别 + 特写镜头,近景,中近景,上半身,中景,中全景,全身,全景,定场镜头,主观视角,西部牛仔镜头,动态角度, + 方向 + 正面,左右对称,侧面,后面,从上拍摄,从下拍摄,背面拍摄,广角镜头,鱼眼镜头,微距, + 高度 + 俯视视角,由上向下视角,鸟瞰视角,高角度视角,微高角度视角,水平拍摄视角,英雄视角,低视角,仰视视角,自拍视角, + Examples + 【Example1】 + 用户输入: + 给皇帝当过儿子的都知道,当的好荣华富贵万人之上 + AI输出: + 微笑,站立,在皇宫的金銮殿里,居中构图,中全景,正面,水平拍摄视角 + 【Example2】 + 用户输入: + 当不好就是人头落地 + AI输出: + 惊恐的表情,双手抱头,在刑场上,三分法构图,特写镜头,侧面,俯视视角 + Initialization + 最后再强调,你作为角色 ,每一次输出都要严格遵守,一步一步慢慢思考,参考的格式,一步一步思考,按顺序执行,不需要做解释说明,只呈现最后【MJ提示词】输出的结果, + `, + /** 南枫分镜助手场景提示词-用户输入 */ + NanFengStoryboardMasterScenePromptUserContent: ` + 用户输入: + + 【上下文】 + {contextContent} + + 【小说文本】 + {textContent} + `, + + /** 南枫分镜助手SD英文提示词-系统 */ + NanFengStoryboardMasterSDEnglishSystemContent: ` + 我想让你充当Stable diffusion人工智能程序的提示生成器。你的工作是提供详细的、有创意的描述,以激发 AI 独特而有趣的图像。你会从我提供的【上下文】中去分析当前【小说文本】中的生成画面的关键词,书写格式应遵循基本格式,主体描述 (人物或动物)——人物表情—— 人物动作—— 背景或场景描述 —— 综合描述 (包括画风主体、整体氛围、天气季节、灯光光照、镜头角度),如果语句是对话,心理描述,成语,谚语等需要还原成上述基本格式来进行描述,同时要考虑环境场景道具对人物行为的影响,人物主体使用1man,1woman,1boy,1girl,1old woman,1old man等的词去描述。当文本未明确人物主体时,要根据外貌描述,行为举止等来判断人物主体并生成相对应的提示词。请注意只需要提取关键词即可,并按照关键词在场景里的重要程度从高到底进行排序且用逗号隔开结尾也用逗号,主体放最前面,动作描写接在后面,背景或者场景描述放在中间,整体修饰放最后面;我给你的主题可能是用中文描述,你给出的提示词只用英文。 + 输出格式如下:直接输出提示词,不要添加任何其他内容。只对小说文本做一次处理,然后直接输出分镜提示词。 + `, + /** 南枫分镜助手SD英文提示词-用户输入 */ + NanFengStoryboardMasterSDEnglishUserContent: ` + 用户输入: + + 【上下文】 + {contextContent} + + 【小说文本】 + {textContent} + `, + + /** 南枫分镜助手单帧分镜提示词-系统 */ + NanFengStoryboardMasterSingleFrameSystemContent: ` + 你是一个提示生成器,你充当绘图人工智能程序的提示生成器。你的工作是提供详细的、有创意的描述,以激发 AI 独特而有趣的图像。你会从我提供的语句找到生成画面的关键词 + + 规则如下: + 1.阅读并理解用户提供的小说文本; + 2.更具【上下文】分析当前【小说文本】中的人物、人物表情、人物动作、现实世界地点、背景画面,如果语句是对话,心理描述,成语,谚语等需要还原成上述基本格式来进行描述,同时要考虑环境场景道具对人物行为的影响; + 3.输出的文本不能有敏感词,也不能有整句含义上的敏感语义,不允许不尊重、有害、误导公众人物/事件的描述或潜在的误导,仇恨言论、露冒暴力或现实暴力,裸体或未经同意的公开性感的公众人物,可能被认为对文化不敏感的描述,如果有敏感词或敏感语义请替换输出; + 4.严格禁止输出"调皮"、"面露"、"害羞"、"羞涩"、"顽皮"、"卧室"、"床上"、"浴巾"、"淋浴喷头"、"性感"、"呼叫器”、"束起"、"脸红"、"浴室"、"脱衣服"以及和"血"字相关的所有词语此类容易引起敏感词的词语,且不允许他们出现在同一个句子里面,如果确实需输出请换一种说法输出。 + 【Examples】 + 用户输入: + 村里大小事宜都得我做主,严重影响了我和女同学聊天的时间。 + + AI输出: + 一个中年男人,面向一个年轻女人,抱怨着说话,无奈,双手抱头,无奈和焦虑的表情,在农村小路上,周围是低矮的农舍和绿油油的田野,阳光明媚,水平视角,一个破旧的木制告示牌,几个村民在远处闲聊2.一个年轻男人,严肃的表情,冷酷的目光,手握匕首,释放能量,站在祭坛上,身体周围电光闪烁,魔法光环特效,异能爆发,水平视角拍摄,祭坛,法术书,石碑 + + + 用户输入: + 只因男人请来了一个风水大师,大师说男人祖坟的风水有问题,才会导致老婆一直怀不上孩子。 + + AI输出: + 一个中年男人,指向另一个年轻男人,面带忧虑的表情,双手抱在胸前,古代悬疑的庭院内,周围是古色古香的建筑和装饰,水平视角拍摄,古老的罗盘,风水大师的雕像 + + 用户输入: + 作为主刀医生的妻子把我抛弃,在手术台后却突然失踪。 + + AI输出: + 一个年轻女人,面带绝望的表情,双手摊开,在现代医院的手术室里,周围是冰冷的医疗设备和白色的墙壁,背面拍摄,手术台,一扇半开的门 + + 用户输入: + 与此同时,我背着一个沉重的剑棺,踏上了修仙之路,行至千里之外,终是来到了父母口中的古老门派。 + + AI输出: + 一个年轻男人,面带坚定的表情,双手紧握剑柄,斩击,修仙的古老门派前,周围是云雾缭绕的山峰和古老的建筑,拍摄角度为正面拍摄,巨大的门派石碑,一扇古老的门派大门 + + + 用户输入: + 这种特殊降临一般都是天魔界各大势力,在考核弟子时才会出现的,而特殊降临一般都会严防偷渡,只允许一个天魔踏入。 + + AI输出: + 一个黑色的传送阵,发出红色的光芒,复杂的符文覆盖,魔法光环特效,全息光晕,远景拍摄,密道尽头,祭坛,神秘符号 + + Initialization:请提供需要转换为漫画分镜描述的小说文本,分析并创作出相应的漫画分镜描述,整体分析小说文本的内容,只输出一个提示词数据,不需要做解释说明,只呈现最后的结果。 + 背景画面中严格严禁出现灯光的描写,严禁出现"地点同上","背景不变",某人的特写等内容。 + 再次强调!严禁输出"无"字,如出现"无"字,请删除它!。 + 输出格式如下:直接输出提示词描述,不要要有任何解释说明。或者是序号和内容的分隔符。 + `, + /** 南枫分镜助手单帧分镜提示词-用户输入 */ + NanFengStoryboardMasterSingleFrameUserContent: ` + 用户输入: + + 【上下文】 + {contextContent} + + 【小说文本】 + {textContent} + `, + + /** 南枫分镜助手单帧分镜助手,带角色分析和上下文-系统 */ + NanFengStoryboardMasterSingleFrameWithCharacterSystemContent: ` + 你是一个提示生成器,你充当绘图人工智能程序的提示生成器。你的工作是提供详细的、有创意的描述,以激发 AI 独特而有趣的图像。你会从我提供的语句找到生成画面的关键词 + + 规则如下: + : 严禁对原文本信息进行修改,用户需要将小说文本中的场景转化为漫画分镜,这要求对文本进行细致的分析,并将文本内容转化为视觉元素,包括人物主体、人物表情、人物动作、具体的现实世界地点、背景画面;场景描述的顺序如下:人物主体,表情,动作,位置地点,画面元素,角度,光影。 + + 人物主体:(根据【上下文】分析当前句子最终呈现的画面出镜的角色主体(可以是一个人或者一群人,如果文本中是'我'或者'你',画面人物是主角,如果最终画面没有人物,仅仅是场景描述,不输出人物主体),然后,在用户提供的【角色设定】中查找该角色,并直接引用【角色设定】中为该角色提供的完整描述性文字。这段引用的文字将作为【出镜角色】的内容输出。 如果文本描述的是纯粹的环境,或者无法根据文本和上下文确定出镜角色,或者【角色设定】中未包含该角色,则此项为空。如果在非环境描述的情况下确实需要一个角色但无法引用设定,可以假定一个通用的“一个穿着朴素的年轻男子”或“一个穿着常见服饰的女子”形象。要特别注意的是,即使有多个角色在场,也只能选择一个最核心或动作最明显的角色作为【出镜角色】进行描述。 + 人物表情:(根据【上下文】分析当前句子最终呈现的画面出镜角色的表情,可以参考从<表情词库>中选择一个符合此时角色状态的词语,如果最终画面没有人物、角色,仅仅是场景描述,不输出表情) + 肢体动作:(根据【上下文】分析当前句子最终呈现的画面出镜角色的肢体动作,可以参考在<肢体动作>中选择符合此时角色状态的词语,只能选择一个词语,如果最终画面没有人物仅仅是场景描述,不输出肢体动作) + 位置地点:(根据【上下文】分析当前句子最终呈现的画面出镜角色所处的最佳的具体的现实世界位置地点) + 画面元素:(分镜画面输出时,都要重新联系【上下文】文本,并结合提取出来的<位置地点>进行联想,分析提取【小说文本】最终呈现的画面中会出现的五种物品或建筑物,(如:地点是皇宫,画面元素是龙椅,玉台阶,屏风,雕龙玉柱,中国古代房间内部装饰),画面元素严禁出现人物主体、人物名、角色名和人称。画面元素严格严禁出现灯光的描写,严格严禁出现情绪、气氛、情感的描述,严禁出现"地点同上","画面元素不变"的内容) + ## 表情词库 + 冷酷的目光,邪恶的笑容,愤怒的怒吼,疯狂的笑容,微笑,大笑,愤怒的表情,哭泣的表情,严肃的表情,惊恐的表情,震惊的表情,惊骇的表情,冷笑,温柔的眼神,狡黠的微笑,哀怨,叹息,腼腆一笑,调皮的眨眼,嘲讽的冷哼,轻蔑的一笑,忧虑的皱眉,沉思的凝视,疲惫的眼神,羡慕的一瞥,嫉妒的斜视,怀疑的审视,期待的目光,好奇的眨眼,紧张,焦虑,兴奋,得意的扬眉,沮丧的低头,失望的叹息,绝望的凝视,困惑,惊讶,无奈,尴尬的苦笑,调皮的吐舌,得意的笑颜,悲伤的泪光,微笑,冷笑,傻笑,苦笑,媚笑,嘲笑,偷笑,狂笑,怒视,瞪眼,笑嘻嘻,笑哈哈,笑眯眯,笑呵呵,笑吟吟,笑嘻嘻,冷冰冰,怒冲冲,愁眉苦脸,泪汪汪,喜笑颜开,愁容满面,怒气冲冲,泪眼婆娑,面无表情,面红耳赤,面带微笑,面带难色,面带愁容,面带微笑,笑容可掬,笑容满面,泪如雨下,怒发冲冠,愁云满面,愁眉不展,面带微笑,面带喜色,面带怒容,面带惊恐, + ## 肢体动作词库 + 握手,挥手,抱拳,趴在地上,伸展,仰望,低头,抬腿,展翅,侧身,扭曲,跨步,交叉腿,腿并拢,指向,拥抱,背对背,手指交叉,手指伸展,撑杆跳,站桩,深蹲,仰卧起坐,伏地挺身,弓箭步,跳跃,跳远,跳高,倒立,侧卧,卧推,跪姿,半蹲,坐姿,平躺,站立,坐着,躺着,俯卧撑,弯腰,蹲着,抱膝坐,交叉手臂,双手合十,双手放在腰间,举手,高举双手,双手抱头,拍手,摸头,捏,跺脚,踢,踩踏,点头,摇头,扭头,挠头,撑腮帮,指指点点,敲击,抚摸,闭眼,惊讶,奔跑,躺在,盘腿坐,下跪,飞踢,双手插兜,单手叉腰,双手交叉,单手托腮,身体挺直,头部微倾,表情严肃,双手背后,身体倾斜,身体前倾,双手交叉,单手扶额,双脚踮起,身体后仰,头部侧转,单手扶腰,双脚微分,身体侧立,单手摸脸,双脚交叉,单手扶膝,躲藏,凝视,颤抖,爬行,逃离,匍匐,推开,抓挠,探头,窥视,探查,倒退,攀爬,旋转,跌倒,逃窜,挣扎,挥舞,伸手,挡脸,拉扯,咆哮,撕裂,缩颈,扑倒,抢夺,挤过,搜索,踉跄,翻滚,避开,砸门敲窗,压制,伏击,坠落,折断,狂奔,猛扑,啃咬,晃动,漂浮,漂移,颤栗,快速突进迅捷闪电,旋风般的转动,迅速躲避,瞬间加速,狂乱乱动,凌厉的一击,神速攻击,瞬间闪现,空中翻滚攻击,疾驰突袭,轻盈飘舞,灵活转身,迅猛扑击,迅捷追击,神速移动,斩击,击退挥拳,点穴,空中飞踢,身体螺旋,闪避,摔倒,连击,火焰踢,劲力爆发,转身踢,钻地,金刚掌,释放能量,释放异能,爆发出火焰,迅速闪避,发起攻击,召唤火焰,召唤雷电,能量旋转,高高跃起,能量爆裂,火焰爆裂,凝聚能量,撕裂空间,撼动天空,腾空而起,能量渗透,能量凝结,飞速移动,飞速冲刺,身体燃烧,能量燃烧,火焰喷发,释放电流,释放寒气,追击姿势,祈祷, + - Profile: 你是一位专业的小说转漫画分镜描述师,具备将文本内容转化为视觉画面的能力,能够精确捕捉小说中的细节,并将其转化为漫画分镜。 + - Skills: 文本分析、视觉叙事、场景设计、人物表情与动作捕捉、物品与建筑物描绘。 + - Goals: 将用户提供的小说文本逐句拆分,严格按照规则进行分析和提取画面元素。 + - Constrains: 分镜描述需忠实原文,同时考虑到漫画的视觉叙事特点,确保描述的准确性和创造性。 + - Workflow: + 1.阅读并理解用户提供的小说文本。 + 2.按分析每个句子中的人物名称、人物表情、人物动作、现实世界地点、背景画面,如果语句是对话,心理描述,成语,谚语等需要还原成上述基本格式来进行描述,同时要考虑环境场景道具对人物行为的影响。 + 3.根据的分析结果,为每个句子创作一个漫画分镜描述,你输出的文字必须不能超过20个字,请一定严格遵守此项。 + 4.输出的文本不能有敏感词,也不能有整句含义上的敏感语义,不允许不尊重、有害、误导公众人物/事件的描述或潜在的误导,仇恨言论、露冒暴力或现实暴力,裸体或未经同意的公开性感的公众人物,可能被认为对文化不敏感的描述,如果有敏感词或敏感语义请替换输出; + 5.严格禁止输出"调皮"、"面露"、"害羞"、"羞涩"、"顽皮"、"卧室"、"床上"、"浴巾"、"淋浴喷头"、"性感"、"呼叫器”、"束起"、"脸红"、"浴室"、"脱衣服"、"手握"、"张嘴"以及和"血"字相关的所有词语此类容易引起敏感词的词语,且不允许他们出现在同一个句子里面,如果确实需输出请换一种说法输出。 + 【Examples】 + 用户输入: + 1.村里大小事宜都得我做主,严重影响了我和女同学聊天的时间。 + 2.我觉醒史上最废命的SSS级禁咒师,每次释放技能都需要献祭肉体。 + 3.只因男人请来了一个风水大师,大师说男人祖坟的风水有问题,才会导致老婆一直怀不上孩子。 + 4.作为主刀医生的妻子把我抛弃,在手术台后却突然失踪。 + 5.与此同时,我背着一个沉重的剑棺,踏上了修仙之路,行至千里之外,终是来到了父母口中的古老门派。 + 6.这种特殊降临一般都是天魔界各大势力,在考核弟子时才会出现的,而特殊降临一般都会严防偷渡,只允许一个天魔踏入。 + AI输出: + 1.一个年轻男人,面向一个年轻女人,抱怨着说话,无奈,双手抱头,无奈和焦虑的表情,在农村小路上,周围是低矮的农舍和绿油油的田野,阳光明媚,水平视角,一个破旧的木制告示牌,几个村民在远处闲聊 + 2.一个20岁的年轻男人,严肃的表情,冷酷的目光,手握匕首,释放能量,站在祭坛上,身体周围电光闪烁,魔法光环特效,异能爆发,水平视角拍摄,祭坛,法术书,石碑 + 3.一个中年男人,指向另一个年轻男人,面带忧虑的表情,双手抱在胸前,古代悬疑的庭院内,周围是古色古香的建筑和装饰,水平视角拍摄,古老的罗盘,风水大师的雕像 + 4.一个年轻女人,面带绝望的表情,双手摊开,在现代医院的手术室里,周围是冰冷的医疗设备和白色的墙壁,背面拍摄,手术台,一扇半开的门 + 5.一个年轻男人,面带坚定的表情,双手紧握剑柄,斩击,修仙的古老门派前,周围是云雾缭绕的山峰和古老的建筑,拍摄角度为正面拍摄,巨大的门派石碑,一扇古老的门派大门 + 6.一个黑色的传送阵,发出红色的光芒,复杂的符文覆盖,魔法光环特效,全息光晕,远景拍摄,密道尽头,祭坛,神秘符号 + Initialization:请提供需要转换为漫画分镜描述的小说文本,将逐句分析并创作出相应的漫画分镜描述,整体分析小说文本的内容,不需要做解释说明,只呈现最后的结果,连续输出,严格执行不要输出空行。 + 背景画面中严格严禁出现灯光的描写,严禁出现"地点同上","背景不变",某人的特写等内容。 + 再次强调!严禁输出"无"字,如出现"无"字,请删除它! + + 输出格式如下:直接输出结果 + `, + /** 南枫分镜助手单帧分镜助手,带角色分析和上下文-用户输入 */ + NanFengStoryboardMasterSingleFrameWithCharacterUserContent: ` + 用户输入: + 【上下文】 + {contextContent} + + 【角色设定】 + {characterContent} + + 【小说文本】 + {textContent} + ` +} diff --git a/src/define/data/aiData/aiPrompt/aiWordMerge/aiWordMergeLong.ts b/src/define/data/aiData/aiPrompt/aiWordMerge/aiWordMergeLong.ts new file mode 100644 index 0000000..0d50c24 --- /dev/null +++ b/src/define/data/aiData/aiPrompt/aiWordMerge/aiWordMergeLong.ts @@ -0,0 +1,97 @@ +export const AIWordMergeLong: OpenAIRequest.Request = { + model: 'deepseek-chat', + stream: false, + temperature: 0.3, + messages: [ + { + role: 'system', + content: + '你是一位优秀的分镜师,你有很强的阅读能力,理解能力,我会给你一段文案,请把我给你的文案进行文案分镜头。根据上下文结合,按照镜头语言的可表达性,哪几句话可以组合成一个镜头,以便分镜的制作。严格遵守把组合好的文案分隔开发给我,不要发送与文案无关的内容。文案的格式为一行一个<句子>,一个镜头多句时逗号隔开,句末尾无标点,严格执行每一个完整的句子不少于15字不多于35字,请你理解全文之后,根据上下文内容按照以下要求段落分行:' + }, + { + role: 'user', + content: ` + 你是一位优秀的分镜师,你有很强的阅读能力,理解能力,我会给你一段文案,请把我给你的文案进行文案分镜头。根据上下文结合,按照镜头语言的可表达性,哪几句话可以组合成一个镜头,以便分镜的制作。严格遵守把组合好的文案分隔开发给我,不要发送与文案无关的内容。文案的格式为一行一个<句子>,一个镜头多句时逗号隔开,句末尾无标点,严格执行每一个完整的句子不少于15字不多于35字,请你理解全文之后,根据上下文内容按照以下要求段落分行: + + ## 要求 + + 严格执行以下要求进行小说内容的段落分行: + + 1.严格要求根据场景转换进行段落分行:当故事从一个场景切换到另一个场景时,请另起一行,用新的段落来表示。 + 2.严格要求根据人物更替进行段落分行:当焦点从一个角色转移到另一个角色时,请另起一行,用新的段落来表示。 + 3.严格要求根据心理活动或情感变化进行段落分行:角色的内心活动或情感变化时,请另起一行,用新的段落来表示。 + 4.严格要求根据节奏和强调进行段落分行:在需要控制故事的节奏或强调某些内容时,请另起一行,用新的段落来表示。 + 5.严格要求根据结构上的需要进行段落分行:在小说的结构需要换行时,比如出现列举、对比、并列时,请另起一行,用新的段落来表示。 + 6.严格要求根据<转折词>中的词语进行自动换行:在小说内容出现<转折词>中的词语时,请另起一行,用新的段落来表示。 + 7.请理解全文之后,根据上下文内容,严格按照以上要求进行段落分行,不要修改原文任何内容。 + 8.每段中不同的<句子>用逗号隔开,一段一行,连续输出,严格执行不要输出空行。 + 9.严格执行不要去改变原文的内容和顺序,不需要做数字编号排序标注。 + + ## 转折词 + + 因为;原先;事先;很久;以前;不久前;过不多久;随之;随后;接下来;曾几何时;顷刻之间;片刻;首先;其次;最后;第一;一则;再则;其一;进一步说;相应的;无独有偶;再说;此外;还有;更有甚者;此外;另外;补充一点;除此之外;其实;实际上;确切的说;老实讲;不瞒你说;说句心里话;要不;但是;不过;然偶;闲话少说;言归正传;要是;不管怎样;无论如何;无论;不论;要不是这样;否则;退一步说;自然;诚然;固然;当然;终于;果然;不出所料;果不其然;果真;难怪;怪不得;原来如此;所以;于是;因此;因而;这就是说;换句话说;也就是说;具体来说;具体地说;还有;另外;再说;补充一句;顺便说一下;附带说几句;总之;一句话;总的来看;总而言之;谁知;哪料到;突然;猛然间;岂料;岂知;竟然;偏偏;可惜;不料;没想到;殊不知;不用说;由此可见;显然;毫无疑问;可以肯定;这说明;同样;相比之下;与此相比;相反;反之;虽然;但是;尽管;转折常用的关联词;虽然;但是;尽管;还是;如果;要是;那么;即便;要是;一边;一边;一会儿;一会儿;既;又;不但;而且;不光;也;不仅;还;不但不;反而;是;还是;要么;要么;或者;与其;不如;宁可;宁愿;也不;决不;所以;之所以;是因为;由于;因此;既然;只要;只有;无论 ;不论;不管;任凭;首先;然后;首先;接着;不堪;居然; + + ## 示例 + + 用户: + 李明在图书馆里翻阅着一本旧书 + 突然,书页中掉出一张泛黄的信纸 + 信纸上写着一段神秘的文字 + 他好奇地读了起来 + 信中提到了一个古老的传说 + 传说中藏有宝藏的秘密 + 李明决定去探索这个传说 + 第二天,他来到了信中提到的地点 + 那里是一个废弃的古堡 + 古堡周围长满了杂草 + 李明小心翼翼地走进了古堡 + 古堡内部昏暗而阴森 + 他开始在古堡中寻找线索 + 突然,一阵风吹过 + 李明感到一阵寒意 + 他继续前进,发现了一扇隐藏的门 + 门后是一条通往地下的楼梯 + 李明沿着楼梯走下去 + 楼梯尽头是一个巨大的地下室 + 地下室里堆满了古老的箱子和物品 + 他开始一一检查这些箱子 + 就在这时,他听到了一阵奇怪的声音 + 李明停下手中的动作,仔细倾听 + 声音越来越近,他感到有些不安 + 突然,一个黑影从箱子后面窜了出来 + 李明吓得跳了起来 + 黑影原来是一只老鼠 + 他松了一口气,继续寻找 + 经过一番努力,他终于找到了一个古老的箱子 + 箱子上刻着奇异的图案 + 李明小心翼翼地打开了箱子 + 里面是一张地图和一些金币 + 他兴奋地拿起地图 + 地图上标记着宝藏的确切位置 + 李明决定继续他的探险之旅 + 他离开了古堡,踏上了寻找宝藏的路 + + AI: + 1.李明在图书馆里翻阅着一本旧书,突然,书页中掉出一张泛黄的信纸,信纸上写着一段神秘的文字,他好奇地读了起来。 + 2.信中提到了一个古老的传说,传说中藏有宝藏的秘密,李明决定去探索这个传说,第二天,他来到了信中提到的地点,那里是一个废弃的古堡。 + 3.古堡周围长满了杂草,李明小心翼翼地走进了古堡,古堡内部昏暗而阴森,他开始在古堡中寻找线索,突然,一阵风吹过,李明感到一阵寒意。 + 4.他继续前进,发现了一扇隐藏的门,门后是一条通往地下的楼梯,李明沿着楼梯走下去,楼梯尽头是一个巨大的地下室,地下室里堆满了古老的箱子和物品。 + 5.他开始一一检查这些箱子,就在这时,他听到了一阵奇怪的声音,李明停下手中的动作,仔细倾听,声音越来越近,他感到有些不安。 + 6.突然,一个黑影从箱子后面窜了出来,李明吓得跳了起来,黑影原来是一只老鼠,他松了一口气,继续寻找。 + 7.经过一番努力,他终于找到了一个古老的箱子,箱子上刻着奇异的图案,李明小心翼翼地打开了箱子,里面是一张地图和一些金币。 + 8.他兴奋地拿起地图,地图上标记着宝藏的确切位置,李明决定继续他的探险之旅,他离开了古堡,踏上了寻找宝藏的路。 + + ## + + 最后再强调,你作为一位优秀的小说编辑,每一次输出都要严格遵守<要求>,一步一步慢慢思考,参考<示例>的格式,一步一步思考,按顺序执行<要求>,不需要做解释说明,只呈现最后的结果,严格要求理解全文之后再进行分组,每组中不同的<句子>用逗号隔开,不要修改原文任何内容,无需要在输出用户,直接输出Ai部分,Ai部分前面一定要加上序号,严格执行不要去改变原文的内容和顺序,严格执行每一个你输出的序号后面完整的句子中的文字内容不少于15字不多于35字,连续输出,严格执行两句话中不要输出空行。 + + + + 原文部分: + {textContent} + + 台词部分: + {textContent}` + } + ] +} diff --git a/src/define/data/aiData/aiPrompt/aiWordMerge/aiWordMergeShort.ts b/src/define/data/aiData/aiPrompt/aiWordMerge/aiWordMergeShort.ts new file mode 100644 index 0000000..babbbcf --- /dev/null +++ b/src/define/data/aiData/aiPrompt/aiWordMerge/aiWordMergeShort.ts @@ -0,0 +1,108 @@ +export const AIWordMergeShort = { + model: 'deepseek-chat', + stream: false, + temperature: 0.3, + messages: [ + { + role: 'system', + content: + '你是一位优秀的分镜师,你有很强的阅读能力,理解能力,我会给你一段文案,请把我给你的文案进行文案分镜头。根据上下文结合,按照镜头语言的可表达性,哪几句话可以组合成一个镜头,以便分镜的制作。严格遵守把组合好的文案分隔开发给我,不要发送与文案无关的内容。文案的格式为一行一个<句子>,一个镜头多句时逗号隔开,句末尾无标点,严格执行每一个完整的句子不少于15字不多于35字,请你理解全文之后,根据上下文内容按照以下要求段落分行:' + }, + { + role: 'user', + content: `你是一位优秀的分镜师,你有很强的阅读能力,理解能力,我会给你一段文案,请把我给你的文案进行文案分镜头。根据上下文结合,按照镜头语言的可表达性,哪几句话可以组合成一个镜头,以便分镜的制作。严格遵守把组合好的文案分隔开发给我,不要发送与文案无关的内容。文案的格式为一行一个<句子>,一个镜头多句时逗号隔开,句末尾无标点,严格执行每一个完整的句子不少于15字不多于35字,请你理解全文之后,根据上下文内容按照以下要求段落分行: + + ## 要求 + + 严格执行以下要求进行小说内容的段落分行: + + 1. 严格要求根据场景转换进行段落分行:当故事从一个场景切换到另一个场景时,请另起一行,用新的段落来表示。 + 2. 严格要求根据人物更替进行段落分行:当焦点从一个角色转移到另一个角色时,请另起一行,用新的段落来表示。 + 3. 严格要求根据心理活动或情感变化进行段落分行:角色的内心活动或情感变化时,请另起一行,用新的段落来表示。 + 4. 严格要求根据节奏和强调进行段落分行:在需要控制故事的节奏或强调某些内容时,请另起一行,用新的段落来表示。 + 5. 严格要求根据结构上的需要进行段落分行:在小说的结构需要换行时,比如出现列举、对比、并列时,请另起一行,用新的段落来表示。 + 6. 严格要求根据<转折词>中的词语进行自动换行:在小说内容出现<转折词>中的词语时,请另起一行,用新的段落来表示。 + 7. 请理解全文之后,根据上下文内容,严格按照以上要求进行段落分行,不要修改原文任何内容。 + 8. 每段中不同的<句子>用逗号隔开,一段一行,连续输出,严格执行不要输出空行。 + 9. 严格执行不要去改变原文的内容和顺序,不需要做数字编号排序标注。 + + ## 转折词 + + 因为;原先;事先;很久;以前;不久前;过不多久;随之;随后;接下来;曾几何时;顷刻之间;片刻;首先;其次;最后;第一;一则;再则;其一;进一步说;相应的;无独有偶;再说;此外;还有;更有甚者;此外;另外;补充一点;除此之外;其实;实际上;确切的说;老实讲;不瞒你说;说句心里话;要不;但是;不过;然偶;闲话少说;言归正传;要是;不管怎样;无论如何;无论;不论;要不是这样;否则;退一步说;自然;诚然;固然;当然;终于;果然;不出所料;果不其然;果真;难怪;怪不得;原来如此;所以;于是;因此;因而;这就是说;换句话说;也就是说;具体来说;具体地说;还有;另外;再说;补充一句;顺便说一下;附带说几句;总之;一句话;总的来看;总而言之;谁知;哪料到;突然;猛然间;岂料;岂知;竟然;偏偏;可惜;不料;没想到;殊不知;不用说;由此可见;显然;毫无疑问;可以肯定;这说明;同样;相比之下;与此相比;相反;反之;虽然;但是;尽管;转折常用的关联词;虽然;但是;尽管;还是;如果;要是;那么;即便;要是;一边;一边;一会儿;一会儿;既;又;不但;而且;不光;也;不仅;还;不但不;反而;是;还是;要么;要么;或者;与其;不如;宁可;宁愿;也不;决不;所以;之所以;是因为;由于;因此;既然;只要;只有;无论;不论;不管;任凭;首先;然后;首先;接着;不堪;居然; + + ## 示例 + + 用户: + 李明在图书馆里翻阅着一本旧书 + 突然,书页中掉出一张泛黄的信纸 + 信纸上写着一段神秘的文字 + 他好奇地读了起来 + 信中提到了一个古老的传说 + 传说中藏有宝藏的秘密 + 李明决定去探索这个传说 + 第二天,他来到了信中提到的地点 + 那里是一个废弃的古堡 + 古堡周围长满了杂草 + 李明小心翼翼地走进了古堡 + 古堡内部昏暗而阴森 + 他开始在古堡中寻找线索 + 突然,一阵风吹过 + 李明感到一阵寒意 + 他继续前进,发现了一扇隐藏的门 + 门后是一条通往地下的楼梯 + 李明沿着楼梯走下去 + 楼梯尽头是一个巨大的地下室 + 地下室里堆满了古老的箱子和物品 + 他开始一一检查这些箱子 + 就在这时,他听到了一阵奇怪的声音 + 李明停下手中的动作,仔细倾听 + 声音越来越近,他感到有些不安 + 突然,一个黑影从箱子后面窜了出来 + 李明吓得跳了起来 + 黑影原来是一只老鼠 + 他松了一口气,继续寻找 + 经过一番努力,他终于找到了一个古老的箱子 + 箱子上刻着奇异的图案 + 李明小心翼翼地打开了箱子 + 里面是一张地图和一些金币 + 他兴奋地拿起地图 + 地图上标记着宝藏的确切位置 + 李明决定继续他的探险之旅 + 他离开了古堡,踏上了寻找宝藏的路 + + AI: + 1. 李明在图书馆里翻阅着一本旧书,突然,书页中掉出一张泛黄的信纸。 + 2. 信纸上写着一段神秘的文字,他好奇地读了起来。 + 3. 信中提到了一个古老的传说,传说中藏有宝藏的秘密。 + 4. 李明决定去探索这个传说。 + 5. 第二天,他来到了信中提到的地点,那里是一个废弃的古堡。 + 6. 古堡周围长满了杂草,李明小心翼翼地走进了古堡。 + 7. 古堡内部昏暗而阴森,他开始在古堡中寻找线索。 + 8. 突然,一阵风吹过,李明感到一阵寒意。 + 9. 他继续前进,发现了一扇隐藏的门。 + 10. 门后是一条通往地下的楼梯,李明沿着楼梯走下去。 + 11. 楼梯尽头是一个巨大的地下室,地下室里堆满了古老的箱子和物品。 + 12. 他开始一一检查这些箱子,就在这时,他听到了一阵奇怪的声音。 + 13. 李明停下手中的动作,仔细倾听。 + 14. 声音越来越近,他感到有些不安。 + 15. 突然,一个黑影从箱子后面窜了出来,李明吓得跳了起来。 + 16. 黑影原来是一只老鼠,他松了一口气,继续寻找。 + 17. 经过一番努力,他终于找到了一个古老的箱子,箱子上刻着奇异的图案。 + 18. 李明小心翼翼地打开了箱子,里面是一张地图和一些金币。 + 19. 他兴奋地拿起地图,地图上标记着宝藏的确切位置。 + 20. 李明决定继续他的探险之旅,他离开了古堡,踏上了寻找宝藏的路。 + + ## + + 最后再强调,你作为一位优秀的小说编辑,每一次输出都要严格遵守<要求>,一步一步慢慢思考,参考<示例>的格式,一步一步思考,按顺序执行<要求>,不需要做解释说明,只呈现最后的结果,严格要求理解全文之后再进行分组,每组中不同的<句子>用逗号隔开,不要修改原文任何内容,无需要在输出用户,直接输出Ai部分,Ai部分前面一定要加上序号,严格执行不要去改变原文的内容和顺序,严格执行每一个你输出的序号后面完整的句子中的文字内容不少于15字不多于35字。 + + ## 原文部分 + + {textContent} + + ## 台词部分 + + {textContent}` + } + ] +} diff --git a/src/define/data/apiData.ts b/src/define/data/apiData.ts new file mode 100644 index 0000000..bf28d18 --- /dev/null +++ b/src/define/data/apiData.ts @@ -0,0 +1,80 @@ +export const apiDefineData = [ + { + label: 'LAI API - 香港', + value: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65', + id: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65', + gpt_url: 'https://api.laitool.cc/v1/chat/completions', + mj_url: { + imagine: 'https://api.laitool.cc/mj/submit/imagine', + describe: 'https://api.laitool.cc/mj/submit/describe', + update_file: 'https://api.laitool.cc/mj/submit/upload-discord-images', + once_get_task: 'https://api.laitool.cc/mj/task/${id}/fetch' + }, + d3_url: { + image: 'https://api.laitool.cc/v1/images/generations' + }, + buy_url: 'https://api.laitool.cc/register?aff=RCSW' + }, + { + label: 'LAI API - 美国', + value: '2b443f53-ba12-42b3-a57c-e4df92685c73', + id: '2b443f53-ba12-42b3-a57c-e4df92685c73', + gpt_url: 'https://laitool.net/v1/chat/completions', + mj_url: { + imagine: 'https://laitool.net/mj/submit/imagine', + describe: 'https://laitool.net/mj/submit/describe', + update_file: 'https://laitool.net/mj/submit/upload-discord-images', + once_get_task: 'https://laitool.net/mj/task/${id}/fetch' + }, + d3_url: { + image: 'https://laitool.net/v1/images/generations' + }, + buy_url: 'https://laitool.net/register?aff=RCSW' + } +] + +/** + * 通过ID获取API配置 + * @description 通过ID获取API配置 + * @param id API ID + * @returns + */ +export function GetApiDefineDataById(id: string) { + let mj_api_url_index = apiDefineData.findIndex((item) => item.value == id) + if (mj_api_url_index == -1) { + throw new Error('没有找到对应的API的配置,请先检查配置') + } + return apiDefineData[mj_api_url_index] +} + +/** + * 获取支持指定类型的API + * @param type + * @returns + */ +export function getAPIOptions(type: string) { + switch (type) { + case 'mj': + let options = apiDefineData + .filter((item) => item.mj_url != null) + .map((item) => { + return { + label: item.label, + value: item.value + } + }) + return options + case 'gpt': + let gptOptions = apiDefineData + .filter((item) => item.gpt_url != null) + .map((item) => { + return { + label: item.label, + value: item.value + } + }) + return gptOptions + default: + return [] + } +} diff --git a/src/define/data/bookData.ts b/src/define/data/bookData.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/define/data/imageData.ts b/src/define/data/imageData.ts new file mode 100644 index 0000000..34e884e --- /dev/null +++ b/src/define/data/imageData.ts @@ -0,0 +1,127 @@ +//#region 出图方式 + +import { PromptMergeType } from '../enum/bookEnum' +import { TaskModal } from '../model/task' + +// 出图方式 +export enum ImageCategory { + /** Midjourney 图像生成平台 */ + Midjourney = 'mj', + /** Stable Diffusion 图像生成平台 */ + Stable_Diffusion = 'sd', + /** ComfyUI 图像生成框架 */ + Comfy_UI = 'comfyui', + /** DALL-E 图像生成模型 */ + DALL_E_3 = 'd3', + /** FLUX API 图像生成服务 */ + Flux_API = 'flux-api', + /** FLUX FORGE 图像生成工具 */ + Flux_Forge = 'flux-forge' +} + +/** + * 根据图像生成平台类型获取对应的提示词合并方式 + * + * 该函数将不同的图像生成平台映射到其对应的提示词合并类型,以便系统能够正确处理 + * 各平台特定的提示词格式要求。例如,Midjourney和Stable Diffusion对提示词有不同的格式要求。 + * + * @param {ImageCategory} imageCategory - 图像生成平台类型,如Midjourney、Stable Diffusion等 + * @returns {PromptMergeType} 对应的提示词合并类型 + * - MJ_MERGE: 适用于Midjourney的提示词格式 + * - SD_MERGE: 适用于Stable Diffusion、ComfyUI、DALL-E 3、FLUX相关平台的提示词格式 + * + * @example + * // 获取Midjourney的提示词合并类型 + * const mjMergeType = getMergePromptType(ImageCategory.Midjourney); + * // 返回 PromptMergeType.MJ_MERGE + * + * // 获取Stable Diffusion的提示词合并类型 + * const sdMergeType = getMergePromptType(ImageCategory.Stable_Diffusion); + * // 返回 PromptMergeType.SD_MERGE + */ +export function getMergePromptType(imageCategory: ImageCategory): PromptMergeType { + switch (imageCategory) { + case ImageCategory.Midjourney: + return PromptMergeType.MJ_MERGE + case ImageCategory.Stable_Diffusion: + return PromptMergeType.SD_MERGE + case ImageCategory.Comfy_UI: + return PromptMergeType.SD_MERGE + case ImageCategory.DALL_E_3: + return PromptMergeType.SD_MERGE + case ImageCategory.Flux_API: + return PromptMergeType.SD_MERGE + case ImageCategory.Flux_Forge: + return PromptMergeType.SD_MERGE + default: + return PromptMergeType.MJ_MERGE + } +} + +/** + * 获取所有出图方式的选项列表 + * @returns 包含label和value的选项数组 + */ +export function getImageCategoryOptions(): Array<{ label: string; value: string }> { + return [ + { label: 'Midjourney', value: ImageCategory.Midjourney }, + { label: 'Stable Diffusion', value: ImageCategory.Stable_Diffusion }, + { label: 'ComfyUI', value: ImageCategory.Comfy_UI }, + { label: 'DALL-E', value: ImageCategory.DALL_E_3 }, + { label: 'FLUX API', value: ImageCategory.Flux_API }, + { label: 'FLUX FORGE', value: ImageCategory.Flux_Forge } + ] +} + +/** + * 获取出图方式的标签 + * @param value + * @returns + */ +export function getImageCategoryLabel(value: string): TaskModal.TaskStatus { + const options = getImageCategoryOptions() + const option = options.find((option) => option.value === value) + if (option) { + return { + status: option.value, + label: option.label, + type: 'success' + } as TaskModal.TaskStatus + } else { + return { + status: 'UNKNOWN', + label: 'UNKNOWN', + type: 'warning' + } + } +} + +//#endregion + +//#region 图转视频方式 + +export enum ImageToVideoCategory { + /** runway 生成视频 */ + RUNWAY = 'runway', + /** luma 生成视频 */ + LUMA = 'luma', + /** 可灵生成视频 */ + KLING = 'kling', + /** Pika 生成视频 */ + PIKA = 'pika' +} + +/** + * 获取所有出图方式的选项列表 + * @returns 包含label和value的选项数组 + */ +export function getImageToVideoCategoryOptions(): Array<{ label: string; value: string }> { + return [ + { label: 'Runway', value: ImageToVideoCategory.RUNWAY }, + { label: 'Luma', value: ImageToVideoCategory.LUMA }, + { label: '可灵', value: ImageToVideoCategory.KLING }, + { label: 'Pika', value: ImageToVideoCategory.PIKA } + ] +} + +//#endregion diff --git a/src/define/data/mjData.ts b/src/define/data/mjData.ts new file mode 100644 index 0000000..de2f2b6 --- /dev/null +++ b/src/define/data/mjData.ts @@ -0,0 +1,213 @@ +//#region MJ 出图方式 + +/** + * MJ 图像生成方式,目前仅支持 API 模式 + */ +export enum ImageGenerateMode { + /** API 模式 */ + MJ_API = 'mj_api', + + //本地MJ + LOCAL_MJ = 'local_mj', + + // 代理MJ + REMOTE_MJ = 'remote_mj', + + // 浏览器模式 + BROWSER_MJ = 'browser_mj', + + // 本地 SD + LOCAL_SD = 'local_sd', + + // ComfyUI + ComfyUI = 'comfyui', + // flux-api + FLUX_API = 'flux-api', + + // flxu-forge + FLUX_FORGE = 'flux-forge', + + // 导入 + IMPORT = 'import' +} +/** + * 获取所有出图方式的选项列表 + * @description 获取所有出图方式的选项列表 + * @returns + */ +export function getImageGenerateModeOptions(): Array<{ label: string; value: string }> { + return [{ label: 'API模式', value: ImageGenerateMode.MJ_API }] +} + +//#endregion + +//#region 生图机器人 + +export enum MJRobotType { + // MJ + MJ = 'mj', + + // niji + NIJI = 'niji' +} + +/** + * 获取MJ的机器人列表 + * @returns + */ +export function getMJRobotOptions() { + return [ + { + label: 'MJ', + value: MJRobotType.MJ + }, + { + label: 'NIJI', + value: MJRobotType.NIJI + } + ] +} + +//#endregion + +//#region 机器人出图模型 + +/** + * 获取机器人对应的出图模型 + */ +export function getMJRobotModelOptions(mjRobot?: MJRobotType) { + let allRobotModel = [ + { + label: 'MJ V7.0', + text: 'v 7', + type: MJRobotType.MJ, + value: '0d33ae62-e0a8-4429-89e4-304bfd20cd6f' + }, + { + label: 'MJ V6.1', + text: 'v 6.1', + type: MJRobotType.MJ, + value: 'f799de81-91da-413f-89fd-58b2ff8174db' + }, + { + label: 'MJ V6.0', + text: 'v 6', + type: MJRobotType.MJ, + value: '3e6473ab-9a64-4574-9a38-f5c75af552b6' + }, + { + label: 'MJ V5.2', + text: 'v 5.2', + type: MJRobotType.MJ, + value: '27a0d30e-f46c-4684-96c8-d91334deb94f' + }, + { + label: 'MJ V5.1', + text: 'v 5.1', + type: MJRobotType.MJ, + value: 'e1226715-e969-44c4-b18b-f2ad5dae5d2f' + }, + { + label: 'MJ V5.0', + text: 'v 5', + type: MJRobotType.MJ, + value: 'afb7bea1-4eda-46ea-8165-34701b4566bf' + }, + { + label: 'MJ V4.0', + text: 'v 4', + type: MJRobotType.MJ, + value: 'd05b8497-7f4a-4890-8fac-89f1803984d2' + }, + { + label: 'NIJI V6', + text: 'niji 6', + type: MJRobotType.NIJI, + value: '99377cad-c103-4cee-a958-86a104879328' + }, + { + label: 'NIJI V5', + text: 'niji 5', + type: MJRobotType.NIJI, + value: '53cec077-9885-4635-ab18-e021066b2c4c' + }, + { + label: 'NIJI V4', + text: 'niji 4', + type: MJRobotType.NIJI, + value: '6a7199fe-6e0d-40a9-9772-b5eb3d2e2e66' + } + ] + + switch (mjRobot) { + case MJRobotType.MJ: + return allRobotModel.filter((item) => item.type == MJRobotType.MJ) + case MJRobotType.NIJI: + return allRobotModel.filter((item) => item.type == MJRobotType.NIJI) + default: + return allRobotModel + } +} + +//#endregion + +//#region MJ 出图比例 +/** + * 获取MJ出图的比例Options + * @returns + */ +export function getMJImageScaleOptions() { + return [ + { + label: '1:1', + value: '3e2772f2-041c-49c6-ba13-d0ed120310b8' + }, + { + label: '4:3', + value: 'fcef555c-1958-4082-88fe-434782aa8151' + }, + { + label: '3:4', + value: '13f71d53-73a3-4c9b-9c1e-6e7e939aee73' + }, + { + label: '16:9', + value: 'bf33ce1a-15cd-4901-b38e-89543cf14a1f' + }, + { + label: '9:16', + value: 'fd4641e2-97f4-4a86-8616-4965e05f3348' + } + ] +} + +//#endregion + +//#region 出图速率 + +export enum MJSpeed { + // 快速 + FAST = 'FAST', + + // 休闲 + RELAX = 'RELAXED' +} + +/** + * 获取MJ的速度Options + * @returns + */ +export function getMJSpeedOptions() { + return [ + { + label: '快速', + value: MJSpeed.FAST + }, + { + label: '慢速', + value: MJSpeed.RELAX + } + ] +} + +//#endregion diff --git a/src/define/data/presetData.ts b/src/define/data/presetData.ts new file mode 100644 index 0000000..afe227d --- /dev/null +++ b/src/define/data/presetData.ts @@ -0,0 +1,53 @@ +/** + * 预设数据类型 + */ +export enum PresetCategory { + /** 角色 */ + Character = 'character', + /** 场景 */ + Scene = 'scene', + /** 风格 */ + Style = 'style', + /** 其他 */ + Other = 'other' +} + +/** + * 获取所有预设数据类型的选项列表 + * @returns 包含label和value的选项数组 + */ +export function getPresetCategoryOptions(): Array<{ label: string; value: string }> { + return [ + { label: '风格预设', value: PresetCategory.Style }, + { label: '人物预设', value: PresetCategory.Character }, + { label: '场景预设', value: PresetCategory.Scene } + ] +} + +/** + * 获取预设数据类型的标签 + * @param value 预设数据类型的值 + * @returns 对应的标签,如果未找到则返回空字符串 + */ +export function getPresetCategoryLabel(value: string): string { + const option = getPresetCategoryOptions().find((item) => item.value === value) + return option ? option.label : '未知类型' +} + +/** + * 获取提示词的标签 + * @param value 提示词的值 + * @returns 对应的标签,如果未找到则返回'未知类型' + */ +export function getPromptSortLabel(value: string): string { + const option = getPresetCategoryOptions().find((item) => item.value === value) + if (option) { + return option.label + } else { + if (value == 'prompt') { + return '提示词' + } else { + return '未知类型' + } + } +} diff --git a/src/define/data/softwareData.ts b/src/define/data/softwareData.ts new file mode 100644 index 0000000..5d9b77e --- /dev/null +++ b/src/define/data/softwareData.ts @@ -0,0 +1,54 @@ +interface ISoftwareData { + /** 软件版本号 */ + version: string + /** 发布日期 */ + date: string + /** 更新说明列表 */ + notes: string[] + /** 系统信息 */ + systemInfo: { + /** 快速开始 */ + quickStart: string + /** 使用文档 */ + documentationUrl: string + /** 更新文档 */ + updateUrl: string + /** 软件文档 */ + softwareUrl: string + /** WIKI */ + wikiUrl: string + } +} + +export const SoftwareData: ISoftwareData = { + version: 'V3.4.2', + date: '2025-08-08', + notes: [ + '1. 新增图/文转视频菜单界面,专注实现图/文转视频(目前只集成了 MJ VIDEO)', + ' • 全新的界面排列,小说列表和批次任务更加分明', + ' • 添加转视频进度,在主界面即可看到转视频的比例', + ' • 单独的界面去处理图转视频,避免表格数据过多繁琐', + ' • 新增分页显示,界面加载更快,也可切换不分页', + ' • 单独操作面板,参数修改处理更加清晰,支持多种模式显示', + ' • 批量设置转视频配置,可以批量修改分类', + ' • 友好的选择视频界面', + '2. 重写软件导出剪映,修复若干草稿导出问题', + ' • 修复导出剪映文案和图片对齐问题,解决时长越长越明显的对不上问题', + ' • 修复导出草稿关键帧部分问题', + ' • 导出的文案通过分镜自动导入,不再需要手动选择SRT', + '3. 美化生成草稿界面弹窗,优化部分逻辑', + ' • 删除选择SRT文件,SRT根据聚合推文中导入的SRT自动生成草稿', + ' • 只需选择配音文件即可,配音文件和导入的SRT请自行对应', + ' • 背景音乐不在内部设置,自行选择文件夹或者是MP3、WAV文件', + ' • 背景音乐选择文件夹则读取文件夹,随机获取一个', + ' • 背景音乐选择指定的音乐文件则使用选择的' + ], + systemInfo: { + quickStart: '快速开始', + documentationUrl: 'https://rvgyir5wk1c.feishu.cn/wiki/WdaWwAfDdiLOnjkywIgcaQoKnog', + updateUrl: 'https://pvwu1oahp5m.feishu.cn/docx/CAjGdTDlboJ3nVx0cQccOuNHnvd', + softwareUrl: 'https://pvwu1oahp5m.feishu.cn/docx/FONZdfnrOoLlMrxXHV0czJ3jnkd', + wikiUrl: + 'https://rvgyir5wk1c.feishu.cn/wiki/space/7481893355360190492?ccm_open_type=lark_wiki_spaceLink&open_tab_from=wiki_home' + } +} diff --git a/src/define/db/model/book.ts b/src/define/db/model/book.ts new file mode 100644 index 0000000..a3c5b77 --- /dev/null +++ b/src/define/db/model/book.ts @@ -0,0 +1,64 @@ +// @ts-ignore +import Realm from 'realm' +import { BookType } from '@/define/enum/bookEnum' + +export class BookModel extends Realm.Object { + id!: string + no!: number + name!: string + bookFolderPath!: string + imageFolder!: string | undefined + type!: BookType + oldVideoPath!: string | undefined + srtPath!: string | undefined + audioPath!: string | undefined + draftDepend?: string // 草稿依赖 + draftSrtStyle!: string | undefined // 草稿字幕样式 + backgroundMusic!: string | undefined // 背景音乐ID + friendlyReminder!: string | undefined // 友情提示 + updateTime!: Date + createTime!: Date + version!: string + imageStyle!: string[] | undefined // 软件内置的样式 + autoAnalyzeCharacter!: string | undefined // 自动分析角色设置 + customizeImageStyle!: string[] | undefined // 自定义的样式 + videoConfig!: string | undefined // 合成视频设置 + prefixPrompt!: string | undefined // 前缀 + suffixPrompt!: string | undefined // 后缀 + subtitlePosition!: string | undefined // 字幕位置 + watermarkPosition!: string | undefined // 水印位置,一个json数组字符串 + imageType!: string | undefined // 出图类型 + + static schema: Realm.ObjectSchema = { + name: 'Book', + properties: { + id: 'string', + no: 'int', + name: 'string', + bookFolderPath: 'string', + type: 'string', + oldVideoPath: 'string?', + srtPath: 'string?', + audioPath: 'string?', + draftDepend: 'string?', + draftSrtStyle: 'string?', + backgroundMusic: 'string?', + friendlyReminder: 'string?', + imageFolder: 'string?', + updateTime: 'date', + createTime: 'date', + version: 'string', + imageStyle: 'string?[]', + autoAnalyzeCharacter: 'string?', + customizeImageStyle: 'string?[]', + videoConfig: 'string?', + prefixPrompt: 'string?', + suffixPrompt: 'string?', + subtitlePosition: 'string?', + watermarkPosition: 'string?', + imageType: 'string?' + }, + // 主键为_id + primaryKey: 'id' + } +} diff --git a/src/define/db/model/bookTask.ts b/src/define/db/model/bookTask.ts new file mode 100644 index 0000000..4843482 --- /dev/null +++ b/src/define/db/model/bookTask.ts @@ -0,0 +1,104 @@ +import { ImageCategory } from '@/define/data/imageData' +import { BookTaskStatus } from '@/define/enum/bookEnum' +import Realm, { ObjectSchema } from 'realm' + +export class ImageDefineModel extends Realm.Object { + label!: string + key!: string + value!: string + children!: string + type!: string + prompt!: string + image_url!: string + cref_cw!: number + lora!: string + chinese_prompt!: string + lora_weight!: number + show_image!: string + isShow!: true + static schema: ObjectSchema = { + name: 'ImageDefine', + properties: { + label: 'string', + key: 'string', + value: 'string', + children: 'string', + type: 'string', + prompt: 'string', + image_url: 'string', + cref_cw: 'int', + lora: 'string', + chinese_prompt: 'string', + lora_weight: 'int', + show_image: 'string', + isShow: 'bool' + }, + primaryKey: 'key' + } +} + +export class BookTaskModel extends Realm.Object { + id!: string + no!: number + bookId!: string + name!: string + generateVideoPath!: string | null + srtPath!: string | null + draftDepend?: string // 草稿依赖 + audioPath!: string | null + draftSrtStyle!: string | null // 草稿字幕样式 + backgroundMusic!: string | null // 背景音乐ID + friendlyReminder!: string | null // 友情提示 + imageFolder!: string | null + imageStyle!: string | null // 软件内置的样式 + cacheImageList!: string[] | null // 缓存的图片列表 + autoAnalyzeCharacter!: string | null // 自动分析角色设置 + customizeImageStyle!: string[] | null // 自定义的样式 + videoConfig!: string | null // 合成视频设置 + prefixPrompt!: string | null // 前缀 + suffixPrompt!: string | null // 后缀 + styleList!: string[] | null + status!: BookTaskStatus + errorMsg!: string | null + isAuto!: boolean // 是否自动 + openVideoGenerate!: boolean | null // 是否开启视频生成 + updateTime!: Date + createTime!: Date + imageCategory!: ImageCategory // 图片出图方式 + subImageFolder!: string[] | null // 出图的子文件夹 + + static schema: ObjectSchema = { + name: 'BookTask', + properties: { + id: 'string', + bookId: { type: 'string', indexed: true }, + no: 'int', + name: 'string', + generateVideoPath: 'string?', + srtPath: 'string?', + draftDepend: 'string?', + audioPath: 'string?', + draftSrtStyle: 'string?', + backgroundMusic: 'string?', + friendlyReminder: 'string?', + imageFolder: 'string?', + subImageFolder: 'string?[]', + cacheImageList: 'string?[]', + imageStyle: 'string?[]', + autoAnalyzeCharacter: 'string?', + customizeImageStyle: 'string?[]', + videoConfig: 'string?', + prefixPrompt: 'string?', + suffixPrompt: 'string?', + status: 'string', + openVideoGenerate: 'bool?', + errorMsg: 'string?', + isAuto: 'bool', + updateTime: 'date', + createTime: 'date', + imageCategory: 'string' + }, + // 主键为_id + primaryKey: 'id' + } +} diff --git a/src/define/db/model/bookTaskDetail.ts b/src/define/db/model/bookTaskDetail.ts new file mode 100644 index 0000000..6143b53 --- /dev/null +++ b/src/define/db/model/bookTaskDetail.ts @@ -0,0 +1,236 @@ +import Realm, { ObjectSchema } from 'realm' +import { BookTaskStatus, MJAction } from '@/define/enum/bookEnum' +import { MJImageType } from '@/define/enum/mjEnum' + +export class Subtitle extends Realm.Object { + startTime!: number + endTime!: number + srtValue!: string + id!: string + static schema = { + name: 'Subtitle', + properties: { + startTime: 'int', + endTime: 'int', + srtValue: 'string', + id: 'string' + }, + primaryKey: 'id' + } +} + +export class MJMessage extends Realm.Object { + id!: string + mjApiUrl!: string | null + progress!: number + category!: MJImageType + imageClick!: string | null // 图片点击(显示的小的) + imageShow!: string | null // 图片实际的地址 + messageId!: string // 消息ID(可以是MJ中的,也可以是API中的) + action!: MJAction // 动作(生图,反推之类) + status!: string // 状态 + message!: string | null // 消息 + static schema: ObjectSchema = { + name: 'MJMessage', + properties: { + id: 'string', + mjApiUrl: 'string?', + progress: 'int', + category: 'string', + imageClick: 'string?', + imageShow: 'string?', + messageId: 'string', + action: 'string', + status: 'string', + message: 'string?' + }, + primaryKey: 'id' + } +} + +export class VideoMessage extends Realm.Object { + id!: string + msg!: string | null + videoType!: string + prompt!: string | null + style!: string | null + imageUrl!: string | null + model!: string | null + bookTaskDetailId!: string + status!: string | null + videoUrl!: string | null + taskId!: string | null + runwayOptions!: string | null // 生成视频的一些设置 + lumaOptions!: string | null // 生成视频的一些设置 + klingOptions!: string | null // 生成视频的一些设置 + messageData!: string | null + static schema: ObjectSchema = { + name: 'VideoMessage', + properties: { + id: 'string', + msg: 'string?', + videoType: 'string', + bookTaskDetailId: 'string?', + prompt: 'string?', + style: 'string?', + imageUrl: 'string?', + model: 'string?', + status: 'string?', + videoUrl: 'string?', + taskId: 'string?', + runwayOptions: 'string?', + lumaOptions: 'string?', + klingOptions: 'string?', + messageData: 'string?' + }, + primaryKey: 'id' + } +} + +export class WebuiConfig extends Realm.Object { + sampler_name!: string // 采样器名称 + negative_prompt!: string // 负面提示 + batch_size!: number // 批次大小 + steps!: number // 步数 + cfg_scale!: number // 提示词相关性 + denoising_strength!: number // 降噪强度 + width!: number // 宽度 + height!: number // 高度 + seed!: number // 种子 + init_images!: string // 初始图片(垫图的图片地址) + id!: string + + static schema: ObjectSchema = { + name: 'WebuiConfig', + properties: { + sampler_name: 'string', + negative_prompt: 'string', + batch_size: 'int', + steps: 'int', + cfg_scale: 'int', + denoising_strength: 'int', + width: 'int', + height: 'int', + seed: 'int', + init_images: 'string', + id: 'string' + }, + primaryKey: 'id' + } +} + +export class SDConfig extends Realm.Object { + checkpoints!: string // 大模型 + api!: string // api地址 + model!: string // 生图方式 + webuiConfig!: WebuiConfig + id!: string + static schema: ObjectSchema = { + name: 'SDConfig', + properties: { + checkpoints: 'string', + api: 'string', + model: 'string', + webuiConfig: 'WebuiConfig', + id: 'string' + }, + primaryKey: 'id' + } +} + +// 放反推的提示词的对象 +export class ReversePrompt extends Realm.Object { + id!: string + bookTaskDetailId!: string + prompt!: string + promptCN!: string + isSelect!: boolean + static schema: ObjectSchema = { + name: 'ReversePrompt', + properties: { + id: 'string', + bookTaskDetailId: 'string', + prompt: 'string', + promptCN: 'string', + isSelect: 'bool' + }, + primaryKey: 'id' + } +} + +export class BookTaskDetailModel extends Realm.Object { + id!: string + no!: number + name!: string + bookId!: string + bookTaskId!: string + videoPath!: string | null // 视频地址 + generateVideoPath!: string | null // 生成视频地址 + audioPath!: string | null // 音频地址 + word!: string | null // 文案 + oldImage!: string | null // 旧图片(用于SD的图生图) + afterGpt!: string | null // GPT生成的文案 + startTime!: number | null // 开始时间 + endTime!: number | null // 结束时间 + timeLimit!: string | null // 事件实现(0 -- 3000) + subValue!: string | null // 包含的字幕数据 + characterTags!: string[] | null // 角色标签 + sceneTags!: string[] | null // 场景标签 + styleTags!: string[] | null // 风格标签 + promptSort!: string | null // 提示词排序 + gptPrompt!: string | null // GPT提示词 + mjMessage!: MJMessage | null // MJ消息 + videoMessage!: VideoMessage | null // 视频消息 + outImagePath!: string | null // 输出图片地址 + subImagePath!: string[] | null // 子图片地址 + imageLock!: boolean // 图片锁 + prompt!: string | null // 提示 + adetailer!: boolean // 是否开启修脸 + sdConifg!: SDConfig | null // SD配置 + reversePrompt!: ReversePrompt[] | null // 反推的提示词(数组) + subtitlePosition!: string | null // 字幕位置 + status!: BookTaskStatus + createTime!: Date + updateTime!: Date + + static schema: ObjectSchema = { + name: 'BookTaskDetail', + properties: { + id: 'string', + no: 'int', + name: 'string', + bookId: { type: 'string', indexed: true }, + bookTaskId: { type: 'string', indexed: true }, + videoPath: 'string?', + generateVideoPath: 'string?', // 生成视频地址 + audioPath: 'string?', + word: 'string?', + oldImage: 'string?', + afterGpt: 'string?', + startTime: 'int?', + endTime: 'int?', + timeLimit: 'string?', + subValue: 'string?', + reversePrompt: { type: 'list', objectType: 'ReversePrompt' }, + characterTags: { type: 'list', objectType: 'string' }, + sceneTags: 'string[]', + styleTags: 'string[]', + promptSort: 'string?', + gptPrompt: 'string?', + mjMessage: 'MJMessage?', + videoMessage: 'VideoMessage?', + outImagePath: 'string?', + subImagePath: 'string[]', + imageLock: 'bool', + prompt: 'string?', + adetailer: 'bool', + sdConifg: 'SDConfig?', + subtitlePosition: 'string?', + status: 'string', + createTime: 'date', + updateTime: 'date' + }, + // 主键为_id + primaryKey: 'id' + } +} diff --git a/src/define/db/model/options.ts b/src/define/db/model/options.ts new file mode 100644 index 0000000..d8bd1e6 --- /dev/null +++ b/src/define/db/model/options.ts @@ -0,0 +1,16 @@ +import Realm, { ObjectSchema } from 'realm' + +export class OptionModel extends Realm.Object { + key!: string; + value!: string; + type!: string; + static schema: ObjectSchema = { + name: 'Option', + properties: { + key: 'string', + value: 'string', + type: 'string' + }, + primaryKey: 'key' + } +} diff --git a/src/define/db/model/preset.ts b/src/define/db/model/preset.ts new file mode 100644 index 0000000..4ba2525 --- /dev/null +++ b/src/define/db/model/preset.ts @@ -0,0 +1,38 @@ +import Realm, { ObjectSchema } from 'realm' + +export class PresetModel extends Realm.Object { + id!: string + label!: string + type!: string + showImage?: string[] + prompt!: string + chinesePrompt?: string[] + imageUrl?: string + srefSw?: number + crefCw?: number + lora?: string + loraWeight?: number + isShow!: boolean + aliases?: string[] + createTime!: Date + static schema: ObjectSchema = { + name: 'Preset', + properties: { + id: 'string', + label: 'string', + type: 'string', + showImage: 'string?[]', + imageUrl: 'string?', + prompt: 'string', + chinesePrompt: 'string?', + srefSw: 'int?', + crefCw: 'int?', + lora: 'string?', + loraWeight: 'int?', + isShow: 'bool', + aliases: 'string?[]', + createTime: 'date' + }, + primaryKey: 'id' + } +} diff --git a/src/define/db/model/taskList.ts b/src/define/db/model/taskList.ts new file mode 100644 index 0000000..2a9888b --- /dev/null +++ b/src/define/db/model/taskList.ts @@ -0,0 +1,40 @@ +import Realm, { ObjectSchema } from 'realm' +import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum' + +export class TaskListModel extends Realm.Object { + id!: string + bookId!: string + bookTaskId!: string + bookTaskDetailId!: string + name!: string // 任务名称,小说名+批次名+分镜名 + type!: BookBackTaskType + status!: BookBackTaskStatus + errorMessage!: string | null + executeType!: TaskExecuteType // 任务执行类型,手动还是自动 + createTime!: Date + updateTime!: Date + startTime!: number + endTime!: number + messageName?: string + + static schema: ObjectSchema = { + name: 'TaskList', + properties: { + id: 'string', + bookId: { type: 'string', indexed: true }, + bookTaskId: { type: 'string', indexed: true }, + bookTaskDetailId: { type: 'string', indexed: true }, + name: 'string', + type: 'string', + status: 'string', + errorMessage: 'string?', + executeType: { type: 'string', default: TaskExecuteType.AUTO }, + createTime: 'date', + updateTime: 'date', + startTime: 'int', + endTime: 'int', + messageName: 'string?' + }, + primaryKey: 'id' + } +} diff --git a/src/define/db/service/base/baseService.ts b/src/define/db/service/base/baseService.ts new file mode 100644 index 0000000..5f42dca --- /dev/null +++ b/src/define/db/service/base/baseService.ts @@ -0,0 +1,36 @@ +// 定义抽象基类 +import Realm from 'realm' + +export abstract class BaseService { + protected realm: Realm | null = null + // 抽象类的构造函数应该是protected,以防止外部直接实例化 + protected constructor() { + // 构造函数逻辑,使用someValue进行初始化 + } + + // 定义抽象方法,子类必须实现,打开数据库连接 + abstract open(dbPath: string): void + + // 关闭数据库连接 + close(): void { + // 关闭数据库的连接,防止内存溢出 + // 实现关闭数据库连接的逻辑 + if (this.realm != null) { + console.log('Closing database connection') + this.realm.close() + this.realm = null // 清理引用,确保垃圾回收 + } + } + transaction(func: () => unknown): void { + if (this.realm != null) { + // 判断当前的relam是不是在一个事务中 + if (this.realm.isInTransaction) { + func() + } else { + this.realm.write(() => { + func() + }) + } + } + } +} diff --git a/src/define/db/service/base/realmBase.ts b/src/define/db/service/base/realmBase.ts new file mode 100644 index 0000000..d76c5c7 --- /dev/null +++ b/src/define/db/service/base/realmBase.ts @@ -0,0 +1,79 @@ +import Realm from 'realm' +import { BaseService } from './baseService' +import path from 'path' +import { BookModel } from '../../model/book' +import { + BookTaskDetailModel, + MJMessage, + ReversePrompt, + SDConfig, + Subtitle, + VideoMessage, + WebuiConfig +} from '../../model/bookTaskDetail' +import { TaskListModel } from '../../model/taskList' +import { OptionModel } from '../../model/options' +import { app } from 'electron' +import { BookTaskModel } from '../../model/bookTask' +import { PresetModel } from '../../model/preset' + +// Determine database path based on environment +const isDev = !app.isPackaged +let dbPath = isDev + ? path.resolve(process.cwd(), 'Database/option.realm') // Development path + : path.resolve(app.getPath('userData'), 'Database/option.realm') // Production path + +// 版本迁移 +const migration = (_oldRealm: Realm, _newRealm: Realm) => {} + +export class RealmBaseService extends BaseService { + static instance: RealmBaseService | null = null + protected realm: Realm | null = null + dbpath: string + + protected constructor() { + super() + this.dbpath = dbPath + } + + public static async getInstance() { + if (RealmBaseService.instance === null) { + RealmBaseService.instance = new RealmBaseService() + await RealmBaseService.instance.open() + } + return RealmBaseService.instance + } + + /** + * 创建数据库连接,如果已经存在则直接返回 + * @returns + */ + async open() { + try { + if (this.realm != null) return + // 判断当前全局是不是又当前这个 + const config = { + schema: [ + BookModel, + BookTaskDetailModel, + TaskListModel, + OptionModel, + MJMessage, + VideoMessage, + WebuiConfig, + SDConfig, + ReversePrompt, + Subtitle, + BookTaskModel, + PresetModel + ], + path: this.dbpath, + schemaVersion: 19, // 数据库版本号,修改时需要增加 + migration: migration + } + this.realm = await Realm.open(config) + } catch (error) { + throw error + } + } +} diff --git a/src/define/db/service/book/bookService.ts b/src/define/db/service/book/bookService.ts new file mode 100644 index 0000000..a22f27a --- /dev/null +++ b/src/define/db/service/book/bookService.ts @@ -0,0 +1,365 @@ +import { Book } from '@/define/model/book/book' +import { RealmBaseService } from '../base/realmBase' +import Realm, { UpdateMode } from 'realm' +import { isEmpty } from 'lodash' +import { BookModel } from '../../model/book' +import path from 'path' +import { BookTaskStatus, BookType } from '@/define/enum/bookEnum' +import { CheckFolderExistsOrCreate, CopyFileOrFolder } from '@/define/Tools/file' +import { ImageCategory } from '@/define/data/imageData' +import { getGeneralSetting, getProjectPath } from '@/main/service/option/optionCommonService' +import { SrtHandle } from '@/main/service/common/srtHandle' +import { BookTaskDetailService } from './bookTaskDetailService' +import { BookTaskDetail } from '@/define/model/book/bookTaskDetail' + +export class BookService extends RealmBaseService { + static instance: BookService | null = null + declare realm: Realm + + private constructor() { + super() + } + + /** + * 获取当前实例对象,为空则创建一个新的 + * @returns + */ + public static async getInstance() { + if (BookService.instance === null) { + BookService.instance = new BookService() + await super.getInstance() + } + await BookService.instance.open() + return BookService.instance + } + + /** + * 获取小说信息,没有找到返回null + * @param bookId + */ + async GetBookDataById(bookId: string): Promise { + try { + if (isEmpty(bookId)) { + throw new Error('获取小说信息失败,缺少小说ID') + } + let projectPath: string = await getProjectPath() + + let books = this.realm.objects('Book').filtered('id = $0', bookId) + if (books.length <= 0) { + return null + } else { + // 对返回的数据进行处理 + let resBooks = Array.from(books).map((book) => { + // 这里可以直接操作普通对象 + let bookObj = { + ...book, + bookFolderPath: path.resolve(projectPath, book.bookFolderPath.replace(/\\/g, '/')), + oldVideoPath: book.oldVideoPath + ? path.resolve(projectPath, book.oldVideoPath.replace(/\\/g, '/')) + : '', + imageFolder: book.imageFolder + ? path.resolve(projectPath, book.imageFolder.replace(/\\/g, '/')) + : '' + } + return bookObj + }) + return resBooks[0] + } + } catch (error) { + throw error + } + } + + /** + * 获取小说信息,通过参数查询 + * @returns + */ + async GetBookDataCondition( + bookQuery: Book.QueryBookCondition + ): Promise { + try { + // 获取所有的小说数据,并进行时间降序排序 + let books = this.realm.objects('Book') + let bookLength = books.length + + // 开始开始筛选 + if (bookQuery.id) { + // 查询对应的小说ID的数据 + books = books.filtered('id = $0', bookQuery.id) + } + if (bookQuery.name) { + // 查询对应的小说名字的数据 + books = books.filtered('name CONTAINS $0', bookQuery.name) + } + if (bookQuery.type) { + // 查询对应的小说类型的数据 + books = books.filtered('type = $0', bookQuery.type) + } + + books = books.sorted('updateTime', true) + // 判断是不是有page和pageSize,有的话对查询返回的信息做分页 + if (bookQuery.page && bookQuery.pageSize) { + books = books.slice( + (bookQuery.page - 1) * bookQuery.pageSize, + bookQuery.page * bookQuery.pageSize + ) as unknown as Realm.Results + } + if (books.length <= 0) { + return { + resultBooks: [], + bookLength: 0 + } + } + + let projectPath: string = await getProjectPath() + + // 将realm对象数组转换为普通对象数组 + let res_book = Array.from(books).map((book) => { + // 这里可以直接操作普通对象 + let bookObj = { + ...book, + bookFolderPath: path.resolve(projectPath, book.bookFolderPath.replace(/\\/g, '/')), + oldVideoPath: book.oldVideoPath + ? path.resolve(projectPath, book.oldVideoPath.replace(/\\/g, '/')) + : '', + imageFolder: book.imageFolder + ? path.resolve(projectPath, book.imageFolder.replace(/\\/g, '/')) + : '', + imageStyle: book.imageStyle ? Array.from(book.imageStyle) : [], + customizeImageStyle: book.customizeImageStyle ? Array.from(book.customizeImageStyle) : [] + } as Book.SelectBook + return bookObj + }) + + return { + resultBooks: res_book, + bookLength + } + } catch (error) { + throw error + } + } + + /** + * 新增或者是修小说数据 + * @param {*} book 小说信息 + * @returns + */ + async AddOrModifyBook(book: Book.SelectBook): Promise { + try { + if (book == null) { + throw new Error('小说数据为空,无法修改') + } + + // 当小说的类型是反推的时候,必须传入视频 + if (book.type == BookType.MJ_REVERSE || book.type == BookType.SD_REVERSE) { + // 判断视频是否存在 + if (book.oldVideoPath == null || book.oldVideoPath == '') { + throw new Error('反推必须传入视频') + } + } + + let projectPath: string = await getProjectPath() + + if (book.id == null) { + // 新增 + // 判断指定的名字在数据库中是否存在 + let books = this.realm.objects('Book').filtered('name = $0', book.name) + if (books.length > 0) { + throw new Error(`小说名字 ${book.name} 已经存在,请更换小说名字`) + } + console.log(this) + // 新增数据 + book.id = crypto.randomUUID() + book.createTime = new Date() + book.updateTime = new Date() + // 检查传入的视频文件是不是存在 + // 获取当前最大的no + let maxNo = this.realm.objects('Book').max('no') + book.no = maxNo == null ? 1 : Number(maxNo) + 1 + // 拼接项目文件夹 + book.bookFolderPath = book.id + book.imageFolder = `${book.id}/tmp` + let bookFolderPath = path.resolve(projectPath, book.id) + let imageFolder = path.resolve(projectPath, `${book.id}/tmp`) + let oldVideoPath = path.resolve(projectPath, `${book.id}/data/${book.id}.mp4`) + let bookTaskImageFolder = path.resolve(imageFolder, `${book.name}_00001`) + + // 将视频拷贝一个到项目文件下面 + if (book.oldVideoPath) { + await CopyFileOrFolder(book.oldVideoPath, oldVideoPath) + } + + // 创建对应的文件夹 + await CheckFolderExistsOrCreate(bookFolderPath) + await CheckFolderExistsOrCreate(imageFolder) + await CheckFolderExistsOrCreate(bookTaskImageFolder) // 创建默认的任务文件夹 + // 修改数据 + book.oldVideoPath = path.relative(projectPath, oldVideoPath) + if (book.type == BookType.ORIGINAL) { + book.oldVideoPath = undefined + } + + let imageCategory = ImageCategory.Midjourney + if (book.type == BookType.SD_REVERSE) { + imageCategory = ImageCategory.Stable_Diffusion + } else if (book.type == BookType.MJ_REVERSE) { + imageCategory = ImageCategory.Midjourney + } else if (book.type == BookType.ORIGINAL) { + let generalSetting = await getGeneralSetting() + imageCategory = generalSetting.defaultImgGenMethod ?? ImageCategory.Midjourney + } else { + throw new Error('未知的小说类型') + } + const srtHandle = new SrtHandle() + let srtData = await srtHandle.GetSrtDataByPath(book.srtPath as string) + + let bookTaskDetailService = await BookTaskDetailService.getInstance() + + this.realm.write(() => { + book.version = '' + this.realm.create('Book', book) + + // 添加一个任务 + let bookTask = { + id: crypto.randomUUID(), + bookId: book.id, + no: 1, + name: `${book.name}_00001`, + generateVideoPath: null, + srtPath: null, + audioPath: null, + imageFolder: path.relative(projectPath, bookTaskImageFolder), // 获取文件输出对于项目的相对路径 + styleList: [], + prefix: null, + status: BookTaskStatus.WAIT, + errorMsg: null, + isAuto: false, + updateTime: new Date(), + createTime: new Date(), + version: '', + imageCategory: imageCategory, + openVideoGenerate: false + } + + // 添加任务 + this.realm.create('BookTask', bookTask) + + // 循环添加小说详细信息 + for (let i = 0; i < srtData.length; i++) { + const element = srtData[i] + bookTaskDetailService.AddBookTaskDetail({ + bookTaskId: bookTask.id, + bookId: bookTask.bookId, + startTime: element.start, + endTime: element.end, + status: BookTaskStatus.WAIT, + word: element.text, + afterGpt: element.text, + subValue: JSON.stringify([ + { + id: crypto.randomUUID(), + end_time: element.end, + start_time: element.start, + srt_value: element.text + } + ] as BookTaskDetail.CopywritingSubValue[]), + timeLimit: `${element.start} -- ${element.end}`, + // 新增修脸跟随 + adetailer: false // 默认false,实际更具SD设置中为主 + }) + } + }) + + // 保存成功,返回数据,但是要做处理 + book.bookFolderPath = bookFolderPath + book.imageFolder = imageFolder + book.oldVideoPath = oldVideoPath + + // 创建第一个默认批次的详细信息 + + return book + } else { + // 修改 + // 判断指定的名字在数据库中是否存在(不算自己) + let books = this.realm + .objects('Book') + .filtered('name = $0 AND id != $1', book.name, book.id) + if (books.length > 0) { + throw new Error(`小说名字 ${book.name} 已经存在,请更换小说名字`) + } + // 两个文件夹地址不能改,删除两个属性 + delete book.bookFolderPath + delete book.imageFolder + // 修改数据 + book.updateTime = new Date() + this.transaction(() => { + this.realm.create('Book', book, UpdateMode.Modified) + }) + + // 保存成功,返回数据,但是要做处理 + return book + } + } catch (error) { + throw error + } + } + + /** + * 修改小说数据 + * @param bookId 小说的ID + * @param bookData 要修改的小说数据 + */ + async ModifyBookDataById(bookId: string, bookData: Book.SelectBook): Promise { + try { + if (bookId == null) { + throw new Error('修改小说数据失败,缺少小说ID') + } + if (bookData == null) { + throw new Error('修改小说数据失败,缺少小说数据') + } + + // 检查小说ID对应的数据是不是存在 + let bookRes = await this.GetBookDataById(bookId) + if (bookRes == null) { + throw new Error('修改小说数据失败,小说ID对应的数据不存在') + } + + if (bookData && bookData.id) { + delete bookData.id + } + + // 开始修改 + this.transaction(() => { + this.realm.create('Book', { id: bookId, ...bookData }, UpdateMode.Modified) + }) + + bookRes = await this.GetBookDataById(bookId) + if (bookRes == null) { + throw new Error('获取修改后的小说数据失败,小说ID对应的数据不存在') + } + return bookRes + } catch (error) { + throw error + } + } + + /** + * 删除指定的小说任务 + * @param bookId 需要删除的小说的ID + */ + DeleteBookDataById(bookId: string): void { + try { + this.transaction(() => { + let book = this.realm.objectForPrimaryKey('Book', bookId) + if (book == null) { + throw new Error('未找到对应的小说') + } + // 删除对应的小说 + this.realm.delete(book) + }) + } catch (error) { + throw error + } + } +} diff --git a/src/define/db/service/book/bookTaskDetailService.ts b/src/define/db/service/book/bookTaskDetailService.ts new file mode 100644 index 0000000..939c0e7 --- /dev/null +++ b/src/define/db/service/book/bookTaskDetailService.ts @@ -0,0 +1,401 @@ +import Realm from 'realm' +import path from 'path' +import { cloneDeep, isEmpty } from 'lodash' +import { JoinPath } from '../../../Tools/file' +import { RealmBaseService } from '../base/realmBase' +import { Book } from '@/define/model/book/book' +import { getProjectPath } from '@/main/service/option/optionCommonService' +import { BookTaskDetailModel, ReversePrompt } from '../../model/bookTaskDetail' +import { BookTaskDetail } from '@/define/model/book/bookTaskDetail' + +export class BookTaskDetailService extends RealmBaseService { + static instance: BookTaskDetailService | null = null + declare realm: Realm + + private constructor() { + super() + } + + /** + * 获取当前实例对象,为空则创建一个新的 + * @returns + */ + public static async getInstance() { + if (BookTaskDetailService.instance === null) { + BookTaskDetailService.instance = new BookTaskDetailService() + await super.getInstance() + } + await BookTaskDetailService.instance.open() + return BookTaskDetailService.instance + } + + /** + * 更具条件查询执行的小说的分镜信息 + * @param condition 查询的条件,id,name,bookId,bookTaskId + */ + async GetBookTaskDetailDataByCondition( + condition: Book.QueryBookTaskDetailCondition + ): Promise { + try { + if (condition == null) { + throw new Error('查询小说分镜信息,查询条件不能为空') + } + let tasksToDelete = this.realm.objects('BookTaskDetail') + if (condition.id) { + tasksToDelete = tasksToDelete.filtered('id==$0', condition.id) + } + if (condition.bookId) { + tasksToDelete = tasksToDelete.filtered('bookId==$0', condition.bookId) + } + if (condition.bookTaskId) { + tasksToDelete = tasksToDelete.filtered('bookTaskId==$0', condition.bookTaskId) + } + if (condition.name) { + tasksToDelete = tasksToDelete.filtered('name==$0', condition.name) + } + + let projectPath = await getProjectPath() + + let resData = Array.from(tasksToDelete).map((item) => { + let resObj = { + ...item, + videoPath: JoinPath(projectPath, item.videoPath), + generateVideoPath: JoinPath(projectPath, item.generateVideoPath), + audioPath: JoinPath(projectPath, item.audioPath), + oldImage: JoinPath(projectPath, item.oldImage), + outImagePath: JoinPath(projectPath, item.outImagePath), + subImagePath: (item.subImagePath as string[])?.map((subImage) => { + return JoinPath(projectPath, subImage) + }), + characterTags: item.characterTags ? item.characterTags.map((tag) => tag) : [], + sceneTags: item.sceneTags ? item.sceneTags.map((tag) => tag) : [], + styleTags: item.styleTags ? item.styleTags.map((tag) => tag) : [], + subValue: + isEmpty(item.subValue) || item.subValue == null ? null : JSON.parse(item.subValue), + reversePrompt: item.reversePrompt?.map((reversePrompt) => { + return { + ...reversePrompt + } + }), + mjMessage: item.mjMessage ? item.mjMessage.toJSON() : undefined, + videoMessage: item.videoMessage ? item.videoMessage.toJSON() : undefined + } as Book.SelectBookTaskDetail + // 不是网络地址,并且存在,进行地址的拼接 + if ( + resObj.videoMessage && + resObj.videoMessage.imageUrl && + !isEmpty(resObj.videoMessage.imageUrl) && + !(resObj.videoMessage.imageUrl as string).startsWith('http') + ) { + resObj.videoMessage.imageUrl = JoinPath( + projectPath, + resObj.videoMessage.imageUrl as string + ) + } + return cloneDeep(resObj) + }) + return resData + } catch (error) { + throw error + } + } + + /** + * 返回指定的小说分镜的指定的属性 + * @param bookTaskDetailId 小说分镜的ID + * @param property 小说属性名字 + * @returns + */ + async GetBookTaskDetailProperty(bookTaskDetailId: string, property: string) { + let bookTaskDetail = await this.GetBookTaskDetailDataById(bookTaskDetailId) + if (bookTaskDetail == null) { + throw new Error('未找到对应的小说任务详细信息 ' + bookTaskDetailId) + } + if (bookTaskDetail.hasOwnProperty(property)) { + return bookTaskDetail[property] + } else { + throw new Error(`未找到对应的属性 ${property}`) + } + } + + /** + * 通过ID获取指定的小说任务分镜详细数据 + * @param bookTaskDetailId + */ + public async GetBookTaskDetailDataById( + bookTaskDetailId: string + ): Promise { + try { + if (bookTaskDetailId == null) { + throw new Error('获取小说任务详细信息失败,缺少ID') + } + let bookTaskDetails = await this.GetBookTaskDetailDataByCondition({ id: bookTaskDetailId }) + if (bookTaskDetails.length <= 0) { + return null + } else { + return bookTaskDetails[0] as Book.SelectBookTaskDetail + } + } catch (error) { + throw error + } + } + + /** + * 添加一条小说任务对应的详细数据 + * @param BookTaskDetail + */ + public AddBookTaskDetail(bookTaskDetail: Book.SelectBookTaskDetail) { + try { + // 判断是不是又小说ID + if (isEmpty(bookTaskDetail.bookId) || isEmpty(bookTaskDetail.bookTaskId)) { + throw new Error( + '新增小说任务详细信息到数据库失败,数据不完整,缺少小说ID或者小说批次任务ID' + ) + } + + // 开始初始化数据(获取指定的bookId和bookTaskId)中最大的no + let bookTaskDetails = this.realm + .objects('BookTaskDetail') + .filtered( + 'bookId == $0 AND bookTaskId == $1', + bookTaskDetail.bookId, + bookTaskDetail.bookTaskId + ) + + let maxNo = bookTaskDetails.max('no') + bookTaskDetail.no = maxNo ? Number(maxNo) + 1 : 1 + let name = bookTaskDetail.no.toString().padStart(5, '0') + + bookTaskDetail.name = name + bookTaskDetail.id = crypto.randomUUID() + bookTaskDetail.imageLock = false + bookTaskDetail.createTime = new Date() + bookTaskDetail.updateTime = new Date() + + if (!bookTaskDetail.hasOwnProperty('adetailer')) { + bookTaskDetail.adetailer = false // 设置默认值 + } + // 开始添加 + this.transaction(() => { + this.realm.create('BookTaskDetail', bookTaskDetail) + }) + //创建成功,返回 + return bookTaskDetail + } catch (error) { + throw error + } + } + + /** + * 更新指定ID的指定数据 + * @param bookTaskDetailId + * @param updateData + */ + async ModifyBookTaskDetailById( + bookTaskDetailId: string, + updateData: Book.SelectBookTaskDetail + ): Promise { + try { + this.transaction(() => { + let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId) + if (bookTaskDetail == null) { + throw new Error('未找到对应的小说任务详细信息') + } + // 开始修改 + for (let key in updateData) { + bookTaskDetail[key] = updateData[key] + } + bookTaskDetail.updateTime = new Date() + }) + let res = await this.GetBookTaskDetailDataById(bookTaskDetailId) + return res + } catch (error) { + throw error + } + } + + /** + * 更新指定ID的反推提示词数据 + * @param bookTaskDetailId + * @param mjMessage + */ + UpdateBookTaskDetailMjMessage(bookTaskDetailId: string, mjMessage: Book.MJMessage): void { + try { + this.transaction(() => { + let mjMessageRes = this.realm.objectForPrimaryKey('MJMessage', bookTaskDetailId) + let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId) + if (bookTaskDetail == null) { + throw new Error('没有找到要更新的小说分镜信息') + } + if (bookTaskDetail.mjMessage == null) { + // 新增 + mjMessage.id = bookTaskDetailId + bookTaskDetail.mjMessage = mjMessage + } else { + if (mjMessageRes == null) { + throw new Error('没有找到要更新的出图信息') + } + for (const key in mjMessage) { + if (key != 'id') mjMessageRes[key] = mjMessage[key] + } + } + }) + } catch (error) { + throw error + } + } + + /** + * 更新小说分镜的视频消息 + * @param bookTaskDetailId + * @param videoMessage + */ + async UpdateBookTaskDetailVideoMessage( + bookTaskDetailId: string, + videoMessage: BookTaskDetail.VideoMessage + ): Promise { + let projectPath = await getProjectPath() + this.transaction(() => { + let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId) + let videoMessageRes = this.realm.objectForPrimaryKey('VideoMessage', bookTaskDetailId) + if (bookTaskDetail == null) { + throw new Error('没有找到要更新的小说分镜信息') + } + if (videoMessageRes == null) { + throw new Error('没有找到要更新的视频信息') + } + if (bookTaskDetail.videoMessage == null) { + // 新增 + videoMessage.id = bookTaskDetailId + bookTaskDetail.videoMessage = videoMessage + } else { + for (const key in videoMessage) { + if (key == 'id') { + continue + } + if ( + key == 'imageUrl' && + videoMessage[key] != null && + !videoMessage[key].startsWith('http') + ) { + videoMessage[key] = path.relative(projectPath, videoMessage[key]) + } + videoMessageRes[key] = videoMessage[key] + } + } + }) + } + + /** + * 更新指定ID的反推提示词数据 + * @param bookTaskDetailId 分镜数据的ID + * @param reversePromptId 反推出来的提示词ID + * @param updateData 要更新的数据 + */ + UpdateBookTaskDetailReversePrompt( + bookTaskDetailId: string, + reversePromptId: string, + reversePrompt: Book.ReversePrompt + ) { + try { + this.transaction(() => { + let bookTaskDetails = this.realm.objects('ReversePrompt') + bookTaskDetails = bookTaskDetails.filtered( + 'id = $0 && bookTaskDetailId = $1', + reversePromptId, + bookTaskDetailId + ) + + if (bookTaskDetails.length <= 0) { + throw new Error('未找到执行的翻译数据,无法写回') + } + let bookTaskDetail = bookTaskDetails[0] + // 直接写入 + for (const key in reversePrompt) { + bookTaskDetail[key] = reversePrompt[key] + } + }) + return null + } catch (error) { + throw error + } + } + + /** + * 删除满足条件的对象吗,必传小说ID和小说任务ID + * @param condition bookId,bookTaskId,name,id + */ + DeleteBookTaskDetail(condition: Book.DeleteBookTaskDetailCondition) { + try { + if (isEmpty(condition.id) && isEmpty(condition.bookTaskId) && isEmpty(condition.bookId)) { + throw new Error('删除小说分镜信息失败,没有必要参数') + } + let tasksToDelete = this.realm.objects('BookTaskDetail') + if (condition.id) { + tasksToDelete = tasksToDelete.filtered('id==$0', condition.id) + } + if (condition.bookId) { + tasksToDelete = tasksToDelete.filtered('bookId==$0', condition.bookId) + } + if (condition.bookTaskId) { + tasksToDelete = tasksToDelete.filtered('bookTaskId==$0', condition.bookTaskId) + } + if (condition.name) { + tasksToDelete = tasksToDelete.filtered('name==$0', condition.name) + } + + this.transaction(() => { + this.realm.delete(tasksToDelete) + }) + return null + } catch (error) { + throw error + } + } + + /** + * 删除指定ID的小说任务详细数据中的所有的反推提示词数据 + * @param bookTaskDetailId 小说分镜的ID + */ + DeleteBookTaskDetailReversePromptById(bookTaskDetailId: string): void { + this.transaction(() => { + let bookTaskDetails = this.realm + .objects('BookTaskDetail') + .filtered('id = $0', bookTaskDetailId) + if (bookTaskDetails.length <= 0) { + throw new Error('删除小说任务详细信息的反推提示词失败,未找到对应的分镜信息') + } + let bookTaskDetail = bookTaskDetails[0] + + bookTaskDetail.gptPrompt = null + // 删除所有的反推提示词 + if (bookTaskDetail.reversePrompt) { + bookTaskDetail.reversePrompt.forEach((item) => { + this.realm.delete(item) + }) + } + }) + } + + /** + * 删除MJ中生成图片的所有的信息,包括 outImagePath subImagePath mjMessage (mjMessage是另一张表,要单独生成) + * @param bookTaskDetailId + */ + DeleteBoookTaskDetailGenerateImage(bookTaskDetailId: string): void { + this.transaction(() => { + let bookTaskDetail = this.realm.objectForPrimaryKey( + 'BookTaskDetail', + bookTaskDetailId + ) + if (bookTaskDetail == null) { + throw new Error('没有找到要删除的分镜信息') + } + if (bookTaskDetail.mjMessage) { + this.realm.delete(bookTaskDetail.mjMessage) + } + bookTaskDetail.mjMessage = null + bookTaskDetail.outImagePath = null + bookTaskDetail.subImagePath = [] + }) + } +} diff --git a/src/define/db/service/book/bookTaskService.ts b/src/define/db/service/book/bookTaskService.ts new file mode 100644 index 0000000..5152839 --- /dev/null +++ b/src/define/db/service/book/bookTaskService.ts @@ -0,0 +1,309 @@ +import Realm from 'realm' +import { RealmBaseService } from '../base/realmBase' +import { Book } from '@/define/model/book/book' +import { BookTaskModel } from '../../model/bookTask' +import { getProjectPath } from '@/main/service/option/optionCommonService' +import { ImageCategory } from '@/define/data/imageData' +import { JoinPath } from '@/define/Tools/file' +import { BookTaskStatus } from '@/define/enum/bookEnum' + +export class BookTaskService extends RealmBaseService { + static instance: BookTaskService | null = null + declare realm: Realm + + private constructor() { + super() + } + + /** + * 获取当前实例对象,为空则创建一个新的 + * @returns + */ + public static async getInstance() { + if (BookTaskService.instance === null) { + BookTaskService.instance = new BookTaskService() + await super.getInstance() + } + await BookTaskService.instance.open() + return BookTaskService.instance + } + + /** + * 查询满足条件的小说子任务信息 + * @param bookTaskCondition 查询条件 id,bookId,name,no,page, pageSize + */ + async GetBookTaskDataByCondition( + bookTaskCondition: Book.QueryBookTaskCondition + ): Promise { + try { + // 获取所有的小说数据,并进行时间降序排序 + let bookTasks = this.realm.objects('BookTask') + + // 开始开始筛选 + if (bookTaskCondition.id) { + // 查询对应的小说ID的数据 + bookTasks = bookTasks.filtered('id = $0', bookTaskCondition.id) + } + if (bookTaskCondition.bookId) { + // 查询对应的小说ID的数据 + bookTasks = bookTasks.filtered('bookId = $0', bookTaskCondition.bookId) + } + if (bookTaskCondition.name) { + // 查询对应的小说ID的数据 + bookTasks = bookTasks.filtered('name = $0', bookTaskCondition.name) + } + if (bookTaskCondition.no) { + // 查询对应的小说ID的数据 + bookTasks = bookTasks.filtered('no = $0', bookTaskCondition.no) + } + let bookTask_length = bookTasks.length + + // bookTasks = bookTasks.sorted('updateTime', true) + // 判断是不是有page和pageSize,有的话对查询返回的信息做分页 + if (bookTaskCondition.page && bookTaskCondition.pageSize) { + bookTasks = bookTasks.slice( + (bookTaskCondition.page - 1) * bookTaskCondition.pageSize, + bookTaskCondition.page * bookTaskCondition.pageSize + ) as unknown as Realm.Results + } + let projectPath: string = await getProjectPath() + // 做一下数据转换 + // 将realm对象数组转换为普通对象数组 + // 将realm对象数组转换为普通对象数组,并处理异步操作 + let res_bookTasks = Array.from(bookTasks).map((bookTask) => { + // 直接操作普通对象 + return { + ...bookTask, + imageStyle: bookTask.imageStyle ? Array.from(bookTask.imageStyle) : [], + customizeImageStyle: bookTask.customizeImageStyle + ? Array.from(bookTask.customizeImageStyle) + : [], + generateVideoPath: JoinPath(projectPath, bookTask.generateVideoPath), + srtPath: JoinPath(projectPath, bookTask.srtPath), + audioPath: JoinPath(projectPath, bookTask.audioPath), + imageFolder: JoinPath(projectPath, bookTask.imageFolder), + cacheImageList: bookTask.cacheImageList + ? Array.from(bookTask.cacheImageList).map((item) => JoinPath(projectPath, item)) + : [], + imageCategory: bookTask.imageCategory ? bookTask.imageCategory : ImageCategory.Midjourney // 默认使用MJ出图 + } as Book.SelectBookTask + }) + + return { + bookTasks: JSON.parse(JSON.stringify(res_bookTasks)), + bookTaskLength: bookTask_length + } as Book.QueryBookTaskConditionResponse + } catch (error) { + throw error + } + } + + /** + * 通过ID获取小说批次任务的数据 + * @param bookTaskId + */ + async GetBookTaskDataById(bookTaskId: string): Promise { + try { + if (bookTaskId == null) { + throw new Error('小说任务ID不能为空') + } + let bookTasks = await this.GetBookTaskDataByCondition({ id: bookTaskId }) + if (bookTasks.bookTasks.length <= 0) { + throw new Error('未找到对应的小说任务') + } else { + return bookTasks.bookTasks[0] + } + } catch (error) { + throw error + } + } + + /** + * 修改小说批次任务的状态 + * @param bookTaskId 小说批次任务Id + * @param status 目标状态 + */ + ModifyBookTaskStatus(bookTaskId: string, status: BookTaskStatus, errorMsg?: string): void { + try { + this.transaction(() => { + // 修改对应小说批次任务的状态 + let bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId) + if (bookTask == null) { + throw new Error('未找到对应的小说任务') + } + bookTask.status = status + bookTask.updateTime = new Date() + if (errorMsg != null) { + bookTask.errorMsg = errorMsg + } + }) + } catch (error) { + throw error + } + } + + /** + * 修改小说批次任务数据 + * @param bookTaskId 小说批次任务ID + * @param data 要修改的数据 + */ + async ModifyBookTaskDataById( + bookTaskId: string, + data: Book.SelectBookTask + ): Promise { + try { + this.transaction(() => { + let updateData = this.realm.objectForPrimaryKey('BookTask', bookTaskId) + if (updateData == null) { + throw new Error('未找到对应的小说任务详细信息') + } + // 开始修改 + for (let key in data) { + updateData[key] = data[key] + } + }) + let res = await this.GetBookTaskDataById(bookTaskId) + return res + } catch (error) { + throw error + } + } + + // 添加一条数据 + async AddBookTask(bookTask: Book.SelectBookTask): Promise { + try { + // 新增 + if (bookTask.bookId == '' || bookTask.bookId == null) { + throw new Error('小说ID不能为空') + } + + bookTask.id = crypto.randomUUID() + + // 获取当前bookID对应的最大的no + let maxNo = this.realm.objects('BookTask').filtered('bookId = $0', bookTask.bookId).max('no') + bookTask.no = maxNo == null ? 1 : Number(maxNo) + 1 + bookTask.name = 'output_0000' + bookTask.no + bookTask.status = BookTaskStatus.WAIT + + bookTask.updateTime = new Date() + bookTask.createTime = new Date() + + this.realm.write(() => { + this.realm.create('BookTask', bookTask) + }) + // 处理完毕,返回结果 + let res = await this.GetBookTaskDataById(bookTask.id) + return res + } catch (error) { + throw error + } + } + /** + * 获取最大的小说批次任务的编号 + * @param bookId 小说ID + */ + async GetMaxBookTaskNo(bookId: string): Promise { + let maxNo = this.realm.objects('BookTask').filtered('bookId = $0', bookId).max('no') + let no = maxNo == null ? 1 : Number(maxNo) + 1 + return no + } + + /** + * 重置小说批次数据,清除小说的详细信息 + * @param bookTaskId 小说批次ID + */ + async ResetBookTaskDataById( + bookTaskId: string, + resetBase: boolean = true + ): Promise { + try { + // 开始重置数据,先重置小说批次数据,在重置其他 + this.transaction(() => { + this.ResetBookTaskDataInfo(bookTaskId, resetBase) + }) + let res = await this.GetBookTaskDataById(bookTaskId) + return res + } catch (error) { + throw error + } + } + + /** + * 删除对应的小说批次数据 + * @param bookTaskId 要删除的批次的ID + */ + DeleteBookTaskDataById(bookTaskId: string): void { + try { + this.transaction(() => { + // 先调用清除数据的方法 + this.ResetBookTaskDataInfo(bookTaskId) + // 删除批次数据 + let bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId) + if (bookTask == null) { + throw new Error('未找到对应的小说任务,无法执行删除操作') + } + this.realm.delete(bookTask) + }) + } catch (error) { + throw error + } + } + + /** 重置小说批次任务数据 */ + private ResetBookTaskDataInfo(bookTaskId: string, resetBase: boolean = true) { + try { + let modifyBookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId) + if (modifyBookTask == null) { + throw new Error('未找到对应的小说批次任务,无法执行重置操作') + } + + let book = this.realm.objectForPrimaryKey('Book', modifyBookTask.bookId) + if (book == null) { + throw new Error('未找到对应的小说,无法执行重置操作') + } + + if (resetBase) { + // 基础数据的重置 + modifyBookTask.errorMsg = '' + modifyBookTask.updateTime = new Date() + modifyBookTask.imageStyle = [] + modifyBookTask.autoAnalyzeCharacter = undefined + modifyBookTask.customizeImageStyle = [] + modifyBookTask.videoConfig = undefined + modifyBookTask.prefixPrompt = undefined + modifyBookTask.suffixPrompt = undefined + modifyBookTask.subImageFolder = [] + modifyBookTask.srtPath = book.srtPath ?? undefined + modifyBookTask.audioPath = book.audioPath ?? undefined + } + + // 继承小说的srt和配音文件 + modifyBookTask.imageCategory = ImageCategory.Midjourney // 默认使用MJ出图 + modifyBookTask.status = BookTaskStatus.WAIT + + // 开始删除 分镜信息 + let bookTaskDetails = this.realm + .objects('BookTaskDetail') + .filtered('bookTaskId = $0', bookTaskId) + // 开始删除数据 + for (const bookTaskDetail of bookTaskDetails) { + // 删除MJMessage + if (bookTaskDetail.mjMessage) { + this.realm.delete(bookTaskDetail.mjMessage) + } + + if (bookTaskDetail.reversePrompt) { + ;(bookTaskDetail.reversePrompt as any[]).forEach((item) => { + this.realm.delete(item) + }) + } + if (bookTaskDetail.sdConifg) { + this.realm.delete(bookTaskDetail.sdConifg) + } + this.realm.delete(bookTaskDetail) + } + } catch (error) { + throw error + } + } +} diff --git a/src/define/db/service/book/taskListService.ts b/src/define/db/service/book/taskListService.ts new file mode 100644 index 0000000..7b1b4e5 --- /dev/null +++ b/src/define/db/service/book/taskListService.ts @@ -0,0 +1,424 @@ +import Realm from 'realm' +import { RealmBaseService } from '../base/realmBase' +import { cloneDeep, isEmpty } from 'lodash' +import { Book } from '@/define/model/book/book' +import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum' +import { OtherData } from '@/define/enum/softwareEnum' +import { TaskModal } from '@/define/model/task' + +export class TaskListService extends RealmBaseService { + static instance: TaskListService | null = null + declare realm: Realm + + private constructor() { + super() + } + + /** + * 获取当前实例对象,为空则创建一个新的 + * @returns + */ + public static async getInstance() { + if (TaskListService.instance === null) { + TaskListService.instance = new TaskListService() + await super.getInstance() + } + await TaskListService.instance.open() + return TaskListService.instance + } + + /** + * 获取任务集合 + * + * 该方法根据提供的查询条件从数据库中检索任务列表,支持多种筛选条件和分页功能。 + * 可通过小说名称、任务名称、状态等多个维度进行筛选,并为每个任务补充关联的小说 + * 和任务信息。结果按创建时间降序排列,并根据分页参数返回指定范围的数据。 + * + * 支持的筛选条件包括: + * - 小说名称(模糊匹配) + * - 小说任务名称(模糊匹配) + * - 小说任务详情名称(模糊匹配) + * - 任务名称(模糊匹配) + * - 任务类型(精确匹配) + * - 任务状态(精确匹配) + * - 任务错误信息(模糊匹配) + * + * @param {TaskModal.QueryTaskCondition} queryTaskCondition - 查询条件对象,包含筛选和分页参数 + * @returns {TaskModal.TaskCollection} 包含分页信息和任务数据的集合对象 + * + * @example + * // 查询与"小说项目"相关的所有失败任务,第1页,每页10条 + * const condition = { + * bookName: "小说项目", + * taskStatus: BookBackTaskStatus.FAIL, + * page: 1, + * pageSize: 10 + * }; + * const taskCollection = taskListService.GetTaskCollection(condition); + * console.log(`共找到${taskCollection.count}条记录,当前显示${taskCollection.data.length}条`); + */ + GetTaskCollection(queryTaskCondition: TaskModal.QueryTaskCondition): TaskModal.TaskCollection { + let tasks = this.realm.objects('TaskList') + if (!isEmpty(queryTaskCondition.bookName)) { + let book = this.realm + .objects('Book') + .filtered('name CONTAINS[c] $0', queryTaskCondition.bookName) + let ids = [] as string[] + if (book.length > 0) { + ids = book.map((item) => { + return item.id as string + }) + } + tasks = tasks.filtered('bookId in $0', ids) + } + if (!isEmpty(queryTaskCondition.bookTaskName)) { + let bookTask = this.realm + .objects('BookTask') + .filtered('name CONTAINS[c] $0', queryTaskCondition.bookTaskName) + let ids = [] as string[] + if (bookTask.length > 0) { + ids = bookTask.map((item) => { + return item.id as string + }) + } + tasks = tasks.filtered('bookTaskId in $0', ids) + } + if (!isEmpty(queryTaskCondition.bookTaskDetailName)) { + let bookTaskDetail = this.realm + .objects('BookTaskDetail') + .filtered('name CONTAINS[c] $0', queryTaskCondition.bookTaskDetailName) + let ids = [] as string[] + if (bookTaskDetail.length > 0) { + ids = bookTaskDetail.map((item) => { + return item.id as string + }) + } + tasks = tasks.filtered('bookTaskDetailId in $0', ids) + } + if (!isEmpty(queryTaskCondition.taskName)) { + tasks = tasks.filtered('name CONTAINS[c] $0', queryTaskCondition.taskName) + } + if (!isEmpty(queryTaskCondition.taskType)) { + tasks = tasks.filtered('type == $0', queryTaskCondition.taskType) + } + if (!isEmpty(queryTaskCondition.taskStatus)) { + tasks = tasks.filtered('status == $0', queryTaskCondition.taskStatus) + } + if (!isEmpty(queryTaskCondition.taskErrorMessage)) { + tasks = tasks.filtered('errorMessage CONTAINS[c] $0', queryTaskCondition.taskErrorMessage) + } + let count = tasks.length + tasks = tasks.sorted('createTime', true) + let task = tasks.slice( + (queryTaskCondition.page - 1) * queryTaskCondition.pageSize, + queryTaskCondition.page * queryTaskCondition.pageSize + ) as TaskModal.BackTaskCollection[] + + let taskList = Array.from(task).map((item) => { + let resObj = { + ...item + } as TaskModal.BackTaskCollection + return cloneDeep(resObj) + }) + + for (let i = 0; i < taskList.length; i++) { + const element = taskList[i] + let book = this.realm.objectForPrimaryKey('Book', element.bookId) + if (book) { + element.bookName = book.name as string + } + let bookTask = this.realm.objectForPrimaryKey('BookTask', element.bookTaskId) + if (bookTask) { + element.bookTaskName = bookTask.name as string + } + let bookTaskDetail = this.realm.objectForPrimaryKey( + 'BookTaskDetail', + element.bookTaskDetailId + ) + if (bookTaskDetail) { + element.bookTaskDetailName = bookTaskDetail.name as string + } + } + + return { + page: queryTaskCondition.page, + pageSize: queryTaskCondition.pageSize, + count: count, + data: taskList + } + } + + /** + * 获取等待状态的任务和返回指定数量的数据 + * @param executeType 任务的执行类型 + * @param count 返回数据的数量 + */ + GetWaitTaskAndSlice(executeType: TaskExecuteType, count: number) { + try { + let tasks = this.realm + .objects('TaskList') + .filtered( + '(status == $0 || status == $1) && executeType == $2', + BookBackTaskStatus.WAIT, + BookBackTaskStatus.RECONNECT, + executeType ? executeType : TaskExecuteType.AUTO + ) + .sorted('createTime', false) + + let tasksArray = Array.from(tasks) + if (count != null) { + tasksArray = tasksArray.slice(0, count) + } + let res = tasksArray.map((item) => { + let resObj = { + ...item + } + return resObj + }) + + return res + } catch (error) { + throw error + } + } + + /** + * 获取指定状态的任务数量 + * + * 该方法查询数据库中符合指定状态集合的任务总数,用于统计分析不同状态的任务分布。 + * 使用Realm的IN操作符高效查询匹配多个状态条件的任务。 + * + * @param {BookBackTaskStatus[]} status - 要统计的任务状态数组,如[WAIT, RUNNING] + * @returns {number} 符合指定状态的任务总数 + * + * @example + * // 获取所有等待中的任务数量 + * const waitingCount = taskListService.GetAssignStatusTaskCount([BookBackTaskStatus.WAIT]); + * + * // 获取所有正在运行或重连的任务数量 + * const activeCount = taskListService.GetAssignStatusTaskCount([ + * BookBackTaskStatus.RUNNING, + * BookBackTaskStatus.RECONNECT + * ]); + */ + GetAssignStatusTaskCount(status: BookBackTaskStatus[]): number { + let taskLength = this.realm + .objects('TaskList') + .filtered('status IN $0', status).length + return taskLength + } + + /** + * 新增一个小说相关的后台任务队列 + * @param bookBackTask 要添加的小说数据 + */ + AddOneTask( + bookId: string, + taskType: BookBackTaskType, + executeType: TaskExecuteType = TaskExecuteType.AUTO, + bookTaskId: string | undefined = undefined, + bookTaskDetailId: string | undefined = undefined, + responseMessageName?: string + ): TaskModal.Task { + try { + // 通过bookid获取book信息 + let book = this.realm.objectForPrimaryKey('Book', bookId) + if (book == null) { + throw new Error('新增后台队列任务到数据库失败,没有找到对应的小说') + } + let bookTask + if (bookTaskId) { + bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId) + if (bookTask == null) { + throw new Error('新增后台队列任务到数据库失败,没有找到对应的小说批次任务') + } + } + + let bookTaskDetail + if (bookTaskDetailId) { + bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId) + if (bookTaskDetail == null) { + throw new Error( + '新增后台队列任务到数据库失败,没有找到对应的小说批次任务详情(分镜数据)' + ) + } + } + + // 开始往数据库中写数据 + let name = `${book.name}-${bookTask ? bookTask.name : 'default'}-${ + bookTaskDetail ? bookTaskDetail.name : 'default' + }-${taskType}` + + let bookBackTask = { + id: crypto.randomUUID(), + bookId: bookId, + bookTaskId: bookTaskId ? bookTaskId : OtherData.DEFAULT, + bookTaskDetailId: bookTaskDetailId ? bookTaskDetailId : OtherData.DEFAULT, + name: name, + type: taskType, + executeType: executeType, + status: BookBackTaskStatus.WAIT, + createTime: new Date(), + updateTime: new Date(), + messageName: responseMessageName, + startTime: 0, + endTime: 0 + } as TaskModal.Task + this.realm.write(() => { + this.realm.create('TaskList', bookBackTask) + }) + + return bookBackTask + } catch (error) { + throw error + } + } + + /** + * 修改一个小说相关的后台任务队列中的详细信息 + * (对于后台的队列任务只能修改状态)和错误信息 + * @param bookBackTask 修改的数据 + */ + UpdateTaskStatus(bookBackTask: Book.UpdateBookTaskListStatus): Book.UpdateBookTaskListStatus { + try { + // 判断数据是不是存在 + if (isEmpty(bookBackTask.id) || isEmpty(bookBackTask.status)) { + throw new Error('修改后台队列任务失败,数据不完整,缺少必要字段') + } + // 开始修改 + this.transaction(() => { + // 获取指定ID的队列任务 + let _bookBackTask = this.realm.objectForPrimaryKey( + 'TaskList', + bookBackTask.id + ) as TaskModal.Task + // 判断数据是不是存在 + if (_bookBackTask == null) { + throw new Error('修改后台队列任务失败,数据不存在') + } + // 修改数据 + _bookBackTask.status = bookBackTask.status + if (bookBackTask.errorMessage) { + _bookBackTask.errorMessage = bookBackTask.errorMessage + } + // 根据状态修改结束和完成时间 + if (bookBackTask.status == BookBackTaskStatus.RUNNING) { + _bookBackTask.startTime = new Date().getTime() + } else if ( + bookBackTask.status == BookBackTaskStatus.DONE || + bookBackTask.status == BookBackTaskStatus.FAIL || + bookBackTask.status == BookBackTaskStatus.PAUSE + ) { + _bookBackTask.endTime = new Date().getTime() + } else if (bookBackTask.status == BookBackTaskStatus.RECONNECT) { + _bookBackTask.startTime = 0 + _bookBackTask.endTime = 0 + } + }) + return bookBackTask + } catch (error) { + throw error + } + } + + /** + * 删除满足条件的数据,包含 id、bookId、bookTaskId + * 上面的条件,至少要有一个 + * @param bookBackTask 删除的数据 + */ + async DeleteBookBackTask(bookBackTask) { + try { + if ( + !bookBackTask.hasOwnProperty('id') && + !bookBackTask.hasOwnProperty('bookId') && + !bookBackTask.hasOwnProperty('bookTaskId') + ) { + throw new Error('删除后台队列任务失败,缺少必要的删除条件') + } + + this.transaction(() => { + // 构建查询条件 + const tasksToDelete = this.realm.objects('TaskList').filtered('id == $0', bookBackTask.id) + + if (bookBackTask.bookId) { + tasksToDelete.filtered('bookId == $0', bookBackTask.bookId) + } + if (bookBackTask.bookTaskId) { + tasksToDelete.filtered('bookTaskId == $0', bookBackTask.bookTaskId) + } + + this.realm.delete(tasksToDelete) + + return bookBackTask + }) + } catch (error) { + throw error + } + } + + /** + * 设置指定消息名称的任务为失败,并设置错误消息 + * @param messageName + * @param errorMessage + */ + async SetMessageNameTaskToFail(messageName: string, errorMessage: string) { + let tasks = this.realm.objects('TaskList').filtered('messageName == $0 ', messageName) + tasks = tasks.filtered('status != $0', BookBackTaskStatus.DONE) + tasks = tasks.filtered('status != $0', BookBackTaskStatus.FAIL) + let ids = tasks.map((item) => { + return item.id + }) + this.transaction(() => { + for (let i = 0; i < ids.length; i++) { + let task = this.realm.objectForPrimaryKey('TaskList', ids[i]) + if (task == null) { + throw new Error('没有找到对应的任务') + } + task.status = BookBackTaskStatus.FAIL + task.errorMessage = errorMessage + } + }) + } + + /** + * 丢弃所有未开始的后台任务 + * + * 该方法将所有处于等待(WAIT)或重新连接(RECONNECT)状态的任务标记为失败(FAIL), + * 并设置错误信息为"任务被丢弃"。通常在需要清理任务队列、重启系统或手动干预任务处理 + * 流程时使用。 + * + * 丢弃操作会在一个数据库事务中执行,确保所有状态更改的原子性,避免部分更新导致的 + * 数据不一致问题。 + * + * @returns {Promise} 无返回值的Promise + * @throws {Error} 当任务ID存在但无法找到对应任务对象时抛出错误 + * + * @example + * // 重启任务队列前丢弃所有未开始的任务 + * await taskListService.GiveUpNotStartBackTask(); + * await taskManager.restart(); + */ + GiveUpNotStartBackTask(): void { + let task = this.realm + .objects('TaskList') + .filtered( + 'status == $0 || status == $1', + BookBackTaskStatus.WAIT, + BookBackTaskStatus.RECONNECT + ) + let ids = task.map((item) => { + return item.id + }) + // 讲所有的人物状态改为放弃,然后errmessage改为 此任务已丢弃 + this.transaction(() => { + for (let i = 0; i < ids.length; i++) { + const element = this.realm.objectForPrimaryKey('TaskList', ids[i]) + if (element == null) { + throw new Error('没有找到对应的任务') + } + element.status = BookBackTaskStatus.FAIL + element.errorMessage = '任务被丢弃' + } + }) + } +} diff --git a/src/define/db/service/optionService.ts b/src/define/db/service/optionService.ts new file mode 100644 index 0000000..db822cf --- /dev/null +++ b/src/define/db/service/optionService.ts @@ -0,0 +1,81 @@ +import Realm from 'realm' +import { isEmpty, cloneDeep } from 'lodash' +import { RealmBaseService } from './base/realmBase' +import { OptionType } from '@/define/enum/option' + +export class OptionRealmService extends RealmBaseService { + static instance: OptionRealmService | null = null + declare realm: Realm + private constructor() { + super() + } + + /** + * 获取当前实例对象,为空则创建一个新的 + * @returns + */ + public static async getInstance() { + if (OptionRealmService.instance === null) { + OptionRealmService.instance = new OptionRealmService() + await super.getInstance() + } + await OptionRealmService.instance.open() + return OptionRealmService.instance + } + + /** + * 获取指定的Option,通过key,不存在返回null + * @param key + * @returns + */ + public GetOptionByKey(key: string): OptionModel.OptionModel | null { + if (isEmpty(key)) { + return null + } + let res = this.realm.objects('Option').filtered(`key = "${key}"`); + + if (res.length > 0) { + let resData = Array.from(res).map((item) => { + let resObj = { + ...item + } + return cloneDeep(resObj) + }) + return resData[0] as OptionModel.OptionModel + } else { + return null; + } + } + + /** + * 修改指定的Option,通过key,不存在则创建 + * @param key + * @param value + */ + public ModifyOptionByKey(key: string, value: string, type: OptionType = OptionType.STRING) { + if (isEmpty(key)) { + return false + } + let option = this.realm.objectForPrimaryKey('Option', key); + if (option) { + this.realm.write(() => { + option.value = value; + option.type = type; + option.updateTime = new Date() + }) + } else { + this.realm.write(() => { + this.realm.create('Option', { + key: key, + value: value, + type: type, + createTime: new Date(), + updateTime: new Date() + }) + }) + } + return true + } +} + + diff --git a/src/define/db/service/presetService.ts b/src/define/db/service/presetService.ts new file mode 100644 index 0000000..3f88d3e --- /dev/null +++ b/src/define/db/service/presetService.ts @@ -0,0 +1,238 @@ +import Realm from 'realm' +import { RealmBaseService } from './base/realmBase' +import { cloneDeep, isEmpty } from 'lodash' +import { PresetModel } from '../model/preset' +import path from 'path' +import { PresetModel as DefinePresetModel } from '@/define/model/preset' + +export class PresetRealmService extends RealmBaseService { + static instance: PresetRealmService | null = null + declare realm: Realm + private constructor() { + super() + } + + /** + * 获取当前实例对象,为空则创建一个新的 + * @returns + */ + public static async getInstance() { + if (PresetRealmService.instance === null) { + PresetRealmService.instance = new PresetRealmService() + await super.getInstance() + } + await PresetRealmService.instance.open() + return PresetRealmService.instance + } + + /** + * 获取指定条件的预设数据 + * + * @param {PresetModel.QueryPresetCondition} queryCondition 查询条件对象 + * @returns 返回符合条件的预设数据数组 + */ + GetPresetByCondition(queryCondition: DefinePresetModel.QueryPresetCondition): { + presetArray: DefinePresetModel.Preset[] + total: number + } { + // 获取数据 + let presets = this.realm.objects('Preset') + if (queryCondition.id) { + presets = presets.filtered('id = $0', queryCondition.id) + } + + if (queryCondition.label) { + presets = presets.filtered('label CONTAINS $0', queryCondition.label) + } + + if (queryCondition.isShow != null) { + presets = presets.filtered('isShow = $0', queryCondition.isShow) + } + + if (queryCondition.type) { + presets = presets.filtered('type = $0', queryCondition.type) + } + + presets = presets.sorted('createTime', true) + let total = presets.length // 获取总条数 + + if (queryCondition.page && queryCondition.pageSize) { + const start = (queryCondition.page - 1) * queryCondition.pageSize + const end = start + queryCondition.pageSize + const slicedPresets = presets.slice(start, end) + let arrays = Array.from(slicedPresets).map((item) => { + let resObj = { + ...item, + showImage: item.showImage + ? item.showImage.map((item) => item != null && path.resolve(__dirname, item)) + : [], + aliases: item.aliases ? item.aliases.map((item) => item) : [] + } as DefinePresetModel.Preset + return cloneDeep(resObj) + }) + return { + presetArray: arrays, + total: total + } + } else { + let arrays = Array.from(presets).map((item) => { + let resObj = { + ...item, + showImage: item.showImage + ? item.showImage.map((item) => item != null && path.resolve(__dirname, item)) + : [], + aliases: item.aliases ? item.aliases.map((item) => item) : [] + } as DefinePresetModel.Preset + return cloneDeep(resObj) + }) + + return { + presetArray: arrays, + total: total + } + } + } + + /** + * 获取指定ID的预设数据 + * + * @param {string} id 预设ID + * @returns 返回符合条件的预设数据对象或null + */ + GetPresetById(id: string): DefinePresetModel.Preset | null { + let res = this.GetPresetByCondition({ + id: id + }) + if (res.presetArray.length > 0) { + return res.presetArray[0] + } else { + return null + } + } + + /** + * 通过ID数组和类型获取多个预设数据 + * + * 该方法接收一个预设ID数组和类型字符串,返回所有匹配的预设数据。 + * 会逐个查询每个ID的预设数据,并将结果合并到一个数组中返回。 + * 如果某个ID找不到对应的预设,则该ID不会产生任何结果。 + * + * @param {string[]} ids - 要查询的预设ID数组 + * @param {string} type - 预设类型,用于过滤特定类型的预设 + * @returns {DefinePresetModel.Preset[]} 匹配的预设对象数组 + * + * @example + * // 获取多个角色类型预设 + * const characterPresets = presetService.GetPresetByIds(['id1', 'id2', 'id3'], 'character'); + */ + GetPresetByIds(ids: string[], type: string): DefinePresetModel.Preset[] { + let res: DefinePresetModel.Preset[] = [] + for (let i = 0; i < ids.length; i++) { + const element = ids[i] + let preset = this.GetPresetByCondition({ + id: element, + type: type, + isShow: true + }) + if (preset.presetArray.length > 0) { + res.push(...preset.presetArray) + } + } + return res + } + + /** + * 添加预设数据 + * + * @param {PresetModel.Preset} preset 预设对象 + * @return {PresetModel.Preset} 返回添加后的预设对象 + * @throws {Error} 如果预设名称和类型已存在,则抛出错误 + * @throws {Error} 如果预设对象的类型不合法,则抛出错误 + * @throws {Error} 如果预设对象的名称为空,则抛出错误 + * @throws {Error} 如果预设对象的创建时间不合法,则抛出错误 + * */ + AddPreset(preset: DefinePresetModel.Preset): DefinePresetModel.Preset { + // 生成一个新的预设对象 + const newPreset = { + ...preset, + id: preset.id || crypto.randomUUID(), // 如果没有提供ID,则生成一个新的UUID + createTime: preset.createTime || new Date() // 如果没有提供创建时间,则使用当前时间 + } + + if (isEmpty(newPreset.label)) { + throw new Error('预设名称不能为空!') + } + if (isEmpty(newPreset.type)) { + throw new Error('预设类型不能为空!') + } + if (isEmpty(newPreset.id)) { + throw new Error('预设ID不能为空!') + } + + // 判断对应的名字和类型是不是存在 + const existingPreset = this.realm + .objects('Preset') + .filtered('label = $0 && type = $1', newPreset.label, newPreset.type) + if (existingPreset.length > 0) { + throw new Error('再相同类型下已存在当前的预设名字,请修改后再试!') + } + + // 开始写入数据 + this.realm.write(() => { + this.realm.create('Preset', newPreset, Realm.UpdateMode.All) + }) + + // 将数据取出返回 + return this.GetPresetById(newPreset.id) as DefinePresetModel.Preset + } + + /** + * 修改预设数据 + * + * @param {string} id 预设ID + * @param {Partial} preset 预设对象 + * @throws {Error} 如果预设ID为空,则抛出错误 + * @throws {Error} 如果预设对象不存在,则抛出错误 + * */ + ModifyPreset(id: string, preset: Partial): DefinePresetModel.Preset { + if (isEmpty(id)) { + throw new Error('要修改的预设ID不能为空!') + } + delete preset.id // 删除ID属性,避免修改ID + delete preset.createTime // 删除创建时间属性,避免修改创建时间 + + this.transaction(() => { + let existingPreset = this.realm.objectForPrimaryKey('Preset', id) + if (existingPreset == null) { + throw new Error('要修改的预设不存在!') + } + // 开始修改 + for (let key in preset) { + existingPreset[key] = preset[key] + } + }) + // 修改完成后,返回修改后的预设对象 + return this.GetPresetById(id) as DefinePresetModel.Preset + } + + /** + * 删除预设数据 + * + * @param {string} id 预设ID + * @throws {Error} 如果预设ID为空,则抛出错误 + * @throws {Error} 如果预设对象不存在,则抛出错误 + * */ + DeletePreset(id: string): void { + if (isEmpty(id)) { + throw new Error('要删除的预设ID不能为空!') + } + this.transaction(() => { + let existingPreset = this.realm.objectForPrimaryKey('Preset', id) + if (existingPreset == null) { + throw new Error('要删除的预设不存在!') + } + // 开始删除 + this.realm.delete(existingPreset) + }) + } +} diff --git a/src/define/define.ts b/src/define/define.ts new file mode 100644 index 0000000..a3ecaa8 --- /dev/null +++ b/src/define/define.ts @@ -0,0 +1,117 @@ +// 检测当前环境 +const isNode = + typeof process !== 'undefined' && process.versions != null && process.versions.node != null + +// 浏览器环境的路径处理函数 +function joinPath(...segments: string[]): string { + return segments.join('/').replace(/\/+/g, '/') +} + +// 动态导入模块 +let appIsPackaged = false +let basePath = '' + +// Node环境下的初始化 +if (isNode) { + try { + const { app } = require('electron') + appIsPackaged = app.isPackaged + + const path = require('path') + basePath = appIsPackaged + ? path.join(__dirname, '../../../resources') + : path.join(__dirname, '../../resources') + } catch (e) { + // 可能是纯Node环境,没有Electron + try { + const path = require('path') + basePath = path.join(process.cwd(), 'resources') + } catch (e) { + console.warn('无法确定Node环境路径基础') + } + } +} else { + // 浏览器环境,使用相对路径或API提供的基础路径 + // 这里可以根据实际部署环境调整 + basePath = '/resources' +} + +// 创建配置对象 +const define = (() => { + if (isNode) { + const path = require('path') + + // Node环境使用文件系统路径 + const createPaths = (base: string) => ({ + icon: path.join(base, 'icon.ico'), + package_path: path.join(base, 'package'), + db_path: path.join(base, 'scripts/db'), + scripts_path: path.join(base, 'scripts'), + log_folder: path.join(base, 'logger'), + image_path: path.join(base, 'image'), + resources_path: base, + cache_path: path.join(base, 'cache'), + draft_temp_path: path.join(base, 'tmp/jianyingTemp.zip'), + clip_speed_temp_path: path.join(base, 'tmp/Clip/speeds_tmp.json'), + add_canvases_temp_path: path.join(base, 'tmp/Clip/canvases_tmp.json'), + add_sound_channel_mappings_temp_path: path.join( + base, + 'tmp/Clip/sound_channel_mappings_tmp.json' + ), + add_vocal_separations_temp_path: path.join(base, 'tmp/Clip/vocal_separations_tmp.json'), + add_material_video_temp_path: path.join(base, 'tmp/Clip/videoMaterialTemp.json'), + add_tracks_segments_temp_path: path.join(base, 'tmp/Clip/tracks_segments_tmp.json'), + add_tracks_type_temp_path: path.join(base, 'tmp/Clip/tracks_type_tmp.json'), + add_material_animations_temp_path: path.join(base, 'tmp/Clip/material_animations_tmp.json'), + add_material_text_temp_path: path.join(base, 'tmp/Clip/material_text_temp.json'), + add_track_text_segments_temp_path: path.join(base, 'tmp/Clip/track_text_segments_temp.json'), + add_materials_beats_tmp_path: path.join(base, 'tmp/Clip/materials_beats_tmp.json'), + add_materials_audios_tmp_path: path.join(base, 'tmp/Clip/materials_audios_tmp.json'), + add_tracks_audio_segments_tmp_path: path.join( + base, + 'tmp/Clip/tracks_audio_segments_tmp.json' + ), + add_keyframe_tmp_path: path.join(base, 'tmp/Clip/keyframe_tmp.json'), + lms_url: 'https://lms.laitool.cn' + }) + + return createPaths(basePath) + } else { + // 浏览器环境使用URL路径 + const createPaths = (base: string) => ({ + icon: joinPath(base, 'icon.ico'), + package_path: joinPath(base, 'package'), + db_path: joinPath(base, 'scripts/db'), + scripts_path: joinPath(base, 'scripts'), + log_folder: joinPath(base, 'logger'), + image_path: joinPath(base, 'image'), + resources_path: base, + cache_path: joinPath(base, 'cache'), + draft_temp_path: joinPath(base, 'tmp/jianyingTemp.zip'), + clip_speed_temp_path: joinPath(base, 'tmp/Clip/speeds_tmp.json'), + add_canvases_temp_path: joinPath(base, 'tmp/Clip/canvases_tmp.json'), + add_sound_channel_mappings_temp_path: joinPath( + base, + 'tmp/Clip/sound_channel_mappings_tmp.json' + ), + add_vocal_separations_temp_path: joinPath(base, 'tmp/Clip/vocal_separations_tmp.json'), + add_material_video_temp_path: joinPath(base, 'tmp/Clip/videoMaterialTemp.json'), + add_tracks_segments_temp_path: joinPath(base, 'tmp/Clip/tracks_segments_tmp.json'), + add_tracks_type_temp_path: joinPath(base, 'tmp/Clip/tracks_type_tmp.json'), + add_material_animations_temp_path: joinPath(base, 'tmp/Clip/material_animations_tmp.json'), + add_material_text_temp_path: joinPath(base, 'tmp/Clip/material_text_temp.json'), + add_track_text_segments_temp_path: joinPath(base, 'tmp/Clip/track_text_segments_temp.json'), + add_materials_beats_tmp_path: joinPath(base, 'tmp/Clip/materials_beats_tmp.json'), + add_materials_audios_tmp_path: joinPath(base, 'tmp/Clip/materials_audios_tmp.json'), + add_tracks_audio_segments_tmp_path: joinPath(base, 'tmp/Clip/tracks_audio_segments_tmp.json'), + add_keyframe_tmp_path: joinPath(base, 'tmp/Clip/keyframe_tmp.json'), + lms_url: 'https://lms.laitool.cn' + }) + + return createPaths(basePath) + } +})() + +// 导出类型定义和实例 +export type DefineType = typeof define +export { define } diff --git a/src/define/enum/bookEnum.ts b/src/define/enum/bookEnum.ts new file mode 100644 index 0000000..1c4e72f --- /dev/null +++ b/src/define/enum/bookEnum.ts @@ -0,0 +1,624 @@ +import { TaskModal } from '@/define/model/task' + +//#region 小说类型 + +export enum BookType { + // 原创 + ORIGINAL = 'original', + // 反推 + SD_REVERSE = 'sd_reverse', + // MJ 反推 + MJ_REVERSE = 'mj_reverse' +} + +/** + * 根据小说类型返回小说类型的label + * @param type 小说类型 + * @returns + */ +export function GetBookTypeLabel(type: BookType) { + switch (type) { + case BookType.ORIGINAL: + return '原创' + case BookType.SD_REVERSE: + return 'SD反推' + case BookType.MJ_REVERSE: + return 'MJ反推' + default: + return '未知类型' + } +} + +/** + * 获取小说类型的选项列表 + * @returns + */ +export function GetBookTypeOptions() { + return [ + { label: '原创', value: BookType.ORIGINAL }, + { label: 'SD反推', value: BookType.SD_REVERSE }, + { label: 'MJ反推', value: BookType.MJ_REVERSE } + ] +} + +//#endregion + +export enum AddBookTaskCopyData { + AFTER_GPT = 'after_gpt', // 文案 + OLD_IMAGE = 'old_image', // 抽帧/视频 + GPT_PROMPT = 'gpt_prompt', // 反推/GPT提示词 + CHARACTER = 'character', // 角色 + IMAGE_STYLE = 'image_style', // 风格 + PROMPT = 'prompt', // 生图提示词 + IMAGE = 'image' // 生图 +} + +export enum MJCategroy { + // 本地MJ + LOCAL_MJ = 'local_mj', + // 代理MJ + REMOTE_MJ = 'remote_mj', + // 浏览器模式 + BROWSER_MJ = 'browser_mj', + // API模式 + API_MJ = 'api_mj' +} + +export enum MJAction { + // 生图 + IMAGINE = 'IMAGINE', + + // 反推describe + DESCRIBE = 'DESCRIBE' +} + +export enum BookBackTaskType { + // 分镜计算 + STORYBOARD = 'storyboard', + // 分割视频 + SPLIT = 'split', + // 提取音频 + AUDIO = 'audio', + // 识别字幕 + RECOGNIZE = 'recognize', + // 抽帧 + FRAME = 'frame', + // MJ反推 + MJ_REVERSE = BookType.MJ_REVERSE, + // SD反推 + SD_REVERSE = BookType.SD_REVERSE, + // MJ生成图片 + MJ_IMAGE = 'mj_image', + // SD 生成图片 + SD_IMAGE = 'sd_image', + // ComfyUI 生成图片 + ComfyUI_IMAGE = 'comfyui_image', + // flux forge 生成图片 + FLUX_FORGE_IMAGE = 'flux_forge_image', + // flux api 生成图片 + FLUX_API_IMAGE = 'flux_api_image', + // D3 生成图片 + D3_IMAGE = 'd3_image', + // 高清 + HD = 'hd', + // 合成视频 + COMPOSING = 'composing', + // 推理 + INFERENCE = 'inference', + // 翻译 + TRANSLATE = 'translate', + + // ruanway 生成视频 + RUNWAY_VIDEO = 'runway_video', + // luma 生成视频 + LUMA_VIDEO = 'luma_video', + // kling 生成视频 + KLING_VIDEO = 'kling_video' +} + +export enum BookBackTaskStatus { + // 等待 + WAIT = 'wait', + // 运行中 + RUNNING = 'running', + // 暂停 + PAUSE = 'pause', + // 完成 + DONE = 'done', + // 失败 + FAIL = 'fail', + // 重连 + RECONNECT = 'reconnect' +} + +export enum TaskExecuteType { + // 自动 + AUTO = 'auto', + + // 手动 + OPERATE = 'operate' +} + +// 弹窗类型 +export enum DialogType { + // 单独弹窗 + DIALOG = 'dialog', + // 消息提示 + MESSAGE = 'message', + // 右上角通知 + NOTIFICATION = 'notification' +} + +/** + * 小说任务状态 + */ +export enum BookTaskStatus { + // 等待 + WAIT = 'wait', + + // 分镜中 + STORYBOARD = 'storyboard', + + // 分镜失败 + STORYBOARD_FAIL = 'storyboard_fail', + + // 分镜完成,等待分割视频 + STORYBOARD_DONE = 'storyboard_done', + + // 分割视频中 + SPLIT = 'split', + + // 分割视频失败 + SPLIT_FAIL = 'split_fail', + + // 分割视频完成,等待提取音频 + SPLIT_DONE = 'split_done', + + // 提取音频中 + AUDIO = 'audio', + + // 提取音频失败 + AUDIO_FAIL = 'audio_fail', + + // 提取音频完成,等待识别字幕 + AUDIO_DONE = 'audio_done', + + // 识别字幕中 + RECOGNIZE = 'recognize', + + // 识别字幕失败 + RECOGNIZE_FAIL = 'recognize_fail', + + // 识别字幕完成,等待抽帧 + RECOGNIZE_DONE = 'recognize_done', + + // 抽帧中 + FRAME = 'frame', + + // 抽帧完成,等待反推 + FRAME_DONE = 'frame_done', + + // 抽帧失败 + FRAME_FAIL = 'frame_fail', + + // 反推中 + REVERSE = 'reverse', + + // 反推失败 + REVERSE_FAIL = 'reverse_fail', + + // 反推完成,等待生成图片 + REVERSE_DONE = 'reverse_done', + + // 生成图片中 + IMAGE = 'image', + + // 图片生成完成,等待高清 + IMAGE_DONE = 'image_done', + + // 图片生成失败 + IMAGE_FAIL = 'image_fail', + + // 高清中 + HD = 'hd', + + // 高清失败 + HD_FAIL = 'hd_fail', + + // 高清完成,等待合成视频 + HD_DONE = 'hd_done', + + // 合成视频中 + COMPOSING = 'composing', + + // 合成视频完成 + COMPOSING_DONE = 'composing_done', + + // 合成视频失败 + COMPOSING_FAIL = 'composing_fail', + /** 添加草稿完成 */ + DRAFT_DONE = 'draft_done', + /** 添加草稿失败 */ + DRAFT_FAIL = 'draft_fail', + + /** 图转视频失败 */ + IMAGE_TO_VIDEO_ERROR = 'image_to_video_error', + /** 图转视频成功 */ + IMAGE_TO_VIDEO_SUCCESS = 'IMAGE_TO_VIDEO_SUCCESS' +} + +export enum TagDefineType { + // 默认风格 + DEFAULT_STYLE = 'default_style', + // 角色标签 + CHARACTER_MAIN = 'character_main', + + // 角色小标签 + CHARACTER_SUB = 'min', + + // 风格主标签 + STYLE_MAIN = 'style_main', + + // 场景主标签 + SCENE_MAIN = 'scene_main' +} + +export enum OperateBookType { + BOOK = 'book', // 这个小说的所有批次 + BOOKTASK = 'bookTask', // 整个小说批次分镜合并 + BOOKTASKDETAIL = 'bookTaskDetail', // 单个分镜合并 + UNDERBOOKTASK = 'underBookTask', // 执行小说批次任务的指定ID以及后面的所有的东西 + ASSIGNBOOKTASK = 'assignBookTask' // 指定小说批次任务 +} + +export enum CopyImageType { + // 所有,包括原图 + ALL = 'all', + // 出原图外其他,一个个对应 + ONE = 'one', + // 不包含图 + NONE = 'none' +} + +export enum PromptMergeType { + // mj 合并 + MJ_MERGE = 'mj_merge', + + // SD 合并 + SD_MERGE = 'sd_merge', + + // D3 合并 + D3_MERGE = 'd3_merge' +} + +/** + * 小说数据替换类型 + */ +export enum BookRepalceDataType { + // 文案 + AFTER_GPT = 'after_gpt', + // GPT提示词 + GPT_PROMPT = 'gpt_prompt', + // 提示词 + PROMPT = 'prompt' +} + +/** + * 小说选择标签的方式类型,可以是下拉和标签 + */ +export enum BookTagSelectType { + // 下拉 + DROP = 'drop', + // 标签 + TAG = 'tag' +} + +/** + * 根据Key返回指定的后台任务类型的label + * @param key + * @returns + */ +export function GetBookBackTaskTypeLabel(key: string) { + switch (key) { + case BookBackTaskType.STORYBOARD: + return '分镜计算' + case BookBackTaskType.SPLIT: + return '分割视频' + case BookBackTaskType.AUDIO: + return '提取音频' + case BookBackTaskType.RECOGNIZE: + return '识别字幕' + case BookBackTaskType.FRAME: + return '抽帧' + case BookBackTaskType.MJ_REVERSE: + return 'MJ反推' + case BookBackTaskType.SD_REVERSE: + return 'SD反推' + case BookBackTaskType.MJ_IMAGE: + return 'MJ生成图片' + case BookBackTaskType.SD_IMAGE: + return 'SD生成图片' + case BookBackTaskType.FLUX_FORGE_IMAGE: + return 'flux forge生成图片' + case BookBackTaskType.FLUX_API_IMAGE: + return 'flux api生成图片' + case BookBackTaskType.D3_IMAGE: + return 'D3生成图片' + case BookBackTaskType.HD: + return '高清' + case BookBackTaskType.COMPOSING: + return '合成视频' + case BookBackTaskType.INFERENCE: + return '推理' + case BookBackTaskType.TRANSLATE: + return '翻译' + case BookBackTaskType.RUNWAY_VIDEO: + return 'runway生成视频' + case BookBackTaskType.LUMA_VIDEO: + return 'luma生成视频' + case BookBackTaskType.KLING_VIDEO: + return 'kling生成视频' + default: + return key + } +} + +/** + * 根据Key返回指定的后台任务状态的label + * @param key + * @returns + */ +export function GetBookTaskDetailStatusLabel(key: string): TaskModal.TaskStatus { + switch (key) { + case BookTaskStatus.WAIT: + return { + status: BookTaskStatus.WAIT, + label: '等待', + type: 'warning' + } + case BookTaskStatus.STORYBOARD: + return { + status: BookTaskStatus.STORYBOARD, + label: '分镜计算中', + type: 'info' + } + case BookTaskStatus.STORYBOARD_FAIL: + return { + status: BookTaskStatus.STORYBOARD_FAIL, + label: '分镜计算失败', + type: 'error' + } + case BookTaskStatus.STORYBOARD_DONE: + return { + status: BookTaskStatus.STORYBOARD_DONE, + label: '分镜计算完成', + type: 'success' + } + case BookTaskStatus.SPLIT: + return { + status: BookTaskStatus.SPLIT, + label: '分割视频中', + type: 'info' + } + case BookTaskStatus.SPLIT_FAIL: + return { + status: BookTaskStatus.SPLIT_FAIL, + label: '分割视频失败', + type: 'error' + } + case BookTaskStatus.SPLIT_DONE: + return { + status: BookTaskStatus.SPLIT_DONE, + label: '分割视频完成', + type: 'success' + } + case BookTaskStatus.AUDIO: + return { + status: BookTaskStatus.AUDIO, + label: '提取音频中', + type: 'info' + } + case BookTaskStatus.AUDIO_FAIL: + return { + status: BookTaskStatus.AUDIO_FAIL, + label: '提取音频失败', + type: 'error' + } + case BookTaskStatus.AUDIO_DONE: + return { + status: BookTaskStatus.AUDIO_DONE, + label: '提取音频完成', + type: 'success' + } + case BookTaskStatus.RECOGNIZE: + return { + status: BookTaskStatus.RECOGNIZE, + label: '识别字幕中', + type: 'info' + } + case BookTaskStatus.RECOGNIZE_FAIL: + return { + status: BookTaskStatus.RECOGNIZE_FAIL, + label: '识别字幕失败', + type: 'error' + } + case BookTaskStatus.RECOGNIZE_DONE: + return { + status: BookTaskStatus.RECOGNIZE_DONE, + label: '识别字幕完成', + type: 'success' + } + case BookTaskStatus.FRAME: + return { + status: BookTaskStatus.FRAME, + label: '抽帧中', + type: 'info' + } + case BookTaskStatus.FRAME_FAIL: + return { + status: BookTaskStatus.FRAME_FAIL, + label: '抽帧失败', + type: 'error' + } + case BookTaskStatus.FRAME_DONE: + return { + status: BookTaskStatus.FRAME_DONE, + label: '抽帧完成', + type: 'success' + } + case BookTaskStatus.REVERSE: + return { + status: BookTaskStatus.REVERSE, + label: '反推中', + type: 'info' + } + case BookTaskStatus.REVERSE_FAIL: + return { + status: BookTaskStatus.REVERSE_FAIL, + label: '反推失败', + type: 'error' + } + case BookTaskStatus.REVERSE_DONE: + return { + status: BookTaskStatus.REVERSE_DONE, + label: '反推完成', + type: 'success' + } + case BookTaskStatus.IMAGE: + return { + status: BookTaskStatus.IMAGE, + label: '生成图片中', + type: 'info' + } + case BookTaskStatus.IMAGE_FAIL: + return { + status: BookTaskStatus.IMAGE_FAIL, + label: '生成图片失败', + type: 'error' + } + case BookTaskStatus.IMAGE_DONE: + return { + status: BookTaskStatus.IMAGE_DONE, + label: '生成图片完成', + type: 'success' + } + case BookTaskStatus.HD: + return { + status: BookTaskStatus.HD, + label: '高清中', + type: 'info' + } + case BookTaskStatus.HD_FAIL: + return { + status: BookTaskStatus.HD_FAIL, + label: '高清失败', + type: 'error' + } + case BookTaskStatus.HD_DONE: + return { + status: BookTaskStatus.HD_DONE, + label: '高清完成', + type: 'success' + } + case BookTaskStatus.COMPOSING: + return { + status: BookTaskStatus.COMPOSING, + label: '合成视频中', + type: 'info' + } + case BookTaskStatus.COMPOSING_FAIL: + return { + status: BookTaskStatus.COMPOSING_FAIL, + label: '合成视频失败', + type: 'error' + } + case BookTaskStatus.COMPOSING_DONE: + return { + status: BookTaskStatus.COMPOSING_DONE, + label: '合成视频完成', + type: 'success' + } + case BookTaskStatus.DRAFT_DONE: + return { + status: BookTaskStatus.DRAFT_DONE, + label: '添加草稿完成', + type: 'success' + } + case BookTaskStatus.DRAFT_FAIL: + return { + status: BookTaskStatus.DRAFT_FAIL, + label: '添加草稿失败', + type: 'error' + } + case BookTaskStatus.IMAGE_TO_VIDEO_ERROR: + return { + status: BookTaskStatus.IMAGE_TO_VIDEO_ERROR, + label: '图转视频失败', + type: 'error' + } + case BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS: + return { + status: BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS, + label: '图转视频成功', + type: 'success' + } + default: + return { + status: 'UNKNOWN', + label: '未知', + type: 'warning' + } + } +} + +/** + * 根据Key返回指定的后台任务状态的label + * @param key + * @returns + */ +export function GetBookBackTaskStatusLabel(key: string): TaskModal.TaskStatus { + switch (key) { + case BookBackTaskStatus.WAIT: + return { + status: BookBackTaskStatus.WAIT, + label: '等待', + type: 'warning' + } + case BookBackTaskStatus.RUNNING: + return { + status: BookBackTaskStatus.RUNNING, + label: '运行中', + type: 'info' + } + case BookBackTaskStatus.PAUSE: + return { + status: BookBackTaskStatus.PAUSE, + label: '暂停', + type: 'warning' + } + case BookBackTaskStatus.DONE: + return { + status: BookBackTaskStatus.DONE, + label: '完成', + type: 'success' + } + case BookBackTaskStatus.FAIL: + return { + status: BookBackTaskStatus.FAIL, + label: '失败', + type: 'error' + } + case BookBackTaskStatus.RECONNECT: + return { + status: BookBackTaskStatus.RECONNECT, + label: '重连', + type: 'warning' + } + default: + return { + status: 'UNKNOWN', + label: '未知', + type: 'warning' + } + } +} diff --git a/src/define/enum/jianyingEnum.ts b/src/define/enum/jianyingEnum.ts new file mode 100644 index 0000000..ca96249 --- /dev/null +++ b/src/define/enum/jianyingEnum.ts @@ -0,0 +1,51 @@ +/** + * 剪映关键帧类型枚举 + * + * 定义了剪映视频编辑中可用的各种关键帧类型,用于控制素材的动画效果。 + * 这些关键帧类型可用于创建视频素材的移动、缩放等动态效果。 + */ +export enum JianyingKeyFrameEnum { + /** 上下关键帧 */ + KFTypePositionY = 'KFTypePositionY', + /** 左右关键帧 */ + KFTypePositionX = 'KFTypePositionX', + /** 缩放关键帧 */ + KFTypeScale = 'KFTypeScale', + /** 随机关键帧 */ + KFTypeRandom = 'KFTypeRandom' +} + +/** + * 获取剪映关键帧类型的选项列表 + * + * 返回格式化后的关键帧类型选项数组,适用于下拉菜单、选择器等UI组件。 + * 每个选项包含人类可读的标签和对应的枚举值。 + * + * @returns {Array<{label: string, value: JianyingKeyFrameEnum}>} 关键帧类型选项数组 + * + * @example + * // 在组件中使用 + * const options = getJianyingKeyFrameOption(); + * // 返回的数据可直接用于下拉选择组件 + * + */ +export function getJianyingKeyFrameOptions(): Array<{ label: string; value: JianyingKeyFrameEnum }> { + return [ + { + label: '上下关键帧', + value: JianyingKeyFrameEnum.KFTypePositionY + }, + { + label: '左右关键帧', + value: JianyingKeyFrameEnum.KFTypePositionX + }, + { + label: '缩放关键帧', + value: JianyingKeyFrameEnum.KFTypeScale + }, + { + label: '随机关键帧', + value: JianyingKeyFrameEnum.KFTypeRandom + } + ] +} diff --git a/src/define/enum/logger.ts b/src/define/enum/logger.ts new file mode 100644 index 0000000..5e3bfd0 --- /dev/null +++ b/src/define/enum/logger.ts @@ -0,0 +1,6 @@ +export enum LoggerLevel { + Info = 'info', + Warn = 'warn', + Error = 'error', + Success = 'success' +} diff --git a/src/define/enum/mjEnum.ts b/src/define/enum/mjEnum.ts new file mode 100644 index 0000000..a9c03d9 --- /dev/null +++ b/src/define/enum/mjEnum.ts @@ -0,0 +1,46 @@ +export enum MJImageType { + //本地MJ + LOCAL_MJ = 'local_mj', + + // 代理MJ + REMOTE_MJ = 'remote_mj', + + // 浏览器模式 + BROWSER_MJ = 'browser_mj', + + // API模式 + API_MJ = 'api_mj', + + // 本地 SD + LOCAL_SD = 'local_sd', + + // ComfyUI + ComfyUI = 'comfyui', + // flux-api + FLUX_API = 'flux-api', + + // flxu-forge + FLUX_FORGE = 'flux-forge', + + // 导入 + IMPORT = 'import' +} + +export enum MJRespoonseType { + // 创建 + CREATED = 'created', + // 更新 + UPDATED = 'updated', + // 完成 + FINISHED = 'finished', + // 删除 + DELETE = 'delete' +} + +export enum MJAction { + // 出图 + IMAGINE = 'IMAGINE', + + // 反推 + DESCRIBE = 'DESCRIBE' +} diff --git a/src/define/enum/option.ts b/src/define/enum/option.ts new file mode 100644 index 0000000..cabfab0 --- /dev/null +++ b/src/define/enum/option.ts @@ -0,0 +1,80 @@ +/** + * Option Value的数据类型,用于数据的格式化 + */ +export enum OptionType { + STRING = 'string', + NUMBER = 'number', + BOOLEAN = 'boolean', + JSON = 'json' +} + +/** + * 将字符串转换为指定类型的值 + * @param value 要转换的字符串值 + * @param type 目标类型 ('string'|'number'|'boolean'|'json') + * @returns 转换后的值 + */ +export function getOptionType(type: number): OptionType { + switch (type) { + case 1: + return OptionType.STRING + case 2: + return OptionType.JSON + case 3: + return OptionType.NUMBER + case 4: + return OptionType.BOOLEAN + default: + throw new Error(`Unsupported type: ${type}`) + } +} + +/** + * Option Key的名称 + */ +export const OptionKeyName = { + Software: { + /** 机器码ID */ + MachineId: 'Software_MachineId', + /** 软件授权码 */ + AuthorizationCode: 'Software_AuthorizationCode', + /** 外观设置 */ + AppearanceSetting: 'Software_AppearanceSetting', + /** 项目文件地址 */ + ProjectPath: 'Software_ProjectPath', + /** 常规设置 */ + GeneralSetting: 'Software_GeneralSetting', + /** 剪映关键帧设置 */ + JianyingKeyFrameSetting: 'Software_JianyingKeyFrameSetting' + }, + Midjourney: { + /** MJ基础设置 */ + GeneralSetting: 'MJ_GeneralSetting', + + /** MJ API设置 */ + ApiSetting: 'MJ_ApiSetting' + }, + InferenceAI: { + /** InferenceAI设置 人工智能AI推理 */ + InferenceSetting: 'InferenceAI_InferenceSetting', + + /** 分镜AI模型 */ + StoryBoardAIModel: 'InferenceAI_StoryBoardAIModel' + }, + SD: { + /** SD基础设置 */ + SDImageSetting: 'SD_SDImageSetting', + + /** SD修脸修手设置 */ + SDADetailerSetting: 'SD_SDADetailerSetting', + + /** SDSampler设置 */ + SDSamplers: 'SD_SDSamplers', + + /** SD的模型 */ + SDModels: 'SD_SDModels', + + /** SD Lora */ + SDLoras: 'SD_Loras' + } +} diff --git a/src/define/enum/softwareEnum.ts b/src/define/enum/softwareEnum.ts new file mode 100644 index 0000000..e8497d5 --- /dev/null +++ b/src/define/enum/softwareEnum.ts @@ -0,0 +1,89 @@ +export enum SoftwareThemeType { + // 暗 + DARK = 'dark', + // 亮 + LIGHT = 'light' +} + +export enum ComponentSize { + // 极小 + TINY = 'tiny', + // 小 + SMALL = 'small', + // 中 + MEDIUM = 'medium', + // 大 + LARGE = 'large' +} + +export enum LoggerType { + // SD反推 + SD_REVERSE = 'sd_reverse', + // 原创 + ORIGINAL = 'original', + // MJ反推 + MJ_REVERSE = 'mj_reverse' +} + +export enum LoggerStatus { + // 成功 + SUCCESS = 'success', + // 失败 + FAIL = 'fail', + // 进行中 + DOING = 'doing' +} + +export enum OtherData { + // 未知 + UNKNOWN = 'unknown', + //默认 + DEFAULT = 'default' +} + +export enum SoftColor { + // 棕黄色 + BROWN_YELLOW = '#e18a3b', + + // 橘色 + ORANGE = '#ee7959', + + // 朱颜酡 + ZHUYANTUO = '#f29a76', + + // 错误红色 + ERROR_RED = '#c8161d' + +} + +export enum ResponseMessageType { + GET_TEXT = 'getText', // 获取文案 + REMOVE_WATERMARK = "REMOVE_WATERMARK",// 删除水印 + MJ_REVERSE = 'MJ_REVERSE',// MJ反推,返回反推结果 + SD_REVERSE = 'SD_REVERSE',// MJ反推,返回反推结果 + REVERSE_PROMPT_TRANSLATE = 'REVERSE_PROMPT_TRANSLATE',// 反推提示词翻译 + GPT_PROMPT_TRANSLATE = 'GPT_PROMPT_TRANSLATE', // GPT提示词翻译 + MJ_IMAGE = 'MJ_IMAGE',// MJ 生成图片 + ComfyUI_IMAGE = 'ComfyUI_IMAGE',// ComfyUI 生成图片 + HD_IMAGE = 'HD_IMAGE',// HD 生成图片 + RUNWAY_VIDEO = "RUNWAY_VIDEO",// Runway生成视频 + LUMA_VIDEO = "LUMA_VIDEO",// Luma生成视频 + KLING_VIDEO = "KLING_VIDEO",// Kling生成视频 + VIDEO_SUCESS = "VIDEO_SUCESS" //视频生成成功 +} + +export enum LaiAPIType { + // 主要的 + MAIN = "main", + // 香港代理 + HK_PROXY = "hk-proxy", + // 备用站点 + BAK_MAIN = 'bak-main' +} + +// 同步GPTkey的分类 +export enum SyncGptKeyType { + // 字幕设置 + SUBTITLE_SETTING = 'subtitle_setting', + +} \ No newline at end of file diff --git a/src/define/enum/translate.ts b/src/define/enum/translate.ts new file mode 100644 index 0000000..728e8f6 --- /dev/null +++ b/src/define/enum/translate.ts @@ -0,0 +1,25 @@ +// 翻译类型(主要用于后端逻辑处理) +export enum TranslateType { + // 反推提示词翻译 + REVERSE_PROMPT_TRANSLATE = 'reverse_prompt_translate', + // GPT提示词翻译 + GPT_PROMPT_TRANSLATE = 'gpt_prompt_translate' +} + +// 翻译API类型 +export enum TranslateAPIType { + // 百度翻译 + BAIDU = 'baidu', + + // 腾讯翻译 + TENCENT = 'tencent', + + // 火山翻译 + VOLCENGINE = 'volcengine', + + // 阿里翻译 + ALI = 'ali', + + // Laitool GPT翻译 + LAITOOL = 'laitool' +} diff --git a/src/define/ipc/index.ts b/src/define/ipc/index.ts new file mode 100644 index 0000000..184387b --- /dev/null +++ b/src/define/ipc/index.ts @@ -0,0 +1,17 @@ +import { SystemIpc } from './subIpc/systemIpc' +import OptionIpc from './subIpc/optionIpc' +import SettingIpc from './subIpc/settingIpc' +import AxiosIpc from './subIpc/axiosIpc' +import BookIpc from './subIpc/bookIpc' +import PresetIpc from './subIpc/presetIpc' +import TaskIpc from './subIpc/taskIpc' + +export function IpcStart() { + SystemIpc() + OptionIpc() + SettingIpc() + AxiosIpc() + BookIpc() + PresetIpc() + TaskIpc() +} diff --git a/src/define/ipc/subIpc/axiosIpc.ts b/src/define/ipc/subIpc/axiosIpc.ts new file mode 100644 index 0000000..90cae1a --- /dev/null +++ b/src/define/ipc/subIpc/axiosIpc.ts @@ -0,0 +1,85 @@ +import { ipcMain } from 'electron' +import axios from 'axios' +import { DEFINE_STRING } from '@/define/ipcDefineString' + +function AxiosIpc() { + // 通用 GET 请求 + ipcMain.handle(DEFINE_STRING.AXIOS.HTTP_GET, async (_, url, config = {}) => { + try { + const response = await axios.get(url, config) + return { + success: true, + data: response.data, + status: response.status + } + } catch (error: any) { + return { + success: false, + error: error.message, + message: error.message, + status: error.response?.status, + data: error.response?.data + } + } + }) + + // 通用 POST 请求 + ipcMain.handle(DEFINE_STRING.AXIOS.HTTP_POST, async (_, url, data = {}, config = {}) => { + try { + const response = await axios.post(url, data, config) + return { + success: true, + data: response.data, + status: response.status + } + } catch (error: any) { + return { + success: false, + error: error.message, + message: error.message, + status: error.response?.status, + data: error.response?.data + } + } + }) + + // 通用 PUT 请求 + ipcMain.handle(DEFINE_STRING.AXIOS.HTTP_PUT, async (_, url, data = {}, config = {}) => { + try { + const response = await axios.put(url, data, config) + return { + success: true, + data: response.data, + status: response.status + } + } catch (error: any) { + return { + success: false, + error: error.message, + status: error.response?.status, + data: error.response?.data + } + } + }) + + // 通用 DELETE 请求 + ipcMain.handle(DEFINE_STRING.AXIOS.HTTP_DELETE, async (_, url, config = {}) => { + try { + const response = await axios.delete(url, config) + return { + success: true, + data: response.data, + status: response.status + } + } catch (error: any) { + return { + success: false, + error: error.message, + status: error.response?.status, + data: error.response?.data + } + } + }) +} + +export default AxiosIpc diff --git a/src/define/ipc/subIpc/bookIPC/bookDataIpc.ts b/src/define/ipc/subIpc/bookIPC/bookDataIpc.ts new file mode 100644 index 0000000..d043a25 --- /dev/null +++ b/src/define/ipc/subIpc/bookIPC/bookDataIpc.ts @@ -0,0 +1,37 @@ +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { bookHandle } from '@/main/service/book' +import { ipcMain } from 'electron' + +export function bookDataIpc() { + //#region 小说数据相关 + /** 添加或修改小说信息 */ + ipcMain.handle( + DEFINE_STRING.BOOK.ADD_OR_MODIFY_BOOK, + async (_, book) => await bookHandle.AddOrModifyBook(book) + ) + + /** 获取小说信息,通过参数查询 */ + ipcMain.handle( + DEFINE_STRING.BOOK.GET_BOOK_DATA_CONDITION, + async (_, queryCondition) => await bookHandle.GetBookDataCondition(queryCondition) + ) + + /** 修改小说数据信息 */ + ipcMain.handle( + DEFINE_STRING.BOOK.MODIFY_BOOK_DATA_BY_ID, + async (_, id, bookData) => await bookHandle.ModifyBookDataById(id, bookData) + ) + + /** 删除小说数据,通过IDs */ + ipcMain.handle( + DEFINE_STRING.BOOK.DELETE_BOOK_DATA_INFO_BY_ID, + async (_, id: string) => await bookHandle.DeleteBookDataInfoById(id) + ) + + /** 重置小说数据 */ + ipcMain.handle( + DEFINE_STRING.BOOK.RESET_BOOK_DATA_INFO, + async (_, bookId: string) => await bookHandle.ResetBookDataInfo(bookId) + ) + //#endregion +} diff --git a/src/define/ipc/subIpc/bookIPC/bookExportIpc.ts b/src/define/ipc/subIpc/bookIPC/bookExportIpc.ts new file mode 100644 index 0000000..0c49ce9 --- /dev/null +++ b/src/define/ipc/subIpc/bookIPC/bookExportIpc.ts @@ -0,0 +1,15 @@ +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { bookHandle } from '@/main/service/book' +import { ipcMain } from 'electron' + +export function bookExportIpc() { + //#region 导出相关 + + /** 添加剪映草稿 */ + ipcMain.handle( + DEFINE_STRING.BOOK.ADD_JIANYING_DRAFT, + async (_, bookTaskId: string) => await bookHandle.AddJianyingDraft(bookTaskId) + ) + + //#endregion +} diff --git a/src/define/ipc/subIpc/bookIPC/bookImageIpc.ts b/src/define/ipc/subIpc/bookIPC/bookImageIpc.ts new file mode 100644 index 0000000..00da3d4 --- /dev/null +++ b/src/define/ipc/subIpc/bookIPC/bookImageIpc.ts @@ -0,0 +1,52 @@ +import { OperateBookType } from "@/define/enum/bookEnum" +import { DEFINE_STRING } from "@/define/ipcDefineString" +import { bookHandle } from "@/main/service/book" +import { ipcMain } from "electron" + +export function bookImageIpc() { + //#region 出图相关 + + /** 移动图片到主图 */ + ipcMain.handle( + DEFINE_STRING.BOOK.MOVE_IMAGE_TO_MAIN_IMAGE, + async (_, bookTaskId: string, bookTaskDetailId: string, sourceImagePath: string) => + await bookHandle.MoveImageToMainImage(bookTaskId, bookTaskDetailId, sourceImagePath) + ) + + /** 重置(删除)所有未锁定的分镜图片数据 */ + ipcMain.handle( + DEFINE_STRING.BOOK.RESET_GENERATE_IMAGE, + async (_, id: string, operateBookType: OperateBookType) => + await bookHandle.ResetGenerateImage(id, operateBookType) + ) + + /** 上传图片到分镜,并且更新出图信息 */ + ipcMain.handle( + DEFINE_STRING.BOOK.UPLOAD_IMAGE_TO_BOOK_AND_UPDATE_MESSAGE, + async (_, bookTaskDetailId: string, imageFile: string | string[], option: string) => + await bookHandle.UpLoadImageToBookAndUpdateMessage(bookTaskDetailId, imageFile, option) + ) + + /** 高清一个分镜 */ + ipcMain.handle( + DEFINE_STRING.BOOK.HD_ONE_IMAGE, + async (_, bookTaskDetailId: string, scale: number) => + await bookHandle.HDOneImage(bookTaskDetailId, scale) + ) + + /** 获取Midjourney图片URL并下载应用到分镜 */ + ipcMain.handle( + DEFINE_STRING.BOOK.GET_IMAGE_URL_AND_DOWNLOAD, + async (_, id: string, operateBookType: OperateBookType, coverData: boolean) => + await bookHandle.GetImageUrlAndDownload(id, operateBookType, coverData) + ) + + /** 下载图片并拆分处理应用到分镜 */ + ipcMain.handle( + DEFINE_STRING.BOOK.DOWNLOAD_IMAGE_URL_AND_SPLIT, + async (_, bookTaskDetailId: string, imageUrl: string) => + await bookHandle.DownloadImageUrlAndSplit(bookTaskDetailId, imageUrl) + ) + + //#endregion +} diff --git a/src/define/ipc/subIpc/bookIPC/bookPromptIpc.ts b/src/define/ipc/subIpc/bookIPC/bookPromptIpc.ts new file mode 100644 index 0000000..9a58080 --- /dev/null +++ b/src/define/ipc/subIpc/bookIPC/bookPromptIpc.ts @@ -0,0 +1,52 @@ +import { PresetCategory } from '@/define/data/presetData' +import { OperateBookType, PromptMergeType } from '@/define/enum/bookEnum' +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { BookTask } from '@/define/model/book/bookTask' +import { TranslateModel } from '@/define/model/translate' +import { bookHandle } from '@/main/service/book' +import { ipcMain } from 'electron' + +export function bookPromptIpc() { + //#region 提示词 + + /** 原创进行AI推理 */ + ipcMain.handle( + DEFINE_STRING.BOOK.ORIGINAL_GET_AI_PROMPT, + async (_, id: string, operateBookType: OperateBookType, coverData: boolean) => + await bookHandle.OriginalGetAiPrompt(id, operateBookType, coverData) + ) + + /** AI分镜头合并 */ + ipcMain.handle( + DEFINE_STRING.BOOK.AI_STORYBOARD_MERGE, + async (_, bookTaskId: string, type: BookTask.StoryboardMergeType) => + await bookHandle.AIStoryboardMerge(bookTaskId, type) + ) + + /** 合并提示词 */ + ipcMain.handle( + DEFINE_STRING.BOOK.MERGE_PROMPT, + async (_, id: string, type: PromptMergeType, operateBookType: OperateBookType) => + await bookHandle.MergePrompt(id, type, operateBookType) + ) + + /** 自动分析提示词或场景数据 */ + ipcMain.handle( + DEFINE_STRING.BOOK.AUTO_ANALYZE_CHARACTER_OR_SCENE, + async (_, bookTaskId: string, type: PresetCategory) => + await bookHandle.AutoAnalyzeCharacterOrScene(bookTaskId, type) + ) + + //#endregion + + //#region 翻译相关 + + /** 翻译小说分镜数据 */ + ipcMain.handle( + DEFINE_STRING.BOOK.TRANSLATE_BOOK_TASK_DETAIL_PROMPT_NOW_RETURN, + async (_, value: TranslateModel.TranslateNowIPCParams[]) => + await bookHandle.TranslateBookTaskDetailPromptNowReturn(value) + ) + + //#endregion +} diff --git a/src/define/ipc/subIpc/bookIPC/bookTaskDetailIpc.ts b/src/define/ipc/subIpc/bookIPC/bookTaskDetailIpc.ts new file mode 100644 index 0000000..8667a79 --- /dev/null +++ b/src/define/ipc/subIpc/bookIPC/bookTaskDetailIpc.ts @@ -0,0 +1,36 @@ +import { DEFINE_STRING } from "@/define/ipcDefineString" +import { bookHandle } from "@/main/service/book" +import { ipcMain } from "electron" + +export function bookTaskDetailIpc() { + //#region 小说批次任务详细数据相关 + + /** 获取小说子任务详细数据 */ + ipcMain.handle( + DEFINE_STRING.BOOK.GET_BOOK_TASK_DETAIL_DATA_CONDITION, + async (_, bookTaskDetailCondition) => + await bookHandle.GetBookTaskDetailDataByCondition(bookTaskDetailCondition) + ) + + /** 获取小说子任务详细数据,通过小说ID查询 */ + ipcMain.handle( + DEFINE_STRING.BOOK.GET_BOOK_TASK_DETAIL_DATA_BY_ID, + async (_, id) => await bookHandle.GetBookTaskDetailDataById(id) + ) + + /** 修改小说子任务详细数据 */ + ipcMain.handle( + DEFINE_STRING.BOOK.MODIFY_BOOK_TASK_DETAIL_BY_ID, + async (_, bookTaskDetailId, updateData) => + await bookHandle.ModifyBookTaskDetailById(bookTaskDetailId, updateData) + ) + + /** 保持小说批次数据分镜信息 */ + ipcMain.handle( + DEFINE_STRING.BOOK.SAVE_COPYWRITING_INFO, + async (_, bookTaskId, copywritingData, operateBookType) => + await bookHandle.SaveCopywritingInfo(bookTaskId, copywritingData, operateBookType) + ) + + //#endregion +} diff --git a/src/define/ipc/subIpc/bookIPC/bookTaskIpc.ts b/src/define/ipc/subIpc/bookIPC/bookTaskIpc.ts new file mode 100644 index 0000000..b0ae1fe --- /dev/null +++ b/src/define/ipc/subIpc/bookIPC/bookTaskIpc.ts @@ -0,0 +1,66 @@ +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { Book } from '@/define/model/book/book' +import { BookTask } from '@/define/model/book/bookTask' +import { bookHandle } from '@/main/service/book' +import { ipcMain } from 'electron' + +export function bookTaskIpc() { + //#region 小说批次任务相关 + + /** 获取小说子任务数据 */ + ipcMain.handle( + DEFINE_STRING.BOOK.GET_BOOK_TASK_DATA_CONDITION, + async (_, bookTaskCondition) => await bookHandle.GetBookTaskDataByCondition(bookTaskCondition) + ) + + /** 修改小说数据,通过小说ID修改 */ + ipcMain.handle( + DEFINE_STRING.BOOK.MODIFY_BOOK_TASK_DATA_BY_ID, + async (_, id, bookData: Book.SelectBookTask) => + await bookHandle.ModifyBookTaskDataById(id, bookData) + ) + + /** 删除小说数据,通过小说ID删除 */ + ipcMain.handle( + DEFINE_STRING.BOOK.DELETE_BOOK_TASK_BY_IDS, + async (_, ids: string[]) => await bookHandle.DeleteBookTaskByIds(ids) + ) + + /** 重置小说批次任务数据 */ + ipcMain.handle( + DEFINE_STRING.BOOK.RESET_BOOK_TASK_DATA_BY_ID, + async (_, bookTaskId: string) => await bookHandle.ResetBookTaskDataById(bookTaskId) + ) + + /** 重置小说分镜数据(不包含bootask本身数据),并且初始化全新的分镜信息 */ + ipcMain.handle( + DEFINE_STRING.BOOK.RESET_AND_INITIALIZE_BOOK_TASK, + async (_, bookTaskId: string) => await bookHandle.ResetAndInitializeBookTask(bookTaskId) + ) + + /** 删除小说批次任务数据 */ + ipcMain.handle( + DEFINE_STRING.BOOK.DELETE_BOOK_TASK_DATA_BY_ID, + async (_, bookTaskId: string) => await bookHandle.DeleteBookTaskDataById(bookTaskId) + ) + + /** 添加小说子任务数据 */ + ipcMain.handle( + DEFINE_STRING.BOOK.ADD_NEW_BOOK_TASK, + async (_, addData: BookTask.AddBookTaskParam) => await bookHandle.AddNewBookTask(addData) + ) + + /** 获取小说批次任务的图片生成进度 */ + ipcMain.handle( + DEFINE_STRING.BOOK.GET_BOOK_TASK_IMAGE_GENERATE_PROGRESS, + async (_, bookId: string) => await bookHandle.GetBookTaskImageGenerateProgress(bookId) + ) + + /** 获取小说批次任务的第一张图片路径 */ + ipcMain.handle( + DEFINE_STRING.BOOK.GET_BOOK_TASK_FIRST_IMAGE_PATH, + async (_, bookId: string) => await bookHandle.GetBookTaskFirstImagePath(bookId) + ) + + //#endregion +} diff --git a/src/define/ipc/subIpc/bookIpc.ts b/src/define/ipc/subIpc/bookIpc.ts new file mode 100644 index 0000000..7d1be31 --- /dev/null +++ b/src/define/ipc/subIpc/bookIpc.ts @@ -0,0 +1,28 @@ +import { bookDataIpc } from './bookIPC/bookDataIpc' +import { bookTaskIpc } from './bookIPC/bookTaskIpc' +import { bookTaskDetailIpc } from './bookIPC/bookTaskDetailIpc' +import { bookPromptIpc } from './bookIPC/bookPromptIpc' +import { bookImageIpc } from './bookIPC/bookImageIpc' +import { bookExportIpc } from './bookIPC/bookExportIpc' + +function BookIpc() { + // 小说数据相关 + bookDataIpc() + + //小说批次任务相关 + bookTaskIpc() + + // 小说任务详情相关 + bookTaskDetailIpc() + + // 小说提示词相关 + bookPromptIpc() + + // 小说图像相关 + bookImageIpc() + + // 小说导出相关 + bookExportIpc() +} + +export default BookIpc diff --git a/src/define/ipc/subIpc/optionIpc.ts b/src/define/ipc/subIpc/optionIpc.ts new file mode 100644 index 0000000..ba60add --- /dev/null +++ b/src/define/ipc/subIpc/optionIpc.ts @@ -0,0 +1,21 @@ +import OptionService from '@/main/service/option/index' +import { DEFINE_STRING } from '../../ipcDefineString' +import { OptionType } from '../../enum/option' +import { ipcMain } from 'electron' + +function OptionIpc() { + /** 获取指定的Option,通过key,不存在返回null */ + ipcMain.handle( + DEFINE_STRING.OPTION.GET_OPTION_BY_KEY, + async (_, key: string) => await OptionService.GetOptionByKey(key) + ) + + /** 修改指定的Option,通过key,不存在则创建 */ + ipcMain.handle( + DEFINE_STRING.OPTION.MODIFY_OPTION_BY_KEY, + async (_, key: string, value: string, type: OptionType) => + await OptionService.ModifyOptionByKey(key, value, type) + ) +} + +export default OptionIpc diff --git a/src/define/ipc/subIpc/presetIpc.ts b/src/define/ipc/subIpc/presetIpc.ts new file mode 100644 index 0000000..86f68e5 --- /dev/null +++ b/src/define/ipc/subIpc/presetIpc.ts @@ -0,0 +1,40 @@ +import { DEFINE_STRING } from '../../ipcDefineString' +import { ipcMain } from 'electron' +import { presetHandle } from '@/main/service/preset' +import { PresetModel } from '@/define/model/preset' + +function PresetIpc() { + /** 通过条件获取预设 */ + ipcMain.handle( + DEFINE_STRING.PRESET.GET_PRESET_BY_CONDITION, + async (_, queryCondition: PresetModel.Preset) => + await presetHandle.GetPresetByCondition(queryCondition) + ) + + /** 通过ID获取预设 */ + ipcMain.handle( + DEFINE_STRING.PRESET.GET_PRESET_BY_ID, + async (_, id: string) => await presetHandle.GetPresetById(id) + ) + + /** 添加预设 */ + ipcMain.handle( + DEFINE_STRING.PRESET.ADD_PRESET, + async (_, preset: PresetModel.Preset) => await presetHandle.AddPreset(preset) + ) + + /** 修改预设 */ + ipcMain.handle( + DEFINE_STRING.PRESET.MODIFY_PRESET, + async (_, id: string, preset: Partial) => + await presetHandle.ModifyPreset(id, preset) + ) + + /** 删除预设 */ + ipcMain.handle( + DEFINE_STRING.PRESET.DELETE_PRESET, + async (_, id: string) => await presetHandle.DeletePreset(id) + ) +} + +export default PresetIpc diff --git a/src/define/ipc/subIpc/settingIpc.ts b/src/define/ipc/subIpc/settingIpc.ts new file mode 100644 index 0000000..7852312 --- /dev/null +++ b/src/define/ipc/subIpc/settingIpc.ts @@ -0,0 +1,13 @@ +import { DEFINE_STRING } from '../../ipcDefineString' +import { ipcMain } from 'electron' +import SettingHandle from '@/main/service/setting/index' + +function SettingIpc() { + /** 获取默认的剪映草稿地址 */ + ipcMain.handle( + DEFINE_STRING.SETTING.GET_DEFAULT_JIANYING_DRAFT_PATH, + async () => await SettingHandle.GetDefaultJianyingDraftPath() + ) +} + +export default SettingIpc diff --git a/src/define/ipc/subIpc/systemIpc.ts b/src/define/ipc/subIpc/systemIpc.ts new file mode 100644 index 0000000..a82c586 --- /dev/null +++ b/src/define/ipc/subIpc/systemIpc.ts @@ -0,0 +1,133 @@ +import { app, ipcMain, BrowserWindow } from 'electron' +import { DEFINE_STRING } from '../../ipcDefineString' +import ElectronInterface, { OpenFolderParams } from '@/main/service/system/electronInterface' +import { UserSoftware } from '@/main/service/system/userSoftware' +import JianyingService from '@/main/service/jianying/jianyingService' + +const electronInterface = new ElectronInterface() +const userSoftware = new UserSoftware() +const jianyingService = new JianyingService() + +function SystemIpc() { + //#region 窗口相关 + /** + * 设置当前窗口的标题 + */ + ipcMain.on(DEFINE_STRING.SYSTEM.SET_TITLE, (e, data) => { + const webContents = e.sender + const wins = BrowserWindow.fromWebContents(webContents) + if (wins) { + wins.setTitle(data) + } + }) + + /** + * 检查当前的窗口是不是最大化 + */ + ipcMain.handle(DEFINE_STRING.SYSTEM.IS_MAXIMIZED, (_) => { + const win = BrowserWindow.getFocusedWindow() + return win?.isMaximized() + }) + + /** + * 最小化当前窗口 + */ + ipcMain.on(DEFINE_STRING.SYSTEM.MIN_WINDOW, (_) => { + const win = BrowserWindow.getFocusedWindow() + win?.minimize() + }) + + /** + * 退出当前应用 + */ + ipcMain.on(DEFINE_STRING.SYSTEM.QUIT_APP, (_) => { + app.quit() + }) + + /** + * 打开开发者工具 + */ + ipcMain.on(DEFINE_STRING.SYSTEM.OPEN_DEV_TOOLS, (_) => { + const win = BrowserWindow.getFocusedWindow() + win?.webContents.openDevTools() + }) + //#endregion + + /** 打开指定的URL */ + ipcMain.handle(DEFINE_STRING.SYSTEM.OPEN_URL, async (_, url: string) => { + electronInterface.OpenUrl(url) + }) + + //#region 系统相关 + + /** * 切换主题 */ + ipcMain.handle( + DEFINE_STRING.SYSTEM.TOGGLE_THEME, + async (_, theme: string) => await electronInterface.ToggleTheme(theme) + ) + + /** 同步授权信 */ + ipcMain.on( + DEFINE_STRING.SYSTEM.SYNC_AUTHORIZATION, + async (_, am: SoftwareModal.SoftwareAuthorizationMessage) => + await userSoftware.SyncAuthorization(am) + ) + + /** 获取剪映草稿文件列表 */ + ipcMain.handle( + DEFINE_STRING.SYSTEM.GET_JIANYING_DRAFT_FILE_LIST, + async () => await jianyingService.GetJianyingDraftFileList() + ) + + //#endregion + + //#region 文件相关 + + /** 选择多个文件,指定文件后缀 */ + ipcMain.handle( + DEFINE_STRING.SYSTEM.SELECT_MULTIPLE_FILE, + async (_, extensions: string[]) => await electronInterface.SelectMultipleFile(extensions) + ) + + /** + * 选择单个文件,指定文件后缀 + */ + ipcMain.handle( + DEFINE_STRING.SYSTEM.SELECT_SINGLE_FILE, + async (_, extensions: string[]) => await electronInterface.SelectSingleFile(extensions) + ) + + /** 选择单个文件夹 */ + ipcMain.handle(DEFINE_STRING.SYSTEM.SELECT_SINGLE_FOLDER, async (_, defaultPath: string) => { + return await electronInterface.SelectSingleFolder(defaultPath) + }) + + /** 打开指定的文件夹 */ + ipcMain.handle( + DEFINE_STRING.SYSTEM.OPEN_FOLDER, + async (_, params: OpenFolderParams) => await electronInterface.OpenFolder(params) + ) + + /** 打开指定的文件 */ + ipcMain.handle( + DEFINE_STRING.SYSTEM.OPEN_FILE, + async (_, filePath: string) => await electronInterface.OpenFile(filePath) + ) + + /** 复制指定的文件夹中的所有内容到另一个文件夹 */ + ipcMain.handle( + DEFINE_STRING.SYSTEM.COPY_FOLDER_CONTENTS, + async (_, { source, destination }) => + await electronInterface.CopyFolderContents(source, destination) + ) + + /** 选择文件夹或指定后缀的文件 */ + ipcMain.handle( + DEFINE_STRING.SYSTEM.SELECT_FOLDER_OR_FILE, + async (_, extensions?: string[]) => await electronInterface.SelectFolderOrFile(extensions) + ) + + //#endregion +} + +export { SystemIpc } diff --git a/src/define/ipc/subIpc/taskIpc.ts b/src/define/ipc/subIpc/taskIpc.ts new file mode 100644 index 0000000..22b1b23 --- /dev/null +++ b/src/define/ipc/subIpc/taskIpc.ts @@ -0,0 +1,62 @@ +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { ipcMain } from 'electron' +import { TaskHandle } from '@/main/service/task' +import { BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum' + +let taskHandle: TaskHandle = new TaskHandle() + +function TaskIpc() { + /** 启动后台任务 */ + ipcMain.handle( + DEFINE_STRING.TASK.START_TASK_QUEUE, + async (_, isGiveUp) => await taskHandle.StartTaskQueue(isGiveUp) + ) + + /** 添加单个个任务 */ + ipcMain.handle( + DEFINE_STRING.TASK.ADD_ONE_TASK, + async ( + _, + bookId: string, + taskType: BookBackTaskType, + executeType: TaskExecuteType = TaskExecuteType.AUTO, + bookTaskId: string | undefined = undefined, + bookTaskDetailId: string | undefined = undefined, + responseMessageName?: string + ) => + await taskHandle.AddOneTask( + bookId, + taskType, + executeType, + bookTaskId, + bookTaskDetailId, + responseMessageName + ) + ) + + /** 添加多个任务 */ + ipcMain.handle( + DEFINE_STRING.TASK.ADD_MULTI_TASK, + async (_, tasks) => await taskHandle.AddMultiTask(tasks) + ) + + /** 获取指定的状态的任务 */ + ipcMain.handle( + DEFINE_STRING.TASK.GET_ASSIGN_STATUS_TASK_COUNT, + async (_, status) => await taskHandle.GetAssignStatusTaskCount(status) + ) + + /** 获取任务集合 */ + ipcMain.handle( + DEFINE_STRING.TASK.GET_TASK_COLLECTION, + async (_, queryTaskCondition) => await taskHandle.GetTaskCollection(queryTaskCondition) + ) + + /** 更新后台任务状态 */ + ipcMain.handle( + DEFINE_STRING.TASK.UPDATE_TASK_STATUS, + async (_, bookBackTask) => await taskHandle.UpdateTaskStatus(bookBackTask) + ) +} + +export default TaskIpc diff --git a/src/define/ipcDefineString/index.ts b/src/define/ipcDefineString/index.ts new file mode 100644 index 0000000..0f9d61d --- /dev/null +++ b/src/define/ipcDefineString/index.ts @@ -0,0 +1,19 @@ +import LOGGER from './subDefineString/loggerDefineString' +import OPTION from './subDefineString/optionDefineString' +import SYSTEM from './subDefineString/systemDefineString' +import SETTING from './subDefineString/settingDefineString' +import AXIOS from './subDefineString/axiosDefineString' +import BOOK from './subDefineString/bookDefineString' +import PRESET from './subDefineString/presetDefineString' +import TASK from './subDefineString/taskDefineString' + +export const DEFINE_STRING = { + OPTION: OPTION, + SYSTEM: SYSTEM, + LOGGER: LOGGER, + SETTING: SETTING, + AXIOS: AXIOS, + BOOK: BOOK, + PRESET: PRESET, + TASK: TASK +} diff --git a/src/define/ipcDefineString/subDefineString/axiosDefineString.ts b/src/define/ipcDefineString/subDefineString/axiosDefineString.ts new file mode 100644 index 0000000..a8ef37b --- /dev/null +++ b/src/define/ipcDefineString/subDefineString/axiosDefineString.ts @@ -0,0 +1,12 @@ +const AXIOS = { + /** http的get请求 */ + HTTP_GET: 'http:get', + /** http的post请求 */ + HTTP_POST: 'http:post', + /** http的put请求 */ + HTTP_PUT: 'http:put', + /** http的delete请求 */ + HTTP_DELETE: 'http:delete' +} + +export default AXIOS diff --git a/src/define/ipcDefineString/subDefineString/bookDefineString.ts b/src/define/ipcDefineString/subDefineString/bookDefineString.ts new file mode 100644 index 0000000..2fde70a --- /dev/null +++ b/src/define/ipcDefineString/subDefineString/bookDefineString.ts @@ -0,0 +1,145 @@ +const BOOK = { + //#region 小说数据相关 + + /** 新增或者是修改小说数据 */ + ADD_OR_MODIFY_BOOK: 'ADD_OR_MODIFY_BOOK', + + /** 获取小说数据,通过参数查询 */ + GET_BOOK_DATA_CONDITION: 'GET_BOOK_DATA_CONDITION', + + /** 修改小说数据信息 */ + MODIFY_BOOK_DATA_BY_ID: 'MODIFY_BOOK_DATA_BY_ID', + + /** 删除小说数据,通过ID */ + DELETE_BOOK_DATA_INFO_BY_ID: 'DELETE_BOOK_DATA_INFO_BY_ID', + + /** 重置小说数据 */ + RESET_BOOK_DATA_INFO: 'RESET_BOOK_DATA_INFO', + + //#endregion + + //#region 小说批次任务相关 + + /** 获取小说批次任务数据,通过参数查询 */ + GET_BOOK_TASK_DATA_CONDITION: 'GET_BOOK_TASK_DATA_CONDITION', + + /** 修改小说批次数据,通过ID */ + MODIFY_BOOK_TASK_DATA_BY_ID: 'MODIFY_BOOK_TASK_DATA_BY_ID', + + /** 删除指定ID得小说子任务 */ + DELETE_BOOK_TASK_BY_IDS: 'DELETE_BOOK_TASK_BY_IDS', + + /** 重置小说批次任务数据 */ + RESET_BOOK_TASK_DATA_BY_ID: 'RESET_BOOK_TASK_DATA_BY_ID', + + /** 重置小说分镜数据(不包含bootask本身数据),并且初始化全新的分镜信息 */ + RESET_AND_INITIALIZE_BOOK_TASK: 'RESET_AND_INITIALIZE_BOOK_TASK', + + /** 删除小说批次任务数据 */ + DELETE_BOOK_TASK_DATA_BY_ID: 'DELETE_BOOK_TASK_DATA_BY_ID', + + /** 添加小说子任务数据 */ + ADD_NEW_BOOK_TASK: 'ADD_NEW_BOOK_TASK', + + /** 获取小说批次任务的图片生成进度 */ + GET_BOOK_TASK_IMAGE_GENERATE_PROGRESS: 'GET_BOOK_TASK_IMAGE_GENERATE_PROGRESS', + + /** 获取小说批次任务的第一张图片路径 */ + GET_BOOK_TASK_FIRST_IMAGE_PATH: 'GET_BOOK_TASK_FIRST_IMAGE_PATH', + + //#endregion + + //#region 小说批次任务详细数据相关 + + /** 获取小说子任务详细数据 */ + + GET_BOOK_TASK_DETAIL_DATA_CONDITION: 'GET_BOOK_TASK_DETAIL_DATA_CONDITION', + + /** 获取小说子任务详细数据,通过小说ID查询 */ + GET_BOOK_TASK_DETAIL_DATA_BY_ID: 'GET_BOOK_TASK_DETAIL_DATA_BY_ID', + + /** 修改小说批次任务状态 */ + MODIFY_BOOK_TASK_DETAIL_BY_ID: 'MODIFY_BOOK_TASK_DETAIL_BY_ID', + + /** 保持小说批次数据分镜信息 */ + SAVE_COPYWRITING_INFO: 'SAVE_COPYWRITING_INFO', + + //#endregion + + //#region 提示词 + + /** 原创进行AI推理 */ + ORIGINAL_GET_AI_PROMPT: 'ORIGINAL_GET_AI_PROMPT', + + /** AI分镜头合并 */ + AI_STORYBOARD_MERGE: 'AI_STORYBOARD_MERGE', + + /** 原创获取AI提示词返回 */ + ORIGINAL_GET_AI_PROMPT_RETURN: 'ORIGINAL_GET_AI_PROMPT_RETURN', + + /** 合并提示词 */ + MERGE_PROMPT: 'MERGE_PROMPT', + + /** 自动分析提示词或场景数据 */ + AUTO_ANALYZE_CHARACTER_OR_SCENE: 'AUTO_ANALYZE_CHARACTER_OR_SCENE', + + //#endregion + + //#region 翻译相关 + + /** 翻译小说详细任务提示词数据 */ + TRANSLATE_BOOK_TASK_DETAIL_PROMPT_NOW_RETURN: 'TRANSLATE_BOOK_TASK_DETAIL_PROMPT_NOW_RETURN', + + /** 翻译小说详细任务提示词数据监听 */ + TRANSLATE_BOOK_TASK_DETAIL_PROMPT_NOW_RETURN_Listen: + 'TRANSLATE_BOOK_TASK_DETAIL_PROMPT_NOW_RETURN_Listen', + + //#endregion + + //#region 出图相关 + + /** MJ生图返回数据 */ + MJ_IMAGE_GENERATE_RETURN: 'MJ_IMAGE_GENERATE_RETURN', + + /** SD生图返回数据 */ + SD_IMAGE_GENERATE_RETURN: 'SD_IMAGE_GENERATE_RETURN', + + /** ComfyUI 生图返回数据 */ + ComfyUI_IMAGE_GENERATE_RETURN: 'ComfyUI_IMAGE_GENERATE_RETURN', + + /** D3 生图返回数据 */ + D3_IMAGE_GENERATE_RETURN: 'D3_IMAGE_GENERATE_RETURN', + + /** Flux API 生图返回数据 */ + FLUX_API_IMAGE_GENERATE_RETURN: 'FLUX_API_IMAGE_GENERATE_RETURN', + + /** Flux Forge 生图数据返回 */ + FLUX_FORGE_IMAGE_GENERATE_RETURN: 'FLUX_FORGE_IMAGE_GENERATE_RETURN', + + /** 移动图片到主图 */ + MOVE_IMAGE_TO_MAIN_IMAGE: 'MOVE_IMAGE_TO_MAIN_IMAGE', + + /** 重置生成图片数据 */ + RESET_GENERATE_IMAGE: 'RESET_GENERATE_IMAGE', + + /** 上传图片到分镜,并且更新出图信息 */ + UPLOAD_IMAGE_TO_BOOK_AND_UPDATE_MESSAGE: 'UPLOAD_IMAGE_TO_BOOK_AND_UPDATE_MESSAGE', + + /** 高清一个分镜 */ + HD_ONE_IMAGE: 'HD_ONE_IMAGE', + + /**获取Midjourney图片URL并下载应用到分镜 */ + GET_IMAGE_URL_AND_DOWNLOAD: 'GET_IMAGE_URL_AND_DOWNLOAD', + + /** 下载图片并拆分处理应用到分镜 */ + DOWNLOAD_IMAGE_URL_AND_SPLIT: 'DOWNLOAD_IMAGE_URL_AND_SPLIT', + //#endregion + + //#region 视频相关 + /** 添加剪映草稿 */ + ADD_JIANYING_DRAFT: 'ADD_JIANYING_DRAFT' + + //#endregion +} + +export default BOOK diff --git a/src/define/ipcDefineString/subDefineString/loggerDefineString.ts b/src/define/ipcDefineString/subDefineString/loggerDefineString.ts new file mode 100644 index 0000000..b065284 --- /dev/null +++ b/src/define/ipcDefineString/subDefineString/loggerDefineString.ts @@ -0,0 +1,6 @@ +const LOGGER = { + /** 添加日志消息 */ + ADD_LOGGER: 'ADD_LOGGER' +} + +export default LOGGER diff --git a/src/define/ipcDefineString/subDefineString/optionDefineString.ts b/src/define/ipcDefineString/subDefineString/optionDefineString.ts new file mode 100644 index 0000000..e0cab71 --- /dev/null +++ b/src/define/ipcDefineString/subDefineString/optionDefineString.ts @@ -0,0 +1,9 @@ +const OPTION = { + /** 获取指定的Option,通过key,不存在返回null */ + GET_OPTION_BY_KEY: 'GET_OPTION_BY_KEY', + + /** 修改指定的Option,通过key,不存在则创建 */ + MODIFY_OPTION_BY_KEY: 'MODIFY_OPTION_BY_KEY' +} + +export default OPTION diff --git a/src/define/ipcDefineString/subDefineString/presetDefineString.ts b/src/define/ipcDefineString/subDefineString/presetDefineString.ts new file mode 100644 index 0000000..029ee42 --- /dev/null +++ b/src/define/ipcDefineString/subDefineString/presetDefineString.ts @@ -0,0 +1,14 @@ +const PRESET = { + /** 通过ID获取预设 */ + GET_PRESET_BY_ID: 'GET_PRESET_BY_ID', + /** 获取预设通过条件 */ + GET_PRESET_BY_CONDITION: 'GET_PRESET_BY_CONDITION', + /** 添加预设 */ + ADD_PRESET: 'ADD_PRESET', + /** 修改预设 */ + MODIFY_PRESET: 'MODIFY_PRESET', + /** 删除预设 */ + DELETE_PRESET: 'DELETE_PRESET' +} + +export default PRESET diff --git a/src/define/ipcDefineString/subDefineString/settingDefineString.ts b/src/define/ipcDefineString/subDefineString/settingDefineString.ts new file mode 100644 index 0000000..f1474e6 --- /dev/null +++ b/src/define/ipcDefineString/subDefineString/settingDefineString.ts @@ -0,0 +1,6 @@ +const SETTING = { + /** 获取默认的剪映草稿地址 */ + GET_DEFAULT_JIANYING_DRAFT_PATH: 'GET_DEFAULT_JIANYING_DRAFT_PATH' +} + +export default SETTING diff --git a/src/define/ipcDefineString/subDefineString/systemDefineString.ts b/src/define/ipcDefineString/subDefineString/systemDefineString.ts new file mode 100644 index 0000000..7838c98 --- /dev/null +++ b/src/define/ipcDefineString/subDefineString/systemDefineString.ts @@ -0,0 +1,67 @@ +const SYSTEM = { + //#region 系统相关 + /** 设置窗口的标题 */ + SET_TITLE: 'SET_TITLE', + /** 检查当前窗口是不是最大化 */ + IS_MAXIMIZED: 'IS_MAXIMIZED', + /** 最小化当前 */ + MIN_WINDOW: 'MIN_WINDOW', + /** 退出应用 */ + QUIT_APP: 'QUIT_APP', + /** 打开开发者工具 */ + OPEN_DEV_TOOLS: 'OPEN_DEV_TOOLS', + + //#endregion + + //#region 系统相关 + + /** 切换主题 */ + TOGGLE_THEME: 'TOGGLE_THEME', + + /** 同步授权信息 */ + SYNC_AUTHORIZATION: 'SYNC_AUTHORIZATION', + + /** 获取剪映草稿文件列表 */ + GET_JIANYING_DRAFT_FILE_LIST: 'GET_JIANYING_DRAFT_FILE_LIST', + + //#endregion + //#region 文件相关 + + /** 选择多个文件,指定文件后缀 */ + SELECT_MULTIPLE_FILE: 'SELECT_MULTIPLE_FILE', + + /** 选择单个文件,指定文件后缀 */ + SELECT_SINGLE_FILE: 'SELECT_SINGLE_FILE', + + /** 选择单个文件夹 */ + SELECT_SINGLE_FOLDER: 'SELECT_SINGLE_FOLDER', + + /** 打开指定的文件夹 */ + OPEN_FOLDER: 'OPEN_FOLDER', + + /** 打开指定的文件 */ + OPEN_FILE: 'OPEN_FILE', + + /** 复制指定的文件夹中的所有内容到另一个文件夹 */ + COPY_FOLDER_CONTENTS: 'COPY_FOLDER_CONTENTS', + + /** 选择文件夹或指定后缀的文件 */ + SELECT_FOLDER_OR_FILE: 'SELECT_FOLDER_OR_FILE', + + //#endregion + + /** 打开指定的url */ + OPEN_URL: 'OPEN_URL', + + //#region 通知相关 + + /** 显示全局的 message */ + SHOW_MESSAGE_DIALOG: 'SHOW_MESSAGE_DIALOG', + + /** 显示全局的 NOTIFICATION */ + SHOW_MAIN_NOTIFICATION: 'SHOW_MAIN_NOTIFICATION' + + //#endregion +} + +export default SYSTEM diff --git a/src/define/ipcDefineString/subDefineString/taskDefineString.ts b/src/define/ipcDefineString/subDefineString/taskDefineString.ts new file mode 100644 index 0000000..dfc67e3 --- /dev/null +++ b/src/define/ipcDefineString/subDefineString/taskDefineString.ts @@ -0,0 +1,20 @@ +const TASK = { + /* 启动任务队列 */ + START_TASK_QUEUE: 'START_TASK_QUEUE', + /* 添加单个个任务 */ + ADD_ONE_TASK: 'ADD_ONE_TASK', + + /** 添加多个任务 */ + ADD_MULTI_TASK: 'ADD_MULTI_TASK', + + /** 获取指定的状态的任务 */ + GET_ASSIGN_STATUS_TASK_COUNT: 'GET_ASSIGN_STATUS_TASK_COUNT', + + /** 获取任务集合 */ + GET_TASK_COLLECTION: 'GET_TASK_COLLECTION', + + /** 更新后台任务状态 */ + UPDATE_TASK_STATUS: 'UPDATE_TASK_STATUS' +} + +export default TASK diff --git a/src/define/model/ai/aiConfig.d.ts b/src/define/model/ai/aiConfig.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/define/model/ai/openaiRequest.d.ts b/src/define/model/ai/openaiRequest.d.ts new file mode 100644 index 0000000..ce2a1d6 --- /dev/null +++ b/src/define/model/ai/openaiRequest.d.ts @@ -0,0 +1,44 @@ +declare namespace OpenAIRequest { + /** + * AI聊天消息接口 + * + * 定义AI聊天中单条消息的结构 + */ + interface RequestMessage { + /** 消息角色类型 */ + role: 'system' | 'user' | 'assistant' + /** 消息内容 */ + content: string + } + + /** + * AI词语合并配置接口 + * + * 定义AI分镜师进行文案分镜头合并时的完整配置参数 + * 用于文案的智能分镜处理 + */ + interface Request { + /** AI模型名称,指定使用的AI模型 */ + model?: string + /** 是否启用流式输出 */ + stream: boolean + /** 温度参数,控制生成内容的随机性 (0-1) */ + temperature: number + /** 消息列表,包含系统提示和用户输入 */ + messages: AIMessage[] + } + + /** + * AI配置基础接口 + * + * 定义所有AI相关配置的通用属性 + */ + interface RequestBase { + /** AI模型名称 */ + model: string + /** 是否启用流式输出 */ + stream: boolean + /** 温度参数 (0-1) */ + temperature: number + } +} diff --git a/src/define/model/book/book.d.ts b/src/define/model/book/book.d.ts new file mode 100644 index 0000000..2a2261b --- /dev/null +++ b/src/define/model/book/book.d.ts @@ -0,0 +1,343 @@ +import { BookTaskDetailModel } from '@/define/db/model/bookTaskDetail' +import { + BookBackTaskStatus, + BookBackTaskType, + BookTaskStatus, + BookType, + TaskExecuteType, + BookRepalceDataType +} from '../../enum/bookEnum' +import { MJAction } from '../../enum/bookEnum' +import { BookTaskDetail } from './bookTaskDetail' +import { ImageCategory } from '@/define/data/imageData' +import { ImageGenerateMode } from '@/define/data/mjData' + +declare namespace Book { + //#region 小说相关 + + /** + * 小说数据类型 + */ + type SelectBook = { + id?: string + no?: number + name?: string + bookFolderPath?: string + type?: BookType + oldVideoPath?: string + srtPath?: string + audioPath?: string + draftDepend?: string // 草稿依赖 + imageFolder?: string + draftSrtStyle?: string | null // 草稿字幕样式 + backgroundMusic?: string | null // 背景音乐ID + friendlyReminder?: string | null // 友情提示 + subtitlePosition?: string + watermarkPosition?: string + updateTime?: Date + createTime?: Date + version?: string + imageStyle?: string[] | null // 软件内置的样式 + autoAnalyzeCharacter?: string | null // 自动分析角色设置 + customizeImageStyle?: string[] | null // 自定义的样式 + videoConfig?: string | null // 合成视频设置 + prefixPrompt?: string | null // 前缀 + suffixPrompt?: string | null // 后缀 + } + + /** + * 查询小说的条件 + */ + type QueryBookCondition = { + id?: string + name?: string + page?: number + pageSize?: number + type?: BookType + } + + /** + * 查询小说的返回类型 + */ + type QueryBookConditionResponse = { + resultBooks: SelectBook[] + bookLength: number + } + + //#endregion + + //#region 小说任务相关 + + /** + * 小说批次任务类型 + */ + type SelectBookTask = { + no?: number + id?: string + bookId?: string + name?: string + generateVideoPath?: string + srtPath?: string + audioPath?: string + draftDepend?: string + draftSrtStyle?: string | null // 草稿字幕样式 + backgroundMusic?: string | null // 背景音乐ID + friendlyReminder?: string | null // 友情提示 + imageFolder?: string + customizeImageStyle?: string[] + cacheImageList?: string[] + prefixPrompt?: string + suffixPrompt?: string + styleList?: BookStyle[] | DefineBookStyle[] // 样式列表, + status?: BookTaskStatus + errorMsg?: string + version?: string + imageCategory?: ImageCategory // 出图方式 + imageStyle?: string[] | null // 软件内置的样式 + autoAnalyzeCharacter?: string | null // 自动分析角色设置 + customizeImageStyle?: string[] | null // 自定义的样式 + videoConfig?: string | null // 合成视频设置 + prefixPrompt?: string | null // 前缀 + suffixPrompt?: string | null // 后缀 + isAuto?: boolean // 是否标记全自动 + subImageFolder?: string[] | null // 子图片文件夹地址,多个 + openVideoGenerate?: boolean // 是否开启视频生成 + createTime?: Date + updateTime?: Date + } + + /** + * 查询小说批次任务的条件 + */ + type QueryBookTaskCondition = { + id?: string + no?: number + name?: string + bookId?: string + bookTaskId?: string + page?: number + pageSize?: number + } + + /** + * 查询小说人物的返回数据类型 + */ + type QueryBookTaskConditionResponse = { + bookTasks: SelectBookTask[] + bookTaskLength: number + } + + //#endregion + + //#region 小说任务详细数据相关 + + type SelectBookTaskDetail = { + id?: string + no?: number + name?: string + bookId?: string + bookTaskId?: string + videoPath?: string // 视频地址 + generateVideoPath?: string // 生成的视频地址 + audioPath?: string // 音频地址 + draftDepend?: string // 草稿依赖 + word?: string // 文案 + oldImage?: string // 旧图片(用于SD的图生图) + afterGpt?: string // GPT生成的文案 + startTime?: number // 开始时间 + endTime?: number // 结束时间 + timeLimit?: string // 事件实现(0 -- 3000) + subValue?: BookTaskDetail.CopywritingSubValue[] | string // 包含的字幕数据 + characterTags?: string[] // 角色标签 + sceneTags?: string[] // 场景标签 + styleTags?: string[] // 风格标签 + promptSort?: string // 提示词排序 + gptPrompt?: string // GPT提示词 + mjMessage?: MJMessage // MJ消息 + videoMessage?: BookTaskDetail.VideoMessage // 视频消息 + outImagePath?: string // 输出图片地址 + subImagePath?: string[] // 子图片地址 + imageLock?: boolean // 图片锁 + reversePrompt?: ReversePrompt[] // 反推的提示词数据 + prompt?: string // 提示 + adetailer?: boolean // 是否开启修脸 + sdConifg?: SDConfig // SD配置 + subtitlePosition?: string // 字幕位置 + status?: BookTaskStatus + createTime?: Date + updateTime?: Date + } + + type QueryBookTaskDetailCondition = { + id?: string + name?: string + bookId?: string + bookTaskId?: string + page?: number + pageSize?: number + } + + //#endregion + + // 添加批次任务 + type AddBookTask = { + copyBookTask: boolean // 是否复制旧的小说任务 + count: number // 批次数量 + prefixPrompt?: string // 前缀提示 + suffixPrompt?: string // 后缀提示词 + selectBookTask?: string // 选择的旧的小说任务 + selectTaskDataCategory?: string[] // 选择复制的数据类型 + selectBookId: string // 选择的小说ID + } + + // 返回的MJ消息 + type MJMessage = { + id?: string + mjApiUrl?: string + progress: number + category: ImageGenerateMode + imageClick?: string // 图片点击(显示的小的) + imageShow?: string // 图片实际的地址 + messageId?: string // 消息ID(可以是MJ中的,也可以是API中的) + action?: MJAction // 动作(生图,反推之类) + status: string // 状态 + message?: string // 消息 + } + + type WebuiConfig = { + sampler_name: string // 采样器名称 + negative_prompt: string // 负面提示 + batch_size: number // 批次大小 + steps: number // 步数 + cfg_scale: number // 提示词相关性 + denoising_strength: number // 降噪强度 + width: number // 宽度 + height: number // 高度 + seed: number // 种子 + init_images: string // 初始图片(垫图的图片地址) + id: string + } + + type SDConfig = { + checkpoints: string // 大模型 + api: string // api地址 + model: string // 生图方式 + webuiConfig: WebuiConfig + id: string + } + + type ReversePrompt = { + id?: string + bookTaskDetailId?: string + prompt?: string + promptCN?: string + isSelect?: boolean + } + + // 查询小说任务的 + type QueryBookBackTaskCondition = { + id: string + bookId: string + bookTaskId: string + name: string + type: string + status: string + executeType: string + count: number + } + + // 更新小说任务状态 + type UpdateBookTaskListStatus = { + id: string + status: BookBackTaskStatus + errorMessage?: string + startTime?: number + endTime?: number + } + + // 小说人物 + type BookTask = { + id: string + bookId: string + bookTaskId: string + bookTaskDetailId: string + name: string // 任务名称,小说名+批次名+分镜名 + type: BookBackTaskType + status: BookBackTaskStatus + errorMessage?: string + executeType: TaskExecuteType // 任务执行类型,手动还是自动 + createTime: Date + updateTime: Date + startTime?: number + endTime?: number + } + + // 获取小说文案的参数 + type GetVideoFrameTextParams = { + id: string + type: SubtitleSavePositionType + videoPath: string + subtitlePosition?: string + } + + // 小说风格 + type BookStyle = { + id?: string + label?: string + key?: string + value?: string + children?: string + type?: string + prompt?: string + image_url?: string + cref_cw?: number + lora?: string + chinese_prompt?: string + lora_weight?: number + show_image?: string + isShow?: boolean + } + + type DefineBookStyle = { + chinese_style?: string + english_style?: string + id?: string + image?: string + } + + /** + * 裁剪小视频的返回数据类型 + */ + type BookFrameShortClip = { + startTime: number + endTime: number + videoPath: string + duration: number + } + + type DeleteBookTaskDetailCondition = { + id?: string + bookId?: string + bookTaskId?: string + name?: string + } + + //#region 替换小说数据 + + type BookReplaceData = { + before: string + after: string + replaceAll?: boolean + type: BookRepalceDataType + } + + type ReplaceDataRes = { + bookTaskDetailId: string + newData: string | BookTaskDetail.CopywritingSubValue[] + type?: 'reversePrompt' | 'gptPrompt' | 'afterGpt' | 'prompt' | 'subValue' + reversePromptType?: 'prompt' | 'promptCN' + reversePromptId?: string + } + + //#endregion +} diff --git a/src/define/model/book/bookTask.d.ts b/src/define/model/book/bookTask.d.ts new file mode 100644 index 0000000..714bc87 --- /dev/null +++ b/src/define/model/book/bookTask.d.ts @@ -0,0 +1,88 @@ +import { PresetCategory } from '@/define/data/presetData' + +declare namespace BookTask { + type BookTaskImageCacheImageList = { + bookTaskName: string + bookTaskId: string + bookId: string + imagePaths: string[] + } + + /** + * 书籍任务中的角色或场景定义 + * + * 该类型用于定义书籍项目中的角色或场景信息,包括序号、名称和相关提示词。 + * 在图像生成过程中,这些信息会被用于构建完整的提示词,以确保角色和场景的一致性。 + * + * @property {number} no - 角色或场景的序号/编号,用于排序和引用 + * @property {string} name - 角色或场景的名称,如"主角张三"或"现代都市街景" + * @property {string} prompt - 描述角色或场景的提示词,用于AI图像生成 + */ + type BookTaskCharacterAndScene = { + [PresetCategory.Character]: BookTaskCharacterAndSceneObject[] + [PresetCategory.Scene]: BookTaskCharacterAndSceneObject[] + } + + type BookTaskCharacterAndSceneObject = { + no: number + name: string + prompt: string + } + + /** + * 添加小说批次任务的参数 + */ + interface AddBookTaskParam { + /** 批量生成的数量 */ + count: number + /** 是否复制小说数据 */ + copyBookTask: boolean + /** 选中的要复制的小说子批次 */ + selectBookTask?: string + /** 需要复制的数据类别 */ + selectTaskDataCategory: Array + /** 通用前缀,目前不需要 */ + prefixPrompt?: string + /** 通用后缀,目前不需要 */ + suffixPrompt?: string + /** 当前小说ID */ + selectBookId: string + } + + /** + * 进度数据接口 + * + * 用于跟踪和显示小说任务的生成进度信息,包括图像和视频的生成进度统计 + * + * @property {number} imageProgress - 图像生成进度,已完成的图像数量 + * @property {number} videoProgress - 视频生成进度,已完成的视频数量 + * @property {number} totalCount - 总任务数量,需要生成的总数 + * @property {number} imageRate - 图像生成进度百分比 (0-100) + * @property {number} videoRate - 视频生成进度百分比 (0-100) + */ + interface ProgressData { + imageProgress: number + videoProgress: number + totalCount: number + imageRate: number + videoRate: number + } + + /** + * 小说任务进度记录类型 + * + * 以任务ID为键,进度数据为值的记录映射,用于管理多个小说任务的进度状态 + * 键为小说任务的唯一标识符,值为对应的进度数据 + */ + type BookTaskProgressRecord = Record + + /** + * 分镜合并类型 + * + * 定义AI分镜头合并的模式类型 + * + * @type {'long'} long - 长镜头模式,生成较长的分镜序列 + * @type {'short'} short - 短镜头模式,生成较短的分镜序列 + */ + type StoryboardMergeType = 'long' | 'short' +} diff --git a/src/define/model/book/bookTaskDetail.d.ts b/src/define/model/book/bookTaskDetail.d.ts new file mode 100644 index 0000000..0e572c3 --- /dev/null +++ b/src/define/model/book/bookTaskDetail.d.ts @@ -0,0 +1,111 @@ +import { + ImageToVideoModels, + KlingMode, + RunawayModel, + RunwaySeconds, + VideoModel, + VideoStatus +} from '@/define/enum/video' + +declare namespace BookTaskDetail { + //#region 图生视频相关 + + /** VideoMessage Model */ + type VideoMessage = { + id?: string + msg?: string + videoType?: ImageToVideoModels + prompt?: string + style?: string + imageUrl?: string + model?: VideoModel + bookTaskDetailId?: string + status?: VideoStatus + videoUrl?: string + taskId?: string + runwayOptions?: string + lumaOptions?: string + klingOptions?: string + messageData?: string + } + + /** runway 合成视频的参数(逆向) */ + type RunwayOption = { + callback_url: string // 回调地址 + image: string // 图片地址 + style?: string // 风格 + model: RunawayModel // 模型 + prompt: string // 提示词 + options: { + // 参数 + motion_vector?: { + // 镜头控制,目前仅支持GEN2 + isUse?: boolean // 是否使用镜头控制 + x?: number // x轴 -10.0到10.0之间 + y?: number //垂直 -10.0到10.0之间 + z?: number //缩放 -10.0到10.0之间 + r?: number //旋转 -10.0到10.0之间 + bg_x_pan?: number //左右方向倾斜 -10.0到10.0之间 + bg_y_pan?: number //上下方向倾斜 -10.0到10.0之间 + } + seconds?: RunwaySeconds // 生成视频的时长 + image_as_end_frame?: boolean // 目前仅支持gen-3,设置为true后,此图片将作为生成视频的结尾参考,默认为false,即将参考图作为首帧参考进行视频生成 + flip?: boolean // 默认为宽屏,即16:9 + } + last_image?: string // 尾帧 + } + + type lumaOptions = { + user_prompt: string // 用户提示词 + aspect_ratio: string // 宽高比 + expand_prompt?: boolean // 是否扩展提示词 + loop: boolean // 循环一张参考图 + image_url?: string // 图片地址(参考图,支持第三方图片地址、Base64) + image_end_url?: string // 尾帧图片地址(支持第三方图片地址、Base64) + notify_hook?: string // 回调地址 + request_model?: string // 请求的模型,快速还是慢速 + } + + type klingOptions = { + model?: string // 模型(kling-v1) + image: string // 图片地址,必须,支持Base64编码或图片URL,支持.jpg / .jpeg / .png格式,大小不能超过10MB,分辨率不小于300*300px + image_tail?: string // 尾帧图片地址,支持Base64编码或图片URL,支持.jpg / .jpeg / .png格式,大小不能超过10MB,分辨率不小于300*300px + prompt?: string // 提示词,正向文本提示, 可选,不能超过500个字符 + negative_prompt?: string // 负面提示,负向文本提示,可选,不能超过200个字符 + cfg_scale?: number // 提示词相关性,可选,范围0-1 + mode?: KlingMode // 生成视频的模式,可选,枚举值:std(高性能)或 pro(高表现) + duration?: RunwaySeconds // 生成视频时长,单位秒,可选,枚举值:5,10(包含尾帧的请求仅支持5秒) + callback_url?: string // 回调地址,可选,生成视频完成后,会向该地址发送通知 + } + + //#endregion + + //#region 小说文案相关 + + /** + * 文案数据包含的字幕数据 + */ + type CopywritingSubValue = { + end_time: number + start_time: number + id: string + srt_value: string + } + + /** + * 保存文案数据类型 + */ + type SaveCopywritingData = { + id: string + lastId?: string + no: number + afterGpt: string + startTime: number + endTime: number + subValue: CopywritingSubValue[] + timeLimit: string + word: string + } + + //#endregion +} diff --git a/src/define/model/generalResponse.d.ts b/src/define/model/generalResponse.d.ts new file mode 100644 index 0000000..a37b5b5 --- /dev/null +++ b/src/define/model/generalResponse.d.ts @@ -0,0 +1,52 @@ + +import { TranslateModel } from "./translate" +import { DialogType } from "../enum/bookEnum" +import { ResponseMessageType } from "../enum/softwareEnum" +import { MJ } from "./mj" + +declare namespace GeneralResponse { + type SuccessItem = { + code: number + message?: string + data: any + } + + type ErrorItem = { + code: number + message: string + data?: any + } + + type ProgressResponse = { + total: number, // 总数 + current: number, // 当前进度 + } + + type SubtitleProgressResponse = { + content: string, // 文案内容 + progress: ProgressResponse + } + + // 主线程主动返回前端的消息类 + type MessageResponse = { + code: number, + id: string, // 消息的唯一标识(可以和前端相关联) + type?: ResponseMessageType, + dialogType?: DialogType = DialogType.MESSAGE, + message?: string, + data?: MJ.MJResponseToFront | Buffer | string | TranslateModel.TranslateResponseMessageModel | ProgressResponse | SubtitleProgressResponse + } +} + + +export type SuccessItem = { + code: number + message?: string + data: any +} + +export type ErrorItem = { + code: number + message: string + data?: any +} \ No newline at end of file diff --git a/src/define/model/logger.d.ts b/src/define/model/logger.d.ts new file mode 100644 index 0000000..7e936a0 --- /dev/null +++ b/src/define/model/logger.d.ts @@ -0,0 +1,10 @@ +import { LoggerLevel } from '../enum/logger' + +declare namespace LoggerModel { + interface LogItem { + id: string + timestamp: string + level: LoggerLevel + message: string + } +} diff --git a/src/define/model/mj.d.ts b/src/define/model/mj.d.ts new file mode 100644 index 0000000..dec4721 --- /dev/null +++ b/src/define/model/mj.d.ts @@ -0,0 +1,30 @@ +import { MJRespoonseType } from "../define/enum/mjEnum" + +declare namespace MJ { + + // MJ的API进行反推的参数 + type APIDescribeParams = { + image: string // 图片的地址(可以是网络,也可以是本地) + taskId: string // 任务ID + } + + type MJResponseToFront = { + code: number, // 返回前端的码 0/1 + id: string, // 对应分镜的ID + type: MJRespoonseType, // 返回前端的操作类型 + mjType: MJAction, // 执行MJ的类型 + category: MJImageType, // 调用MJ分类 + messageId?: string, // 返回消息的id,就是任务ID + imageClick?: string, // 预览的图片(再使用浏览器模式的时候需要,其他都是null) + imageShow?: string, // 实际下载的图片的地址 + imagePath?: string, //实际下载的图片的地址 + prompt?: string, // 提示词消息 + progress: number, // 实现的进程 + message?: string // 消息 + status: string + mjApiUrl?: string // 请求的MJ地址 + outImagePath?: string // 输出的图片地址 + subImagePath?: string[] // 子图片地址 + } + +} \ No newline at end of file diff --git a/src/define/model/option.d.ts b/src/define/model/option.d.ts new file mode 100644 index 0000000..d4b5bc5 --- /dev/null +++ b/src/define/model/option.d.ts @@ -0,0 +1,42 @@ +declare namespace OptionModel { + type OptionModel = { + key?: string + value?: string + type?: string | number + createTime?: Date + updateTime?: Date + } + + /** Option类型 */ + type OptionType = { + label: string + path: string + value: string + } + + /** 背景音乐Option Model */ + type BackgroundMusicOption = { + id: string // 唯一ID + musicName: string // 音乐名称 + musicPath: string // 音乐路径 + } + + /** 图片 Option Model */ + type ImageOption = { + id: string // 唯一ID + imagePath: string // 图片路径 + imageName: string // 图片名称 + } + + /** + * 用户的登录信息 + */ + type UserInformation = { + userId: string + userName: string + token: string + expirationTime: Date // 到期时间 + isForver: boolean // 是否永久有效 + machineId?: string + } +} diff --git a/src/define/model/preset.d.ts b/src/define/model/preset.d.ts new file mode 100644 index 0000000..be1f6ca --- /dev/null +++ b/src/define/model/preset.d.ts @@ -0,0 +1,88 @@ +import { PresetCategory } from '../data/presetData' + +declare namespace PresetModel { + /** + * 预设类型定义 + */ + interface Preset { + /** 预设唯一标识符 */ + id?: string + + /** 预设显示名称/标签 */ + label: string + + /** 预设类型 */ + type: string + + /** 预设展示图片 */ + showImage?: string[] + + /** 预设提示词 */ + prompt: string + + /** 中文提示词 */ + chinesePrompt?: string + + /** 图片URL */ + imageUrl?: string + + /** 参数 srefSw */ + srefSw?: number + + /** 参数 crefCw */ + crefCw?: number + + /** LoRA模型 */ + lora?: string | null + + /** LoRA权重 */ + loraWeight?: number + + /** 是否显示 */ + isShow: boolean + + /** 子预设列表 */ + aliases?: string[] + + /** 创建时间 */ + createTime?: Date + + /** 显示的图片 */ + coverImage?: string + + /** 是否被选中 */ + checked?: boolean + } + + /** + * Represents the condition parameters for querying presets. + * 表示查询预设的条件参数。预设是否应该显示。 + */ + type QueryPresetCondition = { + /** 预设的唯一标识符。 */ + id?: string + /** 预设的显示名称或标签。 */ + label?: string + /** 预设的类别或类型。 */ + type?: string | null + /** 预设是否应该显示。 */ + isShow?: boolean + /** 页码 */ + page?: number + /** 每页数量 */ + pageSize?: number + } + + /** + * 预设分类 + * PresetCategory 是一个枚举类型,表示预设的分类。 + */ + type PresetCategoryCollection = { + /** 人物预设集合 */ + [PresetCategory.Character]: Preset[] + /** 风格预设集合 */ + [PresetCategory.Style]: Preset[] + /** 场景预设集合 */ + [PresetCategory.Scene]: Preset[] + } +} diff --git a/src/define/model/setting.d.ts b/src/define/model/setting.d.ts new file mode 100644 index 0000000..e843b18 --- /dev/null +++ b/src/define/model/setting.d.ts @@ -0,0 +1,207 @@ +import { ImageGenerateMode, MJRobotType, MJSpeed } from '../data/mjData' +import { JianyingKeyFrameEnum } from '../enum/jianyingEnum' + +declare namespace SettingModal { + //#region 基础设置 + /** + * 通用设置接口 + * 定义应用程序的常规设置项 + */ + export interface GeneralSettings { + /** 剪映草稿箱路径 */ + draftPath: string + + /** 项目保存路径 */ + projectPath: string + + /** 系统并发数 (1-16) */ + concurrency: number + + /** 默认生图方式 */ + defaultImgGenMethod: ImageCategory + + /** 高清倍数 (1-4) */ + hdScale: number + + /** 默认图转视频方式 */ + defaultImg2Video: ImageToVideoCategory + + /** 系统语言 */ + language: string + } + + //#endregion + + //#region MJ设置 + /** + * Midjourney 通用设置接口 + */ + interface MJGeneralSettings { + /** 出图模式 */ + outputMode: ImageGenerateMode + + /** 生图机器人类型 */ + robot: MJRobotType + + /** 机器人模型ID */ + model: string + + /** 生图尺寸ID */ + size: string + + /** 命令后缀 */ + commandSuffix: string + + /** 生图任务量 (1-10) */ + taskCount: number + + /** 间隔时间(秒) */ + interval: number + } + + /** + * Midjourney API 设置接口 + * 定义了与 MJ API 相关的配置参数 + */ + interface MJApiSettings { + /** API 服务地址 */ + apiUrl: string + + /** API 密钥/授权令牌 */ + apiKey: string + + /** 出图速度设置 + * 可选值通常为"fast"(快速)或"relaxed"(慢速) + */ + apiSpeed: MJSpeed + } + + //#endregion + + //#region AI推理设置 + + /** + * AI 推理设置接口 + * 定义了用于 AI 推理和翻译的配置参数 + */ + interface InferenceAISettings { + /** API 服务提供商标识 */ + apiProvider: string + + /** API 访问令牌/密钥 */ + apiToken: string + + /** 推理模型名称 + * 例如: 'deepseek-chat', 'gpt-4', 'claude-3-opus' 等 + */ + inferenceModel: string + + /** AI 提示词值 - 用于生成图像的提示内容 */ + aiPromptValue: string + + /** 翻译模型名称 + * 例如: 'Doubao-lite-32k', 'gpt-3.5-turbo' 等 + */ + translationModel: string + } + + //#endregion + + //#region SD设置 + /** + * Stable Diffusion 设置接口 + * 定义了 SD 生成图像所需的所有参数 + */ + interface SDSettings { + /** SD API服务器请求地址 */ + requestUrl: string + + /** 出图方式: 'txt2img' (文生图) 或 'img2img' (图生图) */ + generationMethod: string + + /** 正向提示词 - 描述你希望生成的图像内容 */ + positivePrompt: string + + /** 反向提示词 - 描述你希望避免的图像内容 */ + negativePrompt: string + + /** CFG Scale - 提示词相关性权重 (1-20) */ + cfgScale: number + + /** 单次出图张数 (1-6) */ + batchCount: number + + /** + * 种子值 + * -1表示随机种子,指定具体数值可复现相同结果 + */ + seed: number + + /** 重绘幅度 (0-1) - 在图生图模式下影响保留原图程度 */ + denoisingStrength: number + + /** 采样方式 - 如 'DPM++ SDE Karras', 'Euler a' 等 */ + sampler: string + + /** 迭代步数 (1-150) - 影响生成质量和时间 */ + steps: number + + /** 生成图片宽度 (像素) */ + width: number + + /** 生成图片高度 (像素) */ + height: number + + /** 是否启用修脸/修手功能 */ + faceHandFix: boolean + } + + /** + * ADetailer 模型接口 + * 定义了 ADetailer 插件使用的模型及其设置 + */ + interface SDADetailerModel { + /** 模型唯一标识符 */ + id: string + + /** 模型文件名称 + * 例如: 'face_yolov8n.pt', 'hand_yolov8n.pt' 等 + */ + model: string + + /** 识别信任度 + * 范围通常为 10-100 的整数值,表示百分比 + * 数值越高,模型识别越严格 + */ + threshold: number + } + //#endregion + + //#region 剪映关键帧设置 + /** + * 剪映关键帧设置接口 + * 定义了剪映视频编辑中关键帧的设置项 + */ + interface JianyingKeyFrameSettings { + keyFrame: JianyingKeyFrameEnum + isFixedSpeed: boolean + keyFrameTime: number + upDownKeyFrame: { + defaultScale: number + startPosition: number + endPosition: number + } + leftRightKeyFrame: { + defaultScale: number + startPosition: number + endPosition: number + } + scaleKeyFrame: { + defaultScale: number + startPosition: number + endPosition: number + } + } + + //#endregion +} diff --git a/src/define/model/software.d.ts b/src/define/model/software.d.ts new file mode 100644 index 0000000..92c2ae9 --- /dev/null +++ b/src/define/model/software.d.ts @@ -0,0 +1,79 @@ +declare namespace SoftwareModal { + /** + * 软件授权信息 + */ + interface SoftwareAuthorizationMessage { + /** 机器码 */ + machineID: string + /** 授权软件类型 都是0 */ + type: number + /** 使用类型 1 基础 2 赞助*/ + useType: number + /** 授权天数 */ + expiryTime: number + /** 授权时间 */ + authorizedDate: Date + /** 到期时间 */ + expiryDate: Date + /** 授权码 */ + authorizationCode: string + } + + //#region 更新信息和版本信息 + + /** + * 更新变更类型 + */ + export type ChangeType = 'bugfix' | 'add' | 'improvement' | 'remove' | 'security' | 'performance' + + /** + * 单个变更项 + */ + export interface ChangeItem { + /** 变更类型 */ + type: ChangeType + /** 变更描述 */ + description: string + } + + /** + * 版本更新信息 + */ + export interface SubUpdateInfo { + /** 版本号 */ + version: string + /** 更新日期 */ + updateDate: string + /** 变更列表 */ + changes: ChangeItem[] + } + + /** + * 完整的更新信息结构 + */ + export interface UpdateInfo { + /** 最新版本号 */ + latestVersion: string + /** 最新更新日期 */ + updateDate: string + /** 所有版本的更新信息 */ + updateInfo: SubUpdateInfo[] + } + + /** + * 系统更新信息 + */ + interface VersionInfo { + /** 当前版本 */ + currentVersion: string + /** 最新版本 */ + latestVersion: string + + /** 更新信息 */ + updateInfo: UpdateInfo + /** 是否可以更新 */ + canUpdate: boolean + } + + //#endregion +} diff --git a/src/define/model/task.d.ts b/src/define/model/task.d.ts new file mode 100644 index 0000000..a06c171 --- /dev/null +++ b/src/define/model/task.d.ts @@ -0,0 +1,60 @@ +import { + BookBackTaskStatus, + BookBackTaskType, + BookTaskStatus, + TaskExecuteType +} from '@/define/enum/bookEnum' + +declare namespace TaskModal { + interface Task { + id?: string + bookId?: string + bookTaskId?: string + bookTaskDetailId?: string + name?: string // 任务名称,小说名+批次名+分镜名 + type?: BookBackTaskType + status?: BookBackTaskStatus + errorMessage?: string | null + executeType?: TaskExecuteType // 任务执行类型,手动还是自动 + createTime?: Date + updateTime?: Date + startTime?: number + endTime?: number + messageName?: string + } + + interface TaskCondition { + bookName?: string + bookTaskName?: string + bookTaskDetailName?: string + taskName?: string + taskType?: BookBackTaskType + taskStatus?: BookBackTaskStatus + taskErrorMessage?: string + } + + interface QueryTaskCondition extends TaskCondition { + page: number + pageSize: number + } + + interface BackTaskCollection extends Task { + bookName?: string + bookTaskName?: string + bookTaskDetailName?: string + } + + type TaskCollection = { + page: number + pageSize: number + count: number + data: BackTaskCollection[] + } + + type TaskStatus = { + status: BookBackTaskStatus | 'UNKNOWN' | BookTaskStatus | string + label: string + type?: string + color?: string + } & ({ type: string } | { color: string }) +} diff --git a/src/define/model/translate.d.ts b/src/define/model/translate.d.ts new file mode 100644 index 0000000..2450fa8 --- /dev/null +++ b/src/define/model/translate.d.ts @@ -0,0 +1,56 @@ +import { TranslateType, TranslateAPIType } from '../enum/translate' + +declare namespace TranslateModel { + type TranslateNowReturnParams = { + text: string // 需要翻译的文本 + from: string // 源语言 + to: string // 目标语言 + isSplit: false // 是否需要分割 + } + + type TranslateNowIPCParams = { + text: string // 需要翻译的文本 + from: string // 源语言 + to: string // 目标语言 + isSplit: false // 是否需要分割 + bookTaskDetailId: string // 小说分镜ID + reversePromptId: string // 反推提示词ID + windowId?: number // 窗口ID + type: TranslateType // 翻译类型 + responseMessgeName: string // 返回的消息名称(用作自定义) + } + + type TranslateResponseMessageModel = { + bookTaskDetailId: string // 分镜ID + reversePromptId: string // 反推提示词ID + from: string // 源语言 + to: string // 目标语言 to 是 zh 值重新保存到 promptCN 中,to 是 en ,需要重新覆盖prompt和prom + prompt: string // 英文数据 + promptCN: string // 中文翻译数据(但是也可以存储英文) + progress: number // 索引(翻译到了哪,用于进度条) + total: number // 总数 + } + + type TranslateReturnDataModel = { + src: string // 字符串 + dst: string // 翻译结果 + } + + type TranslateReturnModel = { + to: string // 目标语言 + data: TranslateReturnDataModel[] // 翻译结果 + } + + type subTranslateModel = { + name: TranslateAPIType // 翻译API名称 + translation_business: string // 翻译业务 + translation_app_id: string // 翻译APP ID + translation_secret: string // 翻译密钥 + } + + type TranslateModel = { + selectModel: TranslateAPIType // 选择的模型 + translation_auto: boolean // 是否自动翻译 + translates: subTranslateModel[] // 可用的翻译API + } +} diff --git a/src/define/quene.ts b/src/define/quene.ts new file mode 100644 index 0000000..b9f8f64 --- /dev/null +++ b/src/define/quene.ts @@ -0,0 +1,391 @@ +import { DEFINE_STRING } from './ipcDefineString' +import { TimeDelay } from './Tools/time' + +// 要清理的批次ID +let GlobalClearBatchId: string[] = [] + +export class AsyncQueue { + global: any + tasks: any[] + concurrencyLimit: number + manualMode: boolean + currentConcurrency: number + batchCompletion: any + taskDeadline: string + showEndTime: boolean + currentCreateItem: any + taskProgress: any[] + + constructor(global, concurrencyLimit = 1, manualMode = false) { + this.global = global + this.tasks = [] + this.concurrencyLimit = concurrencyLimit + this.manualMode = manualMode + this.currentConcurrency = 0 + // 扩展批次完成状态对象以支持子批次 + this.batchCompletion = {} + this.taskDeadline = global.endTime + this.showEndTime = true + this.currentCreateItem = null + + // 只有在手动模式下才会使用。要设置当前正在执行的任务,将正在执行的任务添加到里面,需要在任务完成之后手动移除 + this.taskProgress = [] + } + + async enqueue( + task: Function, + taskId: string, + batchId: string, + subBatchId: string = 'default', + errorFunc: Function | null = null + ) { + // if (batchId && batchId != DEFINE_STRING.QUEUE_BATCH.IMAGE_SAVE_TO_OTHER_FOLDER) { + // // 判断当前的任务是否已经存在,存在则不添加 + // let index = this.tasks.findIndex( + // (item) => + // item.taskId === taskId && item.batchId === batchId && item.subBatchId === subBatchId + // ) + // if (index != -1) { + // throw new Error(`Task ${taskId} in batch ${batchId} already exists.`) + // } + // } + if (!this.batchCompletion[batchId]) { + this.batchCompletion[batchId] = { + remaining: 0, + subBatches: {}, + callback: null, + failedTasks: [] + } + } + if (!this.batchCompletion[batchId].subBatches[subBatchId]) { + this.batchCompletion[batchId].subBatches[subBatchId] = { remaining: 0, failedTasks: [] } + } + + this.batchCompletion[batchId].remaining++ + this.batchCompletion[batchId].subBatches[subBatchId].remaining++ + this.tasks.push({ task, taskId, batchId, subBatchId, errorFunc }) + if (!this.manualMode) { + await this.process() + } + } + + setBatchCompletionCallback(batchId, callback) { + if (this.batchCompletion[batchId]) { + this.batchCompletion[batchId].callback = callback + } + } + + setSubBatchCompletionCallback(batchId, subBatchId, callback) { + if (this.batchCompletion[batchId]) { + this.batchCompletion[batchId].subBatches[subBatchId].callback = callback + } + } + + setTaskDeadline(deadline) { + this.taskDeadline = deadline + } + + async process(task_count = 0) { + // 判断是不是有机器码检测的标识 + // if (!this.global.CheckMachineId) { + // // throw new Error("Machine ID not detected, please check the machine ID."); + // this.global.wins[0].webContents.send(DEFINE_STRING.SYSTEM.SHOW_MESSAGE_DIALOG, { + // code: 0, + // message: '请联系管理员激活' + // }) + // return + // } + + if (this.global.endTime && new Date(Date.now()).toISOString() > this.global.endTime) { + this.tasks = [] + if (this.showEndTime) { + this.global.wins[0].webContents.send(DEFINE_STRING.SYSTEM.SHOW_MESSAGE_DIALOG, { + code: 0, + message: '试用时间已到,请联系客服' + }) + } + this.showEndTime = false + return + } + + while ( + this.tasks.length > 0 && + (this.manualMode + ? this.taskProgress.length < task_count + : this.currentConcurrency < this.concurrencyLimit) + ) { + const { task, taskId, batchId, subBatchId, errorFunc } = this.tasks.shift() + this.currentConcurrency++ + Promise.resolve(task()) + .then(() => { + this.currentConcurrency-- + this.handleTaskCompletion(batchId, subBatchId, null) + if (!this.manualMode) { + this.process() + } + }) + .catch((error) => { + // 判断是不是网络请求,不是网络请求直接报错 + let retryCount = 0 + const maxRetryCount = 5 + let retryTask = async () => { + if (retryCount < maxRetryCount) { + retryCount++ + await TimeDelay(2000) + this.global.wins[0].webContents.send(DEFINE_STRING.SYSTEM.SHOW_MAIN_NOTIFICATION, { + code: 0, + message: `生图失败,共5次尝试,目前第${retryCount}次` + }) + task() + .then(() => { + this.currentConcurrency-- + this.handleTaskCompletion(batchId, subBatchId, null) + if (!this.manualMode) { + this.process() + } + }) + .catch((_error) => { + retryTask() + }) + } else { + // console.error(`Task ${taskId} failed after ${maxRetryCount} retries`); + this.currentConcurrency-- + this.handleTaskCompletion(batchId, subBatchId, { taskId, error }) + // 执行传入的错误后执行方法,一般用于修改后台任务或者是批量提示 + if (errorFunc) { + errorFunc( + batchId, + `任务 ${taskId} 执行失败,错误信息:${error.toString()},开始清理整个批次,批次ID:${batchId}` + ) + } + if (!this.manualMode) { + this.process() + } + } + } + + if (GlobalClearBatchId.includes(batchId)) { + retryTask() + } else { + console.error('An error occurred in task ' + taskId + ':', error) + // 出现报错。直接停掉当前对应的的子批次 + this.currentConcurrency-- + this.handleTaskCompletion(batchId, subBatchId, { taskId, error }) + // 执行传入的错误后执行方法,一般用于修改后台任务或者是批量提示 + if (errorFunc) { + errorFunc( + batchId, + `任务 ${taskId} 执行失败,错误信息:${error.toString()},开始清理整个批次,批次ID:${batchId}` + ) + } + if (!this.manualMode) { + this.process() + } else { + // 将当前的设置删除,将历史人物删除 + this.setCurrentCreateItem(null) + this.taskProgress = [] + } + } + }) + } + } + + // 任务完成执行的函数,用于处理任务完成后的状态更新等 + handleTaskCompletion(batchId, subBatchId, taskError) { + const batch = this.batchCompletion[batchId] + const subBatch = this.batchCompletion[batchId].subBatches[subBatchId] + + if (taskError) { + batch.failedTasks.push(taskError) + subBatch.failedTasks.push(taskError) + // 删除整个子批次 + this.removeTask(batchId, null, subBatchId) + } + this.batchCompletion[batchId].remaining-- + this.batchCompletion[batchId].subBatches[subBatchId].remaining-- + + // 子批次完成 + if (this.batchCompletion[batchId].subBatches[subBatchId].remaining === 0) { + console.log(`Sub-batch ${subBatchId} in batch ${batchId} completed.`) + const callback = this.batchCompletion[batchId].subBatches[subBatchId].callback + if (callback) { + callback(this.batchCompletion[batchId].subBatches[subBatchId].failedTasks) + } + this.batchCompletion[batchId].subBatches[subBatchId] = { + remaining: 0, + callback: null, + failedTasks: [] + } + } + + if (this.batchCompletion[batchId].remaining === 0) { + const callback = this.batchCompletion[batchId].callback + if (callback) { + callback(this.batchCompletion[batchId].failedTasks) + } + this.batchCompletion[batchId] = { + remaining: 0, + subBatches: {}, + callback: null, + failedTasks: [] + } + } + } + + // 移除任务 + removeTask(batchId, taskId, subBatchId = 'default') { + try { + if (!this.batchCompletion[batchId]) { + throw new Error(`Batch ${batchId} does not exist.`) + } + + // 获取当前批次的所有的任务 + let initialCount = this.tasks.filter((item) => item.batchId === batchId).length + let finalCount = initialCount + + // 首先,判断是否指定了taskId来决定是移除单个任务还是整个批次 + if (taskId == null) { + // 删除整个批次 + this.tasks = this.tasks.filter((item) => item.batchId !== batchId) + finalCount = this.tasks.filter((item) => item.batchId === batchId).length + } else if (taskId == 'all') { + // 删除所有的指定的子批次 + this.tasks = this.tasks.filter( + (item) => !(item.subBatchId == subBatchId && item.batchId == batchId) + ) + finalCount = this.tasks.filter( + (item) => item.subBatchId == subBatchId && item.batchId == batchId + ).length + } else { + // 删除指定的任务,需要考虑subBatchId + this.tasks = this.tasks.filter( + (item) => + !(item.taskId === taskId && item.batchId === batchId && item.subBatchId === subBatchId) + ) + finalCount = this.tasks.filter( + (item) => + item.taskId === taskId && item.batchId === batchId && item.subBatchId === subBatchId + ).length + } + // 在移除后再次计算该批次的任务数量 + // const finalCount = this.tasks.length; + // 使用两次计数的差值更新 remaining + const removedTasks = initialCount - finalCount + if (removedTasks > 0) { + this.batchCompletion[batchId].remaining -= removedTasks + if (this.batchCompletion[batchId].subBatches[subBatchId]) { + this.batchCompletion[batchId].subBatches[subBatchId].remaining -= removedTasks + this.batchCompletion[batchId].subBatches[subBatchId].callback = null + } + } + + // 检查并处理完整批次和小批次的完成状态 + const batch = this.batchCompletion[batchId] + // 判断子批次的数量 + const subBatch = this.batchCompletion[batchId].subBatches[subBatchId] + // 子批次完成 + if (subBatch && subBatch.remaining == 0 && subBatch.callback) { + subBatch.callback(subBatch.failedTasks) + delete this.batchCompletion[batchId].subBatches[subBatchId] // 清理批次完成状态 + } + // 总批次完成 + if (batch && batch.remaining === 0 && batch.callback) { + const callback = batch.callback + callback(batch.failedTasks) + delete this.batchCompletion[batchId] // 清理批次完成状态 + } else { + // 遍历小批次,看是否有需要清理的 + for (let subBatchId in batch.subBatches) { + const subBatch = batch.subBatches[subBatchId] + if (subBatch.remaining === 0) { + // 这里可以进行小批次完成后的处理,比如调用小批次的特定回调 + console.log(`Sub-batch ${subBatchId} in batch ${batchId} completed.`) + // 可以选择删除空的小批次状态,如果不需要保留它的完成记录 + delete batch.subBatches[subBatchId] + } + } + } + return { + code: 1 + } + } catch (error: any) { + return { + code: 0, + message: error.toString() + } + } + } + + // 获取指定batch的任务列表 + getTasks(batchId) { + return this.tasks.filter((item) => item.batchId === batchId) + } + + // 获取失败的任务 + getFailedTasks(batchId, subBatchId = 'default') { + if (this.batchCompletion[batchId] && this.batchCompletion[batchId].subBatches[subBatchId]) { + return this.batchCompletion[batchId].subBatches[subBatchId].failedTasks + } + return [] + } + + // 手动开启下一个任务 + async startNextTask(taskCount = 3) { + // 判断当前是不是有任务正在执行 + if (this.currentCreateItem) { + return + } + console.log('调用开始下一个任务', this.taskProgress) + if (this.manualMode && this.tasks.length > 0 && this.taskProgress.length < taskCount) { + this.process(taskCount) + } + } + + // 修改currentItem + setCurrentCreateItem(item) { + this.currentCreateItem = item + if (item) { + // 判断相同的ID的任务是否存在,存在则不添加 + let index = this.taskProgress.findIndex((task) => task.id === item.id) + if (index == -1) { + this.taskProgress.push(item) + } else { + // 直接修改 + this.taskProgress[index] = item + } + } + } + + // 获取currentItem + getCurrentCreateItem() { + return this.currentCreateItem + } + + // 获取当前正在执行的任务数量 + getCurrentConcurrency() { + return this.currentConcurrency + } + + // 获取等待中的队列 + getWaitingQueue() { + return this.tasks.length + } + + // 获取当前任务的限制任务数量 + getConcurrencyLimit() { + return this.concurrencyLimit + } + + // 修改任务数据 + + // 获取当前正在执行的任务对象() + getTaskProgress() { + return this.taskProgress + } + + // 删除自动设置的任务 + removeTaskProgress(callback) { + this.taskProgress = callback(this.taskProgress) + console.log('删除对应消息的任务', this.taskProgress) + } +} diff --git a/src/define/response/ForwardResponse.ts b/src/define/response/ForwardResponse.ts new file mode 100644 index 0000000..b176da1 --- /dev/null +++ b/src/define/response/ForwardResponse.ts @@ -0,0 +1,25 @@ +import { ValidateJson } from "../Tools/validate"; + +/** + * 获取转发请求的数据,序列化后返回 + * @param response + * @returns + */ +function GetForwardResponseData(response: any) { + if (response.status != 200) { + throw new Error(response.message) + } + if (response.data.code != 1) { + throw new Error(response.data.message) + } + if (!ValidateJson(response.data.data)) { + throw new Error(response.data.data) + } + return JSON.parse(response.data.data); +} + +let ForwardResponse = { + GetForwardResponseData, +} + +export default ForwardResponse; \ No newline at end of file diff --git a/src/define/response/openAIResponse.ts b/src/define/response/openAIResponse.ts new file mode 100644 index 0000000..4c348c3 --- /dev/null +++ b/src/define/response/openAIResponse.ts @@ -0,0 +1,95 @@ +export type OpenAISuccessResponse = { + id: string + object: string + created: number + model: string + choices: [ + { + index: number + message: { + role: string + content: string + } + finish_reason: string + } + ] + usage: { + prompt_tokens: number + completion_tokens: number + total_tokens: number + } +} + +type RixApiErrorResponse = { + error: { + message: string // 错误信息 + type: string + param: string + code: string + } +} + +type KimiErrorResponse = { + error: { + message: string + type: string + } +} + +type DoubaoErrorResponse = { + error: { + code: string + message: string + param: string + type: string + } +} + +/** + * 处理OpenAI系列返回的成功response + * @param response OpenAI返回的response + * @returns 处理后的返回的数据 + */ +export function GetOpenAISuccessResponse(response: string | OpenAISuccessResponse): string { + if (typeof response === 'string') { + response = JSON.parse(response) as OpenAISuccessResponse + } + // 开始处理response + return response.choices[0].message.content +} + +/** + * 处理RixApi系列返回的错误response + * @param response RixApi返回的response + * @returns 处理后的错误信息 + */ +export function GetRixApiErrorResponse(response: string | RixApiErrorResponse): string { + if (typeof response === 'string') { + response = JSON.parse(response) as RixApiErrorResponse + } + return response.error.message +} + +/** + * 处理kimi的错误信息返回 + * @param response + * @returns + */ +export function GetKimiErrorResponse(response: string | KimiErrorResponse): string { + if (typeof response === 'string') { + response = JSON.parse(response) as KimiErrorResponse + } + return response.error.message +} + +/** + * 获取豆包的错误返回信息 + * @param response + * @returns + */ +export function GetDoubaoErrorResponse(response: string | DoubaoErrorResponse): string { + if (typeof response === 'string') { + response = JSON.parse(response) as DoubaoErrorResponse + } + return response.error.message +} diff --git a/src/define/window/initFunc.ts b/src/define/window/initFunc.ts new file mode 100644 index 0000000..6f85636 --- /dev/null +++ b/src/define/window/initFunc.ts @@ -0,0 +1,153 @@ +import path from 'path' +import { CheckFolderExistsOrCreate } from '../Tools/file' +import fs from 'fs' +import { define } from '../define' +import { errorMessage, successMessage } from '@/public/generalTools' +import { machineId } from 'node-machine-id' +import { OptionRealmService } from '../db/service/optionService' +import { OptionKeyName, OptionType } from '../enum/option' +import { isEmpty } from 'lodash' +import { execSync } from 'child_process' +import os from 'os' +import crypto from 'crypto' + +export class InitFunc { + constructor() {} + + /** + * 初始化文件夹或者是文件,判断是不是要新建 + */ + async InitConfigFolderOrFile() { + let folderData = [ + { + path: 'config', + default: null, + folder: true, + fileType: 'json' + } + ] + + for (let i = 0; i < folderData.length; i++) { + const element = folderData[i] + // 判断是不是文件夹 + if (element.folder) { + // 判断对应的文件夹位置是不是存在,不纯在创建 + await CheckFolderExistsOrCreate(element.path) + } else { + switch (element.fileType) { + case 'json': + // 判断文件是不是存在,不存在创建 + await CheckFolderExistsOrCreate(path.dirname(element.path)) + fs.promises.writeFile(element.path, JSON.stringify(element.default), 'utf-8') + break + case 'txt': + // 判断文件是不是存在,不存在创建 + await CheckFolderExistsOrCreate(path.dirname(element.path)) + fs.promises.writeFile(element.path, element.default ?? '', 'utf-8') + break + default: + break + } + } + } + } + + async InitConfig() { + try { + let sp = path.resolve(define.resources_path, 'config', 'init.txt') + let res = await fs.promises.readFile(sp, 'utf-8') + // 取第一行 + let arr = res.split('\n') + let data = arr[0] ?? '' + global.softwareId = data + return true + } catch (error) { + errorMessage('读取初始化配置文件失败 init', 'InitFunc_InitConfig') + // 确保创建必要的目录 + await CheckFolderExistsOrCreate(path.resolve(define.resources_path, 'config')) + // 创建一个默认的init.txt文件 + await fs.promises.writeFile( + path.resolve(define.resources_path, 'config', 'init.txt'), + 'default-software-id', + 'utf-8' + ) + global.softwareId = 'default-software-id' + return true + } + } + + /** + * 获取电脑机器码,每次电脑启动进行更新 + * @returns + */ + async InitMachineId() { + try { + let baseId = await machineId(true) + let hardwareInfo = '' + try { + if (process.platform === 'win32') { + // Windows: 获取BIOS和磁盘序列号 + hardwareInfo = execSync( + 'wmic bios get serialnumber && wmic diskdrive get serialnumber' + ).toString() + } else if (process.platform === 'darwin') { + // macOS: 获取硬件UUID + hardwareInfo = execSync( + 'system_profiler SPHardwareDataType | grep "Hardware UUID"' + ).toString() + } else { + // Linux: 获取主板序列号 + hardwareInfo = execSync( + 'cat /sys/class/dmi/id/board_serial 2>/dev/null || echo "unknown"' + ).toString() + } + } catch (e) { + hardwareInfo = 'exec-failed' + } + + // 方法2: 用户和系统信息 + const userInfo = os.userInfo().username + '-' + os.homedir() + + // 组合所有信息 + const combinedInfo = `${baseId}|${hardwareInfo}|${userInfo}` + + // 生成最终ID + let id = crypto.createHash('sha256').update(combinedInfo).digest('hex') + + const optionRealmService = await OptionRealmService.getInstance() + optionRealmService.ModifyOptionByKey(OptionKeyName.Software.MachineId, id, OptionType.STRING) + successMessage(id, '重新获取机器码成功!', 'InitFunc_InitMachineId') + return true + } catch (error) { + errorMessage('初始设置机器码失败', 'InitFunc_InitMachineId') + return false + } + } + + /** + * 初始化项目地址 + */ + async InitProjectPath() { + try { + let projectPath = path.resolve(define.resources_path, 'project') + const optionRealmService = await OptionRealmService.getInstance() + let projectPathOption = optionRealmService.GetOptionByKey(OptionKeyName.Software.ProjectPath) + if ( + projectPathOption == null || + projectPathOption.value == null || + isEmpty(projectPathOption.value) + ) { + // 如果没有设置过项目地址,则创建一个默认的项目地址 + await CheckFolderExistsOrCreate(projectPath) + optionRealmService.ModifyOptionByKey( + OptionKeyName.Software.ProjectPath, + projectPath, + OptionType.STRING + ) + successMessage(projectPath, '项目地址初始化成功', 'InitFunc_InitProjectPath') + } + } catch (error: any) { + errorMessage('项目地址初始化失败,' + error.message, 'InitFunc_InitMachineId') + } + } +} diff --git a/src/define/window/windowManager.ts b/src/define/window/windowManager.ts new file mode 100644 index 0000000..e6821dd --- /dev/null +++ b/src/define/window/windowManager.ts @@ -0,0 +1,250 @@ +import { app, BrowserWindow } from 'electron' +const { join } = require('path') +import { is } from '@electron-toolkit/utils' +import { InitFunc } from './initFunc' +import { IpcStart } from '../ipc/index' +import { define } from '../define' + +type DefaultConfig = { + id: null | number // 窗口唯一id + background: string // 背景色 + route: string // 路由地址url + title: string // 标题 + data: any // 传入数据参数 + width: number | string // 窗口宽度 + height: number | string // 窗口高度 + minWidth: number | string // 窗口最小宽度 + minHeight: number | string // 窗口最小高度 + x: number | string // 窗口相对于屏幕左侧坐标 + y: number | string // 窗口相对于屏幕顶端坐标 + resize: boolean // 是否支持缩放 + maximize: boolean // 最大化窗口 + parent: string | null // 父窗口(需传入父窗口id) + modal: boolean // 模态窗口(模态窗口是浮于父窗口上,禁用父窗口) + alwaysOnTop: boolean // 置顶窗口 + outUrl: string | null // 外部链接 + openDevTools: boolean // 是否打开开发者工具 + theme: string // 主题 +} + +// 配置参数 +const defaultConfig: DefaultConfig = { + id: null, // 窗口唯一id + background: '#fff', // 背景色 + route: '', // 路由地址url + title: '', // 标题 + data: null, // 传入数据参数 + width: '', // 窗口宽度 + height: '', // 窗口高度 + minWidth: '', // 窗口最小宽度 + minHeight: '', // 窗口最小高度 + x: '', // 窗口相对于屏幕左侧坐标 + y: '', // 窗口相对于屏幕顶端坐标 + resize: true, // 是否支持缩放 + maximize: false, // 最大化窗口 + parent: '', // 父窗口(需传入父窗口id) + modal: false, // 模态窗口(模态窗口是浮于父窗口上,禁用父窗口) + alwaysOnTop: false, // 置顶窗口 + outUrl: null, // 外部链接 + openDevTools: false, // 是否打开开发者工具 + theme: 'light' // 主题 +} + +export class CreatWindow { + mainWin: BrowserWindow | null + wins: any + initFunc: InitFunc + constructor() { + this.mainWin = null + this.wins = {} + this.initFunc = new InitFunc() + } + /** + * 返回创建窗口的配置 + * @returns + */ + winOpts(): Electron.BrowserWindowConstructorOptions { + let icon = define.icon + return { + // 窗口图标 + icon: icon, + autoHideMenuBar: true, + width: 900, + height: 640, + minimizable: true, + maximizable: true, + show: false, + ...(process.platform === 'linux' ? { icon } : {}), + webPreferences: { + contextIsolation: true, // 启用上下文隔离(为了安全性)(默认true) + nodeIntegration: false, // 启用Node集成(默认false) + preload: join(__dirname, '../preload/index.js'), + sandbox: false, + nodeIntegrationInWorker: false, + webSecurity: false, + allowRunningInsecureContent: true, + partition: 'persist:my-partition' + } + } + } + + // 创建新窗口 + createWin(name, options) { + const args: any = Object.assign({}, defaultConfig, options) + console.log(args) + + // 判断窗口是否存在 + for (let i in this.wins) { + // 判断当前名字的窗口在不在在的话,就聚焦 + if (this.getWin(i) && this.wins[i].route === args.route) { + const win = this.getWin(i) + if (win) { + win.focus() + } + return + } + } + + let opt: Electron.BrowserWindowConstructorOptions = this.winOpts() + if (args.parent) { + const parentWin = this.getWin(args.parent) + if (parentWin) { + opt.parent = parentWin + } + } + + if (typeof args.modal === 'boolean') opt.modal = args.modal + if (typeof args.resize === 'boolean') opt.resizable = args.resize + if (typeof args.alwaysOnTop === 'boolean') opt.alwaysOnTop = args.alwaysOnTop + if (args.background) opt.backgroundColor = args.background + if (args.width) opt.width = args.width + if (args.height) opt.height = args.height + if (args.minWidth) opt.minWidth = args.minWidth + if (args.minHeight) opt.minHeight = args.minHeight + if (args.x) opt.x = args.x + if (args.y) opt.y = args.y + + console.log(opt) + + // 创建窗口对象 + let win = new BrowserWindow(opt) + // 是否最大化 + if (args.maximize && args.resize) { + win.maximize() + } + args.id = win.id + + // 加载页面 + let $url + if (args.outUrl) { + $url = args.outUrl + } else { + // 判断是不是开发环境 + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + if (args.route != '') { + $url = process.env['ELECTRON_RENDERER_URL'] + '/#/' + args.route + } else { + $url = process.env['ELECTRON_RENDERER_URL'] + } + + if (args.openDevTools) { + win.webContents.openDevTools() + } + } else { + if (args.route != '') { + $url = `file://${join(__dirname, '../renderer/index.html')}#/${args.route}` + } else { + $url = join(__dirname, '../renderer/index.html') + } + } + } + if (is.dev) { + win.loadURL($url) + } else { + win.loadFile($url) + } + + win.once('ready-to-show', () => { + win.show() + }) + + win.on('close', () => win.setOpacity(0)) + // 初始化渲染进程 + win.webContents.on('did-finish-load', () => { + // win.webContents.send('win-loaded', '加载完成~!') + win.webContents.send('win-loaded', args) + }) + + this.wins[name] = { + route: args.route, + id: win.id + } + + return win + } + + // 获取全部窗口 + getAllWin() { + return BrowserWindow.getAllWindows() + } + + // 获取窗口 + getWin(name) { + let id = this.wins[name].id + return BrowserWindow.fromId(Number(id)) + } + + // 关闭全部窗口 + closeAllWin() { + try { + for (let i in this.wins) { + if (this.getWin(i)) { + const win = this.getWin(i) + if (win) { + win.close() + } + } else { + app.quit() + } + } + } catch (error: any) { + throw new Error(error) + } + } + + // 关闭指定的窗口 + closeWin(name) { + try { + if (this.getWin(name)) { + const win = this.getWin(name) + if (win) { + win.close() + } + } + } catch (error: any) { + throw new Error(error) + } + } + + // 下面的方法创建初始条件和文件 + async initConfigFolderOrFile() { + await this.initFunc.InitConfigFolderOrFile() + } + + // 初始化配置文件 + async InitConfig() { + try { + await this.initFunc.InitConfig() + await this.initFunc.InitMachineId() + await this.initFunc.InitProjectPath() + } catch (error) { + console.error('配置初始化失败:', error) + // 这里可以添加额外的错误处理 + } + } + + // 开启主进程监听 + ipcMainListen() { + IpcStart() + } +} diff --git a/src/main/index.ts b/src/main/index.ts new file mode 100644 index 0000000..9394382 --- /dev/null +++ b/src/main/index.ts @@ -0,0 +1,61 @@ +import { app, BrowserWindow } from 'electron' +import { electronApp, optimizer } from '@electron-toolkit/utils' +import { CreatWindow } from '../define/window/windowManager' +import { Logger } from '../define/Tools/logger' +import { successMessage } from '@/public/generalTools' +// import { machineId } from 'node-machine-id' + +// 在文件顶部添加这段代码 +process.on('unhandledRejection', (reason, promise) => { + console.error('未处理的Promise拒绝:', promise, '原因:', reason) + // 可以选择记录到日志文件或者向用户显示错误 +}) + +// 在应用启动前,设置正确的编码 +process.env.LANG = 'zh_CN.UTF-8' + +async function createWindow() { + try { + // 创建主窗口 + global.wm = new CreatWindow() + let win = global.wm.createWin('main', { openDevTools: false, minWidth: 1000, minHeight: 750 }) + global.wins = [win] + global.wm.ipcMainListen() + + // 先初始化日志 + global.logger = new Logger() + + // 再初始化文件夹和配置 + await global.wm.initConfigFolderOrFile() + await global.wm.InitConfig() + + successMessage( + null, + '软件初始化成功,创建窗体成功,初始化配置,初始化日志成功', + 'SoftWare.Init' + ) + } catch (error: any) { + console.error('应用初始化失败:', error) + // 移除throw error,改为返回一个自定义错误消息 + throw error + } +} + +app.whenReady().then(async () => { + electronApp.setAppUserModelId('com.electron') + + app.on('browser-window-created', (_, window) => { + optimizer.watchWindowShortcuts(window) + }) + + await createWindow() + app.on('activate', async function () { + if (BrowserWindow.getAllWindows().length === 0) await createWindow() + }) +}) + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit() + } +}) diff --git a/src/main/service/ai/aiStoryboard.ts b/src/main/service/ai/aiStoryboard.ts new file mode 100644 index 0000000..64e96ea --- /dev/null +++ b/src/main/service/ai/aiStoryboard.ts @@ -0,0 +1,84 @@ +import { BookTask } from '@/define/model/book/bookTask' +import { BookBasicHandle } from '../book/subBookHandle/bookBasicHandle' +import { AIWordMergeLong } from '@/define/data/aiData/aiPrompt/aiWordMerge/aiWordMergeLong' +import { AIWordMergeShort } from '@/define/data/aiData/aiPrompt/aiWordMerge/aiWordMergeShort' +import { AiReasonCommon } from '../aiReason/aiReasonCommon' +import { groupWordsByCharCount } from '@/define/Tools/write' +import { cloneDeep, isEmpty } from 'lodash' +import { OptionKeyName } from '@/define/enum/option' + +export class AIStoryboard extends BookBasicHandle { + aiReasonCommon: AiReasonCommon + constructor() { + super() + this.aiReasonCommon = new AiReasonCommon() + } + + AIStoryboardMerge = async (bookTaskId: string, type: BookTask.StoryboardMergeType) => { + try { + await this.InitBookBasicHandle() + let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId) + if (!bookTask) throw new Error('小说批次任务未找到') + + let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: bookTask.id + }) + + if (!bookTaskDetails || bookTaskDetails.length === 0) throw new Error('小说分镜信息未找到') + + let word = bookTaskDetails.map((item) => item.afterGpt) + if (word == undefined || word.length === 0) throw new Error('分镜内容为空') + + let groupWord = groupWordsByCharCount(word as string[], 500) + + // 开始处理文案 + await this.aiReasonCommon.InitAiReasonCommon() + // 获取推理设置 + await this.aiReasonCommon.GetAISetting() + + let result: string[] = [] + + for (let i = 0; i < groupWord.length; i++) { + // 开始做数据 + let request: OpenAIRequest.Request + if (type === 'long') { + request = cloneDeep(AIWordMergeLong) + } else if (type === 'short') { + request = cloneDeep(AIWordMergeShort) + } else { + throw new Error('不支持的分镜合并类型') + } + const element = groupWord[i] + let newWord = element.map((item) => item).join('\n') + for (let j = 0; j < request.messages.length; j++) { + const messageItem = request.messages[j] + messageItem.content = this.aiReasonCommon.replaceObject(messageItem.content, { + textContent: newWord + }) + } + + // 判断模型是不是有设置值 + let modelRes = this.optionRealmService.GetOptionByKey( + OptionKeyName.InferenceAI.StoryBoardAIModel + ) + + if (modelRes == null || isEmpty(modelRes.value)) { + delete request.model + } else { + request.model = modelRes.value as string + } + + let res = await this.aiReasonCommon.FetchGpt(request.messages, { + ...request + }) + + // console.log('res:', res) + result.push(res) + } + console.log('分镜合并结果:', result) + return result + } catch (error) { + throw error + } + } +} diff --git a/src/main/service/ai/index.ts b/src/main/service/ai/index.ts new file mode 100644 index 0000000..2c73ab1 --- /dev/null +++ b/src/main/service/ai/index.ts @@ -0,0 +1,11 @@ +import { mixin } from '@/main/utils/mixin' +import { AIStoryboard } from './aiStoryboard' + +interface AIHandle extends AIStoryboard {} + +@mixin(AIStoryboard) +class AIHandle { + constructor() {} +} + +export const aiHandle = new AIHandle() as AIHandle diff --git a/src/main/service/aiReason/aiReasonCommon.ts b/src/main/service/aiReason/aiReasonCommon.ts new file mode 100644 index 0000000..86aa583 --- /dev/null +++ b/src/main/service/aiReason/aiReasonCommon.ts @@ -0,0 +1,301 @@ +import { OptionRealmService } from '@/define/db/service/optionService' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../option/optionSerialization' +import { SettingModal } from '@/define/model/setting' +import { isEmpty } from 'lodash' +import { GetOpenAISuccessResponse } from '@/define/response/openAIResponse' +import { GetApiDefineDataById } from '@/define/data/apiData' +import axios from 'axios' +import { RetryWithBackoff } from '@/define/Tools/common' +import { Book } from '@/define/model/book/book' +import { AiInferenceModelModel, GetAIPromptOptionByValue } from '@/define/data/aiData/aiData' + +/** + * AI推理通用工具类 + * + * 该类提供了与AI推理相关的各种通用功能,包括: + * - 初始化和获取推理设置 + * - 获取API提供商和模型信息 + * - 文本中占位符替换 + * - 获取上下文数据 + * - 构建请求消息 + * - 执行推理请求 + * + * 主要用于处理小说分镜任务的AI推理流程,负责构建请求、发送请求 + * 和处理响应数据。 + * + * @class AiReasonCommon + * @example + * const aiReason = new AiReasonCommon(); + * await aiReason.GetAISetting(); + * const result = await aiReason.OriginalInferencePrompt(taskDetail, allDetails, 2, characterData); + */ +export class AiReasonCommon { + optionRealmService!: OptionRealmService + aiReasonSetting!: SettingModal.InferenceAISettings + + /** + * * 初始化 AiReasonCommon 类的实例 + * @returns {Promise} - 返回一个 Promise 对象,表示初始化操作的完成状态 + */ + async InitAiReasonCommon(): Promise { + if (!this.optionRealmService) { + this.optionRealmService = await OptionRealmService.getInstance() + } + } + + /** + * 获取推理设置 + * @returns {Promise} - 返回一个 Promise 对象,表示获取操作的完成状态 + * @throws {Error} - 如果推理设置不完整,则抛出错误 + */ + async GetAISetting(): Promise { + await this.InitAiReasonCommon() + let res = this.optionRealmService.GetOptionByKey(OptionKeyName.InferenceAI.InferenceSetting) + + let aiReasonSetting = optionSerialization( + res, + '‘设置-> 推理设置’' + ) + if ( + isEmpty(aiReasonSetting.apiProvider) || + isEmpty(aiReasonSetting.apiToken) || + isEmpty(aiReasonSetting.inferenceModel) || + isEmpty(aiReasonSetting.aiPromptValue) + ) { + throw new Error( + '请检查 ‘设置-> 推理设置’ 的API提供商、API令牌、推理模型、推理模式等是不是存在!' + ) + } + + this.aiReasonSetting = aiReasonSetting + } + + /** + * 获取当前的API提供商信息 + * @returns + */ + GetAPIProviderMessage() { + let apiProviders = GetApiDefineDataById(this.aiReasonSetting.apiProvider) + return apiProviders + } + + /** + * 获取当前的推理模型信息 + * @returns {any} - 返回当前的推理模型信息 + * @throws {Error} - 如果推理模型不存在,则抛出错误 + */ + GetInferenceModelMessage(): AiInferenceModelModel { + let selectInferenceModel = GetAIPromptOptionByValue(this.aiReasonSetting.aiPromptValue) + if (isEmpty(selectInferenceModel)) { + throw new Error('请检查推理模型是否存在!') + } + return selectInferenceModel + } + + /** + * 替换字符串中的占位符为指定值 + * + * 此方法查找字符串中所有格式为 {key} 的占位符, + * 并用 replacements 对象中对应的值进行替换。 + * + * @param {string} content - 包含占位符的原始字符串 + * @param {Record} replacements - 键值对对象,键是要替换的占位符,值是替换内容 + * @returns {string} 完成所有占位符替换后的字符串 + * + * @example + * // 返回 "你好,张三,今天是星期一" + * replaceObject("你好,{name},今天是{day}", { name: "张三", day: "星期一" }) + */ + replaceObject(content: string, replacements: Record): string { + let result = content + for (let key in replacements) { + result = result.replaceAll(`{${key}}`, replacements[key]) + } + return result + } + + /** + * 获取当前分镜的上下文数据 + * @param currentBookTaskDetail 当前分镜数据 + * @param bookTaskDetails 所有的小说分镜数据 + * @param contextCount 上下文行数 + */ + GetBookTaskDetailContextData( + currentBookTaskDetail: Book.SelectBookTaskDetail, + bookTaskDetails: Book.SelectBookTaskDetail[], + contextCount: number + ): string { + let prefix = '' + // 拼接一个word + let i = (currentBookTaskDetail.no as number) - 1 + if (i <= contextCount) { + prefix = bookTaskDetails + .filter((_item, index) => index < i) + .map((item) => item.afterGpt) + .join('\r\n') + } else if (i > contextCount) { + prefix = bookTaskDetails + .filter((_item, index) => i - index <= contextCount && i - index > 0) + .map((item) => item.afterGpt) + .join('\r\n') + } + + let suffix = '' + let o_i = bookTaskDetails.length - i + if (o_i <= contextCount) { + suffix = bookTaskDetails + .filter((_item, index) => index > i) + .map((item) => item.afterGpt) + .join('\r\n') + } else if (o_i > contextCount) { + suffix = bookTaskDetails + .filter((_item, index) => index - i <= contextCount && index - i > 0) + .map((item) => item.afterGpt) + .join('\r\n') + } + + return `${prefix}\r\n${currentBookTaskDetail.afterGpt}\r\n${suffix}` + } + + /** + * 返回当前推理数据的请求体中的message + * @param currentBookTaskDetail 当前推理的提示词数据 + * @param contextData 上下文数据 + * @param autoAnalyzeCharacter 自动分析的角色数据 + * @returns + */ + GetGPTRequestMessage( + currentBookTaskDetail: Book.SelectBookTaskDetail, + contextData: string, + autoAnalyzeCharacter: string, + selectInferenceModel: AiInferenceModelModel + ): any[] { + let message: any = [] + if (selectInferenceModel.hasExample) { + // // 有返回案例的 + // message = gptDefine.GetExamplePromptMessage(global.config.gpt_auto_inference) + // // 加当前提问的 + // message.push({ + // role: 'user', + // content: currentBookTaskDetail.afterGpt + // }) + } else { + // 直接返回,没有案例的 + message = [ + { + role: 'system', + content: this.replaceObject(selectInferenceModel.systemContent, { + textContent: contextData, + characterContent: autoAnalyzeCharacter + }) + }, + { + role: 'user', + content: this.replaceObject(selectInferenceModel.userContent, { + contextContent: contextData, + textContent: currentBookTaskDetail.afterGpt ?? '', + characterContent: autoAnalyzeCharacter, + wordCount: '40' + }) + } + ] + } + return message + } + + /** + * 发起推理请求 + * @description 该方法用于发起推理请求,获取推理结果。包含重试机制和错误处理。 + * + * @param {OpenAISuccessResponse} message - 要发送的消息对象 + * @returns {Promise} - 返回一个 Promise 对象,表示获取操作的完成状态 + * @throws {Error} - 如果推理设置不完整,则抛出错误 + * @throws {Error} - 如果请求失败,则抛出错误 + * @throws {Error} - 如果响应数据格式不正确,则抛出错误 + * + */ + async FetchGpt(message: any, option: any = {}): Promise { + try { + let data = { + model: this.aiReasonSetting.inferenceModel, + messages: message, + ...option + } + let apiProvider = this.GetAPIProviderMessage() + let config = { + method: 'post', + maxBodyLength: Infinity, + url: apiProvider.gpt_url, + headers: { + Authorization: `Bearer ${this.aiReasonSetting.apiToken}`, + 'Content-Type': 'application/json' + }, + data: JSON.stringify(data) + } + + let res = await RetryWithBackoff( + async () => { + return await axios.request(config) + }, + 5, + 2000 + ) + let content = GetOpenAISuccessResponse(res.data) + // this.GetResponseContent(res, this.gptUrl) + return content + } catch (error) { + throw error + } + } + + /** + * 原创推理提示词数据 + * @param currentBookTaskDetail 要推理的小说分镜任务 + * @param bookTaskDetails 所有的小说分镜任务 + * @param contextCount 上下文的数量 + * @param autoAnalyzeCharacter 自动分析的角色数据字符串 + */ + async OriginalInferencePrompt( + currentBookTaskDetail: Book.SelectBookTaskDetail, + bookTaskDetails: Book.SelectBookTaskDetail[], + contextCount: number, + autoAnalyzeCharacter: string + ) { + await this.GetAISetting() + + // 获取当前的推理模式信息 + let selectInferenceModel = this.GetInferenceModelMessage() + + // 内置模式 + let context = this.GetBookTaskDetailContextData( + currentBookTaskDetail, + bookTaskDetails, + contextCount + ) + + if (isEmpty(autoAnalyzeCharacter) && selectInferenceModel.mustCharacter) { + throw new Error('当前模式需要提前分析或者设置角色场景数据,请先分析角色/场景数据!') + } + + let message = this.GetGPTRequestMessage( + currentBookTaskDetail, + context, + autoAnalyzeCharacter, + selectInferenceModel + ) + + // 开始请求 + let res = await this.FetchGpt(message) + if (res) { + // 处理返回的数据,删除部分数据 + res = res + .replace(/\)\s*\(/g, ', ') + .replace(/^\(/, '') + .replace(/\)$/, '') + .replaceAll('*', '') + .replaceAll('--', ' ') + } + return res + } +} diff --git a/src/main/service/book/bookIndex/bookDataEntrance.ts b/src/main/service/book/bookIndex/bookDataEntrance.ts new file mode 100644 index 0000000..531f2d1 --- /dev/null +++ b/src/main/service/book/bookIndex/bookDataEntrance.ts @@ -0,0 +1,41 @@ +import { Book } from '@/define/model/book/book' +import { BookServiceHandle } from '../subBookHandle/bookServiceHandle' + +/** + * 小说数据方法导出的入口类 + */ +export class BookDataEntrance { + bookServiceHandle: BookServiceHandle + constructor() { + this.bookServiceHandle = new BookServiceHandle() + } + + //#region 小说数据相关 + + /** 添加或修改小说信息 */ + AddOrModifyBook = async (book: Book.SelectBook) => + await this.bookServiceHandle.AddOrModifyBook(book) + + /** 获取小说信息,通过参数查询 */ + GetBookDataCondition = async (queryCondition: Book.QueryBookCondition) => + await this.bookServiceHandle.GetBookDataCondition(queryCondition) + + /** 获取小说数据,通过小说ID查询 */ + GetBookDataById = async (id: string) => await this.bookServiceHandle.GetBookDataById(id) + + /** 修改小说数据,通过小说ID修改 */ + ModifyBookDataById = async (id: string, bookData: Book.SelectBook) => + await this.bookServiceHandle.ModifyBookDataById(id, bookData) + + /** 重置小说数据 */ + async ResetBookDataInfo(bookId: string) { + return await this.bookServiceHandle.ResetBookDataInfo(bookId) + } + + /** 删除小说数据,通过小说ID删除 */ + async DeleteBookDataInfoById(id: string) { + return await this.bookServiceHandle.DeleteBookDataInfoById(id) + } + + //#endregion +} diff --git a/src/main/service/book/bookIndex/bookExportEntrance.ts b/src/main/service/book/bookIndex/bookExportEntrance.ts new file mode 100644 index 0000000..fc19e5e --- /dev/null +++ b/src/main/service/book/bookIndex/bookExportEntrance.ts @@ -0,0 +1,22 @@ +/** + * 小说导出入口类 + * + * 提供小说相关的导出功能,包括视频处理等操作的统一入口点。 + * 负责协调各种导出操作,简化外部调用接口。 + */ +import { BookVideoHandle } from "../subBookHandle/bookVideoHandle" + + +export class BookExportEntrance { + bookVideoHandle: BookVideoHandle + constructor() { + this.bookVideoHandle = new BookVideoHandle() + } + + //#region 视频相关 + /** 添加剪映草稿 */ + AddJianyingDraft = async (bookTaskId: string) => + await this.bookVideoHandle.AddJianyingDraft(bookTaskId) + + //#endregion +} diff --git a/src/main/service/book/bookIndex/bookImageEntrance.ts b/src/main/service/book/bookIndex/bookImageEntrance.ts new file mode 100644 index 0000000..403c9c7 --- /dev/null +++ b/src/main/service/book/bookIndex/bookImageEntrance.ts @@ -0,0 +1,61 @@ +/** + * 图书小说入口类 + * + * 提供小说分镜图片相关的操作接口,包括图片上传、下载、处理和管理等功能。 + * 作为小说操作的统一入口点,内部委托给BookImageHandle处理具体业务逻辑。 + * + * @class BookImageEntrance + */ +import { OperateBookType } from "@/define/enum/bookEnum" +import { BookImageHandle } from "../subBookHandle/bookImageHandle" + + +export class BookImageEntrance { + bookImageHandle: BookImageHandle + constructor() { + this.bookImageHandle = new BookImageHandle() + } + + //#region 出图相关 + + /** 将指定图片设置为分镜的主图片 */ + MoveImageToMainImage = async ( + bookTaskId: string, + bookTaskDetailId: string, + sourceImagePath: string + ) => + await this.bookImageHandle.MoveImageToMainImage(bookTaskId, bookTaskDetailId, sourceImagePath) + + /** 重置(删除)所有未锁定的分镜图片数据 */ + ResetGenerateImage = async (id: string, operateBookType: OperateBookType) => + await this.bookImageHandle.ResetGenerateImage(id, operateBookType) + + /** 上传图片到分镜并更新相关信息 */ + UpLoadImageToBookAndUpdateMessage = async ( + bookTaskDetailId: string, + imageFile: string | string[], + option: string + ) => + await this.bookImageHandle.UpLoadImageToBookAndUpdateMessage( + bookTaskDetailId, + imageFile, + option + ) + + /** 对分镜图片进行高清处理 */ + HDOneImage = async (bookTaskDetailId: string, scale: number) => + await this.bookImageHandle.HDOneImage(bookTaskDetailId, scale) + + /** 获取Midjourney图片URL并下载应用到分镜 */ + GetImageUrlAndDownload = async ( + id: string, + operateBookType: OperateBookType, + coverData: boolean + ) => await this.bookImageHandle.GetImageUrlAndDownload(id, operateBookType, coverData) + + /** 下载图片并拆分处理应用到分镜 */ + DownloadImageUrlAndSplit = async (bookTaskDetailId: string, imageUrl: string) => + await this.bookImageHandle.DownloadImageUrlAndSplit(bookTaskDetailId, imageUrl) + + //#endregion +} diff --git a/src/main/service/book/bookIndex/bookPromptEntrance.ts b/src/main/service/book/bookIndex/bookPromptEntrance.ts new file mode 100644 index 0000000..50ba9b6 --- /dev/null +++ b/src/main/service/book/bookIndex/bookPromptEntrance.ts @@ -0,0 +1,51 @@ +/** + * 小说提示词入口类 + * + * 负责处理小说相关的AI提示词生成、合并以及翻译功能的统一入口。 + * 通过组合BookPromptHandle和TranslateHandle来提供完整的书籍处理服务。 + * + * @class BookPromptEntrance + */ +import { PresetCategory } from '@/define/data/presetData' +import { OperateBookType, PromptMergeType } from '@/define/enum/bookEnum' +import { TranslateModel } from '@/define/model/translate' +import { BookPromptHandle } from '../subBookHandle/bookPromptHandle' +import { TranslateHandle } from '../../translate' +import { BookTask } from '@/define/model/book/bookTask' + +export class BookPromptEntrance { + bookPromptHandle: BookPromptHandle + translateHandle: TranslateHandle + + constructor() { + this.bookPromptHandle = new BookPromptHandle() + this.translateHandle = new TranslateHandle() + } + + //#region 提示词 + + /** 为小说分镜生成AI提示词 */ + OriginalGetAiPrompt = async (id: string, operateBookType: OperateBookType, coverData: boolean) => + await this.bookPromptHandle.OriginalGetAiPrompt(id, operateBookType, coverData) + + /** AI分镜头合并 */ + AIStoryboardMerge = async (bookTaskId: string, type: BookTask.StoryboardMergeType) => + await this.bookPromptHandle.AIStoryboardMerge(bookTaskId, type) + + /** 合并提示词 */ + MergePrompt = async (id: string, type: PromptMergeType, operateBookType: OperateBookType) => + await this.bookPromptHandle.MergePrompt(id, type, operateBookType) + + /** 自动分析提示词或场景数据 */ + AutoAnalyzeCharacterOrScene = async (bookTaskId: string, type: PresetCategory) => + await this.bookPromptHandle.AutoAnalyzeCharacterOrScene(bookTaskId, type) + //#endregion + + //#region 翻译相关 + + /** 翻译小说分镜数据 */ + TranslateBookTaskDetailPromptNowReturn = async (value: TranslateModel.TranslateNowIPCParams[]) => + await this.translateHandle.TranslateBookTaskDetailPromptNowReturn(value) + + //#endregion +} diff --git a/src/main/service/book/bookIndex/bookTaskDetailEntrance.ts b/src/main/service/book/bookIndex/bookTaskDetailEntrance.ts new file mode 100644 index 0000000..eb7556e --- /dev/null +++ b/src/main/service/book/bookIndex/bookTaskDetailEntrance.ts @@ -0,0 +1,50 @@ +/** + * 小说任务详细数据入口类 + * + * 负责处理小说批次任务的详细数据操作,包括查询、修改和保存等功能。 + * 作为服务层的入口点,封装了对BookTaskDetailServiceHandle的调用。 + */ +import { OperateBookType } from '@/define/enum/bookEnum' +import { Book } from '@/define/model/book/book' +import { BookTaskDetail } from '@/define/model/book/bookTaskDetail' +import { BookTaskDetailServiceHandle } from '../subBookHandle/bookTaskDetailServiceHandle' + +export class BookTaskDetailEntrance { + bookTaskDetailServiceHandle: BookTaskDetailServiceHandle + + constructor() { + this.bookTaskDetailServiceHandle = new BookTaskDetailServiceHandle() + } + + //#region 小说批次任务详细数据相关 + + /** 获取小说子任务详细数据 */ + GetBookTaskDetailDataByCondition = async ( + bookTaskDetailCondition: Book.QueryBookTaskDetailCondition + ) => + await this.bookTaskDetailServiceHandle.GetBookTaskDetailDataByCondition(bookTaskDetailCondition) + + /** 获取小说子任务详细数据,通过小说ID查询 */ + GetBookTaskDetailDataById = async (id: string) => + await this.bookTaskDetailServiceHandle.GetBookTaskDetailDataById(id) + + /** 修改小说子任务详细数据 */ + ModifyBookTaskDetailById = async ( + bookTaskDetailId: string, + updateData: Book.SelectBookTaskDetail + ) => await this.bookTaskDetailServiceHandle.ModifyBookTaskDetailById(bookTaskDetailId, updateData) + + /** 保存小说批次数据分镜信息 */ + SaveCopywritingInfo = async ( + bookTaskId: string, + copywritingData: BookTaskDetail.SaveCopywritingData[], + operateBookType: OperateBookType + ) => + await this.bookTaskDetailServiceHandle.SaveCopywritingInfo( + bookTaskId, + copywritingData, + operateBookType + ) + + //#endregion +} diff --git a/src/main/service/book/bookIndex/bookTaskEmtrance.ts b/src/main/service/book/bookIndex/bookTaskEmtrance.ts new file mode 100644 index 0000000..b17d62d --- /dev/null +++ b/src/main/service/book/bookIndex/bookTaskEmtrance.ts @@ -0,0 +1,80 @@ +import { BookTaskStatus } from '@/define/enum/bookEnum' +import { Book } from '@/define/model/book/book' +import { BookTask } from '@/define/model/book/bookTask' +import { BookTaskServiceHandle } from '../subBookHandle/bookTaskServiceHandle' + +/** + * 小说任务入口类 + * 负责处理小说相关的批次任务操作,包括任务的增删改查、状态管理等功能 + */ +export class BookTaskEntrance { + bookTaskServiceHandle: BookTaskServiceHandle + + constructor() { + this.bookTaskServiceHandle = new BookTaskServiceHandle() + } + + //#region 小说子任务数据相关 + + /** 获取小说子任务数据 */ + async GetBookTaskDataByCondition(bookTaskCondition: Book.QueryBookTaskCondition) { + return await this.bookTaskServiceHandle.GetBookTaskDataByCondition(bookTaskCondition) + } + + /** 获取小说子任务数据,通过小说ID查询 */ + async GetBookTaskDataById(id: string) { + return await this.bookTaskServiceHandle.GetBookTaskDataById(id) + } + + /** 更新小说批次任务状态 */ + async ModifyBookTaskStatus(bookTaskId: string, status: BookTaskStatus, errorMsg?: string) { + return await this.bookTaskServiceHandle.ModifyBookTaskStatus(bookTaskId, status, errorMsg) + } + + /** 修改小说批次任务数据,通过小说ID修改 */ + async ModifyBookTaskDataById(bookTaskId: string, data: Book.SelectBookTask) { + return await this.bookTaskServiceHandle.ModifyBookTaskDataById(bookTaskId, data) + } + + /** 删除小说批次任务数据,通过小说ID删除 */ + async DeleteBookTaskByIds(ids: string[]) { + return await this.bookTaskServiceHandle.DeleteBookTaskByIds(ids) + } + + /** 添加小说任务 */ + async AddNewBookTask(addData: BookTask.AddBookTaskParam) { + return await this.bookTaskServiceHandle.AddNewBookTask(addData) + } + + /** 获取小说批次任务的图片生成进度 */ + async GetBookTaskImageGenerateProgress(bookId: string) { + return await this.bookTaskServiceHandle.GetBookTaskImageGenerateProgress(bookId) + } + + /** 获取小说批次任务的第一张图片路径 */ + async GetBookTaskFirstImagePath(bookId: string) { + return await this.bookTaskServiceHandle.GetBookTaskFirstImagePath(bookId) + } + + /** 添加小说子任务数据 */ + async AddBookTask(bookTask: Book.SelectBookTask) { + return await this.bookTaskServiceHandle.AddBookTask(bookTask) + } + + /** 重置小说子任务数据 */ + async ResetBookTaskDataById(bookTaskId: string) { + return await this.bookTaskServiceHandle.ResetBookTaskDataById(bookTaskId) + } + + /** 重置小说分镜数据(不包含bootask本身数据),并且初始化全新的分镜信息 */ + async ResetAndInitializeBookTask(bookTaskId: string) { + return await this.bookTaskServiceHandle.ResetAndInitializeBookTask(bookTaskId) + } + + /** 删除小说批次任务数据 */ + async DeleteBookTaskDataById(bookTaskId: string) { + return await this.bookTaskServiceHandle.DeleteBookTaskDataById(bookTaskId) + } + + //#endregion +} diff --git a/src/main/service/book/index.ts b/src/main/service/book/index.ts new file mode 100644 index 0000000..9da9fbb --- /dev/null +++ b/src/main/service/book/index.ts @@ -0,0 +1,33 @@ +import { BookDataEntrance } from './bookIndex/bookDataEntrance' +import { BookTaskEntrance } from './bookIndex/bookTaskEmtrance' +import { BookTaskDetailEntrance } from './bookIndex/bookTaskDetailEntrance' +import { BookPromptEntrance } from './bookIndex/bookPromptEntrance' +import { BookImageEntrance } from './bookIndex/bookImageEntrance' +import { BookExportEntrance } from './bookIndex/bookExportEntrance' +import { mixin } from '@/main/utils/mixin' + +// 使用装饰器实现多重继承 +// 类型接口声明,让TypeScript知道合并后的类型 +interface BookHandle + extends BookDataEntrance, + BookTaskEntrance, + BookTaskDetailEntrance, + BookPromptEntrance, + BookImageEntrance, + BookExportEntrance {} + +@mixin( + BookTaskEntrance, + BookTaskDetailEntrance, + BookPromptEntrance, + BookImageEntrance, + BookExportEntrance +) +class BookHandle extends BookDataEntrance { + constructor() { + super() + } +} + +// debugger +export const bookHandle = new BookHandle() as BookHandle diff --git a/src/main/service/book/subBookHandle/bookBasicHandle.ts b/src/main/service/book/subBookHandle/bookBasicHandle.ts new file mode 100644 index 0000000..56010f2 --- /dev/null +++ b/src/main/service/book/subBookHandle/bookBasicHandle.ts @@ -0,0 +1,38 @@ +import { BookTaskDetailService } from '@/define/db/service/book/bookTaskDetailService' +import { BookTaskService } from '@/define/db/service/book/bookTaskService' +import { OptionRealmService } from '@/define/db/service/optionService' +import { BookService } from '@/define/db/service/book/bookService' + +export class BookBasicHandle { + bookTaskDetailService!: BookTaskDetailService + bookTaskService!: BookTaskService + optionRealmService!: OptionRealmService + bookService!: BookService + + constructor() { + // 初始化 + } + + async InitBookBasicHandle() { + // 如果 bookTaskDetailService 已经初始化,则直接返回 + if (!this.bookTaskDetailService) { + this.bookTaskDetailService = await BookTaskDetailService.getInstance() + } + if (!this.bookTaskService) { + this.bookTaskService = await BookTaskService.getInstance() + } + if (!this.optionRealmService) { + this.optionRealmService = await OptionRealmService.getInstance() + } + if (!this.bookService) { + this.bookService = await BookService.getInstance() + } + } + + async transaction(callback: (realm: any) => void) { + await this.InitBookBasicHandle() + this.bookService.transaction(() => { + callback(this.bookService.realm) + }) + } +} diff --git a/src/main/service/book/subBookHandle/bookImageHandle.ts b/src/main/service/book/subBookHandle/bookImageHandle.ts new file mode 100644 index 0000000..a59ef77 --- /dev/null +++ b/src/main/service/book/subBookHandle/bookImageHandle.ts @@ -0,0 +1,724 @@ +import path from 'path' +import fs from 'fs' +import { BookBasicHandle } from './bookBasicHandle' +import { + CheckFileOrDirExist, + CheckFolderExistsOrCreate, + CopyFileOrFolder, + DownloadImageFromUrl +} from '@/define/Tools/file' +import { getProjectPath } from '../../option/optionCommonService' +import { errorMessage, successMessage } from '@/public/generalTools' +import { GeneralResponse } from '@/define/model/generalResponse' +import { BookType, MJAction, OperateBookType } from '@/define/enum/bookEnum' +import { Book } from '@/define/model/book/book' +import { isEmpty } from 'lodash' +import { ImageGenerateMode } from '@/define/data/mjData' +import { define } from '@/define/define' +import util from 'util' +import { exec } from 'child_process' +import { ImageCategory } from '@/define/data/imageData' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../../option/optionSerialization' +import { SettingModal } from '@/define/model/setting' +const execAsync = util.promisify(exec) +import { MJApiService } from '../../mj/mjApiService' +import { ImageSplit } from '@/define/Tools/image' + +export class BookImageHandle extends BookBasicHandle { + mjApiService: MJApiService + constructor() { + // 初始化 + super() + this.mjApiService = new MJApiService() + } + + /** + * 将指定图片设置为分镜的主图片 + * + * 该方法接收一个源图片路径和分镜ID,将源图片复制到小说项目的图片文件夹中, + * 并以分镜名称命名。如果分镜已有主图片,则会先删除原有图片再进行复制。 + * 复制完成后,会更新分镜数据库中的图片路径信息。 + * + * @param {string} bookTaskId - 小说任务ID,用于定位小说项目 + * @param {string} bookTaskDetailId - 分镜ID,表示要设置主图的分镜 + * @param {string} sourceImagePath - 源图片路径,可以是本地路径或file:协议的URL + * @returns {Promise} + * 成功时返回新图片路径,失败时返回错误信息 + * + * @throws {Error} 当找不到分镜数据、小说项目图片文件夹或源图片不存在时抛出错误 + * + * @example + * // 设置ID为"detail-123"的分镜主图 + * const result = await bookImageHandle.MoveImageToMainImage( + * "task-456", + * "detail-123", + * "D:/temp/selected_image.png" + * ); + * if (result.code === 1) { + * console.log("设置成功,新图片路径:", result.data); + * } + */ + public async MoveImageToMainImage( + bookTaskId: string, + bookTaskDetailId: string, + sourceImagePath: string + ): Promise { + try { + await this.InitBookBasicHandle() + let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId) + let bookTaskDetail = + await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId) + if (bookTaskDetail == null) { + throw new Error('没有找到对应的小说子任务数据,请检查ID是否正确') + } + // 获取小说项目的地址 + let imageFolder = bookTask.imageFolder + if (imageFolder == null) { + throw new Error('没有找到对应的小说项目的图片地址,请检查') + } + + let currentImagePath = path.join(imageFolder, `${bookTaskDetail.name}.png`) + // 判断地址对应的文件是不是存在 + if (await CheckFileOrDirExist(currentImagePath)) { + await fs.promises.unlink(currentImagePath) + } + + sourceImagePath = sourceImagePath.split('?t=')[0] + sourceImagePath = sourceImagePath.split('?time=')[0] + if (sourceImagePath.startsWith('file:/')) { + sourceImagePath = sourceImagePath.replace('file:///', '') + sourceImagePath = sourceImagePath.replace('file://', '') + sourceImagePath = sourceImagePath.replace('file:/', '') + } + sourceImagePath = path.resolve(sourceImagePath) + if (!(await CheckFileOrDirExist(sourceImagePath))) { + throw new Error(`图片文件 ${sourceImagePath} 不存在,请检查`) + } + + // 复制文件,判断父文件夹是不是存在 + await CopyFileOrFolder(sourceImagePath, currentImagePath, true) + + let projectPath = await getProjectPath() + // 修改数据库数据 + await this.bookTaskDetailService.ModifyBookTaskDetailById(bookTaskDetailId, { + outImagePath: path.relative(projectPath, currentImagePath) + }) + // 返回数据 + return successMessage( + currentImagePath + `?t=${Date.now()}`, + '将指定的图片放到主图中成功', + 'BookImage_MoveImageToMainImage' + ) + } catch (error: any) { + return errorMessage( + '将指定的图片放到主图中失败,错误信息如下:' + error.message, + 'BookImage_MoveImageToMainImage' + ) + } + } + + /** + * 重置(删除)所有未锁定的分镜图片数据 + * + * 该方法用于批量清除指定书籍任务或单个分镜的图片数据。删除过程中会跳过所有 + * 已锁定的图片,确保重要图片不会被误删。清除操作包括两部分: + * 1. 删除数据库中的图片路径记录和相关生成信息 + * 2. 删除本地文件系统中的实际图片文件 + * + * 注意:虽然方法名为"Reset",但实际执行的是删除操作,删除后数据无法恢复 + * + * @param {string} id - 目标ID,可以是书籍任务ID或分镜ID,取决于operateBookType参数 + * @param {OperateBookType} operateBookType - 操作类型,指定id参数代表的是书籍任务还是单个分镜 + * - BOOKTASK: 删除整个书籍任务下的所有未锁定分镜图片 + * - BOOKTASKDETAIL: 仅删除单个指定分镜的图片 + * + * @returns {Promise} + * 成功时返回被删除的分镜数据数组,失败时返回错误信息 + * + * @throws {Error} 当找不到指定ID的数据、没有可删除的分镜、或操作类型不支持时抛出错误 + * + * @example + * // 删除整个书籍任务的图片数据 + * const result = await bookImageHandle.ResetGenerateImage( + * "task-123", + * OperateBookType.BOOKTASK + * ); + * + * // 删除单个分镜的图片数据 + * const result = await bookImageHandle.ResetGenerateImage( + * "detail-456", + * OperateBookType.BOOKTASKDETAIL + * ); + */ + async ResetGenerateImage( + id: string, + operateBookType: OperateBookType + ): Promise { + try { + await this.InitBookBasicHandle() + let bookTaskDetails: Book.SelectBookTaskDetail[] = [] + if (operateBookType == OperateBookType.BOOKTASK) { + bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: id + }) + } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + let tempBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id) + if (tempBookTaskDetail == null) { + throw new Error('没有找到要删除的分镜数据,请检查ID是否正确') + } + bookTaskDetails = [tempBookTaskDetail] + } else { + throw new Error('不支持的操作类型,请检查') + } + //这边过滤被锁定的数据 + bookTaskDetails = bookTaskDetails.filter((item) => !item.imageLock) + + if (bookTaskDetails.length <= 0) { + throw new Error('没有要删除的分镜数据,请检查') + } + + // 开始删除数据,要删除图片数据和出图的信息 + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i] + this.bookTaskDetailService.DeleteBoookTaskDetailGenerateImage(element.id as string) + // 上面的信息重置完毕之后,开始删除文件信息 + let outImage = element.outImagePath + if (await CheckFileOrDirExist(outImage)) { + await fs.promises.unlink(outImage as string) + } + } + + return successMessage( + bookTaskDetails, + '删除所有的生图数据成功', + 'BookImage_ResetGenerateImage' + ) + } catch (error: any) { + return errorMessage( + '删除所有的图片数据失败,失败信息如下:' + error.toString(), + 'BookImage_ResetGenerateImage' + ) + } + } + + /** + * 上传单个图片到分镜中 + * + * 该私有方法处理单个图片的上传过程,支持上传为主图或子图。 + * 根据指定选项,将图片复制到适当的项目目录,并更新数据库中的相关信息。 + * + * 上传流程: + * 1. 验证源图片文件存在 + * 2. 检索分镜数据并验证其有效性 + * 3. 将图片复制到项目的指定目录中 + * 4. 根据选项更新数据库中的图片路径信息 + * 5. 对于主图,同时更新生成状态信息 + * + * @param {string} bookTaskDetailId - 目标分镜ID + * @param {string} imageFile - 源图片文件的本地路径 + * @param {string} option - 图片类型选项: + * - 'outImagePath': 设置为分镜主图 + * - 'subImagePath': 添加为分镜子图 + * + * @returns {Promise} 上传后的图片路径(含时间戳) + * @throws {Error} 当图片不存在、分镜数据无效或选项不支持时抛出错误 + * + * @private 仅供类内部使用 + */ + private async UploadOne( + bookTaskDetailId: string, + imageFile: string, + option: string + ): Promise { + console.log('开始上传图片', bookTaskDetailId, imageFile, option) + if (!(await CheckFileOrDirExist(path.resolve(imageFile)))) { + throw new Error(`图片文件 ${imageFile} 不存在,请检查`) + } + // 修改数据库数据 + let bookTaskDetail = + await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId) + if (bookTaskDetail == null) { + throw new Error('没有找到对应的小说子任务数据,请检查ID是否正确') + } + let projectPath = await getProjectPath() + // 将图片复制到对应的文件夹中 + // 生成一个0-10的随机数 + let random = Math.floor(Math.random() * 10) + let newImagePath = path.join( + projectPath, + `${bookTaskDetail.bookId}/data/Upload/${bookTaskDetail.name}_${Date.now()}_${random}.png` + ) + await CheckFolderExistsOrCreate(path.dirname(newImagePath)) + await CopyFileOrFolder(imageFile, newImagePath) + if (option == 'outImagePath') { + let outImagePath = bookTaskDetail.outImagePath + let bookTask = await this.bookTaskService.GetBookTaskDataById( + bookTaskDetail.bookTaskId as string + ) + if (isEmpty(bookTaskDetail.outImagePath)) { + outImagePath = path.join( + projectPath, + `${bookTaskDetail.bookId}/tmp/${bookTask.name}/${bookTaskDetail.name}.png` + ) + } + await CheckFolderExistsOrCreate(path.dirname(outImagePath as string)) + await CopyFileOrFolder(newImagePath, outImagePath) + await this.bookTaskDetailService.ModifyBookTaskDetailById(bookTaskDetailId, { + outImagePath: path.relative(projectPath, outImagePath as string) + }) + this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(bookTaskDetailId, { + progress: 100, + status: 'success', + category: ImageGenerateMode.MJ_API, + messageId: '', + action: MJAction.IMAGINE + }) + return outImagePath + '?t=' + new Date().getTime() + } else if (option == 'subImagePath') { + // 修改数据库数据 + let subImagePath = bookTaskDetail.subImagePath ?? [] + subImagePath.push(newImagePath) + await this.bookTaskDetailService.ModifyBookTaskDetailById(bookTaskDetailId, { + subImagePath: subImagePath.map((item) => path.relative(projectPath, item)) + }) + return newImagePath + '?t=' + new Date().getTime() + } else { + throw new Error('无效的操作类型,请检查') + } + } + + /** + * 上传图片到分镜并更新相关信息 + * + * 该方法支持将单张图片设置为分镜主图或将多张图片添加为分镜子图。 + * 上传过程包括复制图片文件到项目目录并更新数据库中的相关图片路径信息。 + * 对于主图,会同时更新生成状态、完成进度等元数据。 + * + * @param {string} bookTaskDetailId - 目标分镜ID + * @param {string | string[]} imageFile - 要上传的图片路径,主图为单个字符串,子图为字符串数组 + * @param {string} option - 上传类型选项 + * - 'outImagePath': 上传为分镜主图,替换现有主图 + * - 'subImagePath': 上传为分镜子图,添加到现有子图集合中 + * + * @returns {Promise} + * 成功时返回上传后的图片路径(含时间戳),主图返回单个路径,子图返回路径数组 + * @throws {Error} 当操作类型无效、文件不存在或数据操作失败时抛出错误 + * + * @example + * // 上传主图 + * const mainResult = await bookImageHandle.UpLoadImageToBookAndUpdateMessage( + * "detail-123", + * "D:/images/main.png", + * "outImagePath" + * ); + * + * // 上传多个子图 + * const subResult = await bookImageHandle.UpLoadImageToBookAndUpdateMessage( + * "detail-123", + * ["D:/images/sub1.png", "D:/images/sub2.png"], + * "subImagePath" + * ); + */ + public async UpLoadImageToBookAndUpdateMessage( + bookTaskDetailId: string, + imageFile: string | string[], + option: string + ): Promise { + try { + await this.InitBookBasicHandle() + if (option == 'outImagePath') { + let res = await this.UploadOne(bookTaskDetailId, imageFile as string, option) + return successMessage( + res, + '上传图片,并修改小说信息成功', + 'BookImage_UpLoadImageToBookAndUpdateMessage' + ) + } else if (option == 'subImagePath') { + let subImage = [] as string[] + for (let i = 0; i < (imageFile as string[]).length; i++) { + let res = await this.UploadOne(bookTaskDetailId, imageFile[i], option) + subImage.push(res) + } + return successMessage( + subImage as string[], + '上传图片,并修改小说信息成功', + 'BookImage_UpLoadImageToBookAndUpdateMessage' + ) + } else { + throw new Error('无效的操作类型,请检查') + } + } catch (error: any) { + return errorMessage( + '上传图片到小说中,并修改小说信息失败,错误信息如下:' + error.message, + 'BookImage_UpLoadImageToBookAndUpdateMessage' + ) + } + } + + /** + * 对分镜图片进行高清处理的内部实现方法 + * + * 该私有方法实现了图片高清处理的核心逻辑,包括验证分镜数据和图片存在性, + * 生成临时高清图片路径,以及调用外部高清工具进行实际处理。处理完成后, + * 高清图片会替换原始图片,保持原文件名不变。 + * + * 处理流程: + * 1. 验证分镜数据存在且有效 + * 2. 检查原图文件是否存在 + * 3. 生成临时高清图片路径 + * 4. 调用外部高清工具(rnv.exe)处理图片 + * 5. 返回处理后的图片路径 + * + * @param {string} bookTaskDetailId - 要处理的分镜ID + * @param {number} scale - 放大比例,默认为2倍 + * @returns {Promise} 处理后的图片路径 + * @throws {Error} 当分镜数据不存在、原图不存在或处理过程出错时抛出异常 + * + * @private 仅供类内部使用 + */ + private async HDOneImagePrivate(bookTaskDetailId: string, scale: number = 2): Promise { + let bookTaskDetail = + await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId) + if (bookTaskDetail == null) { + throw new Error('没有找到要高清的分镜数据,请检查ID是否正确') + } + + let outImagePath = bookTaskDetail.outImagePath as string + if (isEmpty(outImagePath)) { + throw new Error('没有找到要高清的分镜数据,请检查ID是否正确') + } + let imageExist = await CheckFileOrDirExist(outImagePath as string) + if (!imageExist) { + throw new Error('没有找到要高清的图片,请检查图片是否存在') + } + + let newImagePath = path.join( + path.dirname(outImagePath as string), + `${path.basename(outImagePath as string, '.png')}_hd.png` + ) + // 开始高清 + await CopyFileOrFolder(outImagePath as string, newImagePath, true) + + let command = `"${path.join(define.package_path, 'Improve/rnv.exe')}" -i "${newImagePath}" -o "${outImagePath}" -s ${scale}` + await execAsync(command, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' }) + // 高清完成之后删除临时文件 + await fs.promises.unlink(newImagePath) + return outImagePath + } + + /** + * 对分镜图片进行高清处理 + * + * 该方法使用AI超分辨率技术对分镜的主图进行放大增强处理,生成更高分辨率的图片。 + * 内部调用RealESRGAN-ncnn-vulkan工具执行实际的超分辨率算法,处理后的高清图片 + * 会替换原始图片。整个过程包括验证分镜数据、检查原图存在性、执行超分处理等步骤。 + * + * @param {string} bookTaskDetailId - 要处理的分镜ID + * @param {number} scale - 放大比例,默认为2倍 + * @returns {Promise} + * 成功时返回处理后的图片路径(含时间戳),失败时返回错误信息 + * + * @example + * // 将ID为"detail-123"的分镜图片放大2倍 + * const result = await bookImageHandle.HDOneImage("detail-123"); + * if (result.code === 1) { + * console.log("高清处理成功,图片路径:", result.data); + * } + * + * // 将ID为"detail-456"的分镜图片放大4倍 + * const result = await bookImageHandle.HDOneImage("detail-456", 4); + */ + public async HDOneImage( + bookTaskDetailId: string, + scale: number = 2 + ): Promise { + try { + await this.InitBookBasicHandle() + let outImagePath = await this.HDOneImagePrivate(bookTaskDetailId, scale) + // 高清完成 + return successMessage( + outImagePath + '?t=' + new Date().getTime(), + '高清分镜成功', + 'BookImage_HDOneImage' + ) + } catch (error: any) { + return errorMessage('高清分镜失败,失败信息如下:' + error.toString(), 'BookImage_HDOneImage') + } + } + + /** + * 下载图片并拆分处理应用到分镜 + * + * 该方法支持从URL下载图片或复制本地图片文件,然后根据图片类别进行适当处理。 + * 对于Midjourney图片,会自动拆分为多个子图;对于其他类型图片,则直接应用为主图。 + * 处理完成后,会将图片复制到项目目录,并更新分镜数据库中的相关信息。 + * + * 处理流程: + * 1. 验证分镜、书籍和任务数据存在性 + * 2. 确定图片源类型(URL或本地文件)并获取图片 + * 3. 根据图片类别执行不同处理(MJ图片拆分,其他图片直接使用) + * 4. 复制处理后的图片到项目目录 + * 5. 更新数据库中分镜的图片路径和状态信息 + * + * @param {string} bookTaskDetailId - 目标分镜ID + * @param {string} imageUrl - 图片URL或本地文件路径,支持网络链接或本地路径 + * + * @returns {Promise} + * 成功时返回处理后的图片信息,包括主图路径、子图路径数组和状态信息 + * + * @throws {Error} 当分镜数据不存在、图片下载失败或MJ图片拆分失败时抛出错误 + * + * @example + * // 从网络URL下载图片并应用到分镜 + * const result = await bookImageHandle.DownloadImageUrlAndSplit( + * "detail-123", + * "https://example.com/image.png" + * ); + * + * // 从本地路径复制图片并应用到分镜 + * const result = await bookImageHandle.DownloadImageUrlAndSplit( + * "detail-123", + * "D:/images/local_image.png" + * ); + */ + async DownloadImageUrlAndSplit( + bookTaskDetailId: string, + imageUrl: string + ): Promise { + try { + await this.InitBookBasicHandle() + let bookTaskDetail = + await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId) + if (bookTaskDetail == null) { + throw new Error('获取到的数据分镜为空,无法执行操作') + } + let book = await this.bookService.GetBookDataById(bookTaskDetail.bookId as string) + if (book == null) { + throw new Error('获取到的小说为空,无法执行操作') + } + let bookTask = await this.bookTaskService.GetBookTaskDataById( + bookTaskDetail.bookTaskId as string + ) + if (bookTask == null) { + throw new Error('获取到的任务为空,无法执行操作') + } + let imagePath = path.join( + book.bookFolderPath as string, + `data\\MJOriginalImage\\${bookTaskDetail.id}_${new Date().getTime()}.png` + ) + + // 判断是不是一个链接 + const urlRegex = /^(http|https):\/\/[^ "]+$/ + if (!urlRegex.test(imageUrl)) { + // 本地图片,直接复制 + await CopyFileOrFolder(imageUrl, imagePath) + } else { + // 网络图片下载 + await DownloadImageFromUrl(imageUrl, imagePath) + } + + let imageCategory = bookTask.imageCategory + let out_file: string + let imageRes: string[] = [] + if (imageCategory == ImageCategory.Midjourney) { + // 只有MJ需要裁剪,其他的不需要 + // 进行图片裁剪 + imageRes = await ImageSplit( + imagePath, + bookTaskDetail.name as string, + path.join(book.bookFolderPath as string, 'data\\MJOriginalImage') + ) + if (imageRes && imageRes.length < 4) { + throw new Error('图片裁剪失败') + } + // 修改数据 + // 修改数据库数据,将图片保存到对应的文件夹中 + let firstImage = imageRes[0] + if (book.type == BookType.ORIGINAL) { + await CopyFileOrFolder( + firstImage, + path.join( + book.bookFolderPath as string, + `data\\import\\${bookTaskDetail.name}_${new Date().getTime()}.png` + ) + ) + } + out_file = path.join(bookTask.imageFolder as string, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(firstImage, out_file) + } else { + // 其他的导入,每次只能导入一张图 + out_file = path.join(bookTask.imageFolder as string, `${bookTaskDetail.name}.png`) + if (book.type == BookType.ORIGINAL) { + await CopyFileOrFolder( + imagePath, + path.join( + book.bookFolderPath as string, + `data\\import\\${bookTaskDetail.name}_${new Date().getTime()}.png` + ) + ) + } + await CopyFileOrFolder(imagePath, out_file) + imageRes = [out_file] + } + let projectPath = await getProjectPath() + // 修改分镜的数据 + await this.bookTaskDetailService.ModifyBookTaskDetailById(bookTaskDetailId, { + outImagePath: path.relative(projectPath, out_file), + subImagePath: imageRes.map((item) => path.relative(projectPath, item)) + }) + + let mjMessage = { + status: 'success', + progress: 100, + category: ImageGenerateMode.MJ_API, + messageId: '', + action: MJAction.IMAGINE + } + this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(bookTaskDetailId, mjMessage) + + return successMessage( + { + outImagePath: out_file + '?time=' + new Date().getTime(), + subImagePath: imageRes.map((item) => item + '?time=' + new Date().getTime()), + mjMessage: mjMessage + }, + '下载指定的图片地址并且分割成功', + 'BookImage_DownloadImageUrlAndSplit' + ) + } catch (error: any) { + return { + code: 0, + message: '下载指定的图片地址并且分割错误,错误信息如下:' + error.message + } + } + } + + /** + * 获取Midjourney图片URL并下载应用到分镜 + * + * 该方法从MJ API获取已生成任务的图片URL,下载这些图片并应用到相应分镜中。 + * 支持批量处理整个书籍任务的分镜,或单独处理指定的单个分镜。下载过程包括 + * 获取MJ图片链接、下载图片、分割处理、保存到项目目录和更新数据库信息。 + * + * 注意:此方法仅在MJ API模式下有效,其他图片生成模式不支持此操作。 + * + * @param {string} id - 目标ID,可以是书籍任务ID或分镜ID,取决于operateBookType参数 + * @param {OperateBookType} operateBookType - 操作类型,指定id参数代表的是书籍任务还是单个分镜 + * - BOOKTASK: 处理整个书籍任务下的所有分镜 + * - BOOKTASKDETAIL: 仅处理单个指定分镜 + * @param {boolean} coverData - 是否覆盖现有图片数据 + * - true: 覆盖所有分镜的图片数据,包括已有图片 + * - false: 仅处理尚未有图片的分镜 + * + * @returns {Promise} + * 成功时返回包含下载和处理后的图片数据的对象数组,失败时返回错误信息 + * + * @throws {Error} 当找不到分镜数据、不支持的操作类型、非MJ模式、非MJ API模式、 + * 图片链接为空或下载失败时抛出错误 + * + * @example + * // 下载并应用整个书籍任务的所有分镜图片(不覆盖已有图片) + * const result = await bookImageHandle.GetImageUrlAndDownload( + * "task-123", + * OperateBookType.BOOKTASK, + * false + * ); + * + * // 下载并应用单个分镜图片(覆盖已有图片) + * const result = await bookImageHandle.GetImageUrlAndDownload( + * "detail-456", + * OperateBookType.BOOKTASKDETAIL, + * true + * ); + */ + async GetImageUrlAndDownload( + id: string, + operateBookType: OperateBookType, + coverData: boolean + ): Promise { + try { + console.log('GetImageUrlAndDownload', id, operateBookType, coverData) + await this.InitBookBasicHandle() + let bookTaskDetail: Book.SelectBookTaskDetail[] = [] + let bookTask: Book.SelectBookTask + + if (operateBookType == OperateBookType.BOOKTASK) { + bookTask = await this.bookTaskService.GetBookTaskDataById(id) + bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: bookTask.id + }) + // 这边过滤出图成功的数据 + if (!coverData) { + bookTaskDetail = bookTaskDetail.filter((item) => !item.outImagePath) + } + } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + let currentBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id) + if (currentBookTaskDetail == null) { + throw new Error('没有找到要采集的分镜数据,请检查ID是否正确') + } + bookTask = await this.bookTaskService.GetBookTaskDataById( + currentBookTaskDetail.bookTaskId as string + ) + bookTaskDetail = [currentBookTaskDetail] + } else { + throw new Error('不支持的操作类型') + } + + // 这边再做个详细的筛选 + + if (bookTaskDetail.length < 0) { + throw new Error('没有找到需要采集的数据') + } + if (bookTask.imageCategory != ImageCategory.Midjourney) { + throw new Error('只有MJ模式下才能使用这个功能') + } + let mjGeneralSettingOption = this.optionRealmService.GetOptionByKey( + OptionKeyName.Midjourney.GeneralSetting + ) + let mjGeneralSetting = optionSerialization( + mjGeneralSettingOption, + '‘设置 -> MJ设置’' + ) + if (mjGeneralSetting.outputMode != ImageGenerateMode.MJ_API) { + throw new Error('只有MJ API模式下才能使用这个功能') + } + let result: any[] = [] + + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i] + if (!element.mjMessage) continue + if (element.mjMessage.status == 'error') continue + if (isEmpty(element.mjMessage.messageId)) continue + // 这边开始采集 + let res = await this.mjApiService.GetMJAPITaskById( + element.mjMessage.messageId as string, + '' + ) + if (isEmpty(res.imagePath)) { + throw new Error('获取图片地址链接为空') + } + + // 开始下载 + let dr = await this.DownloadImageUrlAndSplit(element.id as string, res.imagePath as string) + if (dr.code == 0) { + throw new Error(dr.message) + } + result.push({ + id: element.id, + data: dr.data + }) + } + + if (result.length <= 0) { + throw new Error('没有找到需要采集的数据') + } + return successMessage(result, '获取图片链接并且下载成功', 'BookImage_GetImageUrlAndDownload') + } catch (error: any) { + return errorMessage( + '获取图片链接并且下载失败,错误信息如下:' + error.message, + 'BookImage_GetImageUrlAndDownload' + ) + } + } +} diff --git a/src/main/service/book/subBookHandle/bookPromptHandle.ts b/src/main/service/book/subBookHandle/bookPromptHandle.ts new file mode 100644 index 0000000..0bf2a52 --- /dev/null +++ b/src/main/service/book/subBookHandle/bookPromptHandle.ts @@ -0,0 +1,379 @@ +import { OperateBookType, PromptMergeType } from '@/define/enum/bookEnum' +import { Book } from '@/define/model/book/book' +import { errorMessage, SendReturnMessage, successMessage } from '@/public/generalTools' + +import { isEmpty } from 'lodash' +import { AiReasonCommon } from '../../aiReason/aiReasonCommon' +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { GeneralResponse } from '@/define/model/generalResponse' +import { ExecuteConcurrently } from '@/define/Tools/common' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../../option/optionSerialization' +import { SettingModal } from '@/define/model/setting' +import { MJServiceHandle } from '@/main/service/mj/mjServiceHandle' +import { BookBasicHandle } from './bookBasicHandle' +import { PresetCategory } from '@/define/data/presetData' +import { aiPrompts } from '@/define/data/aiData/aiPrompt' +import { ValidateJsonAndParse } from '@/define/Tools/validate' +import { BookTask } from '@/define/model/book/bookTask' +import { SDServiceHandle } from '../../sd/sdServiceHandle' +import { aiHandle } from '../../ai' + +export class BookPromptHandle extends BookBasicHandle { + aiReasonCommon: AiReasonCommon + mjServiceHandle: MJServiceHandle + sdServiceHandle: SDServiceHandle + + constructor() { + super() + this.aiReasonCommon = new AiReasonCommon() + this.mjServiceHandle = new MJServiceHandle() + this.sdServiceHandle = new SDServiceHandle() + } + + /** + * 为小说分镜生成AI提示词 + * + * 该方法根据操作类型获取相应的分镜数据,然后使用AI推理为每个分镜生成提示词。 + * 支持三种操作模式: + * 1. 对整个小说任务的所有分镜进行处理(BOOKTASK) + * 2. 对单个指定分镜进行处理(BOOKTASKDETAIL) + * 3. 对指定分镜及其后续所有分镜进行处理(UNDERBOOKTASK) + * + * 生成过程中会实时向前端发送进度通知,并根据系统设置控制并发处理数量。 + * + * @param {string} id - 根据operateBookType不同,可能是小说任务ID或分镜ID + * @param {OperateBookType} operateBookType - 操作类型,决定处理范围 + * @param {boolean} coverData - 是否覆盖已有提示词数据: + * true-处理所有分镜, false-只处理空白提示词的分镜 + * @returns {Promise} 操作结果,成功或失败的标准化响应 + * + * @throws 如果找不到指定分镜数据或操作类型未知,将抛出异常 + * + * @example + * // 为整个小说任务生成提示词,不覆盖已有数据 + * const result = await bookPromptHandle.OriginalGetAiPrompt( + * "task-123", + * OperateBookType.BOOKTASK, + * false + * ); + */ + OriginalGetAiPrompt = async ( + id: string, + operateBookType: OperateBookType, + coverData: boolean + ): Promise => { + try { + let bookTask: Book.SelectBookTask = {} as Book.SelectBookTask + let bookTaskDetails: Book.SelectBookTaskDetail[] = [] + let allBookTaskDetails: Book.SelectBookTaskDetail[] = [] + + await this.InitBookBasicHandle() + if (operateBookType == OperateBookType.BOOKTASK) { + bookTask = await this.bookTaskService.GetBookTaskDataById(id) + allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: id + }) + if (!coverData) { + // 不覆盖数据,只推理空白提示词 + bookTaskDetails = allBookTaskDetails.filter((item) => isEmpty(item.gptPrompt)) + } else { + // 不覆盖,就是全部 + bookTaskDetails = allBookTaskDetails + } + } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + let singleBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id) + if (singleBookTaskDetail == null) { + throw new Error('没有找到要推理的分镜数据,请检查ID是否正确') + } + bookTask = await this.bookTaskService.GetBookTaskDataById( + singleBookTaskDetail.bookTaskId as string + ) + bookTaskDetails = [singleBookTaskDetail] + } else if (operateBookType == OperateBookType.UNDERBOOKTASK) { + let singleBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id) + if (singleBookTaskDetail == null) { + throw new Error('没有找到要推理的分镜数据,请检查ID是否正确') + } + bookTask = await this.bookTaskService.GetBookTaskDataById( + singleBookTaskDetail.bookTaskId as string + ) + // 获取全部的分镜数据 + allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: bookTask.id + }) + + // 只要当前行往下的数据 + for (let i = 0; i < allBookTaskDetails.length; i++) { + const element = allBookTaskDetails[i] + if (bookTaskDetails == undefined) { + bookTaskDetails = [] + } + if (i + 1 >= (singleBookTaskDetail.no as number)) { + bookTaskDetails.push(element) + } + } + } else { + throw new Error('未知的操作类型') + } + if (!allBookTaskDetails || allBookTaskDetails.length <= 0) { + allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: bookTask.id + }) + } + + if (bookTaskDetails.length <= 0) { + throw new Error('没有找到要推理的分镜数据') + } + let generalSettingOption = this.optionRealmService.GetOptionByKey( + OptionKeyName.Software.GeneralSetting + ) + let generalSetting = optionSerialization( + generalSettingOption, + '‘设置 -> 通用设置’' + ) + let tasks = [] as Array<() => Promise> + + // 获取当前task的角色和场景数据 + let autoAnalyzeCharacter = bookTask.autoAnalyzeCharacter ?? '{}' + let autoAnalyzeCharacterData = + ValidateJsonAndParse(autoAnalyzeCharacter) + let characterData = autoAnalyzeCharacterData[PresetCategory.Character] ?? [] + let sceneData = autoAnalyzeCharacterData[PresetCategory.Scene] ?? [] + let characterString = '' + let sceneString = '' + let characterAndScene = '' + if (characterData.length > 0) { + characterString = characterData.map((item) => item.name + ':' + item.prompt).join('\n') + characterAndScene = '角色设定:' + '\n' + characterString + } + if (sceneData.length > 0) { + sceneString = sceneData.map((item) => item.name + ':' + item.prompt).join('\n') + characterAndScene = characterAndScene + '\n' + '场景设定:' + '\n' + sceneString + } + + // 添加异步任务 + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i] + tasks.push(async () => { + let content = await this.aiReasonCommon.OriginalInferencePrompt( + element, + allBookTaskDetails, + 15, // 上下文关联行数 + characterAndScene + ) + // 修改推理出来的数据 + await this.bookTaskDetailService.ModifyBookTaskDetailById(element.id as string, { + gptPrompt: content + }) + // 每次完成,都要向前端返回信息 + SendReturnMessage( + { + code: 1, + id: element.id as string, + data: { + content: content, + progress: { + current: i, + total: bookTaskDetails.length + } as GeneralResponse.ProgressResponse + } + }, + DEFINE_STRING.BOOK.ORIGINAL_GET_AI_PROMPT_RETURN + ) + }) + } + // 分批次执行异步任务 + await ExecuteConcurrently(tasks, global.am.isPro ? (generalSetting.concurrency ?? 1) : 1) + // 执行完毕 + return successMessage(null, '推理所有数据完成', 'BookPrompt_OriginalGetPrompt') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `获取小说子任务详细数据失败,失败原因如下:${error.message}`, + 'BookPromptHandle_OriginalGetAiPrompt' + ) + } + } + + AIStoryboardMerge = async (bookTaskId: string, type: BookTask.StoryboardMergeType) => { + try { + let res = await aiHandle.AIStoryboardMerge(bookTaskId, type) + return successMessage(res, 'AI分镜头合并成功', 'BookPromptHandle_AIStoryboardMerge') + } catch (error: any) { + return errorMessage( + `AI分镜头合并失败,失败原因如下:${error.message}`, + 'BookPromptHandle_AIStoryboardMerge' + ) + } + } + + /** + * 合并提示词 + * + * 该方法根据提供的合并类型(MJ或SD),将分镜中的提示词按照规则进行合并处理。 + * 支持两种合并类型: + * - MJ_MERGE: 使用MidJourney规则合并提示词 + * - SD_MERGE: 使用Stable Diffusion规则合并提示词 + * + * 合并操作支持不同的处理范围,由operateBookType参数决定: + * - BOOKTASK: 处理整个小说任务中的所有分镜 + * - BOOKTASKDETAIL: 仅处理单个指定分镜 + * - UNDERBOOKTASK: 处理指定分镜及其后续所有分镜 + * + * @param {string} id - 根据operateBookType不同,可能是小说任务ID或分镜ID + * @param {PromptMergeType} type - 合并类型,决定使用哪种规则合并提示词 + * @param {OperateBookType} operateBookType - 操作类型,决定处理范围 + * @returns {Promise} 操作结果,成功或失败的标准化响应 + * + * @throws {Error} 如果合并类型未知,将抛出异常 + * + * @example + * // 使用MidJourney规则合并单个分镜的提示词 + * const result = await bookPromptHandle.MergePrompt( + * "detail-123", + * PromptMergeType.MJ_MERGE, + * OperateBookType.BOOKTASKDETAIL + * ); + */ + MergePrompt = async ( + id: string, + type: PromptMergeType, + operateBookType: OperateBookType + ): Promise => { + try { + if (type == PromptMergeType.MJ_MERGE) { + return await this.mjServiceHandle.MergeMJPrompt(id, operateBookType) + } else if (type == PromptMergeType.SD_MERGE) { + return await this.sdServiceHandle.MergeSDPrompt(id, operateBookType) + } else { + throw new Error('未知的合并模式,请检查') + } + } catch (error: any) { + return errorMessage( + '合并提示词失败,错误信息如下:' + error.message, + 'ReverseBook_MergePrompt' + ) + } + } + + /** + * 自动分析书籍任务中的角色或场景 + * + * 该方法使用AI技术分析书籍任务中的所有分镜文本,自动识别并提取角色或场景信息。 + * 处理过程包括合并所有分镜文本、调用AI分析、解析返回结果并格式化为结构化数据。 + * 分析结果会更新到书籍任务的autoAnalyzeCharacter字段中,以JSON格式存储。 + * + * @param {string} bookTaskId - 要分析的书籍任务ID + * @param {PresetCategory} type - 分析类型,必须是PresetCategory.Character(角色)或PresetCategory.Scene(场景) + * + * @returns {Promise} + * 成功时返回分析得到的角色或场景数据对象数组,失败时返回错误信息 + * + * @throws {Error} 当分析类型无效、找不到书籍任务数据或分镜数据为空时抛出错误 + * + * @example + * // 分析书籍任务中的角色 + * const characterResult = await bookPromptHandle.AutoAnalyzeCharacterOrScene( + * "task-123", + * PresetCategory.Character + * ); + * + * // 分析书籍任务中的场景 + * const sceneResult = await bookPromptHandle.AutoAnalyzeCharacterOrScene( + * "task-123", + * PresetCategory.Scene + * ); + */ + AutoAnalyzeCharacterOrScene = async (bookTaskId: string, type: PresetCategory) => { + try { + if (type != PresetCategory.Character && type != PresetCategory.Scene) { + throw new Error('分析的类型只能是角色或场景,请检查') + } + await this.InitBookBasicHandle() + + let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId) + let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: bookTaskId + }) + if (bookTaskDetails.length <= 0) { + throw new Error('没有找到要分析的分镜数据,请先导入文案或者时srt!') + } + + let words = bookTaskDetails + .map((item) => { + return item.afterGpt + }) + .join('\r\n') + + let systemContent = '' + let userContent = '' + if (type == PresetCategory.Character) { + systemContent = aiPrompts.NanFengCharacterSystemContent + userContent = aiPrompts.NanFengCharacterUserContent + } else if (type == PresetCategory.Scene) { + systemContent = aiPrompts.NanFengSceneSystemContent + userContent = aiPrompts.NanFengSceneUserContent + } + + let message = [ + { + role: 'system', + content: this.aiReasonCommon.replaceObject(systemContent, { + textContent: words + }) + }, + { + role: 'user', + content: this.aiReasonCommon.replaceObject(userContent, { + textContent: words + }) + } + ] + await this.aiReasonCommon.GetAISetting() + let content = await this.aiReasonCommon.FetchGpt(message) + + let autoAnalyzeCharacter = bookTask.autoAnalyzeCharacter ?? '{}' + let autoAnalyzeCharacterData = + ValidateJsonAndParse(autoAnalyzeCharacter) + let returnData = content.split('\n').filter((item) => !isEmpty(item)) + let newData: BookTask.BookTaskCharacterAndSceneObject[] = [] + for (let i = 0; i < returnData.length; i++) { + const element = returnData[i] + let splitData = element.split('.') + if (splitData.length < 3) { + continue + } + let tempData = { + no: Number(splitData[0]), + id: crypto.randomUUID(), + name: splitData[1], + prompt: splitData[2] + } as BookTask.BookTaskCharacterAndSceneObject + newData.push(tempData) + } + + if (type == PresetCategory.Character) { + autoAnalyzeCharacterData[PresetCategory.Character] = newData + } else if (type == PresetCategory.Scene) { + // 场景数据 + autoAnalyzeCharacterData[PresetCategory.Scene] = newData + } + // 重新写入 + await this.bookTaskService.ModifyBookTaskDataById(bookTaskId, { + autoAnalyzeCharacter: JSON.stringify(autoAnalyzeCharacterData) + }) + + return successMessage( + autoAnalyzeCharacterData, + '自动分析角色或场景成功', + 'ReverseBook_AutoAnalyzeCharacterOrScene' + ) + } catch (error: any) { + return errorMessage( + '自动分析角色或场景失败,错误信息如下:' + error.message, + 'ReverseBook_AutoAnalyzeCharacterOrScene' + ) + } + } +} diff --git a/src/main/service/book/subBookHandle/bookServiceHandle.ts b/src/main/service/book/subBookHandle/bookServiceHandle.ts new file mode 100644 index 0000000..e6e061a --- /dev/null +++ b/src/main/service/book/subBookHandle/bookServiceHandle.ts @@ -0,0 +1,218 @@ +import { Book } from '@/define/model/book/book' +import { ErrorItem, SuccessItem } from '@/define/model/generalResponse' +import { errorMessage, successMessage } from '@/public/generalTools' +import { BookBasicHandle } from './bookBasicHandle' +import { CheckFileOrDirExist, DeleteFolderAllFile } from '@/define/Tools/file' +import path from 'path' +import { isEmpty } from 'lodash' + +/** + * 书籍服务处理器类 + * + * 该类作为书籍服务的处理层,封装了对底层 BookService 的调用, + * 提供了书籍信息的添加、修改等功能接口。处理器会自动初始化 + * BookService 实例,并对各种操作进行错误处理,返回标准化的 + * 成功或错误响应。 + * + * @class + */ +export class BookServiceHandle extends BookBasicHandle { + constructor() { + super() + } + + /** + * 添加或修改小说信息 + * + * 异步调用 BookService 的 AddOrModifyBook 方法,传入小说对象。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {Book.SelectBook} book - 书籍对象 + * @returns {Promise} - 返回操作结果 + * @async + */ + async AddOrModifyBook(book: Book.SelectBook): Promise { + try { + await this.InitBookBasicHandle() + let res = await this.bookService.AddOrModifyBook(book) + return successMessage(res, '添加/修改小说信息成功!', 'BookServiceHandle_AddOrModifyBook') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `添加/修改小说信息失败,失败原因如下:${error.message}`, + 'BookServiceHandle_AddOrModifyBook' + ) + } + } + + /** + * 获取小说信息,通过参数查询 + * + * 异步调用 BookService 的 GetBookDataCondition 方法,传入查询条件对象。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {Book.QueryBookCondition} queryCondition - 查询条件对象 + * @returns {Promise} - 返回操作结果 + * @async + */ + async GetBookDataCondition( + queryCondition: Book.QueryBookCondition + ): Promise { + try { + await this.InitBookBasicHandle() + let res = await this.bookService.GetBookDataCondition(queryCondition) + return successMessage(res, '获取小说数据成功!', 'BookServiceHandle_GetBookDataCondition') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `获取小说数据失败,失败原因如下:${error.message}`, + 'BookServiceHandle_GetBookDataCondition' + ) + } + } + + /** + * 获取小说数据,通过小说ID查询 + * + * 异步调用 BookService 的 GetBookDataById 方法,传入小说ID。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * * @param {string} id - 小说ID + * @returns {Promise} - 返回操作结果 + * @async + */ + async GetBookDataById(id: string): Promise { + try { + await this.InitBookBasicHandle() + let res = await this.bookService.GetBookDataById(id) + return successMessage(res, '获取小说数据成功!', 'BookServiceHandle_GetBookDataById') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `获取小说数据失败,失败原因如下:${error.message}`, + 'BookServiceHandle_GetBookDataById' + ) + } + } + + /** + * 修改小说数据,通过小说ID修改 + * + * 异步调用 BookService 的 ModifyBookDataById 方法,传入小说ID和小说数据对象。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} id - 小说ID + * @param {Book.SelectBook} bookData - 小说数据对象 + * @returns {Promise} - 返回操作结果 + * @async + */ + async ModifyBookDataById( + id: string, + bookData: Book.SelectBook + ): Promise { + try { + await this.InitBookBasicHandle() + let res = await this.bookService.ModifyBookDataById(id, bookData) + return successMessage(res, '修改小说数据成功!', 'BookServiceHandle_ModifyBookDataById') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `修改小说数据失败,失败原因如下:${error.message}`, + 'BookServiceHandle_ModifyBookDataById' + ) + } + } + + // 重置小说批次任务 + async ResetBookDataInfo(bookId: string) { + try { + await this.InitBookBasicHandle() + let book = await this.bookService.GetBookDataById(bookId) + if (book == null) { + return errorMessage( + '未找到小说数据,重置小说数据失败!', + 'BookServiceHandle_ResetBookDataInfo' + ) + } + + let bookDataPath = path.resolve(book.bookFolderPath as string, 'data') + let bookScriptPath = path.resolve(book.bookFolderPath as string, 'script') + let bookTmpPath = path.resolve(book.bookFolderPath as string, 'tmp') + + // 开始执行重置操作,获取所有的批次任务,然后删除全部 + let bookTasks = await this.bookTaskService.GetBookTaskDataByCondition({ bookId: book.id }) + if (bookTasks.bookTasks.length > 0) { + // 有批次任务才删除 + for (let i = 0; i < bookTasks.bookTasks.length; i++) { + const element = bookTasks.bookTasks[i] + this.bookTaskService.DeleteBookTaskDataById(element.id as string) + } + } + + // 删除完成之后,删除文件夹 + if (!isEmpty(bookDataPath) && (await CheckFileOrDirExist(bookDataPath))) { + await DeleteFolderAllFile(bookDataPath, false) + } + + if (!isEmpty(bookScriptPath) && (await CheckFileOrDirExist(bookScriptPath))) { + await DeleteFolderAllFile(bookScriptPath, false) + } + + if (!isEmpty(bookTmpPath) && (await CheckFileOrDirExist(bookTmpPath))) { + await DeleteFolderAllFile(bookTmpPath, false) + } + + // 删除完毕 返回 + return successMessage(null, '重置小说数据成功!', 'BookServiceHandle_ResetBookDataInfo') + } catch (error: any) { + return errorMessage( + `重置小说数据失败,失败原因如下:${error.message}`, + 'BookServiceHandle_ResetBookDataInfo' + ) + } + } + + /** + * 删除小说数据,通过小说ID删除 + * + * 异步调用 BookService 的 DeleteBookDataById 方法,传入小说ID。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} id - 小说ID + * @returns {Promise} - 返回操作结果 + * @async + */ + async DeleteBookDataInfoById(id: string): Promise { + try { + await this.InitBookBasicHandle() + + // 先调用重置方法 再调用删除 + let resetRes = await this.ResetBookDataInfo(id) + if (resetRes.code != 1) { + return errorMessage( + `删除小说失败,失败原因如下:${resetRes.message}`, + 'BookServiceHandle_DeleteBookDataInfoById' + ) + } + + let book = await this.bookService.GetBookDataById(id) + if (book == null) { + return errorMessage( + '未找到小说数据,删除小说失败!', + 'BookServiceHandle_DeleteBookDataInfoById' + ) + } + + let projectPath = book.bookFolderPath as string + + this.bookService.DeleteBookDataById(id) + + // 删除成功 删除对应的文件 + if (!isEmpty(projectPath) && (await CheckFileOrDirExist(projectPath))) { + await DeleteFolderAllFile(projectPath, true) + } + return successMessage(null, '删除小说数据成功!', 'BookServiceHandle_DeleteBookDataById') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `删除小说数据失败,失败原因如下:${error.message}`, + 'BookServiceHandle_DeleteBookDataById' + ) + } + } +} diff --git a/src/main/service/book/subBookHandle/bookTaskDetailServiceHandle.ts b/src/main/service/book/subBookHandle/bookTaskDetailServiceHandle.ts new file mode 100644 index 0000000..35141a3 --- /dev/null +++ b/src/main/service/book/subBookHandle/bookTaskDetailServiceHandle.ts @@ -0,0 +1,248 @@ +import { BookTaskDetailService } from '@/define/db/service/book/bookTaskDetailService' +import { BookTaskStatus, OperateBookType } from '@/define/enum/bookEnum' +import { Book } from '@/define/model/book/book' +import { BookTaskDetail } from '@/define/model/book/bookTaskDetail' +import { errorMessage, successMessage } from '@/public/generalTools' +import { BookTaskServiceHandle } from './bookTaskServiceHandle' +import { ErrorItem, SuccessItem } from '@/define/model/generalResponse' + +export class BookTaskDetailServiceHandle { + private bookTaskDetailService!: BookTaskDetailService + private bookTaskServiceHandle: BookTaskServiceHandle + + constructor() { + this.bookTaskServiceHandle = new BookTaskServiceHandle() + } + private async InitBookTaskDetailServiceHandle() { + // 如果 bookTaskDetailService 已经初始化,则直接返回 + if (this.bookTaskDetailService) return + this.bookTaskDetailService = await BookTaskDetailService.getInstance() + } + + /** + * 获取小说子任务详细数据 + * + * 异步调用 BookTaskDetailService 的 GetBookTaskDetailDataByCondition 方法,传入查询条件对象。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {Book.QueryBookTaskDetailCondition} bookTaskDetailCondition - 查询条件对象 + * @return {Promise} - 返回操作结果 + * @async + */ + async GetBookTaskDetailDataByCondition( + bookTaskDetailCondition: Book.QueryBookTaskDetailCondition + ): Promise { + try { + await this.InitBookTaskDetailServiceHandle() + let res = + await this.bookTaskDetailService.GetBookTaskDetailDataByCondition(bookTaskDetailCondition) + return successMessage( + res, + '获取小说子任务详细数据成功!', + 'BookTaskDetailServiceHandle_GetBookTaskDetailData' + ) + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `获取小说子任务详细数据失败,失败原因如下:${error.message}`, + 'BookTaskDetailServiceHandle_GetBookTaskDetailData' + ) + } + } + + /** + * 获取小说子任务详细数据通过ID + * + * 异步调用 BookTaskDetailService 的 GetBookTaskDetailDataById 方法,传入小说子任务详细 ID。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} id - 小说子任务详细 ID + * @return {Promise} - 返回操作结果 + * @async + */ + async GetBookTaskDetailDataById(id: string): Promise { + try { + await this.InitBookTaskDetailServiceHandle() + let res = await this.bookTaskDetailService.GetBookTaskDetailDataById(id) + return successMessage( + res, + '获取小说子任务详细数据成功!', + 'BookTaskDetailServiceHandle_GetBookTaskDetailDataById' + ) + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `获取小说子任务详细数据失败,失败原因如下:${error.message}`, + 'BookTaskDetailServiceHandle_GetBookTaskDetailDataById' + ) + } + } + + /** + * 修改小说子任务详细数据 + * + * 异步调用 BookTaskDetailService 的 ModifyBookTaskDetailById 方法,传入小说子任务详细 ID 和更新数据。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} bookTaskDetailId - 小说子任务详细 ID + * @param {Book.SelectBookTaskDetail} updateData - 更新数据 + * @return {Promise} - 返回操作结果 + * @async + */ + async ModifyBookTaskDetailById( + bookTaskDetailId: string, + updateData: Book.SelectBookTaskDetail + ): Promise { + try { + await this.InitBookTaskDetailServiceHandle() + let res = await this.bookTaskDetailService.ModifyBookTaskDetailById( + bookTaskDetailId, + updateData + ) + return successMessage( + res, + '修改小说子任务详细数据成功!', + 'BookTaskDetailServiceHandle_ModifyBookTaskDetailById' + ) + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `修改小说子任务详细数据失败,失败原因如下:${error.message}`, + 'BookTaskDetailServiceHandle_ModifyBookTaskDetailById' + ) + } + } + + /** + * 保存小说批次数据分镜信息 + * + * 异步调用 BookTaskDetailService 的 SaveCopywritingInfo 方法,传入小说任务 ID、分镜数据和操作类型。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} bookTaskId - 小说任务 ID + * @param {BookTaskDetail.SaveCopywritingData[]} copywritingData - 分镜数据 + * @param {Book.OperateBookType} operateBookType - 操作类型 + * @return {Promise} - 返回操作结果 + * @async + */ + async SaveCopywritingInfo( + bookTaskId: string, + copywritingData: BookTaskDetail.SaveCopywritingData[], + operateBookType: OperateBookType + ): Promise { + try { + if (operateBookType != OperateBookType.BOOKTASK) { + throw new Error('目前只支持对小说任务的文案保存') + } + + await this.bookTaskServiceHandle.InitBookBasicHandle() + let bookTask = + await this.bookTaskServiceHandle.bookTaskService.GetBookTaskDataById(bookTaskId) + + await this.InitBookTaskDetailServiceHandle() + let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: bookTaskId + }) + + // 判断有没有小说批次任务 没有的话全部新增,有的话判断是不是当前的分镜是不是比旧的多,比旧的多就新增,否则就修改,然后将多余的旧的删除 + if (bookTaskDetails.length > copywritingData.length) { + // 旧的分镜比新的多,删除旧的分镜 + // 先修改分镜数据 + // 开始修改。修改使用事务吧 + this.bookTaskDetailService.transaction(() => { + for (let i = 0; i < copywritingData.length; i++) { + const element = copywritingData[i] + let btd = this.bookTaskDetailService.realm.objectForPrimaryKey( + 'BookTaskDetail', + bookTaskDetails[i].id + ) + if (btd == null) { + throw new Error('未找到对应的分镜数据,请检查') + } + // 开始修改 + btd.startTime = element.startTime + btd.endTime = element.endTime + btd.word = element.word + btd.afterGpt = element.afterGpt + btd.subValue = JSON.stringify(element.subValue) + btd.timeLimit = `${element.startTime} - ${element.endTime}` + } + + // 删除多余的分镜数据 + let ids: string[] = [] + for (let i = copywritingData.length; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i] + ids.push(element.id as string) + } + // 删除数据 + for (let i = 0; i < ids.length; i++) { + const element = ids[i] + let btd = this.bookTaskDetailService.realm.objectForPrimaryKey( + 'BookTaskDetail', + element + ) + if (btd == null) { + continue + } + // 删除数据 + this.bookTaskDetailService.realm.delete(btd) + } + }) + } else { + // 新的分镜比旧的多,新增新的分镜 + // 先修改分镜数据 + this.bookTaskDetailService.transaction(() => { + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = copywritingData[i] + let btd = this.bookTaskDetailService.realm.objectForPrimaryKey( + 'BookTaskDetail', + bookTaskDetails[i].id + ) + if (btd == null) { + throw new Error('未找到对应的分镜数据,请检查') + } + // 开始修改 + btd.startTime = element.startTime + btd.endTime = element.endTime + btd.word = element.word + btd.afterGpt = element.afterGpt + btd.subValue = JSON.stringify(element.subValue) + btd.timeLimit = `${element.startTime} - ${element.endTime}` + } + }) + // 添加新的分镜数据 + for (let i = bookTaskDetails.length; i < copywritingData.length; i++) { + const element = copywritingData[i] + // 开始新增 + this.bookTaskDetailService.AddBookTaskDetail({ + bookTaskId: bookTaskId, + bookId: bookTask.bookId, + startTime: element.startTime, + endTime: element.endTime, + status: BookTaskStatus.WAIT, + word: element.word, + afterGpt: element.afterGpt, + subValue: JSON.stringify(element.subValue), + timeLimit: `${element.startTime} - ${element.endTime}`, + // 新增修脸跟随 + adetailer: false + }) + } + } + // 将新的数据返回 + let newData = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: bookTaskId + }) + if (newData.length == 0) { + throw new Error('没有找到对应的分镜数据,请检查') + } + return successMessage( + newData, + '保存小说批次数据分镜信息成功!', + 'BookTaskDetailServiceHandle_SaveCopywritingInfo' + ) + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `保存小说批次数据分镜信息失败,失败原因如下:${error.message}`, + 'BookTaskDetailServiceHandle_SaveCopywritingInfo' + ) + } + } +} diff --git a/src/main/service/book/subBookHandle/bookTaskServiceHandle.ts b/src/main/service/book/subBookHandle/bookTaskServiceHandle.ts new file mode 100644 index 0000000..02fb889 --- /dev/null +++ b/src/main/service/book/subBookHandle/bookTaskServiceHandle.ts @@ -0,0 +1,820 @@ +import { AddBookTaskCopyData, BookTaskStatus, BookType } from '@/define/enum/bookEnum' +import { Book } from '@/define/model/book/book' +import { BookTask } from '@/define/model/book/bookTask' +import { ErrorItem, SuccessItem } from '@/define/model/generalResponse' +import { + CheckFileOrDirExist, + CheckFolderExistsOrCreate, + DeleteFolderAllFile +} from '@/define/Tools/file' +import { errorMessage, successMessage } from '@/public/generalTools' +import { cloneDeep, isEmpty } from 'lodash' +import { BookBasicHandle } from './bookBasicHandle' +import path from 'path' +import { getProjectPath } from '../../option/optionCommonService' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../../option/optionSerialization' +import { SettingModal } from '@/define/model/setting' +import { ImageCategory } from '@/define/data/imageData' +import fs from 'fs' +import { ValidateJson } from '@/define/Tools/validate' +import { TimeStringToMilliseconds } from '@/define/Tools/time' +import { SrtHandle } from '../../common/srtHandle' +import { BookTaskDetail } from '@/define/model/book/bookTaskDetail' + +export class BookTaskServiceHandle extends BookBasicHandle { + constructor() { + super() + } + + /** + * 获取小说子任务数据 + * + * 异步调用 BookTaskService 的 GetBookTaskDataByCondition 方法,传入查询条件对象。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {Book.QueryBookTaskCondition} bookTaskCondition - 查询条件对象 + * @return {Promise} - 返回操作结果 + * @async + */ + async GetBookTaskDataByCondition( + bookTaskCondition: Book.QueryBookTaskCondition + ): Promise { + try { + await this.InitBookBasicHandle() + let res = await this.bookTaskService.GetBookTaskDataByCondition(bookTaskCondition) + return successMessage( + res, + '获取小说子任务数据成功!', + 'BookTaskServiceHandle_GetBookTaskData' + ) + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `获取小说子任务数据失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_GetBookTaskData' + ) + } + } + + /** + * 获取小说子任务数据,通过小说ID查询 + * + * 异步调用 BookTaskService 的 GetBookTaskDataById 方法,传入小说ID。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} id - 小说ID + * @return {Promise} - 返回操作结果 + * @async + */ + async GetBookTaskDataById(id: string): Promise { + try { + await this.InitBookBasicHandle() + let res = await this.bookTaskService.GetBookTaskDataById(id) + return successMessage( + res, + '获取小说子任务数据成功!', + 'BookTaskServiceHandle_GetBookTaskDataById' + ) + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `获取小说子任务数据失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_GetBookTaskDataById' + ) + } + } + + /** + * 更新小说批次任务状态 + * + * 异步调用 BookTaskService 的 UpdateBookTaskStatus 方法,传入小说ID和状态。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} bookTaskId - 小说ID + * @param {BookTaskStatus} status - 状态 + * @param {string?} errorMsg - 错误信息 + * @return {Promise} - 返回操作结果 + * @async + */ + async ModifyBookTaskStatus( + bookTaskId: string, + status: BookTaskStatus, + errorMsg?: string + ): Promise { + try { + await this.InitBookBasicHandle() + this.bookTaskService.ModifyBookTaskStatus(bookTaskId, status, errorMsg) + } catch (error) { + throw error + } + } + + /** + * 修改小说批次任务数据,通过小说ID修改 + * + * 异步调用 BookTaskService 的 ModifyBookTaskDataById 方法,传入小说ID和数据。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} bookTaskId - 小说ID + * @param {Book.SelectBookTask} data - 数据 + * @return {Promise} - 返回操作结果 + * @async + */ + async ModifyBookTaskDataById( + bookTaskId: string, + data: Book.SelectBookTask + ): Promise { + try { + await this.InitBookBasicHandle() + let res = await this.bookTaskService.ModifyBookTaskDataById(bookTaskId, data) + return successMessage( + res, + '修改小说子任务数据成功!', + 'BookTaskServiceHandle_ModifyBookTaskData' + ) + } catch (error: any) { + return errorMessage( + `修改小说子任务数据失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_ModifyBookTaskData' + ) + } + } + + /** + * 批量删除小说子任务 + * + * 根据提供的ID数组删除多个小说子任务,同时删除关联的图片文件夹。 + * 删除完成后返回当前小说剩余的所有子任务数据。 + * + * @param {string[]} ids - 要删除的小说子任务ID数组 + * @return {Promise} - 成功时返回剩余子任务列表,失败时返回错误信息 + * @async + */ + async DeleteBookTaskByIds(ids: string[]): Promise { + try { + let bookTaskIds = ids.map((id) => id.trim()) + let bookTasks: Book.SelectBookTask[] = [] + await this.InitBookBasicHandle() + for (let i = 0; i < bookTaskIds.length; i++) { + const element = bookTaskIds[i] + let tempBookTask = await this.bookTaskService.GetBookTaskDataById(element) + bookTasks.push(tempBookTask) + } + if (bookTasks.length === 0) { + return errorMessage( + '没有找到要删除的小说子任务数据', + 'BookTaskServiceHandle_DeleteBookTask' + ) + } + + // 开始执行删除 + for (let i = 0; i < bookTasks.length; i++) { + const element = bookTasks[i] + let imageFolder = element.imageFolder + // 先删除每个批次对应的数据,然后删除批次 + this.bookTaskService.DeleteBookTaskDataById(element.id as string) + // 删除成功,直接把对应的出图文件夹删掉 + if (await CheckFileOrDirExist(imageFolder)) { + await DeleteFolderAllFile(imageFolder as string, true) + } + } + // 删除完成 然后将当前小说得所有得子任务返回 + let res = await this.bookTaskService.GetBookTaskDataByCondition({ + bookId: bookTasks[0].bookId + }) + let newBookTasks = res.bookTasks + return successMessage( + { + bookId: bookTasks[0].bookId, + bookTasks: newBookTasks + }, + '删除小说子任务数据成功!', + 'BookTaskServiceHandle_DeleteBookTask' + ) + } catch (error: any) { + return errorMessage( + `删除小说子任务数据失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_DeleteBookTask' + ) + } + } + + /** + * 复制一份小说任务的基础数据 + * @param bookTask 小说任务 + * @param addNewBookTask 新增的小说数据 + * @param no 当前的编号 + * @returns + */ + async CopyBookTaskBaseData( + bookTask: Book.SelectBookTask, + addNewBookTask: Book.AddBookTask, + no: number, + projectPath: string + ): Promise { + let book = await this.bookService.GetBookDataById(bookTask.bookId as string) + if (book == null) { + throw new Error('没有找到小说,请检查') + } + let name = book.name + '_' + no.toString().padStart(5, '0') + + let imageFolder = path.join(projectPath, `${bookTask.bookId}/tmp/${name}`) + + let generalSettingOptions = this.optionRealmService.GetOptionByKey( + OptionKeyName.Software.GeneralSetting + ) + let generalSetting = optionSerialization(generalSettingOptions) + + let imageCategory = generalSetting.defaultImgGenMethod ?? ImageCategory.Midjourney + if (!isEmpty(bookTask.imageCategory)) { + imageCategory = bookTask.imageCategory + } else { + if (book.type == BookType.MJ_REVERSE) { + imageCategory = ImageCategory.Midjourney + } else if (book.type == BookType.SD_REVERSE) { + imageCategory = ImageCategory.Stable_Diffusion + } else if (book.type == BookType.ORIGINAL) { + imageCategory = generalSetting.defaultImgGenMethod ?? ImageCategory.Midjourney + } + } + + let newBookTask = { + id: crypto.randomUUID(), + bookId: bookTask.bookId, + no: no, + name: name, + generateVideoPath: undefined, + srtPath: bookTask.srtPath, + audioPath: bookTask.audioPath, + imageFolder: imageFolder ? path.relative(projectPath, imageFolder) : undefined, + status: BookTaskStatus.WAIT, + errorMsg: undefined, + updateTime: new Date(), + createTime: new Date(), + isAuto: false, + autoAnalyzeCharacter: undefined, + imageStyle: [], + customizeImageStyle: [], + videoConfig: (bookTask.videoConfig ??= undefined), + prefixPrompt: (addNewBookTask.prefixPrompt ??= undefined), + suffixPrompt: addNewBookTask.suffixPrompt ?? undefined, + imageCategory: imageCategory, + subImageFolder: [], + draftSrtStyle: undefined, + backgroundMusic: (bookTask.backgroundMusic ??= undefined), + friendlyReminder: (bookTask.friendlyReminder ??= undefined) + } as Book.SelectBookTask + if ( + addNewBookTask.selectTaskDataCategory && + addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.IMAGE_STYLE) + ) { + newBookTask.imageStyle = bookTask.imageStyle ??= [] + newBookTask.customizeImageStyle = bookTask.customizeImageStyle ??= [] + } + return newBookTask + } + + /** + * 复制小说批次任务的基础数据 + * @param oldBookTaskDetail 需要需要复制的小说批次任务 + */ + async CopyBookTaskDetailBaseData( + bookTask: Book.SelectBookTask, + newBookTask: Book.SelectBookTask, + oldBookTaskDetail: Book.SelectBookTaskDetail[], + addNewBookTask: Book.AddBookTask, + projectPath: string + ): Promise { + let bookTaskDetail = [] as Book.SelectBookTaskDetail[] + let book = await this.bookService.GetBookDataById(bookTask.bookId as string) + if (book == null) { + throw new Error('没有找到小说,请检查') + } + let originalTimePath = path.join(book.bookFolderPath as string, `data/${book.id}.mp4.json`) + let originalTime = [] as any[] + if (await CheckFileOrDirExist(originalTimePath)) { + let originalTimeString = await fs.promises.readFile(originalTimePath, 'utf-8') + if (ValidateJson(originalTimeString)) { + originalTime = JSON.parse(originalTimeString) + } + } + // 判断分镜数据和批次数据是不是相同的 + if (originalTime.length != oldBookTaskDetail.length) { + originalTime = [] + } + + for (let i = 0; i < oldBookTaskDetail.length; i++) { + const element = oldBookTaskDetail[i] + let addOneBookTaskDetail = { + id: crypto.randomUUID(), + no: element.no, + name: element.name, + bookId: element.bookId, + bookTaskId: newBookTask.id, + videoPath: element.videoPath ? path.relative(projectPath, element.videoPath) : undefined, + oldImage: element.oldImage ? path.relative(projectPath, element.oldImage) : undefined, + adetailer: element.adetailer == undefined || element.adetailer == null ? false : true, + sdConifg: element.sdConifg, + createTime: new Date(), + updateTime: new Date(), + audioPath: element.audioPath, + subtitlePosition: element.subtitlePosition, + status: BookTaskStatus.WAIT, + imageLock: false + } as Book.SelectBookTaskDetail + + // 开始添加数据 + if ( + bookTask && + addNewBookTask.selectTaskDataCategory && + addNewBookTask.selectTaskDataCategory.length > 0 + ) { + // 有选择的数据,这边要调用各种方法 + if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.AFTER_GPT)) { + // 是不是要复制文案 + addOneBookTaskDetail.word = element.word + addOneBookTaskDetail.afterGpt = element.afterGpt + addOneBookTaskDetail.subValue = element.subValue + ? JSON.stringify(element.subValue) + : undefined + addOneBookTaskDetail.oldImage = element.oldImage + addOneBookTaskDetail.startTime = element.startTime + addOneBookTaskDetail.endTime = element.endTime + addOneBookTaskDetail.timeLimit = element.timeLimit + } else { + // 初始化时间信息 + if (originalTime[i]) { + addOneBookTaskDetail.startTime = TimeStringToMilliseconds(originalTime[i][0]) + addOneBookTaskDetail.endTime = TimeStringToMilliseconds(originalTime[i][1]) + addOneBookTaskDetail.timeLimit = undefined + } + } + if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.GPT_PROMPT)) { + // 是不是要复制GPT提示词 + addOneBookTaskDetail.gptPrompt = element.gptPrompt + // 复制反推提示词 + // 处理反推数据 + let reverseMessage = [] as Book.ReversePrompt[] + if (element.reversePrompt && element.reversePrompt.length > 0) { + reverseMessage = cloneDeep(element.reversePrompt) + for (let k = 0; k < reverseMessage.length; k++) { + reverseMessage[k].id = crypto.randomUUID() + reverseMessage[k].bookTaskDetailId = element.id + } + } + addOneBookTaskDetail.reversePrompt = reverseMessage + } + if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.CHARACTER)) { + // 是不是要复制角色 + addOneBookTaskDetail.characterTags = element.characterTags + } + if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.IMAGE_STYLE)) { + // 是不是要复制生图风格 + addOneBookTaskDetail.styleTags = element.styleTags + } + if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.PROMPT)) { + // 是不是要复制提示 + addOneBookTaskDetail.prompt = element.prompt + } + } + bookTaskDetail.push(addOneBookTaskDetail) + } + return bookTaskDetail + } + /** + * 添加一个小说批次任务 + * @param bookTask 小说任务 + * @param bookTaskDetails 小说任务分镜信息 + * @param addNewBookTask 添加数据的基础数据信息 + * @returns + */ + async AddOneBookTask( + bookTask: Book.SelectBookTask, + bookTaskDetails: Book.SelectBookTaskDetail[], + addNewBookTask: Book.AddBookTask, + no: number, + projectPath: string + ): Promise<{ + newBookTask: Book.SelectBookTask + newBookTaskDetails: Book.SelectBookTaskDetail[] + }> { + let newBookTask = await this.CopyBookTaskBaseData(bookTask, addNewBookTask, no, projectPath) + let newBookTaskDetails = await this.CopyBookTaskDetailBaseData( + bookTask, + newBookTask, + bookTaskDetails, + addNewBookTask, + projectPath + ) + return { + newBookTask: newBookTask, + newBookTaskDetails: newBookTaskDetails + } + } + + /** + * 添加小说任务 + * @param addData 添加数据的参数 + * @returns + */ + async AddNewBookTask(addData: BookTask.AddBookTaskParam): Promise { + try { + if (!global.am.isPro && addData.count > 1) { + return errorMessage( + '批量添加小说子任务数据失败,免费版只能添加一条数据', + 'BookTaskServiceHandle_AddNewBookTask' + ) + } + if (isEmpty(addData.selectBookId)) { + return errorMessage( + '批量添加小说子任务数据失败,小说ID不能为空', + 'BookTaskServiceHandle_AddNewBookTask' + ) + } + + let bookTasks: Book.SelectBookTask[] = [] + let bookTaskDetail: Book.SelectBookTaskDetail[] = [] + let projectPath = await getProjectPath() + let maxNo = await this.bookTaskService.GetMaxBookTaskNo(addData.selectBookId) + + for (let i = 0; i < addData.count; i++) { + if (addData.copyBookTask && !isEmpty(addData.selectBookTask)) { + let bookTask = await this.bookTaskService.GetBookTaskDataById( + addData.selectBookTask as string + ) + let oldBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition( + { + bookTaskId: addData.selectBookTask + } + ) + let { newBookTask, newBookTaskDetails } = await this.AddOneBookTask( + bookTask, + oldBookTaskDetail, + addData, + maxNo + i, + projectPath + ) + bookTasks.push(newBookTask) + bookTaskDetail.push(...newBookTaskDetails) + } else { + let newBookTask = await this.CopyBookTaskBaseData( + { bookId: addData.selectBookId }, + addData, + maxNo + i, + projectPath + ) + bookTasks.push(newBookTask) + } + } + + // 先要创建文件夹 + for (let i = 0; i < bookTasks.length; i++) { + let imageFolder = path.join(projectPath, bookTasks[i].imageFolder as string) + await CheckFolderExistsOrCreate(imageFolder) + } + + // 这边开始添加数据 + await this.transaction((realm) => { + try { + for (let i = 0; i < bookTasks.length; i++) { + const element = bookTasks[i] + element.openVideoGenerate = false + realm.create('BookTask', element) + } + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i] + element.adetailer = false + realm.create('BookTaskDetail', element) + } + } catch (error) { + throw error + } + }) + return successMessage( + { + bookId: addData.selectBookId, + bookTasks: bookTasks + }, + '添加小说子任务数据成功!', + 'BookTaskServiceHandle_AddNewBookTask' + ) + } catch (error: any) { + return errorMessage( + `添加小说子任务数据失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_AddNewBookTask' + ) + } + } + + /** + * 获取小说任务图片生成进度 + * + * 根据小说ID获取该小说下所有批次任务的图片和视频生成进度信息。 + * 包括每个批次的图片生成数量、视频生成数量、总数量以及对应的完成率。 + * + * @param {string} bookId - 小说ID + * @return {Promise} - 返回包含进度信息的操作结果 + * @async + */ + async GetBookTaskImageGenerateProgress(bookId: string): Promise { + try { + let book = await this.bookService.GetBookDataById(bookId) + if (book == null) { + return errorMessage( + '获取小说任务生成进度失败,小说不存在', + 'BookTaskServiceHandle_GetBookTaskImageGenerateProgress' + ) + } + + // 获取小说批次数据 + let bookTasks = await this.bookTaskService.GetBookTaskDataByCondition({ + bookId: bookId + }) + if (bookTasks.bookTasks.length === 0) { + return successMessage( + {}, + '获取小说任务生成进度成功!', + 'BookTaskServiceHandle_GetBookTaskImageGenerateProgress' + ) + } + + // 这边开始处理数据 + let resData: BookTask.BookTaskProgressRecord = {} + + // 获取对应的批次信息 然后去判断图片和总的进度 + for (let i = 0; i < bookTasks.bookTasks.length; i++) { + const element = bookTasks.bookTasks[i] + + let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: element.id, + bookId: bookId + }) + if (bookTaskDetails.length <= 0) { + // 没有小说批次的详细任务 + resData[element.id as string] = { + imageProgress: 0, + videoProgress: 0, + totalCount: 0, + imageRate: 0, + videoRate: 0 + } + continue + } + + // 遍历 bookTaskDetails 获取每个小说批次任务的进度数据 + let imageProgress = 0 + let videoProgress = 0 + for (let j = 0; j < bookTaskDetails.length; j++) { + const bookTaskDetail = bookTaskDetails[j] as Book.SelectBookTaskDetail + // 检查图片信息 + if ( + !isEmpty(bookTaskDetail.outImagePath) && + (await CheckFileOrDirExist(bookTaskDetail.outImagePath)) + ) { + imageProgress += 1 + } + // 检查视频信息 + if ( + !isEmpty(bookTaskDetail.generateVideoPath) && + (await CheckFileOrDirExist(bookTaskDetail.generateVideoPath)) + ) { + videoProgress += 1 + } + } + // 开始添加数据 + resData[element.id as string] = { + imageProgress: imageProgress, + videoProgress: videoProgress, + totalCount: bookTaskDetails.length, + imageRate: + bookTaskDetails.length > 0 ? (imageProgress / bookTaskDetails.length) * 100 : 0, + videoRate: bookTaskDetails.length > 0 ? (videoProgress / bookTaskDetails.length) * 100 : 0 + } + } + return successMessage( + resData, + '获取小说任务生成进度成功!', + 'BookTaskServiceHandle_GetBookTaskImageGenerateProgress' + ) + } catch (error: any) { + return errorMessage( + `获取小说任务生成进度失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_GetBookTaskImageGenerateProgress' + ) + } + } + + /** + * 获取小说批次任务的第一张图片路径 + * + * 遍历指定小说下的所有批次任务,获取每个批次中第一张有效的输出图片路径。 + * 该方法用于在界面上显示每个批次的预览图片,通常取第一张成功生成的图片作为代表。 + * + * @param {string} bookId - 小说ID,用于查询该小说下的所有批次任务 + * @returns {Promise} 返回包含批次ID和对应第一张图片路径的映射对象 + * - 成功时:data 为 Record 格式,键为批次ID,值为图片路径 + * - 失败时:返回错误信息 + * @example + * // 返回数据格式示例 + * { + * "batch-id-1": "/path/to/first/image1.jpg", + * "batch-id-2": "/path/to/first/image2.png", + * "batch-id-3": undefined // 该批次暂无有效图片 + * } + * @async + */ + async GetBookTaskFirstImagePath(bookId: string): Promise { + try { + let bookTasks = await this.bookTaskService.GetBookTaskDataByCondition({ + bookId: bookId + }) + + if (bookTasks.bookTasks.length === 0) { + return errorMessage( + '获取小说批次任务的第一张图片路径失败,小说批次任务不存在', + 'BookTaskServiceHandle_GetBookTaskFirstImagePath' + ) + } + let resData: Record = {} + // 开始处理所有的批次 + for (let i = 0; i < bookTasks.bookTasks.length; i++) { + const element = bookTasks.bookTasks[i] + + let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: element.id, + bookId: bookId + }) + + if (bookTaskDetails.length <= 0) { + resData[element.id as string] = undefined + continue + } + + for (let j = 0; j < bookTaskDetails.length; j++) { + const bookTaskDetail = bookTaskDetails[j] + if (isEmpty(bookTaskDetail.outImagePath)) { + resData[element.id as string] = undefined + continue + } + if (await CheckFileOrDirExist(bookTaskDetail.outImagePath)) { + resData[element.id as string] = bookTaskDetail.outImagePath as string + break + } + } + } + return successMessage( + resData, + '获取小说批次任务的第一张图片路径成功!', + 'BookTaskServiceHandle_GetBookTaskFirstImagePath' + ) + } catch (error: any) { + return errorMessage( + `获取小说批次任务的第一张图片路径失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_GetBookTaskFirstImagePath' + ) + } + } + + /** + * 添加小说子任务数据 + * + * 异步调用 BookTaskService 的 AddBookTask 方法,传入小说子任务数据。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {Book.SelectBookTask} bookTask - 小说子任务数据 + * @return {Promise} - 返回操作结果 + * @async + */ + async AddBookTask(bookTask: Book.SelectBookTask): Promise { + try { + await this.InitBookBasicHandle() + let res = await this.bookTaskService.AddBookTask(bookTask) + return successMessage(res, '添加小说子任务数据成功!', 'BookTaskServiceHandle_AddBookTask') + } catch (error: any) { + return errorMessage( + `添加小说子任务数据失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_AddBookTask' + ) + } + } + + /** + * 重置小说子任务数据 + * + * 异步调用 BookTaskService 的 ResetBookTaskDataById 方法,传入小说子任务ID。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} bookTaskId - 小说子任务ID + * @return {Promise} - 返回操作结果 + * @async + */ + async ResetBookTaskDataById(bookTaskId: string): Promise { + try { + await this.InitBookBasicHandle() + + let res = await this.bookTaskService.ResetBookTaskDataById(bookTaskId) + return successMessage( + res, + '重置小说子任务数据成功!', + 'BookTaskServiceHandle_ResetBookTaskData' + ) + } catch (error: any) { + return errorMessage( + `重置小说子任务数据失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_ResetBookTaskData' + ) + } + } + + async ResetAndInitializeBookTask(bookTaskId: string) { + try { + // 先调用重置 + await this.InitBookBasicHandle() + let newBookTask = await this.bookTaskService.ResetBookTaskDataById(bookTaskId, false) + + // 加载 SRT 数据 + const srtHandle = new SrtHandle() + let srtData = await srtHandle.GetSrtDataByPath(newBookTask.srtPath as string) + + // 循环添加小说详细信息 + for (let i = 0; i < srtData.length; i++) { + const element = srtData[i] + this.bookTaskDetailService.AddBookTaskDetail({ + bookTaskId: newBookTask.id, + bookId: newBookTask.bookId, + startTime: element.start, + endTime: element.end, + status: BookTaskStatus.WAIT, + word: element.text, + afterGpt: element.text, + subValue: JSON.stringify([ + { + id: crypto.randomUUID(), + end_time: element.end, + start_time: element.start, + srt_value: element.text + } + ] as BookTaskDetail.CopywritingSubValue[]), + timeLimit: `${element.start} -- ${element.end}`, + // 新增修脸跟随 + adetailer: false // 默认false,实际更具SD设置中为主 + }) + } + + // 添加成功 重新获取最新数据 + let newBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookId: newBookTask.bookId, + bookTaskId: newBookTask.id + }) + + if (newBookTaskDetails == null || newBookTaskDetails.length <= 0) { + return errorMessage( + '重置小说分镜数据(不包含bootask本身数据),并且初始化全新的分镜信息失败,小说分镜数据不存在', + 'BookTaskServiceHandle_ResetAndInitializeBookTask' + ) + } + return successMessage( + newBookTaskDetails, + '重置小说分镜数据成功!', + 'BookTaskServiceHandle_ResetAndInitializeBookTask' + ) + } catch (error: any) { + return errorMessage( + `重置小说分镜数据(不包含bootask本身数据),并且初始化全新的分镜信息失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_ResetAndInitializeBookTask' + ) + } + } + + /** + * 删除小说子任务数据 + * + * 异步调用 BookTaskService 的 DeleteBookTaskDataById 方法,传入小说子任务ID。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} bookTaskId - 小说子任务ID + * @return {Promise} - 返回操作结果 + * @async + */ + async DeleteBookTaskDataById(bookTaskId: string): Promise { + try { + await this.InitBookBasicHandle() + + let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId) + if (bookTask == null) { + return errorMessage( + '重置小说子任务数据失败,小说子任务不存在', + 'BookTaskServiceHandle_ResetBookTaskDataById' + ) + } + let imageFolder = bookTask.imageFolder + + let res = this.bookTaskService.DeleteBookTaskDataById(bookTaskId) + + // 删除成功 将对应的生图文件夹删除 + if (!isEmpty(imageFolder) && (await CheckFileOrDirExist(imageFolder))) { + await DeleteFolderAllFile(imageFolder as string, true) + } + return successMessage(res, '删除小说子任务数据成功!', 'BookTaskServiceHandle_DeleteBookTask') + } catch (error: any) { + return errorMessage( + `删除小说子任务数据失败,失败原因如下:${error.message}`, + 'BookTaskServiceHandle_DeleteBookTask' + ) + } + } +} diff --git a/src/main/service/book/subBookHandle/bookVideoHandle.ts b/src/main/service/book/subBookHandle/bookVideoHandle.ts new file mode 100644 index 0000000..409c4cc --- /dev/null +++ b/src/main/service/book/subBookHandle/bookVideoHandle.ts @@ -0,0 +1,350 @@ +import { ErrorItem, SuccessItem } from '@/define/model/generalResponse' +import { BookBasicHandle } from './bookBasicHandle' +import { Book } from '@/define/model/book/book' +import path from 'path' +import compressing from 'compressing' +import fs from 'fs' +import { + CheckFileOrDirExist, + CheckFolderExistsOrCreate, + CopyFileOrFolder, + GetFilesWithExtensions +} from '@/define/Tools/file' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../../option/optionSerialization' +import { SettingModal } from '@/define/model/setting' +import { errorMessage, successMessage } from '@/public/generalTools' +import { isEmpty } from 'lodash' +import { define } from '@/define/define' +import util from 'util' +import { exec } from 'child_process' +import { BookTaskDetail } from '@/define/model/book/bookTaskDetail' +import { ValidateJson } from '@/define/Tools/validate' +const execAsync = util.promisify(exec) +import JianyingService from '../../jianying/jianyingService' + +export class BookVideoHandle extends BookBasicHandle { + jianyingService: JianyingService + constructor() { + super() + this.jianyingService = new JianyingService() + } + + /** + * 生成剪映草稿配置文件 + * @description 根据小说任务信息生成剪映视频编辑所需的配置文件,包含字幕信息、背景音乐、关键帧设置等 + * @param book 小说基本信息对象,包含小说文件夹路径等 + * @param bookTask 小说任务对象,包含音频路径、字幕路径、背景音乐等配置信息 + * @returns Promise<{draftName: string, configJsonPath: string}> 返回草稿名称和配置文件路径 + * @throws {Error} 当背景音乐文件不存在、字幕时间信息不完整等情况时抛出错误 + * @example + * ```typescript + * const result = await this.GenerateConfigFile(bookInfo, taskInfo); + * console.log('草稿名称:', result.draftName); + * console.log('配置文件路径:', result.configJsonPath); + * ``` + */ + private async GenerateConfigFile( + book: Book.SelectBook, + bookTask: Book.SelectBookTask + ): Promise<{ + draftName: string + configJsonPath: string + }> { + try { + // 构建配置文件保存路径,格式: 小说文件夹/scripts/任务名_config.json + let configPath = path.join( + book.bookFolderPath as string, + `scripts/${bookTask.name}_config.json` + ) + // 确保目录存在,不存在则创建 + await CheckFolderExistsOrCreate(path.dirname(configPath)) + + // 获取任务详细信息(包含所有帧的字幕、图片、时间信息等) + let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: bookTask.id + }) + + // ========== 处理背景音乐配置 ========== + let musicPath: string | undefined = undefined + if (!isEmpty(bookTask.backgroundMusic) && bookTask.backgroundMusic != null) { + // 检查背景音乐文件或文件夹是否存在 + if (!CheckFileOrDirExist(bookTask.backgroundMusic)) { + throw new Error('背景音乐文件夹或文件不存在,请检查') + } + + // 判断背景音乐是文件还是文件夹 + let isFolder = await fs.promises + .stat(bookTask.backgroundMusic as string) + .then((stat) => stat.isDirectory()) + .catch(() => false) + + if (!isFolder) { + // 如果是文件,直接使用该文件路径 + musicPath = bookTask.backgroundMusic + } else { + // 如果是文件夹,从中随机选择一个音频文件 + let files = await GetFilesWithExtensions(bookTask.backgroundMusic, ['.mp3', '.wav']) + if (files.length <= 0) { + throw new Error('背景音乐文件夹下面未存在有效的音频文件') + } else { + const randomIndex = Math.floor(Math.random() * files.length) + musicPath = files[randomIndex] + } + } + } + + // ========== 获取用户配置设置 ========== + + // 获取剪映关键帧动画设置(上下移动、左右移动、缩放等动画参数) + let keyFrameSettingOption = this.optionRealmService.GetOptionByKey( + OptionKeyName.Software.JianyingKeyFrameSetting + ) + let keyFrameSetting = + optionSerialization(keyFrameSettingOption) + + // 获取通用设置(包含剪映草稿文件夹路径等) + let generalSettingOption = this.optionRealmService.GetOptionByKey( + OptionKeyName.Software.GeneralSetting + ) + let generalSetting = optionSerialization(generalSettingOption) + + // ========== 准备剪映草稿文件 ========== + let draft_name = bookTask.name as string + let draft_path = path.join(generalSetting.draftPath, draft_name) + + // 清理已存在的草稿文件夹 + await fs.promises.rm(draft_path, { recursive: true, force: true }) + + // 从模板压缩包中解压出草稿文件夹 + await compressing.zip.uncompress(define.draft_temp_path, draft_path) + let draftPath = path.join(draft_path, 'draft_content.json') + + // ========== 构建配置数据对象 ========== + let configData = { + srt_time_information: [] as any[], // 存储所有帧的字幕时间信息 + video_config: { + srt_path: bookTask.srtPath, // 字幕文件路径 + audio_path: bookTask.audioPath, // 音频文件路径 + draft_srt_style: bookTask.draftSrtStyle ? bookTask.draftSrtStyle : '0', // 字幕样式 + background_music: musicPath, // 背景音乐路径 + friendly_reminder: bookTask.friendlyReminder ? bookTask.friendlyReminder : '0', // 友好提醒设置 + draft_content_json_path: draftPath, // 剪映草稿内容文件路径 + key_frame_info: { + key_frame: keyFrameSetting.keyFrame, // 关键帧类型 + isFixedSpeed: keyFrameSetting.isFixedSpeed, // 是否固定速度 + key_frame_time: keyFrameSetting.keyFrameTime, // 关键帧时间 + // 上下移动动画配置 + up_down_key_frame: { + default_scale: keyFrameSetting.upDownKeyFrame.defaultScale, + start_position: keyFrameSetting.upDownKeyFrame.startPosition, + end_position: keyFrameSetting.upDownKeyFrame.endPosition + }, + // 左右移动动画配置 + left_right_key_frame: { + default_scale: keyFrameSetting.leftRightKeyFrame.defaultScale, + start_position: keyFrameSetting.leftRightKeyFrame.startPosition, + end_position: keyFrameSetting.leftRightKeyFrame.endPosition + }, + // 缩放动画配置 + scale_key_frame: { + default_scale: keyFrameSetting.scaleKeyFrame.defaultScale, + start_position: keyFrameSetting.scaleKeyFrame.startPosition, + end_position: keyFrameSetting.scaleKeyFrame.endPosition + }, + is_fixed_speed: keyFrameSetting.isFixedSpeed ? keyFrameSetting.isFixedSpeed : false + } + } + } + + // 判断是否开启视频生成功能 + let openVideo = bookTask.openVideoGenerate ?? false + + // ========== 处理每一帧的详细信息 ========== + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i] + + // 验证时间信息完整性 + if (element.startTime == null || element.endTime == null) { + throw new Error('字幕时间信息不完整') + } + + // 验证字幕内容完整性 + if (element.subValue == null) { + throw new Error('字幕内容信息不完整') + } + + // 处理字幕内容:如果是字符串格式的JSON,则解析为对象 + if (typeof element.subValue === 'string') { + if (ValidateJson(element.subValue)) { + element.subValue = JSON.parse(element.subValue) + } else { + throw new Error('字幕内容信息不完整') + } + } + + // 时间单位转换:从毫秒转换为秒 + element.startTime = (element.startTime as number) / 1000 + element.endTime = (element.endTime as number) / 1000 + + // 处理子字幕数组,同样进行时间单位转换 + element.subValue = Array.isArray(element.subValue) + ? (element.subValue as BookTaskDetail.CopywritingSubValue[]).map((sub) => { + sub.start_time = sub.start_time / 1000 + sub.end_time = sub.end_time / 1000 + return sub + }) + : [] + + // 构建单帧数据对象 + let frameData = { + no: element.no, // 帧序号 + id: element.id, // 帧ID + lastId: i == 0 ? '' : bookTaskDetail[i - 1].id, // 上一帧ID + word: element.word, // 文字内容 + old_image: element.oldImage, // 原始图片 + after_gpt: element.afterGpt, // GPT处理后的内容 + start_time: element.startTime, // 开始时间(秒) + end_time: element.endTime, // 结束时间(秒) + timeLimit: `${element.startTime} -- ${element.endTime}`, // 时间范围字符串 + subValue: element.subValue, // 子字幕数组 + character_tags: [], // 角色标签(预留) + gpt_prompt: element.gptPrompt, // GPT提示词 + mjMessage: element.mjMessage, // MJ消息 + prompt_json: '', // 提示词JSON(预留) + name: element.name + '.png', // 图片文件名 + outImagePath: element.outImagePath, // 输出图片路径 + generateVideoPath: openVideo ? element.generateVideoPath : null, // 生成视频路径(可选) + subImagePath: element.subImagePath, // 子图片路径 + scene_tags: [], // 场景标签(预留) + imageLock: element.imageLock, // 图片锁定状态 + prompt: element.prompt // 提示词 + } + configData.srt_time_information.push(frameData as any) + } + + // ========== 保存配置文件 ========== + // 将配置数据写入到指定路径的JSON文件中 + await fs.promises.writeFile(configPath, JSON.stringify(configData), 'utf-8') + + // 同时复制一份到通用的config.json文件中,供其他程序使用 + let configJsonPath = path.join(book.bookFolderPath as string, 'scripts/config.json') + await CopyFileOrFolder(configPath, configJsonPath) + + return { + draftName: draft_name, + configJsonPath: configJsonPath + } + } catch (error) { + throw error + } + } + + /** + * 添加剪映草稿 + * @param id + * @param operateBookType + * @returns + */ + async AddJianyingDraft(bookTaskId: string): Promise { + try { + await this.InitBookBasicHandle() + let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId) + + if (bookTask == null) { + return errorMessage('没有找到小说批次任务,请检查', 'BookVideoHandle_AddJianyingDraft') + } + let book = await this.bookService.GetBookDataById(bookTask.bookId as string) + if (book == null) { + return errorMessage('没有找到小说,请检查', 'BookVideoHandle_AddJianyingDraft') + } + + if (!isEmpty(bookTask.draftDepend) && bookTask.draftDepend != null) { + if (bookTask.name == bookTask.draftDepend) { + throw new Error('草稿名称不能和任务名称相同,请修改任务名称') + } + await this.jianyingService.GenerateDraftFromDepend( + bookTask.draftDepend as string, + bookTask.imageFolder as string, + bookTask.name as string + ) + return successMessage(bookTask.name, '添加剪映草稿成功!', 'BookVideoHandle_AddJianyingDraft') + } + + let { draftName, configJsonPath } = await this.GenerateConfigFile(book, bookTask) + + // 开始调用 exe 执行 草稿的导出 + let jianyingExePath = path.join(define.scripts_path, 'xiangbei_jianying_main.exe') + if (!CheckFileOrDirExist(jianyingExePath)) { + throw new Error('没有找到导出剪映的执行文件,请检查') + } + let result: string = '' + try { + const env = { + ...process.env, + PYTHONIOENCODING: 'utf-8', + PYTHONLEGACYWINDOWSSTDIO: 'utf-8', + LANG: 'zh_CN.UTF-8', + PYTHONUTF8: '1' + } + + // 尝试最简单的执行方式,不使用chcp + const simpleCommand = `"${jianyingExePath}" "${configJsonPath}"` + + const output = await execAsync(simpleCommand, { + maxBuffer: 1024 * 1024 * 10, + encoding: 'utf-8', + env: env, + cwd: path.dirname(jianyingExePath), + timeout: 300000 + }) + + if ( + output.stderr && + (output.stderr.includes('Error') || + output.stderr.includes('failed') || + output.stderr.includes('UnicodeEncodeError')) + ) { + throw new Error(output.stderr) + } + + // 导出成功 + let stdout = output.stdout + // 将导出的日志写道文件里面 + let exportLogPath = path.join( + book.bookFolderPath as string, + `scripts/JianYingExportLog/${draftName}_export_log_${new Date().getTime()}.txt` + ) + await CheckFolderExistsOrCreate(path.dirname(exportLogPath)) + await fs.promises.writeFile(exportLogPath, stdout, 'utf-8') + + // 导出成功 将草稿名字返回 + result = draftName + } catch (fallbackError: any) { + // 记录详细的错误信息到文件 + const errorLogPath = path.join( + book.bookFolderPath as string, + `scripts/JianYingExportLog/error_${draftName}_${new Date().getTime()}.txt` + ) + await CheckFolderExistsOrCreate(path.dirname(errorLogPath)) + const errorInfo = { + execAsyncError: fallbackError.message, + scriptPath: jianyingExePath, + configPath: configJsonPath, + timestamp: new Date().toISOString() + } + await fs.promises.writeFile(errorLogPath, JSON.stringify(errorInfo, null, 2), 'utf-8') + + throw new Error(`详细错误已记录到: ${errorLogPath}`) + } + + // 所有的草稿都添加完毕之后开始返回 + return successMessage(result, `添加剪映草稿成功!`, 'BookVideoHandle_AddJianyingDraft') + } catch (error: any) { + return errorMessage( + '添加剪映草稿失败,错误信息如下:' + error.toString(), + 'BookVideoHandle_AddJianyingDraft' + ) + } + } +} diff --git a/src/main/service/common/srtHandle.ts b/src/main/service/common/srtHandle.ts new file mode 100644 index 0000000..5e40258 --- /dev/null +++ b/src/main/service/common/srtHandle.ts @@ -0,0 +1,60 @@ +import { CheckFileOrDirExist } from '@/define/Tools/file' +import fs from 'fs' +import { isEmpty } from 'lodash' + +interface SrtDataModel { + start: number + end: number + text: string +} + +export class SrtHandle { + constructor() {} + + /** + * 获取srt文件数据 + * + * @description 读取指定路径的srt文件,并解析其内容为对象数组。 + * 每个对象包含开始时间、结束时间和文本内容。 + * 如果文件不存在或格式不正确,将抛出错误。 + * @param srtPath srt文件路径 + * @returns srt数据 + */ + async GetSrtDataByPath(srtPath: string): Promise { + try { + if (isEmpty(srtPath)) { + throw new Error('srt文件路径不能为空!') + } + if (!srtPath.toLowerCase().endsWith('.srt')) { + throw new Error('srt文件后缀不正确,请检查!') + } + if (!(await CheckFileOrDirExist(srtPath))) { + throw new Error(`srt文件不存在,路径为:${srtPath}`) + } + let srt_data = (await fs.promises.readFile(srtPath)).toString('utf-8') + const entries = srt_data.replace(/\r\n/g, '\n').split('\n\n') + let data = entries + .map((entry) => { + const lines = entry.split('\n') + if (lines.length >= 3) { + const times = lines[1] + const text = lines.slice(2).join(' ') + const [start, end] = times.split(' --> ').map((time) => { + const [hours, minutes, seconds] = time.split(':') + const [sec, millis] = seconds.split(',') + return ( + (parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(sec)) * 1000 + + parseInt(millis) + ) + }) + return { start, end, text } + } + return undefined // Return undefined for entries with less than 3 lines + }) + .filter((entry) => entry != undefined) + return data + } catch (error) { + throw error + } + } +} diff --git a/src/main/service/jianying/jianyingDraft.ts b/src/main/service/jianying/jianyingDraft.ts new file mode 100644 index 0000000..c009cb1 --- /dev/null +++ b/src/main/service/jianying/jianyingDraft.ts @@ -0,0 +1,960 @@ +import path from 'path' +const fspromises = require('fs').promises +import * as musicMetadata from 'music-metadata-browser' +import wavFileInfo from 'wav-file-info' + +import { cloneDeep } from 'lodash' +import compressing from 'compressing' +import { define } from '@/define/define' +import { SettingModal } from '@/define/model/setting' +import { JianyingKeyFrameEnum } from '@/define/enum/jianyingEnum' + +export class JianyingDraft { + speedId?: string + canvasesId?: string + soundChannelId?: string + vocalSeparationsId?: string + materialVideoId?: string + tracksSegmentsId?: string + trackTypeId?: string + materialAnimationsId?: string + materialsTextID?: string + materialsBeatsID?: string + textId?: string + friendlyReminderId?: string + draft_json: any + one_duration_time: number + text_end_time: number + iamge_end_time: number + TTS_emd_time: number + draft_duration_time: number + value: any + draft_name: string + draftPath?: string + image_dir?: string + srtPath: any + style_id: any + num?: number + mp3_path: any + srt_information: any + audios_duration_time?: number + materialsAudiosID: any + tracksAudioId: any + projectPath: string + keyFrame: SettingModal.JianyingKeyFrameSettings + generalSetting: SettingModal.GeneralSettings + + constructor( + value, + draftName: string, + projectPath: string, + keyFrame: SettingModal.JianyingKeyFrameSettings, + generalSetting: SettingModal.GeneralSettings + ) { + this.speedId = undefined + this.canvasesId = undefined + this.soundChannelId = undefined + this.vocalSeparationsId = undefined + this.materialVideoId = undefined + this.tracksSegmentsId = undefined + this.trackTypeId = undefined + this.materialAnimationsId = undefined + this.materialsTextID = undefined + this.materialsBeatsID = undefined + this.textId = undefined + this.friendlyReminderId = undefined + this.draft_json = null + this.one_duration_time = 5000000 + this.text_end_time = 0 + this.iamge_end_time = 0 + this.TTS_emd_time = 0 + this.draft_duration_time = 0 + this.value = value + this.draft_name = draftName + this.keyFrame = keyFrame + this.generalSetting = generalSetting + this.projectPath = projectPath + } + + async InitData() { + let draft_path = path.join(this.generalSetting.draftPath, this.draft_name) + + await fspromises.rm(draft_path, { recursive: true, force: true }) + await compressing.zip.uncompress( + define.draft_temp_path, + path.join(this.generalSetting.draftPath, this.draft_name) + ) + this.draftPath = path.join(draft_path, 'draft_content.json') + this.image_dir = path.join(this.projectPath, `tmp/${this.value[0]}`) + this.srtPath = this.value[1].srt_path + this.style_id = this.value[1].draft_srt_style + this.num = 1 + this.mp3_path = this.value[1].audio_path + this.friendlyReminderId = this.value[1].friendly_reminder + + try { + let draft_json = JSON.parse( + await fspromises.readFile(path.join(this.projectPath, 'scripts/config.json')) + ) + if (draft_json['srt_time_information'] == undefined) { + throw new Error('未找到用于生成草稿的SRT和小说分镜数据,请检查') + } + this.srt_information = draft_json['srt_time_information'] + } catch (error) { + throw error + } + } + + /** + * 加载默认的草稿 + * @returns + */ + async LoadDraftJson() { + let draft_json = JSON.parse(await fspromises.readFile(this.draftPath)) + this.draft_json = draft_json + // console.log(this.draft_json); + } + + /** + * 添加speed + */ + async AddSpeeds() { + // 获取speed的模板地址 + let speed_json = JSON.parse(await fspromises.readFile(define.clip_speed_temp_path)) + // console.log(speed_json) + // 设置ID + let speedID = crypto.randomUUID().toUpperCase() + this.speedId = speedID + speed_json.id = speedID + return speed_json + } + + /** + * 添加Canvases + */ + async AddCanvases() { + let canvases_json = JSON.parse(await fspromises.readFile(define.add_canvases_temp_path)) + // console.log(canvases_json); + // 设置ID + let canvasesId = crypto.randomUUID().toUpperCase() + this.canvasesId = canvasesId + canvases_json.id = canvasesId + return canvases_json + } + + /** + * 添加soundchannel + * @returns + */ + async AddSoundChannelMapping() { + let soundChannelMapping_json = JSON.parse( + await fspromises.readFile(define.add_sound_channel_mappings_temp_path) + ) + // console.log(soundChannelMapping_json) + let sound_channel_mappings_tmp_ID = crypto.randomUUID().toUpperCase() + this.soundChannelId = sound_channel_mappings_tmp_ID + soundChannelMapping_json.id = sound_channel_mappings_tmp_ID + return soundChannelMapping_json + } + + /** + * 添加 vocal_separations + */ + async AddVocalSeparations() { + let vocal_separations_json = JSON.parse( + await fspromises.readFile(define.add_vocal_separations_temp_path) + ) + // console.log(vocal_separations_json); + let vocalSeparationId = crypto.randomUUID().toUpperCase() + this.vocalSeparationsId = vocalSeparationId + vocal_separations_json.id = vocalSeparationId + return vocal_separations_json + } + + /** + * 添加一个文件到原材料地址 + * @param {图片文件地址} imagePath + */ + async AddMaterialVideo(imagePath) { + let materialVideoTmpJson = JSON.parse( + await fspromises.readFile(define.add_material_video_temp_path) + ) + // console.log(materialVideoTmpJson); + let materialId = crypto.randomUUID().toUpperCase() + this.materialVideoId = materialId + materialVideoTmpJson.id = materialId + + // 获取输入的图片宽高 + materialVideoTmpJson.width = 1000 + materialVideoTmpJson.height = 1000 + + materialVideoTmpJson.path = imagePath + let image_name = path.basename(imagePath) + materialVideoTmpJson.material_name = image_name + + return materialVideoTmpJson + } + + /** + * 添加一个轨道 + * @returns + */ + async AddTracksSegments() { + if (this.num == undefined) { + this.num = 1 + } + let tracksJson = JSON.parse(await fspromises.readFile(define.add_tracks_segments_temp_path)) + // console.log(tracksJson); + let tracksSegmentsId = crypto.randomUUID().toUpperCase() + tracksJson.id = tracksSegmentsId + tracksJson.extra_material_refs = [] + tracksJson.extra_material_refs.push(this.speedId) + tracksJson.extra_material_refs.push(this.canvasesId) + tracksJson.extra_material_refs.push(this.soundChannelId) + tracksJson.extra_material_refs.push(this.vocalSeparationsId) + tracksJson.material_id = this.materialVideoId + tracksJson.target_timerange.start = this.num * tracksJson.target_timerange.duration + return tracksJson + } + + /** + * 新建一个track + * @param {track的类型} type + */ + async AddTracks(type) { + let tracks_json = JSON.parse(await fspromises.readFile(define.add_tracks_type_temp_path)) + let track_type_id = crypto.randomUUID() + this.trackTypeId = track_type_id + tracks_json.id = this.trackTypeId + tracks_json.type = type + return tracks_json + } + + /** + * 添加单个图片到轨道 + */ + async AddOneImageToDraft(image_path) { + // 添加 canvases + let canvanses = await this.AddCanvases() + this.draft_json.materials.canvases.push(canvanses) + + // 添加 sound_channel_mappings + let sound_channel = await this.AddSoundChannelMapping() + this.draft_json.materials.sound_channel_mappings.push(sound_channel) + + // 添加 speeds + let speeds = await this.AddSpeeds() + this.draft_json.materials.speeds.push(speeds) + + // 添加 vocal_separations + let vocal_sep = await this.AddVocalSeparations() + this.draft_json.materials.vocal_separations.push(vocal_sep) + + // 添加视频 materials 下面的 Videos + let video = await this.AddMaterialVideo(image_path) + this.draft_json.materials.videos.push(video) + + // 添加track轨道 + let segment = await this.AddTracksSegments() + return segment + // this.draft_json.tracks.segments.push(segment); + } + + /** + * 将所有的文件全部都写轨道上面 + */ + async AddAllImageToTracks() { + let img_dir = path.normalize(this.image_dir as string) + let files = await fspromises.readdir(img_dir) + let imageFiles = files.filter((file) => /\.(png)$/i.test(file)) + imageFiles.sort() + imageFiles = imageFiles.map((item) => path.join(img_dir, item)) + // console.log(imageFiles); + + // 创建一个tracks + let tracks_json = await this.AddTracks('video') + //往tracks里面的segments添加图片数据 + for (let i = 0; i < imageFiles.length; i++) { + const image_path = imageFiles[i] + let segment = await this.AddOneImageToDraft(image_path) + tracks_json.segments.push(segment) + // console.log(tracks_json); + } + + this.draft_json.tracks.push(tracks_json) + // 修改持续时间 + let duration_time = imageFiles.length * this.one_duration_time + this.iamge_end_time = duration_time + } + + /** + * 添加materials中的material_animations + */ + async AddMaterialAnimations() { + let material_animations = JSON.parse( + await fspromises.readFile(define.add_material_animations_temp_path) + ) + let material_animations_id = crypto.randomUUID() + this.materialAnimationsId = material_animations_id + material_animations.id = material_animations_id + return material_animations + } + + /** + * 为字幕添加样式 + */ + async AddTextStyle(material_text_json) { + try { + // 添加默认样式 + let c = JSON.parse(material_text_json.content) + let data = JSON.parse( + `[{\"size\":7.882736,\"fill\":{\"content\":{\"solid\":{\"color\":[1,1,1]}}},\"range\":[0,5]}]` + ) + data[0].range = [0, c.text.length] + c['styles'] = data + material_text_json.content = JSON.stringify(c) + return material_text_json + } catch (error) { + throw error + } + } + + /** + * 获得一个单的materialsText + */ + async AddMaterialsText(textString) { + let material_text_json = JSON.parse( + await fspromises.readFile(define.add_material_text_temp_path) + ) + let material_text_id = crypto.randomUUID() + this.materialsTextID = material_text_id + material_text_json.id = material_text_id + + // 设置内容 + let content = JSON.parse(material_text_json.content) + content.text = textString + material_text_json.content = JSON.stringify(content) + + material_text_json = await this.AddTextStyle(material_text_json) + return material_text_json + } + + // 文字的样式 + async ModifyTextClipTransform(text_segments) { + text_segments.clip.transform.y = -0.833333333334 + // console.log(text_segments); + return text_segments + // let clip_setting = JSON.parse(await fspromises.readFile(define.clip_setting)) + // let text_style = clip_setting.text_style + // if (text_style.length <= 0) return text_segments + // let style = text_style.filter((item) => item.id == this.style_id)[0] + // text_segments.clip = style.clip + // return text_segments + } + + /** + * 添加一个字幕到tracks + * @returns + */ + async AddOneTextToDraft(timeObj) { + try { + // 添加 materials 中的 material_animations + let material_animattions = await this.AddMaterialAnimations() + this.draft_json.materials.material_animations.push(material_animattions) + + // 添加 materials 下面的texts + let material_text = await this.AddMaterialsText(timeObj.text) + this.draft_json.materials.texts.push(material_text) + + let text_segments = JSON.parse( + await fspromises.readFile(define.add_track_text_segments_temp_path) + ) + let textId = crypto.randomUUID() + this.textId = textId + text_segments.id = textId + text_segments.extra_material_refs = [this.materialAnimationsId] + text_segments.material_id = this.materialsTextID + text_segments.target_timerange.start = timeObj.start + text_segments.target_timerange.duration = timeObj.end - timeObj.start + + // 修改样式偏移量 + text_segments = await this.ModifyTextClipTransform(text_segments) + + return text_segments + } catch (error) { + return { + code: 0, + message: `Error Message ${error}` + } + } + } + + /** + * 添加所有的text到tracks里面 + */ + async AddAllTextToTrack() { + // 添加一个tracks + let new_tracks = await this.AddTracks('text') + // 计算时间 + let srt_data = (await fspromises.readFile(this.srtPath)).toString('utf-8') + const entries = srt_data.replace(/\r\n/g, '\n').split('\n\n') + let data = entries + .map((entry: string) => { + const lines = entry.split('\n') + if (lines.length >= 3) { + const times = lines[1] + const text = lines.slice(2).join(' ') + const [start, end] = times.split(' --> ').map((time) => { + const [hours, minutes, seconds] = time.split(':') + const [sec, millis] = seconds.split(',') + return ( + ((parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(sec)) * 1000 + + parseInt(millis)) * + 1000 + ) + }) + return { start, end, text } + } + return undefined + }) + .filter((entry) => entry) + + for (let i = 0; i < data.length; i++) { + if (data[i] == undefined) { + continue + } + const text = data[i] + let text_se = await this.AddOneTextToDraft(text) + // console.log(text_se); + new_tracks.segments.push(text_se) + if (i == data.length - 1) { + this.text_end_time = text.end + } + } + // console.log(this.draft_json) + this.draft_json.tracks.push(new_tracks) + } + + /** + * 添加 materials下面的 beats + */ + async AddMaterialsBeats() { + let beats_json = JSON.parse(await fspromises.readFile(define.add_materials_beats_tmp_path)) + let materialsBeatsID = crypto.randomUUID() + this.materialsBeatsID = materialsBeatsID + beats_json.id = materialsBeatsID + return beats_json + } + + /** + * 获取音频文件的时长 + * @param filePath 音频文件路径 + * @returns 音频时长(微秒) + */ + async getAudioDuration(filePath: string): Promise { + const ext = filePath.split('.').pop()?.toLowerCase() + + if (!ext) { + throw new Error('无法确定文件类型,文件路径中没有扩展名') + } + + if (ext === 'mp3') { + try { + // 读取文件为Buffer + const buffer = await fspromises.readFile(filePath) + + // 尝试解析元数据 + try { + const metadata = await musicMetadata.parseBuffer(buffer, { + mimeType: 'audio/mpeg' + }) + + // 如果成功获取时长 + if (metadata?.format?.duration) { + return metadata.format.duration // 转换为微秒 + } + } catch (metadataError) { + console.warn('解析MP3元数据失败,将使用备用方法', metadataError) + } + + // 备用方法:使用文件大小估算 + const stats = await fspromises.stat(filePath) + const fileSize = stats.size + // 假设平均比特率为128kbps + const avgBitrate = 128000 + const durationSec = (fileSize * 8) / avgBitrate + return durationSec // 转换为微秒 + } catch (error) { + throw new Error(`获取MP3时长失败: ${error}`) + } + } else if (ext === 'wav') { + // 将回调风格的API包装为Promise + return new Promise((resolve, reject) => { + wavFileInfo.infoByFilename(filePath, (err, info) => { + if (err) { + reject(new Error(`获取WAV时长失败: ${err}`)) + } else { + // 确保返回的是微秒单位 + resolve(info.duration) + } + }) + }) + } else { + throw new Error(`不支持的音频格式: ${ext}`) + } + } + + /** + * 添加 materials 下面的 audios + */ + async AddMaterialsAudios(musicPath) { + try { + let audios_json = JSON.parse(await fspromises.readFile(define.add_materials_audios_tmp_path)) + let mp3_name = path.basename(musicPath) + let time = await this.getAudioDuration(path.normalize(musicPath)) + if (time == undefined) { + throw new Error('获取音频时长失败') + } + let duration_time = time * 1000000 + if (this.audios_duration_time == undefined) { + this.audios_duration_time = duration_time + } + let audiosID = crypto.randomUUID() + this.materialsAudiosID = audiosID + audios_json.id = audiosID + audios_json.name = mp3_name + audios_json.duration = duration_time + audios_json.path = musicPath + console.log(audios_json) + return audios_json + } catch (error: any) { + throw new Error(error) + } + } + + /** + * 添加tracks下面的audios下面的Segments + */ + async AddAudioTracksSegments() { + try { + let audio_segments = JSON.parse( + await fspromises.readFile(define.add_tracks_audio_segments_tmp_path) + ) + let audioId = crypto.randomUUID() + this.tracksAudioId = audioId + audio_segments.id = audioId + audio_segments.material_id = this.materialsAudiosID + audio_segments.extra_material_refs = [] + audio_segments.extra_material_refs.push(this.speedID) + audio_segments.extra_material_refs.push(this.materialsBeatsID) + audio_segments.extra_material_refs.push(this.soundChannelId) + audio_segments.extra_material_refs.push(this.vocalSeparationsId) + audio_segments.source_timerange.duration = this.audios_duration_time + audio_segments.target_timerange.duration = this.audios_duration_time + return audio_segments + } catch (error: any) { + throw new Error(error) + } + } + + speedID(speedID: any) { + this.speedId = speedID + throw new Error('Method not implemented.') + } + + /** + * 添加配音 + */ + async AddTTSMusic(musicPath) { + // 添加speeds + let speeds = await this.AddSpeeds() + this.draft_json.materials.speeds.push(speeds) + + // 添加beats + let beats = await this.AddMaterialsBeats() + this.draft_json.materials.beats.push(beats) + + // 添加 sound_channel_mappings + let sound_channel_mappings = await this.AddSoundChannelMapping() + this.draft_json.materials.sound_channel_mappings.push(sound_channel_mappings) + + // 添加 materials 下面的 audios + let audios = await this.AddMaterialsAudios(musicPath) + this.draft_json.materials.audios.push(audios) + + // 添加一个track + let tracks_json = await this.AddTracks('audio') + + let audio_segments_json = await this.AddAudioTracksSegments() + tracks_json.segments.push(audio_segments_json) + this.draft_json.tracks.push(tracks_json) + } + + /** + * 添加背景音乐 + * @param {背景音乐的ID} _background_music_id + */ + async AddRandomBackfroundMusic(_background_music_id) { + try { + return + // 获取背景音乐文件夹 + // let clip_setting_json = JSON.parse(await fspromises.readFile(define.clip_setting)) + // let setting = clip_setting_json.background_music_setting.filter( + // (item) => item.id == background_music_id + // ) + // console.log(setting) + // let folder_path = setting[0].folder_path + // console.log(folder_path) + // let files = await tools.getFilesWithExtensions(folder_path, ['.mp3', '.wav']) + // if (files.length == 0) { + // throw new Error('背景音乐文件夹下面未存在数据') + // } + // // 获取随机的数据 + // const randomIndex = Math.floor(Math.random() * files.length) + // let musicPath = files[randomIndex] + + // // 添加speeds + // let speeds = await this.AddSpeeds() + // this.draft_json.materials.speeds.push(speeds) + + // // 添加beats + // let beats = await this.AddMaterialsBeats() + // this.draft_json.materials.beats.push(beats) + + // // 添加 sound_channel_mappings + // let sound_channel_mappings = await this.AddSoundChannelMapping() + // this.draft_json.materials.sound_channel_mappings.push(sound_channel_mappings) + + // // 添加 materials 下面的 audios + // let audios = await this.AddMaterialsAudios(musicPath) + // // audios.duration = this.text_end_time; + // this.draft_json.materials.audios.push(audios) + + // // 添加一个track + // let tracks_json = await this.AddTracks('audio') + // let audio_segments_json = await this.AddAudioTracksSegments() + // // 修改 + // audio_segments_json.source_timerange.duration = this.audios_duration_time + // audio_segments_json.target_timerange.duration = this.audios_duration_time + // tracks_json.segments.push(audio_segments_json) + // this.draft_json.tracks.push(tracks_json) + } catch (error: any) { + throw new Error(error) + } + } + + /** + * 添加温馨提示 + */ + async AddFriendlyReminder() { + // 直接push + try { + return + + // let friendlyReminder = null + // let friendlyReminderSetting = JSON.parse( + // await fspromises.readFile(define.clip_setting) + // ).friendly_reminder_setting + // friendlyReminderSetting = friendlyReminderSetting.filter( + // (item) => item.id != '0' && item.id != '1' + // ) + // if (friendlyReminderSetting.length <= 0) { + // return + // } + + // if (this.friendlyReminderId == '0') { + // return + // } else if (this.friendlyReminderId == '1') { + // // 获取随机的数据 + // const randomIndex = Math.floor(Math.random() * friendlyReminderSetting.length) + // friendlyReminder = friendlyReminderSetting[randomIndex] + // } else { + // friendlyReminder = friendlyReminderSetting.filter( + // (item) => item.id == this.friendlyReminderId + // ) + // } + + // // 添加 materials 下面的 material_animations + // this.draft_json.materials.material_animations.push(friendlyReminder.material_animations) + // // 添加 materials下面的texts + // this.draft_json.materials.texts.push(friendlyReminder.texts) + // // 添加 tracks + // let track = friendlyReminder.tracks + // // 修改持续时间 + // track.segments[0].target_timerange.duration = this.audios_duration_time + // this.draft_json.tracks.push(track) + } catch (error: any) { + throw new Error(error) + } + } + + /** + * 修改草稿的持续时间 + */ + async ModifyDurationTime() { + if (this.audios_duration_time == undefined) { + throw new Error('未找到音频的持续时间') + } + let max_time = Math.max(this.iamge_end_time, this.text_end_time, this.audios_duration_time) + this.draft_json.duration = max_time + this.draft_json.canvas_config.height = 1440 + this.draft_json.canvas_config.width = 1920 + this.draft_json.canvas_config.ratio = '4:3' + } + + /** + * 将文件写道指定的位置 + */ + async WriteDraftFile() { + await fspromises.writeFile(this.draftPath, JSON.stringify(this.draft_json)) + } + + async find_draft_node(nodes, type, value) { + for (let index = 0; index < nodes.length; index++) { + let node = nodes[index] + if (node[type] == value) { + return node + } + } + } + + /** + * 将草稿图片和文字对齐 + */ + async AlginDraftImgToText() { + if (this.audios_duration_time == undefined) { + throw new Error('未找到音频的持续时间') + } + // 所有的字幕轨道里面的数据,读取出来 + let img_nodes = (await this.find_draft_node(this.draft_json.tracks, 'type', 'video')).segments + + //将最后一个数据修改为背景音乐的最后时间 + this.srt_information[this.srt_information.length - 1].end_time = + this.audios_duration_time / 1000 + + // 开始对齐 + for (let i = 0; i < this.srt_information.length; i++) { + if (img_nodes.length < i) { + break + } + const element = this.srt_information[i] + let duration = 0 + if (i + 1 < this.srt_information.length) { + duration = (this.srt_information[i + 1].start_time - element.start_time - 1) * 1000 + } else { + duration = (element.end_time - element.start_time) * 1000 + } + img_nodes[i].source_timerange.duration = duration + img_nodes[i].target_timerange.duration = duration + img_nodes[i].target_timerange.start = element.start_time * 1000 + } + } + + /** + * 通过 key_frame 返回关键帧数据 + * @param {*} key_frame 关键帧配置 + */ + async GetFrameData( + key_frame_key: JianyingKeyFrameEnum, + key_frame: SettingModal.JianyingKeyFrameSettings + ) { + if (key_frame_key == JianyingKeyFrameEnum.KFTypePositionY) { + return key_frame.upDownKeyFrame + } else if (key_frame_key == JianyingKeyFrameEnum.KFTypePositionX) { + return key_frame.leftRightKeyFrame + } else if (key_frame_key == JianyingKeyFrameEnum.KFTypeScale) { + return key_frame.scaleKeyFrame + } else { + return { + defaultScale: 100, + startPosition: 0, + endPosition: 0 + } + } + } + + /** + * 图片添加关键帧 + */ + async AddKeyFarme() { + let img_nodes = (await this.find_draft_node(this.draft_json.tracks, 'type', 'video')).segments + let key_frame_tmp_data = await fspromises.readFile(define.add_keyframe_tmp_path, 'utf-8') + + if (this.audios_duration_time == undefined) { + throw new Error('未找到音频的持续时间') + } + // 添加关键帧 + // 将最后一个数据修改为背景音乐的最后时间 + this.srt_information[this.srt_information.length - 1].end_time = + this.audios_duration_time / 1000 + + // 判断是不是赞助版 + let isFixedSpeed = global.am.isPro ? this.keyFrame.isFixedSpeed : false + let isDown = true + + // 获取通用的关键帧配置,然后添加到每一个图片上面(可以设置时间。当前图片的持续实现小于设置的时间。会计算不要过快) + for (let i = 0; i < img_nodes.length; i++) { + let element = img_nodes[i] + let image_duartion = cloneDeep(element.source_timerange.duration) + let key_frame = this.keyFrame.keyFrame + // 做随机处理 + let real_key_frame = key_frame + let key_frame_list = [ + JianyingKeyFrameEnum.KFTypePositionY, + JianyingKeyFrameEnum.KFTypePositionX, + JianyingKeyFrameEnum.KFTypeScale + ] + if (key_frame == JianyingKeyFrameEnum.KFTypeRandom) { + // 随机获取 key_frame_list 里面的一个数据 + const randomIndex = Math.floor(Math.random() * key_frame_list.length) + real_key_frame = key_frame_list[randomIndex] + } + + let key_frame_pos = await this.GetFrameData(real_key_frame, this.keyFrame) + + let key_frame_time = this.keyFrame.keyFrameTime * 1000000 + let scale_rate = key_frame_pos.defaultScale / 100 + + let up_pos = Math.abs(key_frame_pos.startPosition) + let down_pos = Math.abs(key_frame_pos.endPosition) + let key_frame_tmp = JSON.parse(key_frame_tmp_data) + + if (real_key_frame == 'KFTypePositionY') { + // 勾选了匀速。需要计算时间(计算比例) + if (isFixedSpeed && image_duartion < key_frame_time) { + let time_rate = image_duartion / key_frame_time + up_pos = up_pos * time_rate + down_pos = down_pos * time_rate + } + let up_pos_rate = isDown + ? up_pos / this.draft_json.canvas_config.height + : 0 - up_pos / this.draft_json.canvas_config.height + key_frame_tmp.id = crypto.randomUUID() + key_frame_tmp.keyframe_list[0].id = crypto.randomUUID() // Corrected assignment + key_frame_tmp.keyframe_list[0].values = [up_pos_rate] + + let dow_pos_rate = isDown + ? 0 - down_pos / this.draft_json.canvas_config.height + : down_pos / this.draft_json.canvas_config.height + key_frame_tmp.keyframe_list[1].id = crypto.randomUUID() + key_frame_tmp.keyframe_list[1].time_offset = image_duartion + key_frame_tmp.keyframe_list[1].values = [dow_pos_rate] + + key_frame_tmp.property_type = real_key_frame + + // 修改缩放倍率 + element.clip.scale.x = scale_rate + element.clip.scale.y = scale_rate + element.clip.transform.y = dow_pos_rate + isDown = !isDown + } else if (real_key_frame == 'KFTypePositionX') { + // 勾选了匀速。需要计算时间(计算比例) + if (isFixedSpeed && image_duartion < key_frame_time) { + let time_rate = image_duartion / key_frame_time + up_pos = up_pos * time_rate + down_pos = down_pos * time_rate + } + let up_pos_rate = isDown + ? up_pos / this.draft_json.canvas_config.width + : 0 - up_pos / this.draft_json.canvas_config.width + key_frame_tmp.id = crypto.randomUUID() + key_frame_tmp.keyframe_list[0].id = crypto.randomUUID() + key_frame_tmp.keyframe_list[0].values = [up_pos_rate] + + let dow_pos_rate = isDown + ? 0 - down_pos / this.draft_json.canvas_config.width + : down_pos / this.draft_json.canvas_config.width + key_frame_tmp.keyframe_list[1].id = crypto.randomUUID() + key_frame_tmp.keyframe_list[1].time_offset = image_duartion + key_frame_tmp.keyframe_list[1].values = [dow_pos_rate] + + key_frame_tmp.property_type = real_key_frame + + // 修改缩放倍率 + element.clip.scale.x = scale_rate + element.clip.scale.y = scale_rate + element.clip.transform.x = dow_pos_rate + isDown = !isDown + } else if (real_key_frame == 'KFTypeScale') { + if (isFixedSpeed && image_duartion < key_frame_time) { + let time_rate = image_duartion / key_frame_time + // 计算方式和上面的不同 + let sub_total = Math.abs(up_pos - down_pos) + let currwnt_rate = sub_total * (1 - time_rate) + up_pos = up_pos + currwnt_rate / 2 + down_pos = down_pos - currwnt_rate / 2 + } + + // 修改上面的数据,添加Y轴缩放 + let up_pos_rate = isDown ? up_pos / 100 : down_pos / 100 + key_frame_tmp.id = crypto.randomUUID() + key_frame_tmp.keyframe_list[0].id = crypto.randomUUID() + key_frame_tmp.keyframe_list[0].values = [up_pos_rate] + + let dow_pos_rate = isDown ? down_pos / 100 : up_pos / 100 + key_frame_tmp.keyframe_list[1].id = crypto.randomUUID() + key_frame_tmp.keyframe_list[1].time_offset = image_duartion + key_frame_tmp.keyframe_list[1].values = [dow_pos_rate] + + key_frame_tmp.property_type = real_key_frame + 'X' + + // 修改上面的数据,添加Y轴缩放 + // 修改缩放倍率 + element.clip.scale.x = isDown ? up_pos : dow_pos_rate + element.clip.scale.y = isDown ? up_pos : dow_pos_rate + element.clip.transform.x = 0 + element.common_keyframes.push(key_frame_tmp) + + key_frame_tmp = JSON.parse(JSON.stringify(key_frame_tmp)) + key_frame_tmp.id = crypto.randomUUID() + key_frame_tmp.keyframe_list[0].id = crypto.randomUUID() + key_frame_tmp.keyframe_list[1].id = crypto.randomUUID() + key_frame_tmp.property_type = real_key_frame + 'Y' + isDown = !isDown + } + element.common_keyframes.push(key_frame_tmp) + } + } + + /** + * 添加草稿 + */ + async addDraft() { + try { + await this.InitData() + await this.LoadDraftJson() + await this.AddAllImageToTracks() + await this.AddAllTextToTrack() + await this.AddTTSMusic(path.normalize(this.value[1].audio_path)) + if ( + this.value[1].background_music != '' && + this.value[1].background_music != undefined && + this.value[1].background_music != null + ) { + await this.AddRandomBackfroundMusic(this.value[1].background_music) + } + + // 添加温馨提示 + // await this.AddFriendlyReminder(); + + await this.ModifyDurationTime() + + // 对齐草稿数据 + await this.AlginDraftImgToText() + + // 添加关键帧 + await this.AddKeyFarme() + + await this.WriteDraftFile() + return { + code: 1, + draft_name: this.draft_name + } + } catch (error) { + return { + code: 0, + message: `An error occurred: ${error}` + } + } + } +} diff --git a/src/main/service/jianying/jianyingService.ts b/src/main/service/jianying/jianyingService.ts new file mode 100644 index 0000000..eb22474 --- /dev/null +++ b/src/main/service/jianying/jianyingService.ts @@ -0,0 +1,425 @@ +import path from 'path' +import { + CheckFileOrDirExist, + CopyFileOrFolder, + DeleteFolderAllFile, + GetFilesWithExtensions, + GetSubdirectoriesWithInfo +} from '../../../define/Tools/file' +import fs from 'fs' +import { ValidateJson } from '../../../define/Tools/validate' +import { errorMessage, successMessage } from '@/public/generalTools' +import { OptionRealmService } from '@/define/db/service/optionService' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../option/optionSerialization' +import { SettingModal } from '@/define/model/setting' + +/** + * 剪映的一些服务 + */ +class JianyingService { + draftTimeLine: DraftTimeLineJson[] + draftJson: any + constructor() { + this.draftTimeLine = [] + } + + /** + * 获取剪映草稿文件列表 + * @description 从用户配置的剪映草稿目录中获取所有草稿文件夹列表, + * 过滤掉回收站文件夹,返回包含文件夹名称、完整路径和创建时间的对象数组 + * @returns Promise<{code: number, data: any, message: string, action?: string}> + * 成功时返回草稿文件夹信息数组,失败时返回错误信息 + * @example + * ```typescript + * const jianyingService = new JianyingService(); + * const result = await jianyingService.GetJianyingDraftFileList(); + * if (result.code === 1) { + * console.log('草稿列表:', result.data); // [{name: '草稿1', fullPath: 'C:\\...', ctime: Date}] + * } + * ``` + */ + public async GetJianyingDraftFileList() { + try { + // 获取数据库选项服务实例 + let optionRealmService = await OptionRealmService.getInstance() + + // 从数据库中获取通用设置配置项 + let res = optionRealmService.GetOptionByKey(OptionKeyName.Software.GeneralSetting) + console.log('获取剪映草稿文件成功', res) + + // 反序列化设置数据为具体的设置对象 + let generalSetting = optionSerialization( + res, + '设置 -> 通用设置' + ) + + // 从设置中获取剪映草稿文件夹路径 + let draftPath = generalSetting.draftPath + + // 获取草稿目录下的所有子文件夹信息(包含名称、完整路径、创建时间) + let draftFolder = await GetSubdirectoriesWithInfo(draftPath) + + // 过滤掉剪映的回收站文件夹 + let result = draftFolder.filter((item) => item.name != '.recycle_bin') + + // 返回成功结果 + return successMessage( + result, + '获取剪映草稿文件列表成功', + 'JianyingService_GetJianyingDraftFileList' + ) + } catch (error: any) { + // 捕获异常并返回错误信息 + return errorMessage( + '获取剪映草稿文件列表失败,失败信息:' + error.message, + 'JianyingService_GetJianyingDraftFileList' + ) + } + } + + // /** + // * 获取剪映草稿的关键帧和文本 + // * @param draftDir 草稿目录 + // * @param projectDir 项目目录 + // */ + // public async GetDraftFrameAndText( + // draftDir: string, + // projectDir: string, + // inputName: string = 'input_crop' + // ): Promise { + // try { + // // 获取草稿文件路径 + // let draftJsonPath = path.resolve(draftDir, 'draft_content.json') + // if (!(await CheckFileOrDirExist(draftJsonPath))) { + // throw new Error('剪映草稿文件不存在,请先检查') + // } + // // 读取草稿文件内容 + // let draftJsonString = await fs.promises.readFile(draftJsonPath, 'utf-8') + // if (!ValidateJson(draftJsonString)) { + // throw new Error('剪映草稿文件格式错误,请检查') + // } + // this.draftJson = JSON.parse(draftJsonString) + + // // 检查输出文件夹是否存在 + // let projectTmp = path.resolve(projectDir, 'tmp') + // if (await CheckFileOrDirExist(projectTmp)) { + // // 删除文件夹 + // await DeleteFolderAllFile(projectTmp) + // } + // // 创建输出文件夹 + // let projectInput = path.resolve(projectTmp, inputName) + // console.log('projectInput', projectInput) + + // // 获取剪映的轨道,并且判断是否包含一个video轨道和一个text轨道 + // let draftTracks = this.draftJson.tracks + // if (!draftTracks) { + // throw new Error('剪映草稿文件格式错误,没有轨道,请检查') + // } + // let hasVideo = draftTracks.some((track: any) => track.type === 'video') + // let hasText = draftTracks.some((track: any) => track.type === 'text') + + // if (!(this.draftJson.tracks && hasVideo && hasText)) { + // throw new Error('没有检测到剪映草稿文件中的video和text轨道,请检查') + // } + + // // 获取视频节点 + // let videoNodes = draftTracks.filter((track: any) => track.type === 'video')[0].segments + // this.GetVideoTime(videoNodes) + + // // 获取文本节点 + // let textNodes = draftTracks.filter((track: any) => track.type === 'text')[0].segments + // this.GetTextTime(textNodes) + + // console.log('场景数:', this.draftTimeLine.length) + + // // 将数据写入到文件中 + // let txtData = this.draftTimeLine.map((item) => item.text) + // let txtPath = path.resolve(projectDir, '文案.txt') + // await fs.promises.writeFile(txtPath, txtData.join('\n'), 'utf-8') + + // // 开始抽取关键帧 + // await this.GetDraftFrame(projectInput) + + // // 将数据写入到json文件中 + // let jsonPath = path.resolve(projectDir, 'draftFrameData.json') + // await fs.promises.writeFile(jsonPath, JSON.stringify(this.draftTimeLine), 'utf-8') + // console.log('GetDraftFrameAndText', jsonPath) + // } catch (error) { + // throw error + // } finally { + // this.draftTimeLine = [] + // } + // } + + /** + * + * @param draftName + * @param imageFolder + * 1、找到对应的依赖的草稿文件, + * 2、直接复制一个全新的草稿文件 + * 3、解析复制出来的草稿文件 + * 3、找到主轴,只能替换主轴的图片 + * 3、找到对应的图片文件夹,然后替换对应的图片 + */ + public async GenerateDraftFromDepend( + dependDraftName: string, + imageFolder: string, + draftName: string + ) { + // ========== 获取用户配置设置 ========== + // 获取数据库选项服务实例 + let optionRealmService = await OptionRealmService.getInstance() + + // 获取通用设置(包含剪映草稿文件夹路径等) + let generalSettingOption = optionRealmService.GetOptionByKey( + OptionKeyName.Software.GeneralSetting + ) + let generalSetting = optionSerialization(generalSettingOption) + + let dependDraftPath = path.resolve(generalSetting.draftPath, dependDraftName) + if (!(await CheckFileOrDirExist(dependDraftPath))) { + throw new Error('依赖的草稿文件不存在,请检查') + } + let draftPath = path.resolve(generalSetting.draftPath, draftName) + if (await CheckFileOrDirExist(draftPath)) { + // 删除文件夹 + await DeleteFolderAllFile(draftPath, true) + } + // 复制 + await CopyFileOrFolder(dependDraftPath, draftPath) + + let images = await GetFilesWithExtensions(imageFolder, ['.png']) + + // 开始处理数据 + let draftJsonPath = path.resolve(draftPath, 'draft_content.json') + if (!(await CheckFileOrDirExist(draftJsonPath))) { + throw new Error('剪映草稿文件数据文件不存在,请先检查') + } + // 读取草稿文件内容 + let draftJsonString = await fs.promises.readFile(draftJsonPath, 'utf-8') + if (!ValidateJson(draftJsonString)) { + throw new Error('剪映草稿文件格式错误,请检查') + } + let draftJson = JSON.parse(draftJsonString) + let draftTracks = draftJson.tracks + if (draftTracks.length <= 0) { + throw new Error('剪映草稿文件格式错误,没有轨道,请检查') + } + + let videoTrack = draftTracks[0] // 只处理主轨道 + if (videoTrack.type !== 'video') { + throw new Error('剪映草稿文件格式错误,主轨道不是Video,请检查') + } + let videoTrackSegments = videoTrack.segments + if (videoTrackSegments.length <= 0) { + throw new Error('剪映草稿文件格式错误,主轨道没有对应的图片节点,请检查') + } + let segmentMaterialNodes: any = [] + for (let i = 0; i < videoTrackSegments.length; i++) { + const element = videoTrackSegments[i] + let materialId = element.material_id + let materialNode = this.FindNode(draftJson.materials.videos, 'id', materialId) + segmentMaterialNodes.push(materialNode) + } + + if (images.length !== segmentMaterialNodes.length) { + throw new Error('当前新增的任务对应的图片数量和依赖的草稿的主轨道的图片数量不一致,请检查') + } + + // 直接修改 + for (let i = 0; i < segmentMaterialNodes.length; i++) { + const element = segmentMaterialNodes[i] + if (!images[i]) { + throw new Error('图片数量不对应,请检查') + } + element.path = images[i] + } + + // 好了 写出草稿文件 + await fs.promises.writeFile(draftJsonPath, JSON.stringify(draftJson), 'utf-8') + } + + /** + * 替换剪映草稿中png到视频 + * @param draftName 草稿名字 + * @param ReplaceOnject 替换的对象 + */ + public async ReplaceDraftMaterialImageToVideo(draftName: string, replaceOnject: ReplaceOnject[]) { + let draftPath = path.resolve(global.config.draft_path, draftName) + if (!(await CheckFileOrDirExist(draftPath))) { + throw new Error('草稿文件不存在,请检查') + } + let draftJsonPath = path.resolve(draftPath, 'draft_content.json') + if (!(await CheckFileOrDirExist(draftJsonPath))) { + throw new Error('剪映草稿文件数据文件不存在,请先检查') + } + // 读取草稿文件内容 + let draftJsonString = await fs.promises.readFile(draftJsonPath, 'utf-8') + if (!ValidateJson(draftJsonString)) { + throw new Error('剪映草稿文件格式错误,请检查') + } + let draftJson = JSON.parse(draftJsonString) + let materialNodes = draftJson.materials.videos + console.log(materialNodes) + for (let i = 0; i < replaceOnject.length; i++) { + const element = replaceOnject[i] + let materialNode = this.FindNode(materialNodes, 'material_name', element.materialName) + if (materialNode == null) { + // 没有找到 跳过 + continue + } + // 找到 修改对应的数据 + materialNode.path = element.videoPath + materialNode.type = 'video' + materialNode.material_name = path.basename(element.videoPath) + } + + // 好了 写出草稿文件 + await fs.promises.writeFile(draftJsonPath, JSON.stringify(draftJson), 'utf-8') + } + + //#region 私有方法 + /** + * 在节点数组中查找指定类型和值的节点 + * @param nodes 节点数组 + * @param type 节点类型 + * @param value 节点值 + * @returns 找到的节点 + * @throws 如果没有找到对应的节点则抛出错误 + */ + private FindNode(nodes: any[], type: string, value: any) { + let res = nodes.filter((node: any) => node[type] === value) + if (res.length === 0) { + throw new Error('没有找到对应的节点') + } + return res[0] + } + + /** + * 判断文本是否在时间轴内 + * @param draftTimeObject 时间轴对象 + * @param textStartTime 文本开始时间 + * @param textEndTile 文本结束时间 + * @returns 如果文本在时间轴内则返回 true,否则返回 false + */ + // private TextIsInTimeLine( + // draftTimeObject: DraftTimeLineJson, + // textStartTime: number, + // textEndTile: number + // ) { + // return textStartTime >= draftTimeObject.startTime && textEndTile <= draftTimeObject.endTime + // } + + // /** + // * 抽取剪映草稿的关键帧 + // * @param projectInput 项目输入目录 + // */ + // private async GetDraftFrame(projectInput: string): Promise { + // for (let i = 0; i < this.draftTimeLine.length; i++) { + // const element = this.draftTimeLine[i] + // let outImagePath = path.resolve(projectInput, (i + 1).toString().padStart(5, '0') + '.png') + + // // 使用 ffmpeg 抽取关键帧 + // let frameRes = await this.ffmpegOptions.FfmpegGetFrame( + // element.middleTime / 1000, + // element.videoPath, + // outImagePath + // ) + // if (frameRes.code == 0) { + // throw new Error(frameRes.message) + // } + // this.draftTimeLine[i].framePath = outImagePath + // console.log('已经抽取第', i + 1, '帧') + // } + // } + + /** + * 获取文本时间 + * @param textNodes 文本节点数组 + */ + // private GetTextTime(textNodes: any[]): void { + // let tempText = '' + // let count = 0 + // for (let i = 0; i < textNodes.length; i++) { + // const element = textNodes[i] + // let textStartTime = element.target_timerange.start + // let textEndTime = textStartTime + element.target_timerange.duration + // let textMaterialId = element.material_id + // let textMaterialNode = this.FindNode(this.draftJson.materials.texts, 'id', textMaterialId) + // let textContent = textMaterialNode.content + // let textContentJson = JSON.parse(textContent) + + // let text = textContentJson.text + '。' + + // // 不在视频的时间轴内,丢弃 + // if (count > this.draftTimeLine.length - 1) { + // break + // } + // if (this.TextIsInTimeLine(this.draftTimeLine[count], textStartTime, textEndTime)) { + // tempText += text + // if (i == textNodes.length - 1) { + // this.draftTimeLine[count].text = tempText + // } + // } else { + // this.draftTimeLine[count].text = tempText + // tempText = text + // count += 1 + // } + // } + // } + + /** + * 获取视频时间 + * @param videoNodes 视频节点数组 + */ + // private GetVideoTime(videoNodes: any[]): void { + // for (let i = 0; i < videoNodes.length; i++) { + // const element = videoNodes[i] + // let startTime = element.target_timerange.start + // let endTime = startTime + element.target_timerange.duration + // let durationTime = element.target_timerange.duration + // let middleTime = startTime + (endTime - startTime) / 2 + + // let videoId = element.material_id + // let materialNode = this.FindNode(this.draftJson.materials.videos, 'id', videoId) + // let videoPath = materialNode.path + // this.draftTimeLine.push({ + // name: (i + 1).toString().padStart(5, '0'), + // startTime, + // endTime, + // durationTime, + // middleTime, + // videoPath, + // text: '', + // framePath: undefined + // }) + // } + // } + + //#endregion +} + +/** + * 存放剪映草稿的时间轴数据 + */ +export type DraftTimeLineJson = { + name: string + startTime: number + endTime: number + durationTime: number + middleTime: number + videoPath: string + text: string + framePath?: string + subVideoPath?: string + audioPath?: string +} + +export type ReplaceOnject = { + materialName: string + imagePath: string + videoPath: string +} + +export default JianyingService diff --git a/src/main/service/mj/index.ts b/src/main/service/mj/index.ts new file mode 100644 index 0000000..7d51599 --- /dev/null +++ b/src/main/service/mj/index.ts @@ -0,0 +1,20 @@ +import { OperateBookType } from '@/define/enum/bookEnum' +import { MJServiceHandle } from './mjServiceHandle' +import { MJBasic } from './mjBasic' +import { TaskModal } from '@/define/model/task' + +export class MJHandle extends MJBasic { + mjServiceHandle: MJServiceHandle + constructor() { + super() + // 初始化 + this.mjServiceHandle = new MJServiceHandle() + } + + /** 合并MidJourney提示词 */ + MergeMJPrompt = async (id: string, operateBookType: OperateBookType) => + await this.mjServiceHandle.MergeMJPrompt(id, operateBookType) + + /** 发起MidJourney图像生成任务 */ + MJImagine = async (task: TaskModal.Task) => await this.mjServiceHandle.MJImagine(task) +} diff --git a/src/main/service/mj/mjApiService.ts b/src/main/service/mj/mjApiService.ts new file mode 100644 index 0000000..236a7c0 --- /dev/null +++ b/src/main/service/mj/mjApiService.ts @@ -0,0 +1,551 @@ +import axios from 'axios' +import { MJBasic } from './mjBasic' +import { ImageGenerateMode, MJRobotType, MJSpeed } from '@/define/data/mjData' +import { MJRespoonseType } from '@/define/enum/mjEnum' +import { GetApiDefineDataById } from '@/define/data/apiData' +import { isEmpty } from 'lodash' +import { BookBackTaskStatus } from '@/define/enum/bookEnum' +import { MJ } from '@/define/model/mj' + +/** + * MidJourney API 账户过滤器接口 + * + * 该接口定义了与MidJourney API请求中账户过滤相关的配置选项。 + * 用于在API请求中指定任务处理速度、备注信息和实例ID等账户级别的设置。 + * + * @interface AccountFilter + * @property {['FAST' | 'RELAX']?} modes - 处理模式数组,可选值为'FAST'(快速)或'RELAX'(普通) + * @property {string?} remark - 可选的备注信息,通常用于标识请求来源 + * @property {string?} instanceId - 可选的实例ID,用于特定场景下的实例标识 + * + * @example + * // 创建一个账户过滤器对象 + * const filter: AccountFilter = { + * modes: ['FAST'], + * remark: '请求来源标识', + * instanceId: '' + * }; + */ +interface AccountFilter { + modes?: ['FAST' | 'RELAX'] + remark?: string // 添加问号使其成为可选属性 + instanceId?: string // 添加问号使其成为可选属性 +} + +/** + * MidJourney API 图像生成请求体接口 + * + * 该接口定义了向MidJourney API提交图像生成请求时所需的请求体结构。 + * 包含指定的机器人类型、提示词文本和可选的账户过滤器设置。 + * + * @interface MJAPIImagineRequestBody + * @property {'MID_JOURNEY' | 'NIJI_JOURNEY'} botType - 使用的机器人类型,MidJourney或NijiJourney + * @property {string} prompt - 用于生成图像的提示词文本 + * @property {AccountFilter} [accountFilter] - 可选的账户过滤设置,用于指定处理速度等参数 + * + * @example + * // 创建一个图像生成请求体 + * const requestBody: MJAPIImagineRequestBody = { + * botType: 'MID_JOURNEY', + * prompt: 'a beautiful sunset over mountains, photorealistic style', + * accountFilter: { + * modes: ['FAST'] + * } + * }; + */ +interface MJAPIImagineRequestBody { + botType: 'MID_JOURNEY' | 'NIJI_JOURNEY' + prompt: string + accountFilter?: AccountFilter +} + +/** + * MidJourney API 图像描述请求体接口 + * + * 该接口定义了向MidJourney API提交图像描述(反推)请求时所需的请求体结构。 + * 包含指定的机器人类型、base64编码的图像数据和可选的账户过滤器设置。 + * + * @interface MJAPIDescribeRequestBody + * @property {'MID_JOURNEY' | 'NIJI_JOURNEY'} botType - 使用的机器人类型,MidJourney或NijiJourney + * @property {string} base64 - base64编码的图像数据字符串,用于图像描述/反推 + * @property {AccountFilter} [accountFilter] - 可选的账户过滤设置,用于指定处理速度等参数 + * + * @example + * // 创建一个图像描述请求体 + * const requestBody: MJAPIDescribeRequestBody = { + * botType: 'MID_JOURNEY', + * base64: 'data:image/png;base64,iVBORw0KGgo...', + * accountFilter: { + * modes: ['FAST'] + * } + * }; + */ +interface MJAPIDescribeRequestBody { + botType: 'MID_JOURNEY' | 'NIJI_JOURNEY' + base64: string + accountFilter?: AccountFilter +} + +/** + * MidJourney API 服务类 + * + * 该类负责处理与MidJourney API的所有交互,包括图像生成和图像描述(反推提示词)功能。 + * 扩展自MJBasic基类,继承了基本设置和配置管理功能。类提供了对MJ API的完整封装, + * 包括初始化配置、构建请求、发送API请求、处理响应和错误处理等。 + * + * 主要功能: + * - 初始化MidJourney API设置和配置 + * - 提交图像生成(imagine)请求 + * - 提交图像描述(describe/反推)请求 + * - 查询任务状态和获取结果 + * - 构建符合API要求的请求体 + * - 处理API响应和错误 + * + * 使用场景: + * - 小说分镜的AI图像生成 + * - 根据已有图像获取描述文本(反推提示词) + * - 监控和获取长时间运行任务的状态 + * + * @class MJApiService + * @extends MJBasic + * + * @example + * // 初始化服务 + * const mjApiService = new MJApiService(); + * + * // 提交图像生成请求 + * const taskId = await mjApiService.SubmitMJImagine( + * "local-task-id", + * "a futuristic cityscape at sunset, hyperrealistic style" + * ); + * + * // 查询任务状态 + * const taskStatus = await mjApiService.GetMJAPITaskById(taskId, "local-task-id"); + */ +export class MJApiService extends MJBasic { + bootType: 'NIJI_JOURNEY' | 'MID_JOURNEY' + + imagineUrl!: string + fetchTaskUrl!: string + describeUrl!: string + + constructor() { + super() + this.bootType = 'MID_JOURNEY' + } + + /** + * 初始化MJ设置 + */ + async InitMJSetting(): Promise { + await this.GetMJGeneralSetting() + await this.GetApiSetting() + + if (this.mjApiSetting?.apiKey == null || this.mjApiSetting?.apiKey == '') { + throw new Error('没有找到对应的API的配置,请检查 ‘设置 -> MJ设置’ 配置!') + } + + this.bootType = + this.mjGeneralSetting?.robot == MJRobotType.NIJI ? 'NIJI_JOURNEY' : 'MID_JOURNEY' + if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_API) { + if (!this.mjApiSetting || isEmpty(this.mjApiSetting.apiUrl)) { + throw new Error('没有找到对应的API的配置,请检查 ‘设置 -> MJ设置’ 配置!') + } + let apiProvider = GetApiDefineDataById(this.mjApiSetting.apiUrl as string) + if (apiProvider.mj_url == null) { + throw new Error('当前API不支持MJ出图,请检查 ‘设置 -> MJ设置’ 配置!') + } + this.imagineUrl = apiProvider.mj_url.imagine + this.describeUrl = apiProvider.mj_url.describe + this.fetchTaskUrl = apiProvider.mj_url.once_get_task + } else { + throw new Error('当前的MJ出图模式不支持,请检查 ‘设置 -> MJ设置’ 配置!') + } + } + + //#region 获取对应的任务,通过ID + /** + * 通过ID获取MidJourney API任务的状态和结果 + * + * 该方法向MidJourney API发送请求,获取指定任务ID的状态信息,包括进度、图像URL和错误信息等。 + * 在获取成功后,会将结果格式化为标准响应对象。如果任务失败,则会相应地更新内部任务状态记录。 + * + * @param {string} taskId - MidJourney API的任务ID,用于查询API任务状态 + * @param {string} backTaskId - 内部系统的任务ID,用于更新本地任务状态记录 + * @returns {Promise} 标准化的任务状态响应对象,包含进度、状态、图像URL等信息 + * @throws {Error} 如果API请求失败或返回不可解析的数据 + * + * @example + * try { + * const taskStatus = await mjApiService.GetMJAPITaskById("task-123", "local-456"); + * if (taskStatus.code === 1 && taskStatus.progress === 100) { + * console.log("任务完成,图像URL:", taskStatus.imageShow); + * } else { + * console.log("任务进度:", taskStatus.progress, "%"); + * } + * } catch (error) { + * console.error("获取任务状态失败:", error.message); + * } + */ + async GetMJAPITaskById(taskId: string, backTaskId: string) { + try { + await this.InitMJSetting() + let APIDescribeUrl = this.fetchTaskUrl.replace('${id}', taskId) + + // 拼接headers + let headers = { + Authorization: this.mjApiSetting?.apiKey + } + // 开始请求 + let res = await axios.get(APIDescribeUrl, { + headers: headers + }) + let resData = res.data + + let progress = + resData.progress && resData.progress.length > 0 + ? parseInt(resData.progress.slice(0, -1)) + : 0 + + let status = resData.status.toLowerCase() + let code = status == 'failure' || status == 'cancel' ? 0 : 1 + // 失败 + if (code == 0) { + if (!isEmpty(backTaskId)) { + this.taskListService.UpdateTaskStatus({ + id: backTaskId, + status: BookBackTaskStatus.FAIL, + errorMessage: resData.message + }) + } + } + let resObj = { + type: MJRespoonseType.UPDATED, + progress: isNaN(progress) ? 0 : progress, + category: this.mjGeneralSetting?.outputMode, + imageClick: resData.imageUrl, + imageShow: resData.imageUrl, + imagePath: resData.imageUrl, + messageId: taskId, + status: status, + code: code, + prompt: resData.prompt == '' ? resData.promptEn : resData.prompt, + message: resData.failReason, + mjApiUrl: this.fetchTaskUrl + } as MJ.MJResponseToFront + return resObj + } catch (error) { + throw error + } + } + //#endregion + + //#region MJ反推相关操作 + + /** + * 提交MidJourney图像描述(反推)任务 + * + * 该方法根据当前设置的输出模式,将图像发送到MidJourney进行描述分析(反推提示词)。 + * 目前仅支持API模式,其他模式将抛出错误。在提交请求前会先初始化MJ设置。 + * + * @param {MJ.APIDescribeParams} param - 包含任务ID和base64编码图像的参数对象 + * @returns {Promise} 成功时返回API任务结果ID,队列已满时返回"23" + * @throws {Error} 如果当前输出模式不支持或API调用失败时抛出错误 + * + * @example + * try { + * const params = { + * taskId: "task-123", + * image: "data:image/png;base64,iVBORw0KGgo..." + * }; + * const resultId = await mjApiService.SubmitMJDescribe(params); + * console.log("提交成功,任务ID:", resultId); + * } catch (error) { + * console.error("提交反推任务失败:", error.message); + * } + */ + async SubmitMJDescribe(param: MJ.APIDescribeParams): Promise { + await this.InitMJSetting() + let res: string + switch (this.mjGeneralSetting?.outputMode) { + case ImageGenerateMode.MJ_API: + res = await this.SubmitMJDescribeAPI(param) + break + default: + throw new Error('MJ反推的类型不支持,反推只支持,API和代理模式') + } + return res + } + + /** + * 生成MidJourney描述请求的主体和配置 + * + * 该方法根据提供的base64图像和当前MJ设置,生成用于调用MidJourney描述API的请求主体和HTTP配置。 + * 配置会根据当前输出模式自动调整,并包含必要的认证信息和内容类型。 + * + * @param {string} imageBase64 - base64编码的图像字符串,用于图像描述/反推 + * @returns {{body: MJAPIDescribeRequestBody, config: Object}} 返回包含请求体和配置的对象 + * @throws {Error} 如果当前MJ输出模式不支持,将抛出错误 + * + * @example + * const imageBase64 = "data:image/png;base64,iVBORw0KGgo..."; + * const { body, config } = mjApiService.GenerateDescribeRequestBody(imageBase64); + * const response = await axios.post(describeUrl, body, config); + */ + GenerateDescribeRequestBody(imageBase64: string): { + body: MJAPIDescribeRequestBody + config: any + } { + // 提交API的反推 + let data = { + botType: this.bootType, + base64: imageBase64, + accountFilter: { + modes: [this.mjApiSetting?.apiSpeed == MJSpeed.FAST ? 'FAST' : 'RELAX'], + remark: global.machineId, + instanceId: '' + } as AccountFilter + } + let config = { + headers: { + 'Content-Type': 'application/json' + } + } + + if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_API) { + delete data.accountFilter.remark + delete data.accountFilter.instanceId + config.headers['Authorization'] = this.mjApiSetting?.apiKey + } else { + throw new Error('MJ出图的类型不支持') + } + return { + body: data, + config: config + } + } + + /** + * 通过API提交MidJourney图像描述(反推)请求 + * + * 该方法发送图像到MidJourney API进行描述(反推)分析,将base64编码的图像数据发送到API, + * 并处理返回结果。根据API响应状态码,会相应地更新任务状态记录并返回结果。 + * + * 支持以下特殊响应处理: + * - 队列已满(code=23): 更新任务状态为RECONNECT,返回"23" + * - 请求失败: 更新任务状态为FAIL,抛出错误 + * - 请求成功: 更新任务状态为RUNNING,返回结果ID + * + * @param {MJ.APIDescribeParams} param - 包含任务ID和base64编码图像数据的参数对象 + * @returns {Promise} 成功时返回API任务结果ID,队列已满时返回"23" + * @throws {Error} 如果API返回失败状态码或错误描述 + * + * @example + * try { + * const params = { + * taskId: "task-123", + * image: "data:image/png;base64,iVBORw0KGgo..." + * }; + * const taskResultId = await mjApiService.SubmitMJDescribeAPI(params); + * if (taskResultId === "23") { + * // 队列已满,需要重试 + * } else { + * // 任务提交成功,可以用taskResultId查询结果 + * } + * } catch (error) { + * console.error("图像描述请求失败:", error.message); + * } + */ + async SubmitMJDescribeAPI(param: MJ.APIDescribeParams): Promise { + // 获取body和config + let { body, config } = this.GenerateDescribeRequestBody(param.image) + // 开始请求 + let res = await axios.post(this.describeUrl, body, config) + + // 某些API的返回的code为23,表示队列已满,需要重新请求 + if (res.data.code == 23) { + this.taskListService.UpdateTaskStatus({ + id: param.taskId, + status: BookBackTaskStatus.RECONNECT + }) + return '23' + } + + if (res.data.code != 1 && res.data.code != 22) { + this.taskListService.UpdateTaskStatus({ + id: param.taskId, + status: BookBackTaskStatus.FAIL, + errorMessage: res.data.description + }) + throw new Error(res.data.description) + } + + this.taskListService.UpdateTaskStatus({ + id: param.taskId, + status: BookBackTaskStatus.RUNNING + }) + + return res.data.result as string + } + //#endregion + + //#region 提交MJ生图任务 + + /** + * 提交MidJourney图像生成任务 + * + * 该方法根据当前设置的输出模式,发送提示词到MidJourney进行图像生成。 + * 目前仅支持API模式,其他模式将抛出错误。在提交请求前会先初始化MJ设置。 + * + * @param {string} taskId - 任务ID,用于追踪和更新任务状态 + * @param {string} prompt - 用于生成图像的提示词文本 + * @returns {Promise} 成功时返回API任务结果ID,队列已满时返回"23" + * @throws {Error} 如果当前输出模式不支持或API调用失败时抛出错误 + * + * @example + * try { + * const resultId = await mjApiService.SubmitMJImagine("task-123", "a beautiful sunset in watercolor style"); + * console.log("提交成功,任务ID:", resultId); + * } catch (error) { + * console.error("提交生图任务失败:", error.message); + * } + */ + async SubmitMJImagine(taskId: string, prompt: string): Promise { + await this.InitMJSetting() + let res: string + switch (this.mjGeneralSetting?.outputMode) { + case ImageGenerateMode.MJ_API: + res = await this.SubmitMJImagineAPI(taskId, prompt) + break + default: + throw new Error('MJ出图的类型不支持') + } + return res + } + + /** + * 生成MidJourney API的imagine请求体和配置 + * + * 该方法根据当前MJ设置生成用于调用imagine API的请求主体和HTTP配置。 + * 配置包含必要的认证信息和内容类型,根据输出模式自动调整请求参数。 + * + * @param {string} prompt - 用于生成图像的提示词文本 + * @returns {{body: MJAPIImagineRequestBody, config: Object}} 返回包含请求体和配置的对象 + * @throws {Error} 如果当前MJ输出模式不支持,将抛出错误 + * + * @example + * const { body, config } = mjApiService.GenerateImagineRequestBody("a beautiful sunset"); + * const response = await axios.post(imagineUrl, body, config); + * + * @note 当前实现有问题,缺少prompt参数,需要修改方法签名为GenerateImagineRequestBody(prompt: string) + */ + GenerateImagineRequestBody(prompt: string): { + body: MJAPIImagineRequestBody + config: any + } { + // 提交API的出图任务 + let data = { + botType: this.bootType, + prompt: prompt, + accountFilter: { + modes: [this.mjApiSetting?.apiSpeed == MJSpeed.FAST ? 'FAST' : 'RELAX'], + remark: global.machineId ?? '', + instanceId: '' + } as AccountFilter + } + let config = { + headers: { + 'Content-Type': 'application/json' + } + } + + if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_API) { + delete data.accountFilter.remark + delete data.accountFilter.instanceId + config.headers['Authorization'] = this.mjApiSetting?.apiKey + } else { + throw new Error('MJ出图的类型不支持') + } + return { + body: data, + config: config + } + } + + /** + * 通过API提交MidJourney图像生成任务 + * + * 该方法向MidJourney API提交图像生成请求,并处理返回结果。在提交前会验证提示词是否包含非法链接, + * 然后构造API请求体并发送。根据API响应,会相应地更新任务状态并返回结果。 + * + * 支持以下特殊响应处理: + * - 队列已满(code=23): 更新任务状态为RECONNECT,返回"23" + * - 请求失败: 更新任务状态为FAIL,抛出错误 + * - 请求成功: 更新任务状态为RUNNING,返回结果ID + * + * @param {string} taskId - 任务ID,用于更新任务状态记录 + * @param {string} prompt - 发送给MidJourney的图像生成提示词 + * @returns {Promise} 成功时返回API任务结果ID,队列已满时返回"23" + * @throws {Error} 如果提示词中包含非法链接、API返回错误或返回数据为空 + * + * @example + * try { + * const taskResultId = await mjApiService.SubmitMJImagineAPI("task-123", "a beautiful sunset"); + * if (taskResultId === "23") { + * // 队列已满,需要重试 + * } else { + * // 任务提交成功,可以用taskResultId查询结果 + * } + * } catch (error) { + * console.error("提交任务失败:", error.message); + * } + */ + async SubmitMJImagineAPI(taskId: string, prompt: string): Promise { + // 这边校验是不是在提示词包含不正确的链接 + if (prompt.includes('feishu.cn')) { + throw new Error('提示词里面出现了 feishu.cn 飞书的链接,请检查并复制正确的链接') + } + + let { body, config } = this.GenerateImagineRequestBody(prompt) + + // 开始请求 + let res = await axios.post(this.imagineUrl, body, config) + let resData = res.data + + // if (this.mjGeneralSetting.outputMode == MJImageType.PACKAGE_MJ) { + // if (resData.code == -1 || resData.success == false) { + // throw new Error(resData.message) + // } + // } + + if (resData == null) { + throw new Error('返回的数据为空') + } + // 某些API的返回的code为23,表示队列已满,需要重新请求 + if (resData.code == 23) { + this.taskListService.UpdateTaskStatus({ + id: taskId, + status: BookBackTaskStatus.RECONNECT + }) + return '23' + } + + if (resData.code != 1 && resData.code != 22) { + this.taskListService.UpdateTaskStatus({ + id: taskId, + status: BookBackTaskStatus.FAIL, + errorMessage: resData.description + }) + throw new Error(resData.description) + } + + this.taskListService.UpdateTaskStatus({ + id: taskId, + status: BookBackTaskStatus.RUNNING + }) + + return resData.result as string + } + + //#endregion +} diff --git a/src/main/service/mj/mjBasic.ts b/src/main/service/mj/mjBasic.ts new file mode 100644 index 0000000..36a5777 --- /dev/null +++ b/src/main/service/mj/mjBasic.ts @@ -0,0 +1,96 @@ +import { OptionRealmService } from '@/define/db/service/optionService' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../option/optionSerialization' +import { SettingModal } from '@/define/model/setting' +import { BookTaskDetailService } from '@/define/db/service/book/bookTaskDetailService' +import { BookTaskService } from '@/define/db/service/book/bookTaskService' +import { BookService } from '@/define/db/service/book/bookService' +import { PresetRealmService } from '@/define/db/service/presetService' +import { TaskListService } from '@/define/db/service/book/taskListService' + +export class MJBasic { + optionRealmService!: OptionRealmService + mjGeneralSetting?: SettingModal.MJGeneralSettings + mjApiSetting?: SettingModal.MJApiSettings + + bookTaskDetailService!: BookTaskDetailService + bookTaskService!: BookTaskService + bookService!: BookService + presetRealmService!: PresetRealmService + taskListService!: TaskListService + + /** + * 初始化MJBasic类基础服务 + * + * 该方法确保MJBasic类所需的选项服务(optionRealmService,bookTaskDetailService,bookTaskService)已正确实例化。 + * 采用懒加载模式,只在服务未初始化时创建实例,避免资源浪费。 + * 此初始化是所有需要访问配置项的MJ相关操作的前提条件。 + * + * @returns {Promise} 无返回值的Promise对象 + * @throws {Error} 如果OptionRealmService.getInstance()失败可能会抛出错误 + */ + + async InitMJBasic(): Promise { + // 如果 mjServiceHandle 已经初始化,则直接返回 + if (!this.optionRealmService) { + this.optionRealmService = await OptionRealmService.getInstance() + } + if (!this.bookTaskDetailService) { + this.bookTaskDetailService = await BookTaskDetailService.getInstance() + } + if (!this.bookTaskService) { + this.bookTaskService = await BookTaskService.getInstance() + } + if (!this.bookService) { + this.bookService = await BookService.getInstance() + } + if (!this.presetRealmService) { + this.presetRealmService = await PresetRealmService.getInstance() + } + if (!this.taskListService) { + this.taskListService = await TaskListService.getInstance() + } + } + + /** + * 获取Midjourney通用设置 + * + * 该方法从配置数据库中读取Midjourney的通用设置,并将其反序列化为可用对象。 + * 在获取设置之前,会确保MJBasic已正确初始化。 + * + * 获取的设置将被存储在类的mjGeneralSetting属性中,以便后续使用。 + * + * @returns {Promise} 无返回值 + * @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误 + */ + async GetMJGeneralSetting(): Promise { + await this.InitMJBasic() + let generalSetting = this.optionRealmService.GetOptionByKey( + OptionKeyName.Midjourney.GeneralSetting + ) + this.mjGeneralSetting = optionSerialization( + generalSetting, + '‘设置 -> MJ设置’' + ) + } + + /** + * 获取Midjourney API设置 + * + * 该方法从配置数据库中读取Midjourney的API设置信息,并将其反序列化为可用对象。 + * 与GetMJGeneralSetting不同,此方法直接访问数据库而不确保初始化, + * 因此通常应在确保已调用InitMJBasic()后使用。 + * + * 获取的API设置将被存储在类的mjApiSetting属性中,以便后续使用。 + * + * @returns {Promise} 无返回值 + * @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误 + */ + async GetApiSetting(): Promise { + let apiSetting = this.optionRealmService.GetOptionByKey(OptionKeyName.Midjourney.ApiSetting) + this.mjApiSetting = optionSerialization( + apiSetting, + '‘设置 -> MJ设置’' + ) + } +} diff --git a/src/main/service/mj/mjServiceHandle.ts b/src/main/service/mj/mjServiceHandle.ts new file mode 100644 index 0000000..15e3d2f --- /dev/null +++ b/src/main/service/mj/mjServiceHandle.ts @@ -0,0 +1,599 @@ +import { + BookBackTaskStatus, + BookTaskStatus, + BookType, + DialogType, + OperateBookType +} from '@/define/enum/bookEnum' +import { MJBasic } from './mjBasic' +import { GeneralResponse } from '@/define/model/generalResponse' +import { + checkStringValueAddPrefix, + checkStringValueAddSuffix, + errorMessage, + SendReturnMessage, + successMessage +} from '@/public/generalTools' +import { Book } from '@/define/model/book/book' +import { isEmpty } from 'lodash' +import { PresetCategory } from '@/define/data/presetData' +import { TaskModal } from '@/define/model/task' +import { MJApiService } from './mjApiService' +import { ResponseMessageType } from '@/define/enum/softwareEnum' +import { MJAction, MJRespoonseType } from '@/define/enum/mjEnum' +import { MJ } from '@/define/model/mj' +import { ImageGenerateMode } from '@/define/data/mjData' +import path from 'path' +import { + CheckFolderExistsOrCreate, + CopyFileOrFolder, + DownloadImageFromUrl +} from '@/define/Tools/file' +import { ImageSplit } from '@/define/Tools/image' +import { getProjectPath } from '../option/optionCommonService' +import { PresetBasicService } from '../preset/presetBasicService' + +export class MJServiceHandle extends MJBasic { + mjApiService: MJApiService + presetBasicService: PresetBasicService + constructor() { + super() + this.mjApiService = new MJApiService() + this.presetBasicService = new PresetBasicService() + } + + /** + * 合并MidJourney提示词 + * + * 该方法根据提供的ID和操作类型,将GPT生成的提示词与预设的风格、人物、场景等内容按指定顺序合并, + * 生成用于MidJourney的完整提示词字符串。合并过程考虑了不同类型预设的特殊处理需求,如垫图链接、 + * 权重设置等。合并结果会保存回数据库,同时返回结果数据。 + * + * 支持两种操作模式: + * - BOOKTASK: 处理整个小说任务中的所有分镜数据 + * - BOOKTASKDETAIL: 仅处理单个指定分镜数据 + * + * 合并规则基于分镜中的promptSort设置(默认为'style-character-scene-prompt'), + * 并会添加小说任务中定义的前缀和后缀提示词。 + * + * @param {string} id - 根据operateBookType不同,可能是小说任务ID或分镜ID + * @param {OperateBookType} operateBookType - 操作类型,决定处理范围 + * @returns {Promise} 操作结果,成功或失败的标准化响应 + * + * @throws {Error} 如果分镜未找到、GPT提示词为空、小说数据未找到或操作类型未知,将抛出错误 + * + * @example + * // 合并单个分镜的提示词 + * const result = await mjService.MergeMJPrompt( + * "detail-123", + * OperateBookType.BOOKTASKDETAIL + * ); + */ + async MergeMJPrompt( + id: string, + operateBookType: OperateBookType + ): Promise { + try { + let bookTaskDetail: Book.SelectBookTaskDetail[] = [] + let bookTask: Book.SelectBookTask + await this.GetMJGeneralSetting() + if (operateBookType == OperateBookType.BOOKTASK) { + bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: id + }) + bookTask = await this.bookTaskService.GetBookTaskDataById(id) + // 判断是不是有为空的 + let emptyName = [] as string[] + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i] + if (isEmpty(element.gptPrompt)) { + emptyName.push(element.name as string) + } + } + if (emptyName.length > 0) { + throw new Error(`${emptyName.join(',')} 的提示词为空,请先推理`) + } + } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + let tempBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id) + if (tempBookTaskDetail == null) { + throw new Error('没有找到要合并的分镜数据,请检查ID是否正确') + } + + if (isEmpty(tempBookTaskDetail.gptPrompt)) { + throw new Error('当前分镜没有推理提示词,请先生成') + } + bookTaskDetail = [tempBookTaskDetail] + bookTask = await this.bookTaskService.GetBookTaskDataById( + bookTaskDetail[0].bookTaskId as string + ) + } else { + throw new Error('未知的合并类型') + } + + if (bookTaskDetail.length <= 0) { + throw new Error('没有找到对应的需要合并的分镜数据') + } + let book = await this.bookService.GetBookDataById(bookTask.bookId as string) + if (book == null) { + throw new Error('没有找到对应的小说数据,请检查小说ID是否正确') + } + + // 获取通用的后缀 + let suffixParam = this.mjGeneralSetting?.commandSuffix + let result = [] as { id: string; prompt: string }[] + + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i] + // 没有推理提示词,直接跳过 + if (isEmpty(element.gptPrompt)) { + continue + } + + let defaultPromptSort = element.promptSort ?? 'style-character-scene-prompt' + let promptSort = defaultPromptSort.split('-') + let promptStr = '' + for (let i = 0; i < promptSort.length; i++) { + const element = promptSort[i] + promptStr += `${'${' + element + '}'} ` + } + + // 查询当前预设的风格 + let styleArr = this.presetRealmService.GetPresetByIds( + element.styleTags ?? [], + PresetCategory.Style + ) + + let characterString = '' // 人物 + let characterUrl = '' + let sceneString = '' // 场景 + + // 获取当前的自定义风格的垫图字符串 + if (book.type == BookType.ORIGINAL) { + let sceneIds = element.sceneTags ? element.sceneTags : [] + let characterIds = element.characterTags ? element.characterTags : [] + if (sceneIds && sceneIds.length > 0) { + sceneString = await this.presetBasicService.GetScenePresetStringByIds(sceneIds) + } + if (characterIds && characterIds.length > 0) { + let res = await this.presetBasicService.GetCharacterPresetStringByIds(characterIds) + characterString = res.characterString + characterUrl = res.characterUrl + } + } + + let styleString = '' + let style_url = '' + let sw: number | undefined = undefined + styleArr.forEach((item) => { + if (sw == undefined) { + sw = item.srefSw + } + if (!isEmpty(item.imageUrl)) { + let url = item.imageUrl ? item.imageUrl : '' + style_url += ' ' + url + } + if (item.prompt) { + styleString += item.prompt + ',' + } + }) + + style_url = checkStringValueAddPrefix(style_url, '--sref ') + if (sw != undefined) { + style_url = checkStringValueAddSuffix(style_url, ` --sw ${sw}`) + } + + promptStr = promptStr.replace('${style}', styleString) + promptStr = promptStr.replace('${character}', characterString) + promptStr = promptStr.replace('${scene}', sceneString) + promptStr = promptStr.replace( + '${prompt}', + checkStringValueAddSuffix(element.gptPrompt ?? '', ',') + ) + + // 判断是不是需要加前后缀 + if (bookTask.prefixPrompt) { + promptStr = checkStringValueAddSuffix(bookTask.prefixPrompt, ',') + promptStr + } + if (bookTask.suffixPrompt) { + promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.suffixPrompt + } + promptStr = ' ' + promptStr + promptStr += ` ${characterUrl} ${style_url}${suffixParam}` + // 修改数据库数据 + await this.bookTaskDetailService.ModifyBookTaskDetailById(element.id as string, { + prompt: promptStr + }) + // 写回数据 + result.push({ + id: element.id as string, + prompt: promptStr + }) + } + return successMessage(result, 'MJ模式合并数据成功', 'MJOpt_MergePrompt') + } catch (error: any) { + return errorMessage( + 'MJ合并提示词失败,错误信息如下:' + error.message, + 'MJServiceHandle_MergeMJPrompt' + ) + } + } + + /** + * 发起MidJourney图像生成任务 + * + * 该方法负责将分镜提示词发送到MidJourney API进行图像生成。处理过程包括: + * 1. 验证和获取任务相关数据(分镜、书籍、任务信息) + * 2. 提交提示词到MidJourney API + * 3. 处理API队列状态和错误情况 + * 4. 更新任务状态并发送实时通知到前端 + * 5. 启动异步监控流程等待任务完成 + * + * 如果API返回队列已满(23)状态码,任务会被标记为需要重新连接(RECONNECT), + * 系统稍后会自动重试。任务开始后,将启动轮询过程监控进度直至完成或失败。 + * + * @param {TaskModal.Task} task - 要执行的任务对象,包含任务ID、分镜ID和消息配置 + * @returns {Promise} 操作结果 + * @throws {Error} 当找不到分镜数据、小说信息、提示词为空或API调用失败时抛出异常 + * + * @example + * const task = { + * id: "task-123", + * bookTaskDetailId: "detail-456", + * messageName: "mj-image-channel" + * }; + * const result = await mjServiceHandle.MJImagine(task); + */ + async MJImagine( + task: TaskModal.Task + ): Promise { + try { + if (isEmpty(task.bookTaskDetailId)) { + throw new Error('MJ出图,没有找到对应的分镜信息') + } + await this.GetMJGeneralSetting() + let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById( + task.bookTaskDetailId as string + ) + if (bookTaskDetail == null) { + throw new Error('没有找到对应的分镜信息') + } + let book = await this.bookService.GetBookDataById(bookTaskDetail.bookId as string) + if (book == null) { + throw new Error('没有找到对应的小说信息') + } + let bookTask = await this.bookTaskService.GetBookTaskDataById( + bookTaskDetail.bookTaskId as string + ) + if (bookTask == null) { + throw new Error('没有找到对应的任务信息') + } + // 调用方法合并提示词 + let mergeRes = await this.MergeMJPrompt( + task.bookTaskDetailId as string, + OperateBookType.BOOKTASKDETAIL + ) + if (mergeRes.code == 0) { + throw new Error(mergeRes.message) + } + // 获取提示词 + bookTaskDetail.prompt = mergeRes.data[0].prompt + + let prompt = bookTaskDetail.prompt + if (isEmpty(prompt) || prompt == undefined) { + throw new Error(`${bookTaskDetail.name} 没有找到对应的提示词`) + } + // 这个就是任务ID + let reqRes = await this.mjApiService.SubmitMJImagine(task.id as string, prompt) + if (reqRes == '23') { + // 任务队列过多,重新提交排队 + this.taskListService.UpdateTaskStatus({ + id: task.id as string, + status: BookBackTaskStatus.RECONNECT + }) + SendReturnMessage( + { + code: 1, + type: ResponseMessageType.MJ_IMAGE, + id: task.bookTaskDetailId as string, + data: { + code: 1, + type: MJRespoonseType.UPDATED, + mjType: MJAction.IMAGINE, + category: this.mjGeneralSetting?.outputMode, + message_id: '', + id: task.bookTaskDetailId, + progress: 0, + status: 're_connect' + } as MJ.MJResponseToFront + }, + task.messageName as string + ) + this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId as string, { + mjApiUrl: this.mjApiService.imagineUrl, + progress: 0, + category: this.mjGeneralSetting?.outputMode as ImageGenerateMode, + imageClick: '', + imageShow: '', + messageId: reqRes, + action: MJAction.IMAGINE, + status: 're_connect' + }) + // throw new Error(`任务队列过多,${task.bookTaskDetailId} 重新提交排队`); + return successMessage( + null, + `任务队列过多,${task.bookTaskDetailId} 重新提交排队`, + 'MJServiceHandle_MJImagine' + ) + } + + await this.bookTaskDetailService.ModifyBookTaskDetailById(task.bookTaskDetailId as string, { + status: BookTaskStatus.IMAGE + }) + this.taskListService.UpdateTaskStatus({ + id: task.id as string, + status: BookBackTaskStatus.RUNNING + }) + + SendReturnMessage( + { + code: 1, + type: ResponseMessageType.MJ_IMAGE, + id: task.bookTaskDetailId as string, + data: { + code: 1, + type: MJRespoonseType.UPDATED, + mjType: MJAction.IMAGINE, + category: this.mjGeneralSetting?.outputMode, + message_id: reqRes, + id: task.bookTaskDetailId, + progress: 0, + status: 'submited' + } as MJ.MJResponseToFront + }, + task.messageName as string + ) + await this.FetchImageTask(task, reqRes, book, bookTask, bookTaskDetail) + return successMessage( + null, + `MJ生图成功,分镜ID:${task.bookTaskDetailId},任务ID:${task.id}`, + 'MJServiceHandle_MJImagine' + ) + } catch (error: any) { + console.log(error.toString()) + let errorMsg = 'MJ生图失败,失败信息如下:' + error.toString() + this.taskListService.UpdateTaskStatus({ + id: task.id as string, + status: BookBackTaskStatus.FAIL, + errorMessage: errorMsg + }) + SendReturnMessage( + { + code: 0, + id: task.bookTaskDetailId as string, + type: ResponseMessageType.MJ_IMAGE, + dialogType: DialogType.NOTIFICATION, + message: errorMsg, + data: { + code: 0, + type: MJRespoonseType.UPDATED, + mjType: MJAction.IMAGINE, + category: this.mjGeneralSetting?.outputMode, + messageId: undefined, + id: task.bookTaskDetailId as string, + progress: 0, + message: errorMsg, + status: 'error' + } + }, + task.messageName as string + ) + this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId as string, { + mjApiUrl: this.mjApiService.imagineUrl, + progress: 0, + category: this.mjGeneralSetting?.outputMode as ImageGenerateMode, + imageClick: '', + imageShow: '', + action: MJAction.IMAGINE, + status: 'error', + message: errorMsg + }) + return errorMessage(errorMsg, 'MJReverse_MJImage2Text') + } + } + + /** + * 循环监听和处理MidJourney图像生成任务 + * + * 该方法轮询检查MidJourney图像生成任务的状态,直到任务完成或失败。 + * 在任务进行过程中,会持续更新数据库中的任务状态和进度信息, + * 并通过消息通知前端更新界面。任务成功完成后,会下载生成的图像, + * 将其分割为四个子图像,并保存到相应的目录中。 + * + * 主要流程: + * 1. 定期轮询API获取任务状态 + * 2. 实时更新数据库和前端进度 + * 3. 处理失败情况并发送错误消息 + * 4. 任务完成后下载和处理图像 + * 5. 更新分镜数据中的图像路径信息 + * + * @param {TaskModal.Task} task - 当前执行的任务对象,包含ID和消息配置 + * @param {string} reqRes - MidJourney API返回的任务ID,用于查询任务状态 + * @param {Book.SelectBook} book - 书籍信息,包含存储路径等 + * @param {Book.SelectBookTask} bookTask - 书籍任务信息 + * @param {Book.SelectBookTaskDetail} bookTaskDetail - 分镜详细信息 + * + * @throws {Error} 当图像下载失败、裁剪失败或API返回错误时抛出异常 + * + * @private 内部方法,由MJImagine方法调用 + */ + async FetchImageTask( + task: TaskModal.Task, + reqRes: string, + book: Book.SelectBook, + bookTask: Book.SelectBookTask, + bookTaskDetail: Book.SelectBookTaskDetail + ) { + while (true) { + try { + // 执行你的操作 + let task_res = await this.mjApiService.GetMJAPITaskById(reqRes, task.id as string) + task_res.id = task.bookTaskDetailId as string + + // 判断他的状态是不是成功 + if (task_res.code == 0) { + // 生图失败 + await this.bookTaskDetailService.ModifyBookTaskDetailById( + task.bookTaskDetailId as string, + { + status: BookTaskStatus.IMAGE_FAIL + } + ) + let errorMsg = `MJ生成图片失败,失败信息如下:${task_res.message}` + this.bookTaskDetailService.UpdateBookTaskDetailMjMessage( + task.bookTaskDetailId as string, + { + mjApiUrl: this.mjApiService.imagineUrl, + progress: 100, + category: this.mjGeneralSetting?.outputMode as ImageGenerateMode, + imageClick: task_res.imageClick, + imageShow: task_res.imageShow, + messageId: task_res.messageId, + action: MJAction.IMAGINE, + status: 'error', + message: errorMsg + } + ) + this.taskListService.UpdateTaskStatus({ + id: task.id as string, + status: BookBackTaskStatus.FAIL, + errorMessage: errorMsg + }) + SendReturnMessage( + { + code: 0, + type: ResponseMessageType.MJ_IMAGE, + id: task.bookTaskDetailId as string, + data: { + ...task_res, + status: 'error', + message: errorMsg + }, + message: errorMsg + }, + task.messageName as string + ) + return + // throw new Error(`${task_res.message}`); + } else { + if (task_res.progress == 100) { + task_res.type == MJRespoonseType.FINISHED + this.taskListService.UpdateTaskStatus({ + id: task.id as string, + status: BookBackTaskStatus.DONE + }) + // 下载图片 + let imagePath = path.join( + book.bookFolderPath as string, + `data\\MJOriginalImage\\${task_res.messageId}.png` + ) + // 判断是不是生图包,是的话需要替换图片的baseurl + // if (this.mj_globalSetting.mj_simpleSetting.type == MJImageType.PACKAGE_MJ) { + // let imageBaseUrl = this.mj_globalSetting.mj_imagePackageSetting.selectedProxy + // if (imageBaseUrl != 'empty' && imageBaseUrl && imageBaseUrl != '') { + // task_res.imageClick = task_res.imageClick.replace(/https?:\/\/[^/]+/, imageBaseUrl) + // } + // } + await CheckFolderExistsOrCreate(path.dirname(imagePath)) + await DownloadImageFromUrl(task_res.imageClick as string, imagePath) + // 进行图片裁剪 + let imageRes = await ImageSplit( + imagePath, + bookTaskDetail.name as string, + path.join(book.bookFolderPath as string, 'data\\MJOriginalImage') + ) + if (imageRes && imageRes.length < 4) { + throw new Error('图片裁剪失败') + } + + // 修改数据库数据,将图片保存到对应的文件夹中 + let firstImage = imageRes[0] + if (book.type == BookType.ORIGINAL && bookTask.name == 'output_00001') { + await CopyFileOrFolder( + firstImage, + path.join(book.bookFolderPath as string, `tmp\\input\\${bookTaskDetail.name}.png`) + ) + } + let out_file = path.join(bookTask.imageFolder as string, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(firstImage, out_file) + task_res.outImagePath = out_file + task_res.subImagePath = imageRes + + task_res.id = task.bookTaskDetailId as string + let projectPath = await getProjectPath() + // 修改分镜的数据 + await this.bookTaskDetailService.ModifyBookTaskDetailById( + task.bookTaskDetailId as string, + { + outImagePath: path.relative(projectPath, out_file), + subImagePath: imageRes.map((item) => path.relative(projectPath, item)) + } + ) + this.bookTaskDetailService.UpdateBookTaskDetailMjMessage( + task.bookTaskDetailId as string, + { + mjApiUrl: this.mjApiService.imagineUrl, + progress: 100, + category: this.mjGeneralSetting?.outputMode as ImageGenerateMode, + imageClick: task_res.imageClick, + imageShow: task_res.imageShow, + messageId: task_res.messageId, + action: MJAction.IMAGINE, + status: task_res.status + } + ) + SendReturnMessage( + { + code: 1, + type: ResponseMessageType.MJ_IMAGE, + id: task.bookTaskDetailId as string, + data: task_res + }, + task.messageName as string + ) + break + } + } + + // 这边也要修改数据 + task_res.id = task.bookTaskDetailId as string + + this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId as string, { + mjApiUrl: this.mjApiService.imagineUrl, + progress: task_res.progress, + category: this.mjGeneralSetting?.outputMode as ImageGenerateMode, + imageClick: task_res.imageClick, + imageShow: task_res.imageShow, + messageId: task_res.messageId, + action: MJAction.IMAGINE, + status: task_res.status, + message: task_res.message + }) + task_res.outImagePath = task_res.imagePath + + SendReturnMessage( + { + code: 1, + type: ResponseMessageType.MJ_IMAGE, + id: task.bookTaskDetailId as string, + data: task_res + }, + task.messageName as string + ) + // 当获取的图片的进度小于100的时候,等待5秒继续监听 + await new Promise((resolve) => setTimeout(resolve, 9000)) + } catch (error) { + throw error + } + } + } +} diff --git a/src/main/service/option/index.ts b/src/main/service/option/index.ts new file mode 100644 index 0000000..cbd5b49 --- /dev/null +++ b/src/main/service/option/index.ts @@ -0,0 +1,20 @@ +import { OptionType } from '@/define/enum/option' +import { OptionOptions } from './optionOptions' + +class OptionService { + optionOptions: OptionOptions + constructor() { + this.optionOptions = new OptionOptions() + } + + //#region 和数据库的option操作 + /** 获取指定的Option,通过key,不存在返回null */ + GetOptionByKey = async (key: string) => await this.optionOptions.GetOptionByKey(key) + /** 修改指定的Option,通过key,不存在则创建 */ + ModifyOptionByKey = async (key: string, value: string, type: OptionType) => + await this.optionOptions.ModifyOptionByKey(key, value, type) + + //#endregion +} + +export default new OptionService() diff --git a/src/main/service/option/optionCommonService.ts b/src/main/service/option/optionCommonService.ts new file mode 100644 index 0000000..e9cd71b --- /dev/null +++ b/src/main/service/option/optionCommonService.ts @@ -0,0 +1,34 @@ +import { OptionRealmService } from '@/define/db/service/optionService' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from './optionSerialization' +import { SettingModal } from '@/define/model/setting' + +/** + * 获取当前项目的路径 + * @returns + */ +export async function getProjectPath() { + const optionRealmService = await OptionRealmService.getInstance() + let projectOption = optionRealmService.GetOptionByKey(OptionKeyName.Software.ProjectPath) + let projectPath: string = optionSerialization( + projectOption, + '‘设置-> 通用设置 -> 项目地址’' + ) as string + return projectPath +} + +/** + * 获取软的通用设置 + * @returns + */ +export async function getGeneralSetting() { + const optionRealmService = await OptionRealmService.getInstance() + let generalSettingOption = optionRealmService.GetOptionByKey( + OptionKeyName.Software.GeneralSetting + ) + let generalSetting: SettingModal.GeneralSettings = optionSerialization( + generalSettingOption, + '‘设置-> 通用设置’' + ) as SettingModal.GeneralSettings + return generalSetting +} diff --git a/src/main/service/option/optionOptions.ts b/src/main/service/option/optionOptions.ts new file mode 100644 index 0000000..515aa97 --- /dev/null +++ b/src/main/service/option/optionOptions.ts @@ -0,0 +1,58 @@ +import { OptionRealmService } from '@/define/db/service/optionService' +import { OptionType } from '@/define/enum/option' +import { ErrorItem, SuccessItem } from '@/define/model/generalResponse' +import { errorMessage, successMessage } from '@/public/generalTools' +export class OptionOptions { + optionRealmService!: OptionRealmService + constructor() {} + + /** 初始化数据库服务 */ + async InitService() { + if (!this.optionRealmService) { + this.optionRealmService = await OptionRealmService.getInstance() + } + } + + /** + * 获取指定的Option,通过key,不存在返回null + * @param key + * @returns + */ + public async GetOptionByKey(key: string): Promise { + try { + await this.InitService() + let res = this.optionRealmService.GetOptionByKey(key) + return successMessage(res, '获取成功 OptionKey: ' + key, 'OptionOptions.GetOptionByKey') + } catch (error: any) { + return errorMessage( + '获取失败 OptionKey: ' + key + ',失败信息如下 : ' + error.message, + 'OptionOptions.GetOptionByKey' + ) + } + } + + /** + * 修改指定的Option,通过key,不存在则创建 + * @param key + * @param value + */ + public async ModifyOptionByKey( + key: string, + value: string, + type: OptionType + ): Promise { + try { + await this.InitService() + if (type == OptionType.BOOLEAN) { + value = value.toString() + } + let res = this.optionRealmService.ModifyOptionByKey(key, value, type) + return successMessage(res, '修改成功 OptionKey: ' + key, 'OptionOptions.ModifyOptionByKey') + } catch (error: any) { + return errorMessage( + `修改失败 OptionKey: ${key} , 失败信息如下: ${error.message}`, + 'OptionOptions.ModifyOptionByKey' + ) + } + } +} diff --git a/src/main/service/option/optionSerialization.ts b/src/main/service/option/optionSerialization.ts new file mode 100644 index 0000000..6b740a1 --- /dev/null +++ b/src/main/service/option/optionSerialization.ts @@ -0,0 +1,66 @@ +import { getOptionType, OptionType } from '@/define/enum/option' +import { isEmpty } from 'lodash' + +/** + * 将字符串转换为指定类型的值 + * @param value 要转换的字符串值 + * @param type 目标类型 ('string'|'number'|'boolean'|'json') + * @returns 转换后的值 + */ +export function convertStringToType(value: string, type: OptionType, checkString?: string): T { + let checkErrorString = '请到 ' + checkString + ' 检查设置!' + // 如果值为空,直接报错 + if (value === undefined || value === null || value === '') { + throw new Error('当前值为空!' + checkString ? checkErrorString : '') + } + + try { + switch (type.toLowerCase()) { + case 'string': + return value as unknown as T + case 'number': + const num = Number(value) + if (isNaN(num)) { + throw new Error( + `Cannot convert "${value}" to number, ${checkString ? checkErrorString : ''}` + ) + } + return num as unknown as T + case 'boolean': + return (value.toLowerCase() === 'true' || value === '1') as unknown as T + case 'json': + try { + return JSON.parse(value) as T + } catch (e) { + throw new Error(`Invalid JSON string: ${value}, ${checkString ? checkErrorString : ''}`) + } + default: + throw new Error(`Unsupported type: ${type}`) + } + } catch (error) { + throw error + } +} + +/** + * 将选项对象的值转换为指定类型 + * @param option 选项对象 + * @param defaultValue 默认值,当值为空时返回的默认值 + * @returns + */ +export const optionSerialization = ( + option: OptionModel.OptionModel | null, + checkString?: string, + defaultValue?: T +): T => { + if (option == null) { + throw new Error('未找到选项对象,请检查所有的选项设置是否存在!') + } + if (option.value == null || option.value == undefined || isEmpty(option.value)) { + throw new Error('option value is null') + } + if (Number.isFinite(option.type)) { + option.type = getOptionType(option.type as number) + } + return convertStringToType(option.value, option.type as OptionType, checkString) +} diff --git a/src/main/service/preset/index.ts b/src/main/service/preset/index.ts new file mode 100644 index 0000000..4a2c9a9 --- /dev/null +++ b/src/main/service/preset/index.ts @@ -0,0 +1,28 @@ +import { PresetModel } from '@/define/model/preset' +import { PresetServiceHandle } from './presetServiceHandle' + +class PresetHandle { + presetServiceHandle: PresetServiceHandle + constructor() { + this.presetServiceHandle = new PresetServiceHandle() + } + + /** 获取预设数据 */ + GetPresetByCondition = async (queryCondition: PresetModel.Preset) => + await this.presetServiceHandle.GetPresetByCondition(queryCondition) + + /** 通过ID获取预设 */ + GetPresetById = async (id: string) => await this.presetServiceHandle.GetPresetById(id) + + /** 添加预设 */ + AddPreset = async (preset: PresetModel.Preset) => await this.presetServiceHandle.AddPreset(preset) + + /** 修改预设 */ + ModifyPreset = async (id: string, preset: Partial) => + await this.presetServiceHandle.ModifyPreset(id, preset) + + /** 删除预设 */ + DeletePreset = async (id: string) => await this.presetServiceHandle.DeletePreset(id) +} + +export const presetHandle = new PresetHandle() diff --git a/src/main/service/preset/presetBasic.ts b/src/main/service/preset/presetBasic.ts new file mode 100644 index 0000000..371896c --- /dev/null +++ b/src/main/service/preset/presetBasic.ts @@ -0,0 +1,19 @@ +import { PresetRealmService } from '@/define/db/service/presetService' + +export class PresetBasic { + presetRealmService!: PresetRealmService + constructor() {} + + /** + * 初始化书籍服务处理器 + * + * 异步获取 PresetRealmService 的单例并将其赋值给 presetRealmService 属性 + * @private + * @async + */ + async InitPresetBasic() { + // 如果 presetRealmService 已经初始化,则直接返回 + if (this.presetRealmService) return + this.presetRealmService = await PresetRealmService.getInstance() + } +} diff --git a/src/main/service/preset/presetBasicService.ts b/src/main/service/preset/presetBasicService.ts new file mode 100644 index 0000000..4e2f3ca --- /dev/null +++ b/src/main/service/preset/presetBasicService.ts @@ -0,0 +1,78 @@ +import { PresetCategory } from '@/define/data/presetData' +import { PresetBasic } from './presetBasic' +import { PromptMergeType } from '@/define/enum/bookEnum' +import { isEmpty } from 'lodash' + +export class PresetBasicService extends PresetBasic { + constructor() { + super() + } + + /** + * 获取场景的提示词 + * @param ids 需要获取的IDs + * @returns + */ + async GetScenePresetStringByIds(ids: string[]): Promise { + await this.InitPresetBasic() + let sceneString = '' + for (let i = 0; i < ids.length; i++) { + const id = ids[i] + let scenes = this.presetRealmService.GetPresetByCondition({ + id: id, + type: PresetCategory.Scene, + isShow: true + }) + if (scenes.presetArray.length <= 0) { + continue + } + + sceneString += scenes.presetArray[0].prompt + ', ' + } + return sceneString + } + + /** + * 获取人物提示词,包括垫图链接 + * @param ids 需要获取的IDs + * @returns + */ + async GetCharacterPresetStringByIds( + ids: string[], + type: PromptMergeType = PromptMergeType.MJ_MERGE + ): Promise<{ characterString: string; characterUrl: string }> { + await this.InitPresetBasic() + let characterString = '' + let characterUrl = '' + let crefCw = '' + let characters = this.presetRealmService.GetPresetByIds(ids, PresetCategory.Character) + if (type == PromptMergeType.MJ_MERGE) { + for (let i = 0; i < characters.length; i++) { + const element = characters[i] + characterString += element.prompt + ', ' + if (element.imageUrl && element.imageUrl != '' && element.crefCw) { + characterUrl += ` ${element.imageUrl} ` + crefCw = (element.crefCw ?? 20).toString() + } + } + + //这边坐下合并 + if (characterUrl != '') { + characterUrl = ` --cref ${characterUrl} --cw ${crefCw}` + } + return { characterString, characterUrl } + } else if (type == PromptMergeType.SD_MERGE) { + let result = '' + for (let i = 0; i < characters.length; i++) { + let character = characters[i] + result += character.prompt + ', ' + if (!isEmpty(character.lora) && character.lora != '无') { + result += `, ` + } + } + return { characterString: result, characterUrl: '' } + } else { + throw new Error('不支持的合并类型') + } + } +} diff --git a/src/main/service/preset/presetServiceHandle.ts b/src/main/service/preset/presetServiceHandle.ts new file mode 100644 index 0000000..ce702d9 --- /dev/null +++ b/src/main/service/preset/presetServiceHandle.ts @@ -0,0 +1,201 @@ +import { define } from '@/define/define' +import { ErrorItem, SuccessItem } from '@/define/model/generalResponse' +import { PresetModel } from '@/define/model/preset' +import { JoinPath } from '@/define/Tools/file' +import { Base64ToFile, GetImageTypeFromBase64 } from '@/define/Tools/image' +import { errorMessage, successMessage } from '@/public/generalTools' +import path from 'path' +import { PresetBasic } from './presetBasic' + +/** + * 预设服务处理器类 + * + * 该类作为预设服务的处理层,封装了对底层 PresetRealmService 的调用, + * 提供了预设信息的添加、修改等功能接口。处理器会自动初始化 + * PresetRealmService 实例,并对各种操作进行错误处理,返回标准化的 + * 成功或错误响应。 + * + * @class + * */ +export class PresetServiceHandle extends PresetBasic { + constructor() { + super() + } + + /** + * 获取预设数据 + * + * 异步调用 PresetRealmService 的 GetPresetByCondition 方法,传入查询条件对象。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {PresetModel.QueryPresetCondition} queryCondition - 查询条件对象 + * @return {Promise} - 返回操作结果 + * @async + * */ + async GetPresetByCondition( + queryCondition: PresetModel.QueryPresetCondition + ): Promise { + try { + await this.InitPresetBasic() + let res = this.presetRealmService.GetPresetByCondition(queryCondition) + return successMessage(res, '获取预设成功!', 'PresetServiceHandle_GetPresetByCondition') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `获取预设失败,失败原因如下:${error.message}`, + 'PresetServiceHandle_GetPresetByCondition' + ) + } + } + + /** + * 通过ID获取预设数据 + * + * 异步调用 PresetRealmService 的 GetPresetById 方法,传入预设ID。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} id - 预设ID + * @return {Promise} - 返回操作结果 + * @async + * */ + async GetPresetById(id: string): Promise { + try { + await this.InitPresetBasic() + let res = this.presetRealmService.GetPresetById(id) + return successMessage(res, '获取预设成功!', 'PresetServiceHandle_GetPresetById') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `获取预设失败,失败原因如下:${error.message}`, + 'PresetServiceHandle_GetPresetById' + ) + } + } + + /** + * 添加预设 + * + * 异步调用 PresetRealmService 的 AddPreset 方法,传入预设对象。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {PresetModel.Preset} preset - 预设对象 + * @return {Promise} - 返回操作结果 + * @async + * */ + async AddPreset(preset: PresetModel.Preset): Promise { + try { + await this.InitPresetBasic() + + // 判断图片,然后将图片存放在指定目录下,然后获取图片的相对路径 + if (preset.showImage && preset.showImage.length > 0) { + let iamges: string[] = [] + for (let i = 0; i < preset.showImage.length; i++) { + const element = preset.showImage[i] + // 判断base64是什么图片类型 + let ext = GetImageTypeFromBase64(element) + if (ext != '.png' && ext != '.jpg' && ext != '.webp') { + throw new Error('图片格式不合法!') + } + + let imagePath = JoinPath( + define.image_path, + `预设/${new Date().getTime()}_${crypto.randomUUID()}${ext}` + ) as string + await Base64ToFile(element, imagePath) + let iamgesPath = path.relative(__dirname, imagePath) + iamges.push(iamgesPath) + } + preset.showImage = iamges + } + + let res = this.presetRealmService.AddPreset(preset) + return successMessage(res, '添加预设成功!', 'PresetServiceHandle_AddPreset') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `添加预设失败,失败原因如下:${error.message}`, + 'PresetServiceHandle_AddPreset' + ) + } + } + + /** + * 修改预设 + * + * 异步调用 PresetRealmService 的 ModifyPreset 方法,传入预设ID和预设对象。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} id - 预设ID + * @param {Partial} preset - 预设对象 + * @return {Promise} - 返回操作结果 + * @async + * */ + async ModifyPreset( + id: string, + preset: Partial + ): Promise { + try { + await this.InitPresetBasic() + + // 判断图片是不是base64,如果是base64就进行处理 + if (preset.showImage && preset.showImage.length > 0) { + let iamges: string[] = [] + for (let i = 0; i < preset.showImage.length; i++) { + const element = preset.showImage[i] + if (element.startsWith('data:')) { + // 判断base64是什么图片类型 + let ext = GetImageTypeFromBase64(element) + if (ext != '.png' && ext != '.jpg' && ext != '.webp') { + throw new Error('图片格式不合法!') + } + + let imagePath = JoinPath( + define.image_path, + `预设/${new Date().getTime()}_${crypto.randomUUID()}${ext}` + ) as string + await Base64ToFile(element, imagePath) + let iamgesPath = path.relative(__dirname, imagePath) + iamges.push(iamgesPath) + } else if (element.startsWith('file://')) { + // 如果是文件路径,就直接获取相对路径 + let iamgesPath = element.replace('file://', '') + iamgesPath = iamgesPath.replace(/\?.*$/, '') + iamgesPath = path.relative(__dirname, iamgesPath) + iamges.push(iamgesPath) + } else { + let iamgesPath = path.relative(__dirname, element) + iamges.push(iamgesPath) + } + } + preset.showImage = iamges + } + let res = this.presetRealmService.ModifyPreset(id, preset) + return successMessage(res, '修改预设成功!', 'PresetServiceHandle_ModifyPreset') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `修改预设失败,失败原因如下:${error.message}`, + 'PresetServiceHandle_ModifyPreset' + ) + } + } + + /** + * 删除预设 + * + * 异步调用 PresetRealmService 的 DeletePreset 方法,传入预设ID。 + * 成功时返回标准化的成功响应,失败时返回标准化的错误响应。 + * @param {string} id - 预设ID + * @return {Promise} - 返回操作结果 + * @async + * */ + async DeletePreset(id: string): Promise { + try { + await this.InitPresetBasic() + this.presetRealmService.DeletePreset(id) + return successMessage(null, '删除预设成功!', 'PresetServiceHandle_DeletePreset') + } catch (error: any) { + // 处理错误,返回错误信息 + return errorMessage( + `删除预设失败,失败原因如下:${error.message}`, + 'PresetServiceHandle_DeletePreset' + ) + } + } +} diff --git a/src/main/service/sd/index.ts b/src/main/service/sd/index.ts new file mode 100644 index 0000000..cf75983 --- /dev/null +++ b/src/main/service/sd/index.ts @@ -0,0 +1,11 @@ +import { TaskModal } from '@/define/model/task' +import { SDServiceHandle } from './sdServiceHandle' +export class SDHandle { + sdServiceHandle: SDServiceHandle + constructor() { + this.sdServiceHandle = new SDServiceHandle() + } + + /** 使用Stable Diffusion生成图像 */ + SDImageGenerate = async (task: TaskModal.Task) => await this.sdServiceHandle.SDImageGenerate(task) +} diff --git a/src/main/service/sd/sdBasic.ts b/src/main/service/sd/sdBasic.ts new file mode 100644 index 0000000..5f62cd3 --- /dev/null +++ b/src/main/service/sd/sdBasic.ts @@ -0,0 +1,127 @@ +import { OptionRealmService } from '@/define/db/service/optionService' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../option/optionSerialization' +import { SettingModal } from '@/define/model/setting' +import { BookTaskDetailService } from '@/define/db/service/book/bookTaskDetailService' +import { BookTaskService } from '@/define/db/service/book/bookTaskService' +import { BookService } from '@/define/db/service/book/bookService' +import { TaskListService } from '@/define/db/service/book/taskListService' +import { PresetRealmService } from '@/define/db/service/presetService' + +export class SDBasic { + optionRealmService!: OptionRealmService + presetRealmService!: PresetRealmService + sdImageSetting!: SettingModal.SDSettings + sdADetailerSetting!: SettingModal.SDADetailerModel[] + bookTaskDetailService!: BookTaskDetailService + taskListService!: TaskListService + bookService!: BookService + bookTaskService!: BookTaskService + + adetailerParam: any[] = [] + constructor() {} + + /** + * 初始化SDBasic类的基础服务 + * + * 该方法确保OptionRealmService实例已经正确初始化,为后续SD相关操作奠定基础。 + * 只有在optionRealmService未初始化的情况下才会执行获取实例操作,避免重复初始化。 + * 这是类中其他方法调用的前置步骤,确保数据库服务可用。 + * + * @returns {Promise} 无返回值,完成后optionRealmService将可用 + * @throws {Error} 如果OptionRealmService实例获取失败可能抛出错误 + * + * @example + * // 使用示例 + * const sdBasic = new SDBasic(); + * await sdBasic.InitSDBasic(); + * // 现在sdBasic.optionRealmService已准备就绪 + */ + async InitSDBasic(): Promise { + // 如果 optionRealmService 已经初始化,则直接返回 + if (!this.optionRealmService) { + this.optionRealmService = await OptionRealmService.getInstance() + } + if (!this.bookTaskDetailService) { + this.bookTaskDetailService = await BookTaskDetailService.getInstance() + } + if (!this.bookTaskService) { + this.bookTaskService = await BookTaskService.getInstance() + } + if (!this.bookService) { + this.bookService = await BookService.getInstance() + } + if (!this.taskListService) { + this.taskListService = await TaskListService.getInstance() + } + if (!this.presetRealmService) { + this.presetRealmService = await PresetRealmService.getInstance() + } + } + + /** + * 获取Stable Diffusion图像生成设置 + * + * 该方法从数据库中读取并初始化SD图像生成相关设置,包括模型选择、分辨率、采样方法、 + * 步数等配置参数。这些参数会影响图像生成的质量和风格。方法执行时会先确保服务已正确 + * 初始化,然后获取设置并反序列化为强类型对象。 + * + * 处理流程: + * 1. 确保OptionRealmService服务已初始化 + * 2. 从数据库中获取SD图像设置配置项 + * 3. 将配置反序列化为SDSettings对象 + * 4. 更新类的sdImageSetting属性 + * + * @returns {Promise} 无返回值,仅更新类的内部属性 + * @throws {Error} 如果数据库操作失败或配置无效可能抛出错误 + * + * @example + * // 初始化并获取SD图像设置 + * const sdBasic = new SDBasic(); + * await sdBasic.GetSDImageSetting(); + * console.log(sdBasic.sdImageSetting); // 输出当前SD图像设置 + */ + async GetSDImageSetting(): Promise { + await this.InitSDBasic() + let sdImageSetting = this.optionRealmService.GetOptionByKey(OptionKeyName.SD.SDImageSetting) + this.sdImageSetting = optionSerialization(sdImageSetting) + } + + /** + * 获取Stable Diffusion的ADetailer增强设置 + * + * 该方法从数据库中读取SD的ADetailer相关设置,这些设置用于图像细节增强处理。 + * ADetailer是SD生态中用于自动检测和修复图像中面部、手部等特定区域的工具。 + * 方法会确保服务已初始化,然后从数据库获取设置并反序列化为强类型对象。 + * + * 处理流程: + * 1. 确保OptionRealmService服务已初始化 + * 2. 从数据库中获取SD的ADetailer设置配置项 + * 3. 将配置反序列化为SDADetailerModel对象 + * 4. 更新类的sdADetailerSetting属性 + * + * @returns {Promise} 无返回值,仅更新类的内部属性 + * @throws {Error} 如果数据库操作失败或配置无效可能抛出错误 + * + * @example + * // 初始化并获取SD的ADetailer设置 + * const sdBasic = new SDBasic(); + * await sdBasic.GetSDADetailerSetting(); + * console.log(sdBasic.sdADetailerSetting); // 输出当前ADetailer设置 + */ + async GetSDADetailerSetting(): Promise { + await this.InitSDBasic() + let sdADetailerSetting = this.optionRealmService.GetOptionByKey( + OptionKeyName.SD.SDADetailerSetting + ) + this.sdADetailerSetting = + optionSerialization(sdADetailerSetting) + + this.sdADetailerSetting.forEach((item) => { + this.adetailerParam.push({ + ad_confidence: item.threshold, + ad_model: item.model + }) + }) + } +} diff --git a/src/main/service/sd/sdServiceHandle.ts b/src/main/service/sd/sdServiceHandle.ts new file mode 100644 index 0000000..345b460 --- /dev/null +++ b/src/main/service/sd/sdServiceHandle.ts @@ -0,0 +1,459 @@ +import { + BookBackTaskStatus, + BookTaskStatus, + BookType, + MJAction, + OperateBookType, + PromptMergeType +} from '@/define/enum/bookEnum' +import { SDBasic } from './sdBasic' +import { ErrorItem, GeneralResponse, SuccessItem } from '@/define/model/generalResponse' +import { Book } from '@/define/model/book/book' +import { isEmpty } from 'lodash' +import { PresetCategory } from '@/define/data/presetData' +import { PresetBasicService } from '../preset/presetBasicService' +import { + checkStringValueAddSuffix, + errorMessage, + SendReturnMessage, + successMessage +} from '@/public/generalTools' +import { TaskModal } from '@/define/model/task' +import axios from 'axios' +import path from 'path' +import { + CheckFolderExistsOrCreate, + CopyFileOrFolder, + DeleteFileExifData +} from '@/define/Tools/file' +import { Base64ToFile } from '@/define/Tools/image' +import { define } from '@/define/define' +import { getProjectPath } from '../option/optionCommonService' +import { MJRespoonseType } from '@/define/enum/mjEnum' +import { ImageGenerateMode } from '@/define/data/mjData' +import { MJ } from '@/define/model/mj' + +export class SDServiceHandle extends SDBasic { + presetBasicService!: PresetBasicService + constructor() { + super() + this.presetBasicService = new PresetBasicService() + } + + /** + * 合并生成Stable Diffusion图像的提示词 + * + * 该方法根据书籍任务或单个分镜的设置,生成格式化的SD提示词。处理过程包括获取基础设置、 + * 组织风格/场景/角色等元素、按指定顺序合并并应用全局前后缀。合并结果会更新到数据库并返回。 + * + * 合并流程: + * 1. 获取SD图像设置及相关分镜数据 + * 2. 验证分镜是否有基础提示词 + * 3. 按照promptSort指定的顺序(如'style-character-scene-prompt')组织提示词 + * 4. 获取并组合风格标签(含LORA)、场景和角色信息 + * 5. 应用任务级别前后缀和全局SD前缀 + * 6. 更新数据库并返回合并结果 + * + * @param {string} id - 目标ID,可以是书籍任务ID或分镜ID,取决于operateBookType参数 + * @param {OperateBookType} operateBookType - 操作类型,指定id参数代表的是书籍任务还是单个分镜 + * - BOOKTASK: 处理整个书籍任务下的所有分镜 + * - BOOKTASKDETAIL: 仅处理单个指定分镜 + * + * @returns {Promise} + * 成功时返回包含合并后提示词的对象数组,每个对象包含分镜ID和对应的提示词 + * 失败时返回错误信息 + * + * @throws {Error} 当分镜数据不存在、提示词为空或操作类型无效时抛出错误 + * + * @example + * // 为整个书籍任务合并SD提示词 + * const result = await sdServiceHandle.MergeSDPrompt( + * "task-123", + * OperateBookType.BOOKTASK + * ); + * + * // 为单个分镜合并SD提示词 + * const result = await sdServiceHandle.MergeSDPrompt( + * "detail-456", + * OperateBookType.BOOKTASKDETAIL + * ); + */ + async MergeSDPrompt( + id: string, + operateBookType: OperateBookType + ): Promise { + try { + let bookTaskDetail: Book.SelectBookTaskDetail[] = [] + let bookTask: Book.SelectBookTask + await this.GetSDImageSetting() + + if (operateBookType == OperateBookType.BOOKTASK) { + bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ + bookTaskId: id + }) + bookTask = await this.bookTaskService.GetBookTaskDataById(id) + // 判断是不是有为空的 + let emptyName = [] as string[] + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i] + if (isEmpty(element.gptPrompt)) { + emptyName.push(element.name as string) + } + } + if (emptyName.length > 0) { + throw new Error(`${emptyName.join(',')} 的提示词为空,请先推理`) + } + } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + let tempBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id) + if (tempBookTaskDetail == null) { + throw new Error('未找到对应的分镜') + } + if (isEmpty(tempBookTaskDetail.gptPrompt)) { + throw new Error('当前分镜没有推理提示词,请先生成') + } + bookTaskDetail = [tempBookTaskDetail] + bookTask = await this.bookTaskService.GetBookTaskDataById( + bookTaskDetail[0].bookTaskId as string + ) + } else { + throw new Error('未知的合并类型') + } + let book = await this.bookService.GetBookDataById(bookTask.bookId as string) + if (book == null) { + throw new Error('未找到对应的小说') + } + + // 获取SD的通用前缀 + + let sdGlobalPrompt = this.sdImageSetting.positivePrompt + let result: any[] = [] // 返回前端的数据数组 + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i] + // 没有推理提示词,直接跳过 + if (isEmpty(element.gptPrompt)) { + continue + } + + let defaultPromptSort = element.promptSort ?? 'style-character-scene-prompt' + let promptSort = defaultPromptSort.split('-') + let promptStr = '' + for (let i = 0; i < promptSort.length; i++) { + const element = promptSort[i] + promptStr += `${'${' + element + '}'} ` + } + + let styleString = '' + // 拿到所有的风格 + let styleArr = this.presetRealmService.GetPresetByIds( + element.styleTags ?? [], + PresetCategory.Style + ) + + for (let i = 0; i < styleArr.length; i++) { + const element = styleArr[i] + + if (!isEmpty(element.prompt)) { + styleString += `${element.prompt}` + ', ' + } + if (element.lora && element.lora != '无') { + styleString += `, ` + } + } + + let sceneString = '' // 场景 + let characterString = '' // 人物 + + // 只有原创才需要获取人物和场景 + if (book.type == BookType.ORIGINAL) { + // 这边获取对应的人物和场景 + let sceneIds = element.sceneTags ? element.sceneTags : [] + let characterIds = element.characterTags ? element.characterTags : [] + if (sceneIds && sceneIds.length > 0) { + sceneString = await this.presetBasicService.GetScenePresetStringByIds(sceneIds) + } + if (characterIds && characterIds.length > 0) { + let res = await this.presetBasicService.GetCharacterPresetStringByIds( + characterIds, + PromptMergeType.SD_MERGE + ) + characterString = res.characterString + } + } + + // 开始合并 + promptStr = promptStr.replace('${style}', styleString) // 风格 + promptStr = promptStr.replace('${character}', characterString) // 人物 + promptStr = promptStr.replace('${scene}', sceneString) // 场景 + promptStr = promptStr.replace( + '${prompt}', + checkStringValueAddSuffix(element.gptPrompt as string, ',') + ) + + // 判断是不是需要加前后缀 + if (bookTask.prefixPrompt) { + promptStr = checkStringValueAddSuffix(bookTask.prefixPrompt, ',') + promptStr + } + if (bookTask.suffixPrompt) { + promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.suffixPrompt + } + if (sdGlobalPrompt) { + promptStr = checkStringValueAddSuffix(sdGlobalPrompt, ',') + promptStr + } + + promptStr = ' ' + promptStr + console.log(promptStr) + // 修改数据库数据 + await this.bookTaskDetailService.ModifyBookTaskDetailById(element.id as string, { + prompt: promptStr + }) + // 写回数据 + result.push({ + id: element.id, + prompt: promptStr + }) + } + + return successMessage(result, 'SD和并提示词数据成功', 'SDOpt_MergePrompt') + } catch (error: any) { + return errorMessage('SD合并提示词,错误信息如下:' + error.toString(), 'SDOpt_MergePrompt') + } + } + + /** + * 使用Stable Diffusion生成图像 + * + * 该方法将一个分镜任务提交给Stable Diffusion API进行图像生成。处理流程包括合并提示词、 + * 配置生成参数、调用SD API、保存生成的图像并更新数据库信息。支持ADetailer修复人脸和手部。 + * + * 处理流程: + * 1. 获取SD图像设置和相关分镜数据 + * 2. 合并提示词(调用MergeSDPrompt方法) + * 3. 准备API请求参数(提示词、反向提示词、分辨率等) + * 4. 根据设置添加ADetailer人脸/手部修复 + * 5. 发送请求到SD API并获取生成的图像 + * 6. 将图像保存到项目目录(原始图、输出图、子图等) + * 7. 更新数据库中的图像路径和任务状态 + * 8. 发送完成或错误消息 + * + * @param {TaskModal.Task} task - 包含生成任务信息的对象,必须包含bookTaskDetailId字段 + * + * @returns {Promise} 无显式返回值,操作结果通过消息通知和数据库更新反映 + * + * @throws {Error} 当找不到分镜数据、提示词合并失败或API请求错误时抛出 + * + * @example + * // 为指定分镜生成SD图像 + * await sdServiceHandle.SDImageGenerate({ + * id: "task-001", + * bookTaskDetailId: "detail-123", + * messageName: "generateImage", + * type: "SD_IMAGE" + * }); + */ + async SDImageGenerate(task: TaskModal.Task): Promise { + let sdSetting = undefined + try { + // 开始生图 + await this.GetSDImageSetting() + let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById( + task.bookTaskDetailId as string + ) + if (bookTaskDetail == null) { + throw new Error('未找到对应的分镜') + } + let bookTask = await this.bookTaskService.GetBookTaskDataById( + bookTaskDetail.bookTaskId as string + ) + let book = await this.bookService.GetBookDataById(bookTask.bookId as string) + if (book == null) { + throw new Error('未找到对应的小说') + } + + // 调用方法合并提示词 + let mergeRes = await this.MergeSDPrompt( + task.bookTaskDetailId as string, + OperateBookType.BOOKTASKDETAIL + ) + if (mergeRes.code == 0) { + throw new Error(mergeRes.message) + } + // 获取提示词 + bookTaskDetail.prompt = mergeRes.data[0].prompt + + let prompt = bookTaskDetail.prompt + let url = this.sdImageSetting.requestUrl + if (url.endsWith('/')) { + url = url + 'sdapi/v1/txt2img' + } else { + url = url + '/sdapi/v1/txt2img' + } + + // 替换url中的localhost为127.0.0.1 + url = url.replace('localhost', '127.0.0.1') + + await this.GetSDADetailerSetting() + // 判断当前是不是有开修脸修手 + let ADetailer = { + args: this.adetailerParam + } + + // 种子默认 -1,随机 + let seed = -1 + let body = { + prompt: prompt, + negative_prompt: this.sdImageSetting.negativePrompt, + seed: seed, + sampler_name: this.sdImageSetting.sampler, + // 提示词相关性 + cfg_scale: this.sdImageSetting.cfgScale, + width: this.sdImageSetting.width, + height: this.sdImageSetting.height, + batch_size: this.sdImageSetting.batchCount, + n_iter: 1, + steps: this.sdImageSetting.steps, + save_images: false + } + if (bookTaskDetail.adetailer) { + body['alwayson_scripts'] = { + ADetailer: ADetailer + } + } + + const response = await axios.post(url, body) + let images = response.data.images + let SdOriginalImage = path.join(book.bookFolderPath as string, 'data/SdOriginalImage') + await CheckFolderExistsOrCreate(SdOriginalImage) + let outputFolder = bookTask.imageFolder + await CheckFolderExistsOrCreate(outputFolder) + let inputFolder = path.join(book.bookFolderPath as string, 'tmp/input') + await CheckFolderExistsOrCreate(inputFolder) + + let subImagePath: string[] = [] + let outImagePath = '' + // 开始写出图片 + for (let i = 0; i < images.length; i++) { + const element = images[i] + // 包含info信息的图片地址 + let infoImgPath = path.join( + SdOriginalImage, + `info_${bookTaskDetail.name}_${new Date().getTime()}_${i}.png` + ) + // 不包含info信息的图片地址 + let imgPath = path.join( + SdOriginalImage, + `${bookTaskDetail.name}_${new Date().getTime()}_${i}.png` + ) + await Base64ToFile(element, infoImgPath) + // 这边去图片信息 + await DeleteFileExifData( + path.join(define.package_path, 'exittool/exiftool.exe'), + infoImgPath, + imgPath + ) + // 写出去 + if (bookTask.name == 'output_00001' && book.type == BookType.ORIGINAL) { + // 复制一个到input + let inputImgPath = path.join(inputFolder, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(imgPath, inputImgPath) + } + if (i == 0) { + // 复制到对应的文件夹里面 + let outPath = path.join(outputFolder as string, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(imgPath, outPath) + outImagePath = outPath + } + subImagePath.push(imgPath) + } + let projectPath = await getProjectPath() + // 修改数据库 + await this.bookTaskDetailService.ModifyBookTaskDetailById(bookTaskDetail.id as string, { + outImagePath: path.relative(projectPath, outImagePath), + subImagePath: subImagePath.map((item) => path.relative(projectPath, item)) + }) + this.taskListService.UpdateTaskStatus({ + id: task.id as string, + status: BookBackTaskStatus.DONE + }) + let resp = { + code: 1, + id: bookTaskDetail.id as string, + type: MJRespoonseType.FINISHED, + mjType: MJAction.IMAGINE, + mjApiUrl: url, + progress: 100, + category: ImageGenerateMode.LOCAL_SD, + imageClick: subImagePath.join(','), + imageShow: subImagePath.join(','), + messageId: subImagePath.join(','), + action: MJAction.IMAGINE, + status: 'success', + outImagePath: outImagePath + '?t=' + new Date().getTime(), + subImagePath: subImagePath.map((item) => item + '?t=' + new Date().getTime()), + message: 'SD生成图片成功' + } as MJ.MJResponseToFront + this.bookTaskDetailService.UpdateBookTaskDetailMjMessage( + task.bookTaskDetailId as string, + resp + ) + SendReturnMessage( + { + code: 1, + message: 'SD生成图片成功', + id: bookTaskDetail.id as string, + data: { + ...resp + } + }, + task.messageName as string + ) + return successMessage(resp, 'SD生成图片成功', 'SDServiceHandle_SDImageGenerate') + } catch (error: any) { + let errorMsg = 'SD生成图片失败,错误信息如下:' + error.toString() + this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId as string, { + mjApiUrl: sdSetting ? this.sdImageSetting.requestUrl : '', + progress: 0, + category: ImageGenerateMode.LOCAL_SD, + imageClick: '', + imageShow: '', + messageId: '', + action: MJAction.IMAGINE, + status: 'error', + message: errorMsg + }) + + await this.bookTaskDetailService.ModifyBookTaskDetailById(task.bookTaskDetailId as string, { + status: BookTaskStatus.IMAGE_FAIL + }) + + this.taskListService.UpdateTaskStatus({ + id: task.id as string, + status: BookBackTaskStatus.FAIL, + errorMessage: errorMsg + }) + SendReturnMessage( + { + code: 0, + message: errorMsg, + id: task.bookTaskDetailId as string, + data: { + code: 0, + id: task.bookTaskDetailId as string, + type: MJRespoonseType.FINISHED, + mjType: MJAction.IMAGINE, + mjApiUrl: sdSetting ? this.sdImageSetting.requestUrl : '', + progress: 0, + category: ImageGenerateMode.LOCAL_SD, + imageClick: '', + imageShow: '', + messageId: '', + action: MJAction.IMAGINE, + status: 'error', + message: errorMsg + } as MJ.MJResponseToFront + }, + task.messageName as string + ) + return errorMessage(errorMsg, 'SDServiceHandle_SDImageGenerate') + } + } +} diff --git a/src/main/service/setting/index.ts b/src/main/service/setting/index.ts new file mode 100644 index 0000000..c99c51d --- /dev/null +++ b/src/main/service/setting/index.ts @@ -0,0 +1,15 @@ +import { SettingService } from './settingService' + +class SettingHandle { + settingService: SettingService + constructor() { + this.settingService = new SettingService() + } + //#region 剪映设置 + /** 获取默认的剪映草稿地址 */ + GetDefaultJianyingDraftPath = async () => await this.settingService.GetDefaultJianyingDraftPath() + + //#endregion +} + +export default new SettingHandle() diff --git a/src/main/service/setting/settingService.ts b/src/main/service/setting/settingService.ts new file mode 100644 index 0000000..e33be7c --- /dev/null +++ b/src/main/service/setting/settingService.ts @@ -0,0 +1,70 @@ +import { errorMessage, successMessage } from '@/public/generalTools' +import { app } from 'electron' +import path from 'path' +import os from 'os' +import { CheckFileOrDirExist } from '@/define/Tools/file' +import fs from 'fs' +import { ValidateJson } from '@/define/Tools/validate' +import { isEmpty } from 'lodash' +import { ErrorItem, SuccessItem } from '@/define/model/generalResponse' + +export class SettingService { + constructor() {} + + /** 初始化数据库服务 */ + async InitService() {} + + //#region 剪映设置 + + /** + * 获取默认的剪映草稿地址 + */ + async GetDefaultJianyingDraftPath(): Promise { + console.log('123') + try { + let appDataPath = app.getPath('appData') + if (process.platform === 'win32') { + const homeDir = os.homedir() + appDataPath = path.join(homeDir, 'AppData', 'Local') + } + + let defaultJianyingDraftPath = path.resolve( + appDataPath, + 'JianyingPro/User Data/Projects/com.lveditor.draft' + ) + let rootMetaInfoPath = path.resolve(defaultJianyingDraftPath, 'root_meta_info.json') + if (!(await CheckFileOrDirExist(rootMetaInfoPath))) { + throw new Error('未找到剪映相关数据,请手动填写或选择') + } + // 读取文件内容,判断是否是剪映的草稿地址 + let fileContent = await fs.promises.readFile(rootMetaInfoPath, 'utf-8') + if (!ValidateJson(fileContent)) { + throw new Error('剪映草稿地址数据错误,请手动填写或选择') + } + let jsonContent = JSON.parse(fileContent) + let all_draft_store = jsonContent.all_draft_store + if (all_draft_store && all_draft_store.length > 0) { + let draft_store = all_draft_store[0] + let draft_root_path = draft_store.draft_root_path + if (draft_root_path && !isEmpty(draft_root_path)) { + return successMessage( + draft_root_path, + '成功', + 'SettingService_GetDefaultJianyingDraftPath' + ) + } else { + throw new Error('剪映草稿地址数据错误,请手动填写或选择') + } + } else { + throw new Error('剪映草稿地址数据错误,请手动填写或选择') + } + } catch (error: any) { + return errorMessage( + '获取默认剪映草稿地址失败, ' + error.message, + 'SettingService_GetDefaultJianyingDraftPath' + ) + } + } + + //#endregion +} diff --git a/src/main/service/system/electronInterface.ts b/src/main/service/system/electronInterface.ts new file mode 100644 index 0000000..f2ca660 --- /dev/null +++ b/src/main/service/system/electronInterface.ts @@ -0,0 +1,245 @@ +import { dialog, nativeTheme, shell } from 'electron' +import { CheckFileOrDirExist, CopyFileOrFolder } from '../../../define/Tools/file' +import path from 'path' +import { errorMessage, successMessage } from '../../../public/generalTools' +import { ErrorItem, SuccessItem } from '@/define/model/generalResponse' + +/** 打开指定的文件夹的方法 */ +export type OpenFolderParams = { + /** 是不是基于项目文件,是的话,会在项目文件夹的基础上进行拼接 */ + baseProject: boolean + /** 判断是不是打开父文件夹 */ + dirFloder: boolean + /** 文件路径,baseProject 为false,需要设置完整的文件路径 */ + folderPath: string +} + +/** 一些对electron接口的封装,配合业务逻辑 */ +export default class ElectronInterface { + constructor() {} + + /** + * 打开指定的文件,试用默认的打开方式 + * @param value + * @returns + */ + public async OpenFile(value: string): Promise { + if (!(await CheckFileOrDirExist(value))) { + return errorMessage('文件/文件夹 不存在', 'SystemIpc_OPEN_FILE') + } + await shell.openPath(value) + return successMessage(null, '打开指定的文件成功', 'SystemIpc_OPEN_FILE') + } + + /** + * 深度复制文件夹内容到目标文件夹 + * @param source 源文件夹路径 + * @param destination 目标文件夹路径 + * @returns + */ + public async CopyFolderContents( + source: string, + destination: string + ): Promise { + try { + // 使用更完善的复制方法 + await CopyFileOrFolder(source, destination, false) + + return successMessage(null, '复制文件夹内容成功', 'SystemIpc_COPY_FOLDER_CONTENTS') + } catch (error: any) { + return errorMessage( + '复制文件夹内容错误,错误信息如下:' + error.message, + 'SystemIpc_COPY_FOLDER_CONTENTS' + ) + } + } + + /** + * 打开对应的文件夹 + * @param params + * @returns + */ + public async OpenFolder(params: OpenFolderParams) { + try { + let openFolder = '' + if (params.baseProject) { + openFolder = path.join(global.config.project_path, params.folderPath) + } + if (params.dirFloder) { + openFolder = path.dirname(params.folderPath) + } + if (!openFolder) { + openFolder = params.folderPath + } + // 判断文件夹是不是存在 + let isExist = await CheckFileOrDirExist(openFolder) + if (!isExist) { + throw new Error('文件夹不存在,请检查') + } + shell.openPath(openFolder) + return successMessage(null, '打开成功') + } catch (error: any) { + return errorMessage('打开文件夹错误,错误信息如下:' + error.message, 'SystemIpc_OPEN_FOLDER') + } + } + + /** + * 选择单个指定文件后缀的文件 + * @param value 后缀列表 + */ + public async SelectSingleFile(value: string[]): Promise { + try { + let { filePaths } = await dialog.showOpenDialog({ + properties: ['openFile'], + filters: [{ name: 'fileName', extensions: value }] + }) + if (filePaths.length === 0) { + throw new Error('没有选择的文件') + } + return successMessage(filePaths[0], '选择文件成功', 'SystemIpc_SelectSingleFile') + } catch (error: any) { + return errorMessage( + '选择文件错误,错误信息如下:' + error.message, + 'SystemIpc_SelectSingleFile' + ) + } + } + + /** + * 选择多个指定文件后缀的文件 + * @param value 文件后缀列表 + * @returns + */ + public async SelectMultipleFile(value: string[]): Promise { + try { + const { filePaths } = await dialog.showOpenDialog({ + properties: ['openFile', 'multiSelections'], + filters: [{ name: 'fileName', extensions: value }] + }) + + if (filePaths.length === 0) { + throw new Error('没有选择的文件') + } + + return successMessage(filePaths, '选择文件成功', 'SystemIpc_SelectMultipleFile') + } catch (error: any) { + console.error('选择文件错误:', error) // 记录错误日志 + return errorMessage( + '选择文件错误,错误信息如下:' + error.message, + 'SystemIpc_SelectMultipleFile' + ) + } + } + + /** + * 切换主题 + * @param theme 主题名称 + * @returns 返回当前是否使用深色模式 + */ + public async ToggleTheme(theme: string) { + if (theme != 'light' && theme != 'dark') { + nativeTheme.themeSource = 'system' + } else { + nativeTheme.themeSource = theme + } + + return nativeTheme.shouldUseDarkColors + } + + /** + * 选择单个文件夹 + * @param defaultPath 可选配置 + * @returns 成功返回选择的文件夹路径,失败返回错误信息 + */ + public async SelectSingleFolder(defaultPath: string): Promise { + try { + const { filePaths } = await dialog.showOpenDialog({ + properties: ['openDirectory'], + defaultPath: defaultPath, + title: '选择文件夹', + buttonLabel: '选择文件夹' + }) + + if (filePaths.length === 0) { + throw new Error('没有选择任何文件夹') + } + + return successMessage(filePaths[0], '选择文件夹成功', 'SystemIpc_SelectSingleFolder') + } catch (error: any) { + return errorMessage( + '选择文件夹错误,错误信息如下:' + error.message, + 'SystemIpc_SelectSingleFolder' + ) + } + } + + /** + * 选择文件夹或指定后缀的文件 + * @param extensions 文件后缀列表(可选) + * @returns + */ + public async SelectFolderOrFile(extensions?: string[]): Promise { + try { + // 使用消息框让用户选择类型 + const choice = await dialog.showMessageBox({ + type: 'question', + title: '选择类型', + message: '请选择要选择的类型:', + buttons: ['选择文件', '选择文件夹', '取消'], + defaultId: 0, + cancelId: 2 + }) + + if (choice.response === 2) { + throw new Error('用户取消选择') + } + + if (choice.response === 0) { + // 选择文件 + const result = await dialog.showOpenDialog({ + properties: ['openFile'], + filters: + extensions && extensions.length > 0 + ? [ + { name: 'Audio Files', extensions }, + { name: 'All Files', extensions: ['*'] } + ] + : [{ name: 'All Files', extensions: ['*'] }], + title: '选择文件' + }) + + if (result.filePaths.length === 0) { + throw new Error('没有选择文件') + } + + return successMessage(result.filePaths[0], '选择文件成功', 'SystemIpc_SelectFolderOrFile') + } else { + // 选择文件夹 + const result = await dialog.showOpenDialog({ + properties: ['openDirectory'], + title: '选择文件夹' + }) + + if (result.filePaths.length === 0) { + throw new Error('没有选择文件夹') + } + + return successMessage(result.filePaths[0], '选择文件夹成功', 'SystemIpc_SelectFolderOrFile') + } + } catch (error: any) { + console.error('选择文件或文件夹错误:', error) + return errorMessage( + '选择文件或文件夹错误,错误信息如下:' + error.message, + 'SystemIpc_SelectFolderOrFile' + ) + } + } + + /** + * 打开指定的URL + * @param url + */ + public OpenUrl(url: string) { + shell.openExternal(url) + } +} diff --git a/src/main/service/system/userSoftware.ts b/src/main/service/system/userSoftware.ts new file mode 100644 index 0000000..c568a00 --- /dev/null +++ b/src/main/service/system/userSoftware.ts @@ -0,0 +1,25 @@ +import { errorMessage } from '@/public/generalTools' + +export class UserSoftware { + constructor() {} + + /** + * 同步授权信息 + * @param authorizationMessage 授权信息 + */ + public async SyncAuthorization(authorizationMessage: SoftwareModal.SoftwareAuthorizationMessage) { + try { + // 直接充数据库获取授权信息 + // let res = await + global.am = authorizationMessage + if (authorizationMessage.useType != 1) { + global.am.isPro = false + } else { + global.am.isPro = true + } + console.log('授权信息', global.am) + } catch (error) { + errorMessage('同步授权信息失败', 'SystemIpc_SyncAuthorization') + } + } +} diff --git a/src/main/service/task/index.ts b/src/main/service/task/index.ts new file mode 100644 index 0000000..41dbcc8 --- /dev/null +++ b/src/main/service/task/index.ts @@ -0,0 +1,47 @@ +import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum' +import { TaskServiceHandle } from './taskServiceHandle' +import { TaskModal } from '@/define/model/task' +import { Book } from '@/define/model/book/book' +export class TaskHandle { + taskServiceHandle: TaskServiceHandle + constructor() { + this.taskServiceHandle = new TaskServiceHandle() + } + + /** 启动任务队列 */ + StartTaskQueue = async (isGiveUp: boolean) => + await this.taskServiceHandle.StartTaskQueue(isGiveUp) + + /** 添加单个个任务 */ + AddOneTask = async ( + bookId: string, + taskType: BookBackTaskType, + executeType: TaskExecuteType = TaskExecuteType.AUTO, + bookTaskId: string | undefined = undefined, + bookTaskDetailId: string | undefined = undefined, + responseMessageName?: string + ) => + await this.taskServiceHandle.AddOneTask( + bookId, + taskType, + executeType, + bookTaskId, + bookTaskDetailId, + responseMessageName + ) + + /** 添加多个任务 */ + AddMultiTask = async (tasks: TaskModal.Task[]) => await this.taskServiceHandle.AddMultiTask(tasks) + + /** 获取指定的状态的任务 */ + GetAssignStatusTaskCount = async (status: BookBackTaskStatus[]) => + await this.taskServiceHandle.GetAssignStatusTaskCount(status) + + /** 获取任务集合 */ + GetTaskCollection = async (queryTaskCondition: TaskModal.QueryTaskCondition) => + await this.taskServiceHandle.GetTaskCollection(queryTaskCondition) + + /** 更新后台任务状态 */ + UpdateTaskStatus = async (bookBackTask: Book.UpdateBookTaskListStatus) => + await this.taskServiceHandle.UpdateTaskStatus(bookBackTask) +} diff --git a/src/main/service/task/taskManage.ts b/src/main/service/task/taskManage.ts new file mode 100644 index 0000000..18735ad --- /dev/null +++ b/src/main/service/task/taskManage.ts @@ -0,0 +1,473 @@ +import { TaskListService } from '@/define/db/service/book/taskListService' +import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum' +import { TaskModal } from '@/define/model/task' +import { SettingModal } from '@/define/model/setting' +import { AsyncQueue } from '@/define/quene' +import { errorMessage, successMessage } from '@/public/generalTools' +import { MJHandle } from '../mj' +import { SDHandle } from '../sd' +import { OptionRealmService } from '@/define/db/service/optionService' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../option/optionSerialization' + +export class TaskManager { + isExecuting: boolean = false + currentTaskList: TaskModal.Task[] = [] + taskListService!: TaskListService + eventListeners: Record = {} + mjSimpleSetting!: SettingModal.MJGeneralSettings + spaceTime: number = 5000 + count = 0 + maxWaitTask: number = 5 // 最大等待任务数 + isListening = false + intervalId: any // 用于存储 setInterval 的 ID + mjHandle: MJHandle + sdHandle: SDHandle + + // reverseBook: ReverseBook = new ReverseBook() + // basicReverse: BasicReverse = new BasicReverse() + // bookVideo: BookVideo + // bookServiceBasic: BookServiceBasic + // videoGlobal: VideoGlobal + + // sdOpt: SDOpt + // comfyUIOpt: ComfyUIOpt + + // d3Opt: D3Opt + // fluxOpt: FluxOpt + + constructor() { + this.isExecuting = false + this.currentTaskList = [] + // this.basicReverse = new BasicReverse() + // this.reverseBook = new ReverseBook() + this.mjHandle = new MJHandle() + this.sdHandle = new SDHandle() + // this.sdOpt = new SDOpt() + // this.comfyUIOpt = new ComfyUIOpt() + // this.d3Opt = new D3Opt() + // this.fluxOpt = new FluxOpt() + } + + async InitService() { + if (!this.taskListService) { + this.taskListService = await TaskListService.getInstance() + } + await this.mjHandle.GetMJGeneralSetting() + this.mjSimpleSetting = this.mjHandle.mjGeneralSetting as SettingModal.MJGeneralSettings + } + + // 初始化事件监听方法 + async InitListeners() { + if (this.isListening) return // 如果已经在监听,直接返回 + + this.isListening = true // 标记为已开始监听 + + // 判断是不是有任务调度 没有的话初始化 + if (!global.taskQueue) { + const optionRealmService = await OptionRealmService.getInstance() + const generalSettingOption = optionRealmService.GetOptionByKey( + OptionKeyName.Software.GeneralSetting + ) + const generalSetting = optionSerialization(generalSettingOption) + + global.taskQueue = new AsyncQueue(global, global.am.isPro ? generalSetting.concurrency : 1) + } + + const executeWithDynamicInterval = async () => { + await this.ExecuteAutoTask() + this.count++ + console.log('等待时间--' + this.spaceTime, this.count) + // 动态调整等待时间 + clearInterval(this.intervalId) + this.intervalId = setInterval(executeWithDynamicInterval, this.spaceTime) + } + this.intervalId = setInterval(executeWithDynamicInterval, this.spaceTime) + } + + // 停止监听的方法 + StopListeners() { + this.isListening = false // 标记为停止监听 + clearInterval(this.intervalId) // 清除定时器 + } + + async ExecuteAutoTask() { + await this.InitService() + + // 加之前先判断是不是还能执行任务 + let waitTask = global.taskQueue.getWaitingQueue() + if (waitTask > this.maxWaitTask) { + // 最懂同时等待十个 + console.log('等待中的任务太多,等待中的任务数量:', waitTask) + this.spaceTime = 20000 + return + } + + // 判断MJ队列是不是存在 + if (!global.mjQueue) { + // MJ 队列不存在,创建 + global.mjQueue = new AsyncQueue(global, global.am.isPro ? this.mjSimpleSetting.taskCount : 3) + } + + // 开始添加 + // 查任务 + const tasks = this.taskListService.GetWaitTaskAndSlice( + TaskExecuteType.AUTO, + this.maxWaitTask - waitTask + ) + + if (!tasks || tasks.length <= 0) { + console.log('没有等待中的任务') + this.spaceTime = 20000 + return + } + + this.spaceTime = 5000 + //循环添加任务 + for (let index = 0; index < tasks.length; index++) { + const element = tasks[index] + if ( + element.type == BookBackTaskType.MJ_IMAGE || + element.type == BookBackTaskType.MJ_REVERSE + ) { + // 判断任务数量是不是又修改 + let taskNumber = global.mjQueue.getConcurrencyLimit() + if (taskNumber != this.mjSimpleSetting.taskCount && global.am.isPro) { + global.mjQueue.concurrencyLimit = this.mjSimpleSetting.taskCount // 重置并发执行的数量 + } + + if (global.mjQueue.getWaitingQueue() > this.maxWaitTask) { + console.log('MJ等待中的任务太多,等待中的任务数量:', global.mjQueue.getWaitingQueue()) + this.spaceTime = 20000 + return + } + // MJ任务 + await this.AddQueue(element) + continue + } else { + // 其他任务 + // 设置并发执行的数量 + await this.AddQueue(element) + } + // 添加完成,修改一下提交时间 // 要判断是否超时 + } + } + + //#region 添加任务到内存任务中 + /** + * 添加分镜计算任务 + * @param task 任务信息 + */ + // AddGetFrameDataTask(task: TaskModal.Task): void { + // let batch = DEFINE_STRING.BOOK.GET_FRAME_DATA + // global.taskQueue.enqueue( + // async () => { + // await this.basicReverse.GetFrameData(task) + // }, + // `${batch}_${task.id}`, + // batch + // ) + // } + + /** + * 添加视频分镜任务 + * @param task 任务信息 + */ + // AddCutVideoData(task: TaskModal.Task): void { + // let batch = DEFINE_STRING.BOOK.FRAMING + // global.taskQueue.enqueue( + // async () => { + // await this.basicReverse.CutVideoData(task) + // }, + // `${batch}_${task.id}`, + // batch + // ) + // } + + /** + * 添加切割视频任务 + * @param task 任务信息 + */ + // AddSplitAudioData(task: TaskModal.Task): void { + // let batch = DEFINE_STRING.BOOK.SPLI_TAUDIO + // global.taskQueue.enqueue( + // async () => { + // await this.basicReverse.SplitAudioData(task) + // }, + // `${batch}_${task.id}`, + // batch + // ) + // } + + /** + * 添加抽帧的任务 + * @param task 任务信息 + */ + // AddGetFrame(task: TaskModal.Task): void { + // let batch = DEFINE_STRING.BOOK.GET_FRAME + // global.taskQueue.enqueue( + // async () => { + // await this.basicReverse.GetFrame(task) + // }, + // `${batch}_${task.id}`, + // batch + // ) + // } + + /** + * 添加识别字幕任务 + * @param task 任务信息 + */ + // AddExtractSubtitlesData(task: TaskModal.Task): void { + // let batch = DEFINE_STRING.BOOK.GET_COPYWRITING + // global.taskQueue.enqueue( + // async () => { + // await this.basicReverse.ExtractSubtitlesData(task) + // }, + // `${batch}_${task.id}`, + // batch + // ) + // } + + /** + * 添加单独反推任务 + * @param task + */ + // AddSingleReversePrompt(task: TaskModal.Task): void { + // let batch = task.messageName + // global.taskQueue.enqueue( + // async () => { + // await this.reverseBook.SingleReversePrompt(task) + // }, + // `${batch}_${task.id}`, + // batch, + // `${batch}_${task.id}_${new Date().getTime()}`, + // this.bookServiceBasic.SetMessageNameTaskToFail + // ) + // } + + /** + * 将MJ生图生成任务添加内存任务中 + * @param task + */ + async AddImageMJImage(task: TaskModal.Task) { + // 判断是不是MJ的任务 + let batch = task.messageName + global.mjQueue.enqueue( + async () => { + await this.mjHandle.MJImagine(task) + }, + `${batch}_${task.id}`, + batch, + `${batch}_${task.id}_${new Date().getTime()}`, + this.taskListService.SetMessageNameTaskToFail.bind(this.taskListService) + ) + } + + // /** + // * 将SD生图任务添加到内存任务中 + // * @param task + // */ + async AddSDImage(task: TaskModal.Task) { + let batch = task.messageName + global.taskQueue.enqueue( + async () => { + await this.sdHandle.SDImageGenerate(task) + }, + `${batch}_${task.id}`, + batch, + `${batch}_${task.id}_${new Date().getTime()}`, + this.taskListService.SetMessageNameTaskToFail.bind(this.taskListService) + ) + } + + // /** + // * 将Comfy UI生图任务添加到内存任务中 + // * @param task + // */ + // async AddComfyUIImage(task: TaskModal.Task) { + // let batch = task.messageName + // global.taskQueue.enqueue( + // async () => { + // await this.comfyUIOpt.ComfyUIImageGenerate(task) + // }, + // `${batch}_${task.id}`, + // batch, + // `${batch}_${task.id}_${new Date().getTime()}`, + // this.taskListService.SetMessageNameTaskToFail + // ) + // } + + // /** + // * 异步添加D3图像生成任务 + // * + // * 此方法接受一个任务对象,将基于该任务生成D3图像 + // * 它使用全局请求队列来管理任务,确保并发处理的效率和稳定性 + // * + // * @param task 任务对象,包含任务的具体信息和标识 + // */ + // async AddD3Image(task: TaskModal.Task) { + // let batch = task.messageName + // global.taskQueue.enqueue( + // async () => { + // await this.d3Opt.D3ImageGenerate(task) + // }, + // `${batch}_${task.id}`, + // batch, + // `${batch}_${task.id}_${new Date().getTime()}`, + // this.taskListService.SetMessageNameTaskToFail + // ) + // } + + /** 添加生成视频的后台任务 */ + // async AddGenerateVideo(task: TaskModal.Task) { + // let batch = task.messageName + // global.taskQueue.enqueue( + // async () => { + // this.bookVideo = new BookVideo() + // await this.bookVideo.GenerateVideo(task) + // }, + // `${batch}_${task.id}`, + // batch, + // `${batch}_${task.id}_${new Date().getTime()}`, + // this.taskListService.SetMessageNameTaskToFail + // ) + // } + + /** 添加图片转视频后台人物 */ + // async AddImageToVideo(task: TaskModal.Task) { + // let batch = task.messageName + // global.taskQueue.enqueue( + // async () => { + // this.videoGlobal = new VideoGlobal() + // await this.videoGlobal.ImageToVideo(task) + // }, + // `${batch}_${task.id}`, + // batch, + // `${batch}_${task.id}_${new Date().getTime()}`, + // this.taskListService.SetMessageNameTaskToFail + // ) + // } + + // /** + // * 添加 flux forge 任务到内存队列中 + // * @param task + // */ + // async AddFluxForgeImage(task: TaskModal.Task) { + // let batch = task.messageName + // global.taskQueue.enqueue( + // async () => { + // await this.fluxOpt.FluxForgeImage(task) + // }, + // `${batch}_${task.id}`, + // batch, + // `${batch}_${task.id}_${new Date().getTime()}`, + // this.taskListService.SetMessageNameTaskToFail + // ) + // } + + // /** + // * 添加 FLUX api 到内存队列中 + // * @param task + // */ + // async AddFluxAPIImage(task: TaskModal.Task) { + // let batch = task.messageName + // global.taskQueue.enqueue( + // async () => { + // await this.fluxOpt.FluxAPIImage(task) + // }, + // `${batch}_${task.id}`, + // batch, + // `${batch}_${task.id}_${new Date().getTime()}`, + // this.taskListService.SetMessageNameTaskToFail + // ) + // } + + /** + * 添加任务到内存队列中,分流 + * @param task 任务相关 + * @returns + */ + async AddQueue(task: TaskModal.Task) { + try { + switch (task.type) { + // case BookBackTaskType.STORYBOARD: + // this.AddGetFrameDataTask(task) + // break + // case BookBackTaskType.SPLIT: + // this.AddCutVideoData(task) + // break + // case BookBackTaskType.AUDIO: + // this.AddSplitAudioData(task) + // break + // case BookBackTaskType.FRAME: + // this.AddGetFrame(task) + // break + // case BookBackTaskType.RECOGNIZE: + // this.AddExtractSubtitlesData(task) + // break + // case BookBackTaskType.MJ_REVERSE: + // case BookBackTaskType.SD_REVERSE: + // this.AddSingleReversePrompt(task) + // break + // case BookBackTaskType.FLUX_FORGE_IMAGE: + // this.AddFluxForgeImage(task) + // break + // case BookBackTaskType.FLUX_API_IMAGE: + // this.AddFluxAPIImage(task) + // break + case BookBackTaskType.MJ_IMAGE: + this.AddImageMJImage(task) + break + case BookBackTaskType.SD_IMAGE: + this.AddSDImage(task) + break + // case BookBackTaskType.ComfyUI_IMAGE: + // this.AddComfyUIImage(task) + // break + // case BookBackTaskType.D3_IMAGE: + // this.AddD3Image(task) + // break + + // case BookBackTaskType.COMPOSING: // 合成视频 + // this.AddGenerateVideo(task) + // break + + // case BookBackTaskType.RUNWAY_VIDEO: + // case BookBackTaskType.LUMA_VIDEO: + // case BookBackTaskType.KLING_VIDEO: + // this.AddImageToVideo(task) + // break + + default: + throw new Error('未知的任务类型') + } + // 是不是要添加自动任务 + // await this.AddTaskHandle(task, true); + + // 添加成功后,更新任务状态 + let updateRes = this.taskListService.UpdateTaskStatus({ + id: task.id as string, + status: BookBackTaskStatus.RUNNING + }) + return successMessage( + updateRes, + `${task.name}_${task.id} 任务添加调度完成`, + 'TaskManager_AddQueue' + ) + } catch (error: any) { + // 修改任务状态为失败 + this.taskListService.UpdateTaskStatus({ + id: task.id as string, + status: BookBackTaskStatus.FAIL, + errorMessage: '任务调度失败,请手动重试' + }) + + return errorMessage( + `处理 ${task.type} 类型任务 ${task.name} 失败,失败信息如下:${error.message}`, + 'TaskManager_handleTask' + ) + } + } + //#endregion +} diff --git a/src/main/service/task/taskServiceHandle.ts b/src/main/service/task/taskServiceHandle.ts new file mode 100644 index 0000000..e3821aa --- /dev/null +++ b/src/main/service/task/taskServiceHandle.ts @@ -0,0 +1,289 @@ +import { TaskListService } from '@/define/db/service/book/taskListService' +import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum' +import { GeneralResponse } from '@/define/model/generalResponse' +import { errorMessage, successMessage } from '@/public/generalTools' +import { TaskManager } from './taskManage' +import { TaskModal } from '@/define/model/task' +import { Book } from '@/define/model/book/book' +export class TaskServiceHandle { + taskListService!: TaskListService + + constructor() {} + + private async InitTaskServiceHandle() { + if (!this.taskListService) { + this.taskListService = await TaskListService.getInstance() + } + } + + /** + * 启动任务队列系统 + * + * 该方法初始化并启动后台任务处理系统,可选择性地先丢弃所有未开始的任务。 + * 流程包括: + * 1. 初始化任务服务依赖 + * 2. 如果指定,则丢弃所有未开始的任务(WAIT和RECONNECT状态) + * 3. 创建全局任务管理器实例(如果不存在) + * 4. 初始化任务事件监听器 + * + * 全局任务管理器一旦创建,将持续运行并处理队列中的任务,直到应用程序关闭。 + * + * @param {boolean} isGiveUp - 是否丢弃所有未开始的任务 + * @returns {Promise} 操作结果 + * @throws {Error} 初始化过程中如有错误,将捕获并返回错误信息 + * + * @example + * // 启动任务队列,保留现有未开始任务 + * await taskService.StartTaskQueue(false); + * + * // 启动任务队列,丢弃所有未开始任务 + * await taskService.StartTaskQueue(true); + */ + async StartTaskQueue( + isGiveUp: boolean + ): Promise { + try { + await this.InitTaskServiceHandle() + if (isGiveUp) { + this.taskListService.GiveUpNotStartBackTask() + } + + // 启动后台任务 + // 这边初始化任务队列 + if (!global.taskManager) { + global.taskManager = new TaskManager() + await global.taskManager.InitListeners() // 启动监听 + // 重新设置 + } + return successMessage(null, '启动后台任务成功', 'BackTaskService_StartBackTask') + } catch (error: any) { + return errorMessage('启动任务队列失败,' + error.message, 'TaskServiceHandle.StartTaskQueue') + } + } + + /** + * 添加书籍后台任务 + * + * 该方法向系统添加一个新的小说相关的后台任务。后台任务可用于执行各种与书籍处理相关的操作, + * 如生成图像、翻译内容、合并提示词等。任务可以自动执行或手动触发,并且可以与特定的书籍任务 + * 或分镜关联。 + * + * @param {string} bookId - 书籍ID,指定任务关联的书籍 + * @param {BookBackTaskType} taskType - 任务类型,如图像生成、翻译等 + * @param {TaskExecuteType} [executeType=TaskExecuteType.AUTO] - 任务执行类型,默认为自动执行 + * @param {string | null} [bookTaskId=null] - 关联的书籍任务ID,可选 + * @param {string | null} [bookTaskDetailId=null] - 关联的分镜ID,可选 + * @param {string} [responseMessageName] - 任务完成后通知的消息通道名称,可选 + * + * @returns {Promise} + * 返回操作结果,成功则包含任务ID,失败则包含错误信息 + * + * @example + * // 添加一个自动执行的MJ图像生成任务 + * const result = await taskService.AddBookBackTask( + * "book-123", + * BookBackTaskType.MJ_GENERATE_IMAGE, + * TaskExecuteType.AUTO, + * "booktask-456", + * "detail-789", + * "mj-channel" + * ); + */ + async AddOneTask( + bookId: string, + taskType: BookBackTaskType, + executeType: TaskExecuteType = TaskExecuteType.AUTO, + bookTaskId: string | undefined = undefined, + bookTaskDetailId: string | undefined = undefined, + responseMessageName?: string + ): Promise { + try { + await this.InitTaskServiceHandle() + let res = this.taskListService.AddOneTask( + bookId, + taskType, + executeType, + bookTaskId, + bookTaskDetailId, + responseMessageName + ) + return successMessage(res, '添加后台任务成功', 'TaskServiceHandle.AddBookBackTask') + } catch (error: any) { + return errorMessage('添加后台任务失败,' + error.message, 'TaskServiceHandle.AddBookBackTask') + } + } + + /** + * 批量添加多个后台任务 + * + * 该方法接收一个任务数组,并将所有任务添加到系统的任务队列中。 + * 每个任务都会通过taskListService单独添加,但整个过程在一个操作中完成, + * 便于进行批量任务处理,如同时生成多个分镜的图像或批量翻译等场景。 + * + * @param {TaskModal.Task[]} tasks - 要添加的任务数组,每个任务包含书籍ID、任务类型、执行类型等信息 + * @returns {Promise} + * 返回操作结果,成功则包含成功消息,失败则包含错误信息 + * @throws {Error} 在添加过程中如有错误,将捕获并返回标准化的错误响应 + * + * @example + * // 批量添加多个MJ图像生成任务 + * const tasks = [ + * { + * bookId: "book-123", + * type: BookBackTaskType.MJ_GENERATE_IMAGE, + * executeType: TaskExecuteType.AUTO, + * bookTaskId: "booktask-456", + * bookTaskDetailId: "detail-789", + * messageName: "mj-channel" + * }, + * { + * bookId: "book-123", + * type: BookBackTaskType.MJ_GENERATE_IMAGE, + * executeType: TaskExecuteType.AUTO, + * bookTaskId: "booktask-456", + * bookTaskDetailId: "detail-790", + * messageName: "mj-channel" + * } + * ]; + * const result = await taskService.AddMultiTask(tasks); + */ + async AddMultiTask( + tasks: TaskModal.Task[] + ): Promise { + try { + for (let i = 0; i < tasks.length; i++) { + const element = tasks[i] + this.taskListService.AddOneTask( + element.bookId as string, + element.type as BookBackTaskType, + element.executeType, + element.bookTaskId, + element.bookTaskDetailId, + element.messageName + ) + } + return successMessage(null, `添加多个任务成功`, 'TaskIpc_AddMultiBookBackTask') + } catch (error: any) { + return errorMessage( + '添加多个后台任务失败,' + error.message, + 'TaskServiceHandle.AddMultiTask' + ) + } + } + + /** + * 获取指定状态的任务数量 + * + * 该方法查询数据库中符合指定状态的任务总数,并将结果包装在标准响应对象中返回。 + * 常用于获取等待执行、正在运行或已完成等不同状态的任务统计信息,用于前端展示或系统监控。 + * + * @param {BookBackTaskStatus[]} status - 要统计的任务状态数组,如[WAIT, RUNNING] + * @returns {Promise} + * 返回封装了统计结果的标准响应对象,成功时data字段包含符合条件的任务数量 + * @throws {Error} 当数据库访问失败或其他内部错误发生时,将捕获异常并返回错误响应 + * + * @example + * // 获取所有等待中的任务数量 + * const response = await taskService.GetAssignStatusTaskCount([BookBackTaskStatus.WAIT]); + * if (response.code === 1) { + * console.log(`等待中的任务数量: ${response.data}`); + * } + * + * // 获取所有活跃任务数量(等待和运行中) + * const activeResponse = await taskService.GetAssignStatusTaskCount([ + * BookBackTaskStatus.WAIT, + * BookBackTaskStatus.RUNNING + * ]); + */ + async GetAssignStatusTaskCount( + status: BookBackTaskStatus[] + ): Promise { + try { + await this.InitTaskServiceHandle() + let res = this.taskListService.GetAssignStatusTaskCount(status) + return successMessage( + res, + '获取指定状态的任务成功', + 'TaskServiceHandle.GetAssignStatusTaskCount' + ) + } catch (error: any) { + return errorMessage( + '获取指定状态的任务失败,' + error.message, + 'TaskServiceHandle.GetAssignStatusTaskCount' + ) + } + } + + /** + * 获取任务集合 + * + * 该方法根据提供的查询条件检索任务列表,并将结果包装在标准响应对象中返回。 + * 支持多种筛选条件和分页功能,结果包含分页信息和任务详细数据。 + * 内部调用taskListService的GetTaskCollection方法获取实际数据,然后进行标准化处理。 + * + * @param {TaskModal.QueryTaskCondition} queryTaskCondition - 查询条件对象,包含筛选和分页参数 + * @returns {Promise} + * 返回封装了任务集合的标准响应对象,成功时data字段包含任务集合数据 + * @throws {Error} 当数据库访问失败或其他内部错误发生时,将捕获异常并返回错误响应 + * + * @example + * // 查询与"小说项目"相关的所有失败任务,第1页,每页10条 + * const response = await taskService.GetTaskCollection({ + * bookName: "小说项目", + * taskStatus: BookBackTaskStatus.FAIL, + * page: 1, + * pageSize: 10 + * }); + * if (response.code === 1) { + * console.log(`共找到${response.data.count}条记录,当前显示${response.data.data.length}条`); + * } + */ + async GetTaskCollection( + queryTaskCondition: TaskModal.QueryTaskCondition + ): Promise { + try { + await this.InitTaskServiceHandle() + let res = this.taskListService.GetTaskCollection(queryTaskCondition) + return successMessage(res, '获取后台任务集合成功', 'TaskServiceHandle.GetTaskCollection') + } catch (error: any) { + return errorMessage( + '获取后台任务集合失败,' + error.message, + 'TaskServiceHandle.GetTaskCollection' + ) + } + } + + /** + * 更新后台任务状态 + * + * 该方法用于更新指定后台任务的状态,包括任务的开始时间、结束时间和错误信息等。 + * 内部调用taskListService的UpdateTaskStatus方法进行实际更新,并返回标准化的响应对象。 + * + * @param {Book.UpdateBookTaskListStatus} bookBackTask - 包含任务ID和新状态的对象 + * @returns {Promise} + * 返回封装了操作结果的标准响应对象,成功时包含更新后的任务信息 + * @throws {Error} 当数据库访问失败或其他内部错误发生时,将捕获异常并返回错误响应 + * + * @example + * // 更新任务ID为"task-123"的状态为"完成" + * const response = await taskService.UpdateTaskStatus({ + * id: "task-123", + * status: BookBackTaskStatus.COMPLETE, + * errorMessage: null, + * }); + */ + async UpdateTaskStatus( + bookBackTask: Book.UpdateBookTaskListStatus + ): Promise { + try { + await this.InitTaskServiceHandle() + let res = this.taskListService.UpdateTaskStatus(bookBackTask) + return successMessage(res, '更新后台任务状态成功', 'TaskServiceHandle.UpdateTaskStatus') + } catch (error: any) { + return errorMessage( + '更新后台任务状态失败,' + error.message, + 'TaskServiceHandle.UpdateTaskStatus' + ) + } + } +} diff --git a/src/main/service/translate/index.ts b/src/main/service/translate/index.ts new file mode 100644 index 0000000..d9231c7 --- /dev/null +++ b/src/main/service/translate/index.ts @@ -0,0 +1,27 @@ +import { TranslateModel } from '@/define/model/translate' +import { TranslateServiceHandle } from './translateServiceHandle' + +/** + * 翻译处理类 + * + * 该类作为翻译功能的入口点,封装了翻译服务处理类(TranslateServiceHandle)的功能, + * 提供简化的API接口用于处理翻译请求。主要用于接收来自IPC或其他模块的翻译请求, + * 并将其委托给具体的翻译服务处理类执行。 + * + * 主要功能: + * - 作为翻译服务的统一入口点 + * - 处理批量翻译请求 + * - 转发请求到具体的处理类 + * + * @class TranslateHandle + */ +export class TranslateHandle { + translateServiceHandle: TranslateServiceHandle + constructor() { + this.translateServiceHandle = new TranslateServiceHandle() + } + + /** 处理批量翻译请求并返回结果 */ + TranslateBookTaskDetailPromptNowReturn = async (value: TranslateModel.TranslateNowIPCParams[]) => + await this.translateServiceHandle.TranslateBookTaskDetailPromptNowReturn(value) +} diff --git a/src/main/service/translate/translateCommon.ts b/src/main/service/translate/translateCommon.ts new file mode 100644 index 0000000..3cd7581 --- /dev/null +++ b/src/main/service/translate/translateCommon.ts @@ -0,0 +1,590 @@ +import tencentcloud from 'tencentcloud-sdk-nodejs' +import { MD5 } from 'crypto-js' +import axios from 'axios' +import * as alimt20181012 from '@alicloud/alimt20181012' +import * as OpenApi from '@alicloud/openapi-client' +import * as Util from '@alicloud/tea-util' +import { GetOpenAISuccessResponse } from '../../../define/response/openAIResponse' +import { RetryWithBackoff } from '../../../define/Tools/common' +import { TranslateModel } from '@/define/model/translate' +import { GeneralResponse } from '@/define/model/generalResponse' +import { successMessage } from '@/public/generalTools' + +let { Signer } = require('@volcengine/openapi') +import { OptionRealmService } from '@/define/db/service/optionService' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../option/optionSerialization' +import { SettingModal } from '@/define/model/setting' +import { GetApiDefineDataById } from '@/define/data/apiData' + +export class TranslateCommon { + /** 请求的地址 */ + translationBusiness!: string + /** 翻译的APPID,AI翻译的话是 模型 */ + translationAppId!: string + /** 翻译的密钥,AI翻译的话是token */ + translationSecret!: string + + optionRealmService!: OptionRealmService + + constructor() {} + + /** + * 初始化翻译设置 + */ + private async InitTranslate() { + if (!this.optionRealmService) { + this.optionRealmService = await OptionRealmService.getInstance() + } + // 当前默认是使用API翻译 + let aiSettingOptionString = this.optionRealmService.GetOptionByKey( + OptionKeyName.InferenceAI.InferenceSetting + ) + + let aiSetting = optionSerialization( + aiSettingOptionString, + '‘设置-> 推理设置’' + ) + + let apiProvider = GetApiDefineDataById(aiSetting.apiProvider) + this.translationBusiness = apiProvider.gpt_url + this.translationAppId = aiSetting.translationModel + this.translationSecret = aiSetting.apiToken + } + + /** + * + * @param {*} value 0:当前要翻译的字符串 + * 1:源语言 + * 2:目标语言 + * 3:是否拆分(以逗号拆分) + * [tags,'zh','en',false] + */ + async TranslateReturnNow( + value: TranslateModel.TranslateNowReturnParams + ): Promise { + try { + await this.InitTranslate() + // 百度翻译 + if (this.translationBusiness.includes('baidu')) { + return await this.TranslateReturnNowBaidu(value) + } else if (this.translationBusiness.includes('volcengine')) { + // 火山引擎 + return await this.TranslateReturnNowVolcengine(value) + } else if (this.translationBusiness.includes('tencent')) { + // 腾讯翻译 + return await this.TranslateReturnNowTencent(value) + } else if (this.translationBusiness.includes('aliyun')) { + return await this.TranslateReturnNowAliyun(value) + } else if (this.translationBusiness.includes('laitool')) { + return await this.TranslateReturnNowGPT(value) + } else { + throw new Error('没有找到对应的翻译API') + } + } catch (error) { + throw error + } + } + + //#region LAITOOL GPT翻译(只支持这个) + + /** + * + * @param value 使用laitool GPT翻译 + */ + async TranslateReturnNowGPT( + value: TranslateModel.TranslateNowReturnParams + ): Promise { + try { + // 判断该当前的翻译API + let from = value.from + let to = value.to + if (value.isSplit) { + throw new Error('使用GPT翻译不支持拆分') + } + let model = this.translationAppId + let token = this.translationSecret + + let data = { + model: model, + messages: [] as any[] + } + // 开始调用GPT进行翻译 + if (from == 'en' && to == 'zh') { + data.messages.push({ + role: 'system', + content: + '我想让你充当英译中专家,用中文100%还原描述,不要加其他的联想,只翻译字面意思,请检查所有信息是否准确,并在回答时保持简活,不需要任何其他反馈。' + }) + // 添加示例 + data.messages.push({ + role: 'user', + content: + 'A woman in her twenties with messy hair and a look of shock and despair. She is wearing a simple white shirt and jeans. Her face shows mixed emotions. The background is a dim and quiet room. The historical background is modern. The screen content is a close-up of the woman’s face with tear marks.' + }) + data.messages.push({ + role: 'assistant', + content: + '一位二十多岁的女人,头发凌乱,表情震惊和绝望。她穿着一件简单的白色衬衫和牛仔裤。她的脸上显示出复杂的情感。背景是一个昏暗安静的房间。历史背景是现代的。屏幕内容是女人脸部的特写,带有泪痕。' + }) + data.messages.push({ + role: 'user', + content: + 'A twenty-something woman with short curly hair, smiling, wearing a casual white shirt and jeans, sitting on a comfortable sofa with a cup of coffee in her hand. She is in a small and cozy living room with a few books on the bookshelf, modern interior design, and natural light pouring into the room in the late afternoon. Screen content: Part of the sofa cushions.' + }) + data.messages.push({ + role: 'assistant', + content: + '一位二十多岁的女性,留着短卷发,面带微笑,穿着休闲的白色衬衫和牛仔裤,手拿一杯咖啡,坐在一个舒适的沙发上。她所在的是一个小而温馨的客厅,书架上有几本书,现代室内设计,下午晚些时候,自然光线洒进房间。屏幕内容:沙发垫的一部分。' + }) + data.messages.push({ + role: 'user', + content: + "In a modern city, a streamlined car is parked on the street. A man in his thirties, with short brown hair combed back, a calm, confident look, tall and thin in a clean white shirt and black pants, sits in the car. The interior of the car is clean and modern, and the background is blurred to highlight the man's calm demeanor. The man's cell phone is ringing. The scene is set in the present." + }) + data.messages.push({ + role: 'assistant', + content: + '在现代城市,一辆流线型轿车停在街上。一个三十多岁的男人,短短的棕色头发梳向后,神情冷静,自信,穿着干净的白衬衫和黑裤子,身材高挑瘦长,坐在车里。车内干净而现代,背景模糊,以突出男人平静的神态。男人的手机正在响。场景设定在现在。' + }) + } else if (from == 'zh' && to == 'en') { + data.messages.push({ + role: 'system', + content: + '我想让你充当中译英专家,用中文100%还原描述,不要加其他的联想,只翻译字面意思,请检查所有信息是否准确,并在回答时保持简活,不需要任何其他反馈。' + }) + // 添加示例 + data.messages.push({ + role: 'user', + content: + '一位二十多岁的女人,头发凌乱,表情震惊和绝望。她穿着一件简单的白色衬衫和牛仔裤。她的脸上显示出复杂的情感。背景是一个昏暗安静的房间。历史背景是现代的。屏幕内容是女人脸部的特写,带有泪痕。' + }) + data.messages.push({ + role: 'assistant', + content: + 'A woman in her twenties with messy hair and a look of shock and despair. She is wearing a simple white shirt and jeans. Her face shows mixed emotions. The background is a dim and quiet room. The historical background is modern. The screen content is a close-up of the woman’s face with tear marks.' + }) + data.messages.push({ + role: 'user', + content: + '一位二十多岁的女性,留着短卷发,面带微笑,穿着休闲的白色衬衫和牛仔裤,手拿一杯咖啡,坐在一个舒适的沙发上。她所在的是一个小而温馨的客厅,书架上有几本书,现代室内设计,下午晚些时候,自然光线洒进房间。屏幕内容:沙发垫的一部分。' + }) + data.messages.push({ + role: 'assistant', + content: + 'A twenty-something woman with short curly hair, smiling, wearing a casual white shirt and jeans, sitting on a comfortable sofa with a cup of coffee in her hand. She is in a small and cozy living room with a few books on the bookshelf, modern interior design, and natural light pouring into the room in the late afternoon. Screen content: Part of the sofa cushions.' + }) + data.messages.push({ + role: 'user', + content: + '在现代城市,一辆流线型轿车停在街上。一个三十多岁的男人,短短的棕色头发梳向后,神情冷静,自信,穿着干净的白衬衫和黑裤子,身材高挑瘦长,坐在车里。车内干净而现代,背景模糊,以突出男人平静的神态。男人的手机正在响。场景设定在现在。' + }) + data.messages.push({ + role: 'assistant', + content: + "In a modern city, a streamlined car is parked on the street. A man in his thirties, with short brown hair combed back, a calm, confident look, tall and thin in a clean white shirt and black pants, sits in the car. The interior of the car is clean and modern, and the background is blurred to highlight the man's calm demeanor. The man's cell phone is ringing. The scene is set in the present." + }) + } else { + throw new Error('GPT翻译只支持中英互译') + } + + data.messages.push({ + role: 'user', + content: value.text + }) + + let config = { + method: 'post', + maxBodyLength: Infinity, + url: this.translationBusiness, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + data: JSON.stringify(data) + } + let res = await RetryWithBackoff( + async () => { + return await axios.request(config) + }, + 5, + 2000 + ) + // 将返回的数据进行拼接数据处理 + let content = GetOpenAISuccessResponse(res.data) + + let res_data = [] as any[] + + res_data.push({ + src: value.text, + dst: content + }) + return successMessage( + { + to: to, + data: res_data + }, + `GPT${from == 'en' ? '英' : '中'}译${from == 'en' ? '英' : '中'}翻译成功`, + 'Translate_TranslateReturnNowGPT' + ) + } catch (error) { + throw error + } + } + + //#endregion + + //#region 阿里云翻译 + + /** + * 阿里云翻译实时返回 + * @param {*} value + */ + async TranslateReturnNowAliyun( + value: TranslateModel.TranslateNowReturnParams + ): Promise { + try { + // 判断该当前的翻译API + let from = value.from + let to = value.to + let ts_d = value.text.replaceAll('_', ' ').replaceAll(',', ',') + let req_data = {} + let req_count = 0 + let req_arr = [] as string[] + if (value.isSplit) { + let tmp_arr = ts_d.split(',') + for (let i = 0; i < tmp_arr.length; i++) { + const element = tmp_arr[i] + if (element != '' && element != null) { + req_data[i.toString()] = element + req_arr.push(element) + } + req_count += 1 + } + } else { + req_data['0'] = ts_d + req_count = 1 + req_arr.push(ts_d) + } + if (req_count <= 0) { + throw new Error('没有传入数据') + } + + let config = new OpenApi.Config({ + accessKeyId: this.translationAppId, + accessKeySecret: this.translationSecret + }) + config.endpoint = `mt.cn-hangzhou.aliyuncs.com` + + let client = new alimt20181012.default(config) + + let getBatchTranslateRequest = new alimt20181012.GetBatchTranslateRequest({ + apiType: 'translate_standard', + scene: 'general', + sourceLanguage: from, + targetLanguage: to, + formatType: 'text', + sourceText: JSON.stringify(req_data) + }) + let runtime = new Util.RuntimeOptions({}) + + // 复制代码运行请自行打印 API 的返回值 + let res = await client.getBatchTranslateWithOptions(getBatchTranslateRequest, runtime) + console.log(res) + + if (!res.body) { + throw new Error('aliyun翻译返回数据错误,请检查设置') + } + // 处理返回的数据 + // 检出返回的数据和输入的数据是不是一样的 + let translateList = res.body.translatedList as any[] + if (translateList.length != req_count) { + throw new Error('请求的数据长度和返回的数据长度不一致。请重试') + } + // { + // "src": "blush", + // "dst": "脸红" + // } + // 数据处理 + let res_data = [] as any[] + for (let j = 0; j < req_arr.length; j++) { + const item = req_arr[j] + let res_tmp = translateList.find((item) => item.index == j) + let obj = { + src: item, + dst: res_tmp.translated + } + res_data.push(obj) + } + + // 直接返回数据 + return successMessage( + { + to: to, + data: res_data + }, + '翻译成功', + 'Translate_TranslateReturnNowAliyun' + ) + } catch (error) { + throw error + } + } + + //#endregion + + //#region 腾讯翻译 + + /** + * 腾讯翻译实时返回 + * @param {*} value + */ + async TranslateReturnNowTencent( + value: TranslateModel.TranslateNowReturnParams + ): Promise { + try { + // 判断该当前的翻译API + let from = value.from + let to = value.to + let ts_d = value.text.replaceAll('_', ' ').replaceAll(',', ',') + let req_data = [] as string[] + if (value.isSplit) { + req_data = ts_d.split(',') + } else { + req_data.push(ts_d) + } + req_data = req_data.filter((item) => item != '' && item != null) + if (req_data.length <= 0) { + throw new Error('没有传入数据') + } + const CvmClient = tencentcloud.tmt.v20180321.Client + const client = new CvmClient({ + credential: { + secretId: this.translationAppId, + secretKey: this.translationSecret + }, + // 产品地域 + region: 'ap-shanghai', + // 可选配置实例 + profile: { + signMethod: 'TC3-HMAC-SHA256', // 签名方法 + httpProfile: { + reqMethod: 'POST', // 请求方法 + reqTimeout: 60 // 请求超时时间,默认60s + } + } + }) + + let res = await client.TextTranslateBatch({ + SourceTextList: req_data, + Source: from, + Target: to, + ProjectId: 0 + }) + console.log(res) + + // 处理返回的数据 + if (!res.TargetTextList) { + throw new Error('腾讯翻译返回数据错误,请检查设置') + } + // 检出返回的数据和输入的数据是不是一样的 + let translateList = res.TargetTextList as string[] + if (translateList.length != req_data.length) { + throw new Error('请求的数据长度和返回的数据长度不一致。请重试') + } + // { + // "src": "blush", + // "dst": "脸红" + // } + // 数据处理 + let res_data = [] as any[] + for (let j = 0; j < req_data.length; j++) { + const item = req_data[j] + let obj = { + src: item, + dst: translateList[j] + } + res_data.push(obj) + } + + // 直接返回数据 + return successMessage( + { + to: to, + data: res_data + }, + '翻译成功', + 'Translate_TranslateReturnNowTencent' + ) + } catch (error) { + throw error + } + } + //#endregion + + //#region 火山引擎翻译 + + /** + * 火山引擎翻译实时返回 + * @param {*} value + */ + async TranslateReturnNowVolcengine( + value: TranslateModel.TranslateNowReturnParams + ): Promise { + try { + // 判断该当前的翻译API + let from = value.from + let to = value.to + let ts_d = value.text.replaceAll('_', ' ').replaceAll(',', ',') + let req_data = [] as string[] + if (value.isSplit) { + req_data = ts_d.split(',') + } else { + req_data.push(ts_d) + } + if (req_data.length <= 0) { + throw new Error('没有传入数据') + } + let signer = await this.GetVolcengineSinger() + + let config = { + method: 'post', + maxBodyLength: Infinity, + url: `${this.translationBusiness}${signer}`, + headers: { + 'Content-Type': 'application/json' + }, + data: JSON.stringify({ + SourceLanguage: from, + TargetLanguage: to, + TextList: req_data + }) + } + let res = await axios.request(config) + if (res.status != 200) { + throw new Error('请求错误。请检查网络') + } + // 判断是不是有返回错误 + if (res.data.ResponseMetadata && res.data.ResponseMetadata.Error) { + let err = res.data.ResponseMetadata.Error + throw new Error( + `错误码: ${err.Code} 错误编号:${err.CodeN} 错误详细信息:${err.Message}` + ) + } + + // 处理返回的数据 + // 检出返回的数据和输入的数据是不是一样的 + let translateList = res.data.TranslationList + if (translateList.length != req_data.length) { + throw new Error('请求的数据长度和返回的数据长度不一致。请重试') + } + + // 数据处理 + let res_data = [] as any[] + for (let j = 0; j < req_data.length; j++) { + const item = req_data[j] + let obj = { + src: item, + dst: translateList[j].Translation + } + res_data.push(obj) + } + + // 直接返回数据 + return successMessage( + { + to: to, + data: res_data + }, + '翻译成功', + 'Translate_TranslateReturnNowVolcengine' + ) + } catch (error) { + throw error + } + } + + /** + * 获取火山引擎请求的签名 + */ + async GetVolcengineSinger() { + try { + const openApiRequestData = { + method: 'POST', + region: 'cn-north-1', + params: { + Action: 'TranslateText', + Version: '2020-06-01' + }, + Service: 'translate' + } + + const credentials = { + accessKeyId: this.translationAppId, + secretKey: this.translationSecret + } + + const signer = new Signer(openApiRequestData, 'translate') + + // 最终经过加签的 HTTP Query Params + const signedQueryString = signer.getSignUrl(credentials) + console.log(signedQueryString) + return signedQueryString + } catch (error) { + throw error + } + } + + //#endregion + + //#region 百度翻译 + /** + * 百度翻译引擎翻译单个数据。立即返回 + * @param {*} value + * @returns + */ + async TranslateReturnNowBaidu(value: TranslateModel.TranslateNowReturnParams) { + try { + // 判断该当前的翻译API + let appId = this.translationAppId + let ts_d = value.text.replaceAll('_', ' ').replaceAll(',', ',') + if (value.isSplit) { + ts_d = ts_d.replaceAll('.', '\n') + } + let salt = Date.now() + let sign = MD5(`${this.translationAppId}${ts_d}${salt}${this.translationSecret}`).toString() + let res = await axios.get(this.translationBusiness, { + params: { + q: ts_d, + appid: appId, + salt: salt, + from: value.from, + to: value.to, + sign: sign + } + }) + if (res.status != 200) { + throw new Error('请求错误。请检查网络') + } + // 判断是不是有错误码 + if (res.data.error_code) { + throw new Error(res.data.error_msg) + } + + let res_data = [] + res_data = res.data.trans_result + // 直接返回数据 + return successMessage( + { + to: value.to, + data: res_data + }, + '翻译成功', + 'Translate_TranslateReturnNowBaidu' + ) + } catch (error) { + throw error + } + } + //#endregion +} diff --git a/src/main/service/translate/translateServiceHandle.ts b/src/main/service/translate/translateServiceHandle.ts new file mode 100644 index 0000000..43fd14a --- /dev/null +++ b/src/main/service/translate/translateServiceHandle.ts @@ -0,0 +1,228 @@ +import { Book } from '@/define/model/book/book' +import { TranslateCommon } from './translateCommon' +import { BookTaskDetailService } from '@/define/db/service/book/bookTaskDetailService' +import { TranslateModel } from '@/define/model/translate' +import { TranslateType } from '@/define/enum/translate' +import { isEmpty } from 'lodash' +import { GeneralResponse } from '@/define/model/generalResponse' +import { ResponseMessageType } from '@/define/enum/softwareEnum' +import { errorMessage, SendReturnMessage, successMessage } from '@/public/generalTools' +import { ExecuteConcurrently } from '@/define/Tools/common' +import { OptionRealmService } from '@/define/db/service/optionService' +import { OptionKeyName } from '@/define/enum/option' +import { optionSerialization } from '../option/optionSerialization' +import { SettingModal } from '@/define/model/setting' + +/** + * 翻译服务处理类 + * + * 该类负责处理各种翻译相关的操作,包括文本翻译请求的处理、结果保存以及数据库交互。 + * 支持多种翻译类型,如反推提示词翻译和GPT提示词翻译,并能够批量处理翻译任务。 + * + * 主要功能: + * - 处理单个或批量翻译请求 + * - 将翻译结果保存到相应的数据库记录 + * - 支持多种翻译类型和目标语言 + * - 提供并行处理能力,提高批量翻译效率 + * - 与翻译API提供商集成 + * + * @class TranslateServiceHandle + * @example + * const translateService = new TranslateServiceHandle(); + * await translateService.InitTranslateServiceHandle(); + * + * // 处理单个翻译 + * const params = { + * type: TranslateType.GPT_PROMPT_TRANSLATE, + * bookTaskDetailId: 'task123', + * reversePromptId: 'prompt456', + * from: 'en', + * text: 'Hello world' + * }; + * const result = await translateService.TranslateNowReturn([params]); + */ +export class TranslateServiceHandle { + translateCommon: TranslateCommon + bookTaskDetailService!: BookTaskDetailService + optionRealmService!: OptionRealmService + constructor() { + this.translateCommon = new TranslateCommon() + } + + /** + * 初始化翻译服务处理程序 + * + * 该方法用于初始化翻译服务的相关依赖项,包括翻译服务和选项服务。 + * 如果这些服务尚未实例化,则会创建它们的实例。 + * + * @returns {Promise} - 返回一个 Promise 对象,表示初始化操作的完成状态 + */ + async InitTranslateServiceHandle() { + if (!this.bookTaskDetailService) { + this.bookTaskDetailService = await BookTaskDetailService.getInstance() + } + if (!this.optionRealmService) { + this.optionRealmService = await OptionRealmService.getInstance() + } + } + + /** + * 将反推提示词翻译后的数据写入到数据库中 + * + * 根据目标语言类型,将翻译后的文本内容更新到对应的数据库字段。 + * - 当目标语言为中文(zh)时,更新promptCN字段 + * - 当目标语言为英文(en)时,同时更新prompt和promptCN字段 + * + * @param {string} bookTaskDetailId - 对应的分镜ID + * @param {string} reversePromptId - 对应的反推提示词数据ID + * @param {string} to - 目标语言代码,'zh'表示中文,'en'表示英文 + * @param {string} dstString - 翻译后的字符串内容 + * @returns {Promise} 无返回值 + * @private - 此方法为类的私有方法,仅供内部使用 + */ + private async TranslateProcessReversePrompt( + bookTaskDetailId: string, + reversePromptId: string, + to: string, + dstString: string + ): Promise { + // 开始修改翻译后的数据 + let updateData = {} as Book.ReversePrompt + if (to == 'zh') { + updateData.promptCN = dstString + } else if (to == 'en') { + updateData.prompt = dstString + updateData.promptCN = dstString + } + // 修改数据 + this.bookTaskDetailService.UpdateBookTaskDetailReversePrompt( + bookTaskDetailId, + reversePromptId, + updateData + ) + } + + /** + * 处理翻译结果并将其写入数据库 + * + * 根据翻译类型,将翻译后的文本保存到相应的数据库记录中。支持两种翻译类型: + * 1. 反推提示词翻译:将翻译结果保存到反推提示词数据中 + * 2. GPT提示词翻译:直接更新分镜任务的GPT提示词字段 + * + * @param {TranslateModel.TranslateNowIPCParams} value - 翻译请求参数,包含翻译类型和目标ID信息 + * @param {string} to - 目标语言代码,例如'zh'表示中文,'en'表示英文 + * @param {string} dstString - 翻译后的文本内容 + * @returns {Promise} 无返回值 + * @throws {Error} 当反推提示词ID为空或翻译类型未知时抛出异常 + */ + async TranslateReturnProcess( + value: TranslateModel.TranslateNowIPCParams, + to: string, + dstString: string + ): Promise { + switch (value.type) { + case TranslateType.REVERSE_PROMPT_TRANSLATE: + if (value.reversePromptId == null || isEmpty(value.reversePromptId)) { + throw new Error('反推提示词的ID不能为空') + } + await this.TranslateProcessReversePrompt( + value.bookTaskDetailId as string, + value.reversePromptId, + to, + dstString + ) + break + case TranslateType.GPT_PROMPT_TRANSLATE: + // 这个直接改就行 + await this.bookTaskDetailService.ModifyBookTaskDetailById( + value.bookTaskDetailId as string, + { + gptPrompt: dstString + } + ) + break + default: + throw new Error('未知的翻译类型') + } + } + + /** + * 处理批量翻译请求并返回结果 + * + * 该方法接收多个翻译参数,并行处理所有翻译请求,将翻译结果写入数据库, + * 并通过IPC向渲染进程发送进度通知。支持不同类型的翻译任务,如反推提示词翻译和GPT提示词翻译。 + * + * @param {TranslateModel.TranslateNowIPCParams[]} value - 翻译请求参数数组 + * @returns {Promise} 翻译处理的结果 + * @throws 当初始化失败或处理翻译过程中出错时抛出异常,并返回错误响应 + */ + async TranslateBookTaskDetailPromptNowReturn( + value: TranslateModel.TranslateNowIPCParams[] + ): Promise { + try { + await this.InitTranslateServiceHandle() + // 循环所有的数据,返回翻译结果 + let tasks = [] as (() => Promise)[] + let generalSettingOption = this.optionRealmService.GetOptionByKey( + OptionKeyName.Software.GeneralSetting + ) + let generalSetting = optionSerialization( + generalSettingOption, + '‘设置 -> 通用设置’' + ) + + for (let i = 0; i < value.length; i++) { + const element = value[i] + tasks.push(async () => { + let res = await this.translateCommon.TranslateReturnNow(element) + + // 添加一个对返回信息进行处理的函数 + let data = res.data + // 先将数据进行拼接 + let srcString = '' + if (element.isSplit) { + // let dstStrs = [] + throw new Error('拆分翻译的暂时不支持') + } else { + // 没有拆分的,只有一句 + srcString = data.data[0].dst + } + // 写回数据库 + await this.TranslateReturnProcess(element, data.to, srcString) + + let responseType = ResponseMessageType.REVERSE_PROMPT_TRANSLATE + if (element.type == TranslateType.GPT_PROMPT_TRANSLATE) { + responseType = ResponseMessageType.GPT_PROMPT_TRANSLATE + } + // 做个返回数据 + SendReturnMessage( + { + code: 1, + id: element.bookTaskDetailId as string, + type: responseType, + data: { + progress: i, + total: value.length, + from: element.from, + to: data.to, + bookTaskDetailId: element.bookTaskDetailId as string, + reversePromptId: element.reversePromptId as string, + prompt: srcString, + promptCN: srcString + } + }, + element.responseMessgeName + ) + }) + } + await ExecuteConcurrently(tasks, global.am.isPro ? (generalSetting.concurrency ?? 1) : 1) + // 将翻译后的数据返回,前端进行修改 + return successMessage(null, '全部翻译完成', 'TranslateService_TranslateNowReturn') + } catch (error: any) { + return errorMessage( + '翻译失败,失败信息如下:' + error.toString(), + 'TranslateService_TranslateNowReturn' + ) + } + } +} diff --git a/src/main/utils/mixin.ts b/src/main/utils/mixin.ts new file mode 100644 index 0000000..5817416 --- /dev/null +++ b/src/main/utils/mixin.ts @@ -0,0 +1,102 @@ +/** + * 多重继承装饰器工具 + * 用于在TypeScript中实现类似多重继承的功能 + */ + +/** + * 定义装饰器工厂,用于混入多个类的方法和属性 + * @param mixins 要混入的类数组 + */ +export function mixin(...mixins: any[]) { + return function {}>(target: T) { + // 保存原始构造函数 + const originalConstructor = target; + + mixins.forEach(mixinClass => { + if (typeof mixinClass === 'function' && mixinClass.prototype) { + // 1. 复制原型上的方法 + Object.getOwnPropertyNames(mixinClass.prototype).forEach(name => { + if (name !== 'constructor') { + const descriptor = Object.getOwnPropertyDescriptor(mixinClass.prototype, name); + if (descriptor) { + Object.defineProperty(target.prototype, name, descriptor); + } + } + }); + + // 2. 复制类的属性声明(通过创建临时实例获取属性名) + try { + const tempInstance = new mixinClass(); + Object.getOwnPropertyNames(tempInstance).forEach(propName => { + // 在目标类的原型上定义属性描述符 + if (!target.prototype.hasOwnProperty(propName)) { + Object.defineProperty(target.prototype, propName, { + writable: true, + enumerable: true, + configurable: true + }); + } + }); + } catch (error) { + console.warn(`Could not analyze properties of ${mixinClass.name}:`, error); + } + } + }); + + // 创建新的构造函数,它会自动初始化混入类的实例 + function ExtendedConstructor(...args: any[]) { + // 调用原始构造函数 + const instance = new originalConstructor(...args); + + // 为每个混入类创建实例并复制其属性 + mixins.forEach(mixinClass => { + if (typeof mixinClass === 'function') { + try { + const mixinInstance = new mixinClass(); + // 复制实例属性到目标实例 + Object.getOwnPropertyNames(mixinInstance).forEach(prop => { + if (!instance.hasOwnProperty(prop)) { + (instance as any)[prop] = (mixinInstance as any)[prop]; + } + }); + } catch (error) { + console.warn(`Could not initialize mixin ${mixinClass.name}:`, error); + } + } + }); + + return instance; + } + + // 保持原型链 + ExtendedConstructor.prototype = target.prototype; + + return ExtendedConstructor as any; + }; +} + +/** + * 组合类型辅助工具,用于类型合并 + */ +export type Constructor = new (...args: any[]) => T; + +/** + * 混入接口辅助,用于类型声明 + */ +export interface Mixin extends Constructor {} + +/** + * 更安全的混入函数,支持构造函数调用 + */ +export function applyMixins(derivedCtor: any, constructors: any[]) { + constructors.forEach(baseCtor => { + Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { + if (name !== 'constructor') { + const descriptor = Object.getOwnPropertyDescriptor(baseCtor.prototype, name); + if (descriptor) { + Object.defineProperty(derivedCtor.prototype, name, descriptor); + } + } + }); + }); +} diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts new file mode 100644 index 0000000..cde9ffb --- /dev/null +++ b/src/preload/index.d.ts @@ -0,0 +1,16 @@ +import { OptionType } from '@/define/enum/option' +import { ElectronAPI } from '@electron-toolkit/preload' + +declare global { + interface Window { + electron: ElectronAPI + api: unknown + option: any + system: any + setting: any + axios: any + book: any + preset: any + task: any + } +} diff --git a/src/preload/index.ts b/src/preload/index.ts new file mode 100644 index 0000000..3343879 --- /dev/null +++ b/src/preload/index.ts @@ -0,0 +1,56 @@ +import { contextBridge } from 'electron' +import { electronAPI } from '@electron-toolkit/preload' +import { system } from './subPreload/system' +import { option } from './subPreload/option' +import { setting } from './subPreload/setting' +import { axiosPrelod } from './subPreload/axios' +import { book } from './subPreload/book' +import { preset } from './subPreload/preset' +import { task } from './subPreload/task' +import packageJson from '../../package.json' + +// Custom APIs for renderer +const api = {} +let ea = { + ...electronAPI, + appVersion: packageJson.version, + appName: packageJson.name +} + +// Use `contextBridge` APIs to expose Electron APIs to +// renderer only if context isolation is enabled, otherwise +// just add to the DOM global. +if (process.contextIsolated) { + try { + contextBridge.exposeInMainWorld('electron', ea) + contextBridge.exposeInMainWorld('api', api) + contextBridge.exposeInMainWorld('system', system) + contextBridge.exposeInMainWorld('option', option) + contextBridge.exposeInMainWorld('setting', setting) + contextBridge.exposeInMainWorld('axios', axiosPrelod) + contextBridge.exposeInMainWorld('book', book) + contextBridge.exposeInMainWorld('preset', preset) + contextBridge.exposeInMainWorld('task', task) + } catch (error) { + console.error(error) + } +} else { + // @ts-ignore (define in dts) + window.electron = ea + // @ts-ignore (define in dts) + window.api = api + // @ts-ignore (define in dts) + window.system = system + // @ts-ignore (define in dts) + window.option = option + // @ts-ignore (define in dts) + window.setting = setting + // @ts-ignore (define in dts) + window.axios = axiosPrelod + // @ts-ignore (define in dts) + window.book = book + // @ts-ignore (define in dts) + window.preset = preset + // @ts-ignore (define in dts) + window.task = task +} diff --git a/src/preload/subPreload/axios.ts b/src/preload/subPreload/axios.ts new file mode 100644 index 0000000..8796dd6 --- /dev/null +++ b/src/preload/subPreload/axios.ts @@ -0,0 +1,61 @@ +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { ipcRenderer } from 'electron' + +// 定义请求配置接口 +interface RequestConfig { + headers?: Record + params?: Record + timeout?: number + [key: string]: any +} + +// 定义响应接口 +interface HttpResponse { + success: boolean + data?: T + error?: string + status?: number +} + +// HTTP 客户端接口 +const axiosPrelod = { + /** + * 发送 GET 请求 + * @param url 请求地址 + * @param config 请求配置 + */ + get: async (url: string, config?: RequestConfig): Promise> => + await ipcRenderer.invoke(DEFINE_STRING.AXIOS.HTTP_GET, url, config), + + /** + * 发送 POST 请求 + * @param url 请求地址 + * @param data 请求数据 + * @param config 请求配置 + */ + post: async ( + url: string, + data?: any, + config?: RequestConfig + ): Promise> => + await ipcRenderer.invoke(DEFINE_STRING.AXIOS.HTTP_POST, url, data, config), + + /** + * 发送 PUT 请求 + * @param url 请求地址 + * @param data 请求数据 + * @param config 请求配置 + */ + put: async (url: string, data?: any, config?: RequestConfig): Promise> => + await ipcRenderer.invoke(DEFINE_STRING.AXIOS.HTTP_PUT, url, data, config), + + /** + * 发送 DELETE 请求 + * @param url 请求地址 + * @param config 请求配置 + */ + delete: async (url: string, config?: RequestConfig): Promise> => + await ipcRenderer.invoke(DEFINE_STRING.AXIOS.HTTP_DELETE, url, config) +} + +export { axiosPrelod } diff --git a/src/preload/subPreload/book.ts b/src/preload/subPreload/book.ts new file mode 100644 index 0000000..0cb622d --- /dev/null +++ b/src/preload/subPreload/book.ts @@ -0,0 +1,22 @@ +import { bookDataPreload } from './bookProload/bookDataPreload' +import { bookTaskPreload } from './bookProload/bookTaskPreload' +import { bookTaskDetailPreload } from './bookProload/bookTaskDetailPreload' +import { bookPromptPreload } from './bookProload/bookPromptPreload' +import { bookImagePreload } from './bookProload/bookImagePreload' +import { bookExportPreload } from './bookProload/bookExportPreload' + +const book = { + ...bookDataPreload, + + ...bookTaskPreload, + + ...bookTaskDetailPreload, + + ...bookPromptPreload, + + ...bookImagePreload, + + ...bookExportPreload +} + +export { book } diff --git a/src/preload/subPreload/bookProload/bookDataPreload.ts b/src/preload/subPreload/bookProload/bookDataPreload.ts new file mode 100644 index 0000000..036758c --- /dev/null +++ b/src/preload/subPreload/bookProload/bookDataPreload.ts @@ -0,0 +1,27 @@ +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { Book } from '@/define/model/book/book' +import { ipcRenderer } from 'electron' + +export const bookDataPreload = { + //#region 小说数据相关 + /** 新增或者是修改小说数据,返回小说数据 */ + AddOrModifyBook: async (book: Book.SelectBook) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_OR_MODIFY_BOOK, book), + + /** 获取小说数据,通过参数查询 */ + GetBookDataCondition: async (queryCondition: Book.QueryBookCondition) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_BOOK_DATA_CONDITION, queryCondition), + + /** 修改小说数据信息 */ + ModifyBookDataById: async (id: string, bookData: Book.SelectBook) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.MODIFY_BOOK_DATA_BY_ID, id, bookData), + + /** 删除小说数据,通过ID */ + DeleteBookDataInfoById: async (id: string) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.DELETE_BOOK_DATA_INFO_BY_ID, id), + + /** 重置小说数据 */ + ResetBookDataInfo: async (bookId: string) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_DATA_INFO, bookId) + //#endregion +} diff --git a/src/preload/subPreload/bookProload/bookExportPreload.ts b/src/preload/subPreload/bookProload/bookExportPreload.ts new file mode 100644 index 0000000..7183832 --- /dev/null +++ b/src/preload/subPreload/bookProload/bookExportPreload.ts @@ -0,0 +1,12 @@ +import { DEFINE_STRING } from "@/define/ipcDefineString"; +import { ipcRenderer } from "electron"; + +export const bookExportPreload = { + //#region 导出相关 + + /** 添加剪映草稿 */ + AddJianyingDraft: async (bookTaskId: string) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_JIANYING_DRAFT, bookTaskId) + + //#endregion +} diff --git a/src/preload/subPreload/bookProload/bookImagePreload.ts b/src/preload/subPreload/bookProload/bookImagePreload.ts new file mode 100644 index 0000000..53613ea --- /dev/null +++ b/src/preload/subPreload/bookProload/bookImagePreload.ts @@ -0,0 +1,63 @@ +import { OperateBookType } from '@/define/enum/bookEnum' +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { ipcRenderer } from 'electron' + +export const bookImagePreload = { + // #region 出图相关 + + /** 移动指定的图片链接到主图 */ + MoveImageToMainImage: async ( + bookTaskId: string, + bookTaskDetailId: string, + sourceImagePath: string + ) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.MOVE_IMAGE_TO_MAIN_IMAGE, + bookTaskId, + bookTaskDetailId, + sourceImagePath + ), + + /** 重置(删除)所有未锁定的分镜图片数据 */ + ResetGenerateImage: async (id: string, operateBookType: OperateBookType) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_GENERATE_IMAGE, id, operateBookType), + + /** 上传图片到分镜,并且更新出图信息 */ + UpLoadImageToBookAndUpdateMessage: async ( + bookTaskDetailId: string, + imageFile: string | string[], + option: string + ) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.UPLOAD_IMAGE_TO_BOOK_AND_UPDATE_MESSAGE, + bookTaskDetailId, + imageFile, + option + ), + + /** 高清一个分镜 */ + HDOneImage: async (bookTaskDetailId: string, scale: number) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.HD_ONE_IMAGE, bookTaskDetailId, scale), + + /** 获取Midjourney图片URL并下载应用到分镜 */ + GetImageUrlAndDownload: async ( + id: string, + operateBookType: OperateBookType, + coverData: boolean + ) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.GET_IMAGE_URL_AND_DOWNLOAD, + id, + operateBookType, + coverData + ), + + /** 下载图片并拆分处理应用到分镜 */ + DownloadImageUrlAndSplit: async (bookTaskDetailId: string, imageUrl: string) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.DOWNLOAD_IMAGE_URL_AND_SPLIT, + bookTaskDetailId, + imageUrl + ) + // #endregion +} diff --git a/src/preload/subPreload/bookProload/bookPromptPreload.ts b/src/preload/subPreload/bookProload/bookPromptPreload.ts new file mode 100644 index 0000000..8475d6d --- /dev/null +++ b/src/preload/subPreload/bookProload/bookPromptPreload.ts @@ -0,0 +1,40 @@ +import { PresetCategory } from '@/define/data/presetData' +import { OperateBookType, PromptMergeType } from '@/define/enum/bookEnum' +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { BookTask } from '@/define/model/book/bookTask' +import { TranslateModel } from '@/define/model/translate' +import { ipcRenderer } from 'electron' + +export const bookPromptPreload = { + //#region 提示词 + + /** 原创进行AI推理 */ + OriginalGetAIPrompt: async (id: string, operateBookType: OperateBookType, coverData: boolean) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.ORIGINAL_GET_AI_PROMPT, + id, + operateBookType, + coverData + ), + + /** AI分镜头合并 */ + AIStoryboardMerge: async (bookTaskId: string, type: BookTask.StoryboardMergeType) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.AI_STORYBOARD_MERGE, bookTaskId, type), + + /** 合并提示词 */ + MergePrompt: async (id: string, type: PromptMergeType, operateBookType: OperateBookType) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.MERGE_PROMPT, id, type, operateBookType), + + /** 自动分析提示词或场景数据 */ + AutoAnalyzeCharacterOrScene: async (bookTaskId: string, type: PresetCategory) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.AUTO_ANALYZE_CHARACTER_OR_SCENE, bookTaskId, type), + + //#endregion + + //#region 翻译相关 + + TranslateBookTaskDetailPromptNowReturn: async (value: TranslateModel.TranslateNowIPCParams[]) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.TRANSLATE_BOOK_TASK_DETAIL_PROMPT_NOW_RETURN, value) + + //#endregion +} diff --git a/src/preload/subPreload/bookProload/bookTaskDetailPreload.ts b/src/preload/subPreload/bookProload/bookTaskDetailPreload.ts new file mode 100644 index 0000000..be9d3c6 --- /dev/null +++ b/src/preload/subPreload/bookProload/bookTaskDetailPreload.ts @@ -0,0 +1,48 @@ +import { OperateBookType } from "@/define/enum/bookEnum"; +import { DEFINE_STRING } from "@/define/ipcDefineString"; +import { Book } from "@/define/model/book/book"; +import { BookTaskDetail } from "@/define/model/book/bookTaskDetail"; +import { ipcRenderer } from "electron"; + +export const bookTaskDetailPreload = { + //#region 小说批次任务详细数据相关 + + /** 获取小说子任务详细数据 */ + GetBookTaskDetailDataByCondition: async ( + bookTaskDetailCondition: Book.QueryBookTaskDetailCondition + ) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.GET_BOOK_TASK_DETAIL_DATA_CONDITION, + bookTaskDetailCondition + ), + + /** 获取小说子任务详细数据,通过小说ID查询 */ + GetBookTaskDetailDataById: async (id: string) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_BOOK_TASK_DETAIL_DATA_BY_ID, id), + + /** 修改小说批次任务状态 */ + ModifyBookTaskDetailById: async ( + bookTaskDetailId: string, + updateData: Book.SelectBookTaskDetail + ) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.MODIFY_BOOK_TASK_DETAIL_BY_ID, + bookTaskDetailId, + updateData + ), + + /** 保持小说批次数据分镜信息 */ + SaveCopywritingInfo: async ( + bookTaskId: string, + copywritingData: BookTaskDetail.SaveCopywritingData[], + operateBookType: OperateBookType + ) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.SAVE_COPYWRITING_INFO, + bookTaskId, + copywritingData, + operateBookType + ) + + //#endregion +} diff --git a/src/preload/subPreload/bookProload/bookTaskPreload.ts b/src/preload/subPreload/bookProload/bookTaskPreload.ts new file mode 100644 index 0000000..bb3fe64 --- /dev/null +++ b/src/preload/subPreload/bookProload/bookTaskPreload.ts @@ -0,0 +1,46 @@ +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { Book } from '@/define/model/book/book' +import { BookTask } from '@/define/model/book/bookTask' +import { ipcRenderer } from 'electron' + +export const bookTaskPreload = { + //#region 小说批次任务相关 + + /** 获取小说批次任务数据,通过参数查询 */ + GetBookTaskDataByCondition: async (bookTaskCondition: Book.QueryBookTaskCondition) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_BOOK_TASK_DATA_CONDITION, bookTaskCondition), + + /** 修改小说批次数据,通过ID */ + ModifyBookTaskDataById: async (id: string, bookTaskData: Book.SelectBookTask) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.MODIFY_BOOK_TASK_DATA_BY_ID, id, bookTaskData), + + /** 删除指定ID得小说子任务 */ + DeleteBookTaskByIds: async (ids: string[]) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.DELETE_BOOK_TASK_BY_IDS, ids), + + /** 重置小说批次任务数据 */ + ResetBookTaskDataById: async (bookTaskId: string) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_TASK_DATA_BY_ID, bookTaskId), + + /** 重置小说分镜数据(不包含bootask本身数据),并且初始化全新的分镜信息 */ + ResetAndInitializeBookTask: async (bookTaskId: string) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_AND_INITIALIZE_BOOK_TASK, bookTaskId), + + /** 删除小说批次任务数据 */ + DeleteBookTaskDataById: async (bookTaskId: string) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.DELETE_BOOK_TASK_DATA_BY_ID, bookTaskId), + + /** 添加小说子任务数据 */ + AddNewBookTask: async (addData: BookTask.AddBookTaskParam) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_NEW_BOOK_TASK, addData), + + /** 获取小说批次任务的图片生成进度 */ + GetBookTaskImageGenerateProgress: async (id: string) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_BOOK_TASK_IMAGE_GENERATE_PROGRESS, id), + + /** 获取小说批次任务的第一张图片路径 */ + GetBookTaskFirstImagePath: async (id: string) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_BOOK_TASK_FIRST_IMAGE_PATH, id) + + //#endregion +} diff --git a/src/preload/subPreload/option.ts b/src/preload/subPreload/option.ts new file mode 100644 index 0000000..a14107c --- /dev/null +++ b/src/preload/subPreload/option.ts @@ -0,0 +1,17 @@ +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { OptionType } from '@/define/enum/option' + +const option = { + /** 通过Key获取指定的option */ + GetOptionByKey: async (key: string) => + await ipcRenderer.invoke(DEFINE_STRING.OPTION.GET_OPTION_BY_KEY, key), + + /** 修改指定的Option 通过key */ + ModifyOptionByKey: async (key: string, value: string, type: OptionType) => + await ipcRenderer.invoke(DEFINE_STRING.OPTION.MODIFY_OPTION_BY_KEY, key, value, type) + + //#region 一图 草稿预设相关 +} + +export { option } diff --git a/src/preload/subPreload/preset.ts b/src/preload/subPreload/preset.ts new file mode 100644 index 0000000..4b07464 --- /dev/null +++ b/src/preload/subPreload/preset.ts @@ -0,0 +1,27 @@ +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { PresetModel } from '@/define/model/preset' + +const preset = { + /** 获取预设通过条件 */ + GetPresetByCondition: async (queryCondition: PresetModel.Preset) => + await ipcRenderer.invoke(DEFINE_STRING.PRESET.GET_PRESET_BY_CONDITION, queryCondition), + + /** 通过ID获取预设 */ + GetPresetById: async (id: string) => + await ipcRenderer.invoke(DEFINE_STRING.PRESET.GET_PRESET_BY_ID, id), + + /** 添加预设 */ + AddPreset: async (preset: PresetModel.Preset) => + await ipcRenderer.invoke(DEFINE_STRING.PRESET.ADD_PRESET, preset), + + /** 修改预设 */ + ModifyPreset: async (id: string, preset: Partial) => + await ipcRenderer.invoke(DEFINE_STRING.PRESET.MODIFY_PRESET, id, preset), + + /** 删除预设 */ + DeletePreset: async (id: string) => + await ipcRenderer.invoke(DEFINE_STRING.PRESET.DELETE_PRESET, id) +} + +export { preset } diff --git a/src/preload/subPreload/setting.ts b/src/preload/subPreload/setting.ts new file mode 100644 index 0000000..5860f89 --- /dev/null +++ b/src/preload/subPreload/setting.ts @@ -0,0 +1,10 @@ +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '@/define/ipcDefineString' + +const setting = { + /** 获取默认的草稿保存路径 */ + GetDefaultJianyingDraftPath: async () => + ipcRenderer.invoke(DEFINE_STRING.SETTING.GET_DEFAULT_JIANYING_DRAFT_PATH) +} + +export { setting } diff --git a/src/preload/subPreload/system.ts b/src/preload/subPreload/system.ts new file mode 100644 index 0000000..24b9cc2 --- /dev/null +++ b/src/preload/subPreload/system.ts @@ -0,0 +1,93 @@ +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { OpenFolderParams } from '@/main/service/system/electronInterface' + +const system = { + /** 打开开发者调试工具 */ + OpenDEVTools: () => ipcRenderer.send(DEFINE_STRING.SYSTEM.OPEN_DEV_TOOLS), + + /** 打开指定的网页 */ + OpenUrl: (url: string) => ipcRenderer.invoke(DEFINE_STRING.SYSTEM.OPEN_URL, url), + + //#region 文件或文件夹相关 + + /** 选择多个文件,指定文件后缀 */ + SelectMultipleFile: (extensions: string[]) => + ipcRenderer.invoke(DEFINE_STRING.SYSTEM.SELECT_MULTIPLE_FILE, extensions), + + /** + * 选择单个文件,指定文件后缀 + * @param extensions + * @returns + */ + SelectSingleFile: (extensions: string[]) => + ipcRenderer.invoke(DEFINE_STRING.SYSTEM.SELECT_SINGLE_FILE, extensions), + + /** 选择单个文件夹 */ + SelectSingleFolder: (defaultPath: string) => + ipcRenderer.invoke(DEFINE_STRING.SYSTEM.SELECT_SINGLE_FOLDER, defaultPath), + + /** 打开指定的文件夹 */ + OpenFolder: (params: OpenFolderParams) => + ipcRenderer.invoke(DEFINE_STRING.SYSTEM.OPEN_FOLDER, params), + + /** 打开指定的文件 */ + OpenFile: (filePath: string) => ipcRenderer.invoke(DEFINE_STRING.SYSTEM.OPEN_FILE, filePath), + + /** 复制指定的文件夹中的所有内容到另一个文件夹 */ + CopyFolderContents: (source: string, destination: string) => + ipcRenderer.invoke(DEFINE_STRING.SYSTEM.COPY_FOLDER_CONTENTS, { source, destination }), + + /** 选择文件夹或指定后缀的文件 */ + SelectFolderOrFile: (extensions?: string[]) => + ipcRenderer.invoke(DEFINE_STRING.SYSTEM.SELECT_FOLDER_OR_FILE, extensions), + + //#endregion + + //#region 系统相关 + + /** 切换主题 */ + ToggleTheme: (theme: string) => ipcRenderer.invoke(DEFINE_STRING.SYSTEM.TOGGLE_THEME, theme), + + /** 同步授权信息 */ + SyncAuthorization: (am: SoftwareModal.SoftwareAuthorizationMessage) => + ipcRenderer.send(DEFINE_STRING.SYSTEM.SYNC_AUTHORIZATION, am), + + /** 获取剪映草稿文件列表 */ + GetJianyingDraftFileList: () => + ipcRenderer.invoke(DEFINE_STRING.SYSTEM.GET_JIANYING_DRAFT_FILE_LIST), + + //#endregion + + //#region 渲染进程时间监听 + + /** 移除指定名称的事件 */ + removeEventListen: (eventName: string) => { + if (system.eventMap.has(eventName)) { + // 关键修改:移除所有该事件名称的监听器 + ipcRenderer.removeAllListeners(eventName) + system.eventMap.delete(eventName) + } + }, + + /** 存储当前已监听的事件映射 */ + eventMap: new Map(), + + /** 添加事件监听 */ + setEventListen: (eventName: string, callback: Function) => { + // 无论是否存在,都先移除所有该事件的监听器 + ipcRenderer.removeAllListeners(eventName) + + // 更新Map + system.eventMap.set(eventName, callback) + + // 添加新的监听器 + ipcRenderer.on(eventName, (_, value) => { + callback(value) + }) + } + + //#endregion +} + +export { system } diff --git a/src/preload/subPreload/task.ts b/src/preload/subPreload/task.ts new file mode 100644 index 0000000..f59aea8 --- /dev/null +++ b/src/preload/subPreload/task.ts @@ -0,0 +1,51 @@ +import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum' +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { Book } from '@/define/model/book/book' +import { TaskModal } from '@/define/model/task' +import { ipcRenderer } from 'electron' + +const task = { + /** + * 启动后台任务,判断是不是丢弃,要是丢弃的话,先将等待任务全部丢弃 + * @param isGiveUp + * @returns + */ + StartTaskQueue: async (isGiveUp: boolean) => + await ipcRenderer.invoke(DEFINE_STRING.TASK.START_TASK_QUEUE, isGiveUp), + + /** 添加单个任务 */ + AddOneTask: async ( + bookId: string, + taskType: BookBackTaskType, + executeType = TaskExecuteType.AUTO, + bookTaskId = null, + bookTaskDetailId = null, + responseMessageName: string | null = null + ) => + await ipcRenderer.invoke( + DEFINE_STRING.TASK.ADD_ONE_TASK, + bookId, + taskType, + executeType, + bookTaskId, + bookTaskDetailId, + responseMessageName + ), + + /** 同时添加多个任务 */ + AddMultiTask: async (task: TaskModal.Task[]) => + await ipcRenderer.invoke(DEFINE_STRING.TASK.ADD_MULTI_TASK, task), + + /** 获取等待中的任务 */ + GetAssignStatusTaskCount: async (value: BookBackTaskStatus[]) => + await ipcRenderer.invoke(DEFINE_STRING.TASK.GET_ASSIGN_STATUS_TASK_COUNT, value), + + /** 获取后台任务的集合,分页 */ + GetTaskCollection: async (queryTaskCondition: TaskModal.QueryTaskCondition) => + await ipcRenderer.invoke(DEFINE_STRING.TASK.GET_TASK_COLLECTION, queryTaskCondition), + + /** 更新后台任务状态 */ + UpdateTaskStatus: async (bookBackTask: Book.UpdateBookTaskListStatus) => + await ipcRenderer.invoke(DEFINE_STRING.TASK.UPDATE_TASK_STATUS, bookBackTask) +} +export { task } diff --git a/src/public/generalTools.ts b/src/public/generalTools.ts new file mode 100644 index 0000000..1ff39de --- /dev/null +++ b/src/public/generalTools.ts @@ -0,0 +1,137 @@ +import { GeneralResponse } from '@/define/model/generalResponse' + +/** + * 返回成功的消息,包含code,data,message + * @param {*} data 返回的数据 + * @param {*} message 成功消息 + * @param {*} service 消息日志类型 + * @returns + */ +function successMessage( + data?: any, + message?: string, + service?: string +): GeneralResponse.SuccessItem { + if (service) { + global.logger.success(service, message ? message : '成功返回数据') + } + return { + code: 1, + data: data, + message: message + } +} + +/** + * 返回失败的消息, + * @param {*} message 错误信息 + * @param {*} service 错误日志类型 + * @returns + */ +function errorMessage(message: string, service?: string): GeneralResponse.ErrorItem { + if (service) { + global.logger.error(service, message ? message : '未知报错,没有捕获的错误') + } + return { + code: 0, + message: message + } +} + +function infoMessage(message: string, service?: string): GeneralResponse.ErrorItem { + if (service) { + global.logger.info(service, message) + } + return { + code: 0, + message: message + } +} + +function warningMessage(message: string, service?: string): GeneralResponse.ErrorItem { + if (service) { + global.logger.warn(service, message) + } + return { + code: 0, + message: message + } +} + +/** + * 主动发送消息到前端,用作数据返回 + * @param data 要返回的数据 + * @param message_name 消息名称 + */ +function SendReturnMessage(data: GeneralResponse.MessageResponse, message_name: string) { + global.wins[0].webContents.send(message_name, data) +} + +/** + * 判断字符串的值是不是存在,不是null,不是undefined,不是空字符串,存在的话添加指定的后缀 + * @param {*} value 要检查的字符串 + * @param {*} suffix 要添加的后缀 + * @returns + */ +export function checkStringValueAddSuffix(value: string, suffix: string): string { + if (value && value !== null && value !== undefined && value !== '') { + return value + suffix + } else { + return '' + } +} + +/** + * 判断字符串的值是不是存在,不是null,不是undefined,不是空字符串,存在的话添加指定的前缀 + * @param {*} value 要检查的值 + * @param {*} prefix 要添加的前缀 + * @returns + */ + +export function checkStringValueAddPrefix(value: string, prefix: string): string { + if (value && value !== null && value !== undefined && value !== '') { + return prefix + value + } else { + return '' + } +} + +/** + * 新建一个函数,判断传入的字符串的值是不是存在,存在的话删除后面指定数量的字符 + * @param {*} value 要删除的字符串 + * @param {*} suffix 删除的后缀 + * @returns + */ +export function checkStringValueDeleteSuffix(value: string, suffix: string): string { + // 增加一个判断,当前删除的数量是不是大于字符串的长度 + if (value && value !== null && value !== undefined && value !== '') { + if (suffix.length > value.length) { + return '' + } else { + return value.slice(0, value.length - suffix.length) + } + } else { + return '' + } +} + +/** + * 新建一个函数,判断传入的字符串的值是不是存在,存在的话删除前面指定数量的字符 + * @param {*} value 操作的字符串 + * @param {*} prefix 要删除的字符串 + * @returns + */ +export function checkStringValueDeletePrefix(value: string, prefix: string): string { + // 增加一个判断,当前删除的数量是不是大于字符串的长度 + if (value && value !== null && value !== undefined && value !== '') { + if (prefix.length > value.length) { + return '' + } else { + return value.slice(prefix.length) + } + } else { + return '' + } +} + +export { successMessage, errorMessage, infoMessage, warningMessage, SendReturnMessage } diff --git a/src/renderer/auto-imports.d.ts b/src/renderer/auto-imports.d.ts new file mode 100644 index 0000000..b4ca07c --- /dev/null +++ b/src/renderer/auto-imports.d.ts @@ -0,0 +1,75 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const effectScope: typeof import('vue')['effectScope'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useDialog: typeof import('naive-ui')['useDialog'] + const useId: typeof import('vue')['useId'] + const useLoadingBar: typeof import('naive-ui')['useLoadingBar'] + const useMessage: typeof import('naive-ui')['useMessage'] + const useModel: typeof import('vue')['useModel'] + const useNotification: typeof import('naive-ui')['useNotification'] + const useSlots: typeof import('vue')['useSlots'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/src/renderer/components.d.ts b/src/renderer/components.d.ts new file mode 100644 index 0000000..f42b22a --- /dev/null +++ b/src/renderer/components.d.ts @@ -0,0 +1,145 @@ +/* eslint-disable */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +// biome-ignore lint: disable +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + AddBook: typeof import('./src/components/Original/MainHome/OriginalAddBook.vue')['default'] + AddBookTask: typeof import('./src/components/Original/MainHome/OriginalAddBookTask.vue')['default'] + AddOrModifyPreset: typeof import('./src/components/Preset/AddOrModifyPreset.vue')['default'] + AIGroup: typeof import('./src/components/Original/Copywriter/AIGroup.vue')['default'] + AIGroup_new: typeof import('./src/components/Original/Copywriter/AIGroup_new.vue')['default'] + AISetting: typeof import('./src/components/Setting/AISetting.vue')['default'] + AllImagePreview: typeof import('./src/components/Original/BookTaskDetail/AllImagePreview.vue')['default'] + APIIcon: typeof import('./src/components/common/Icon/APIIcon.vue')['default'] + AppearanceSettings: typeof import('./src/components/Setting/AppearanceSettings.vue')['default'] + BackTaskIcon: typeof import('./src/components/common/Icon/BackTaskIcon.vue')['default'] + BookTaskCard: typeof import('./src/components/Original/MainHome/OriginalBookTaskCard.vue')['default'] + BookTaskDetailTable: typeof import('./src/components/Original/BookTaskDetail/BookTaskDetailTable.vue')['default'] + BookTaskImageCache: typeof import('./src/components/Original/Image/BookTaskImageCache.vue')['default'] + CharacterPreset: typeof import('./src/components/Preset/CharacterPreset.vue')['default'] + CommonDialog: typeof import('./src/components/common/CommonDialog.vue')['default'] + ContactDeveloper: typeof import('./src/components/SoftHome/ContactDeveloper.vue')['default'] + copy: typeof import('./src/components/Original/Copywriter/AIGroup.vue')['default'] + DataTableAction: typeof import('./src/components/Original/BookTaskDetail/DataTableAction.vue')['default'] + DatatableAfterGpt: typeof import('./src/components/Original/BookTaskDetail/DatatableAfterGpt.vue')['default'] + DatatableCharacterAndSceneAndStyle: typeof import('./src/components/Original/BookTaskDetail/DatatableCharacterAndSceneAndStyle.vue')['default'] + DatatableGenerateImage: typeof import('./src/components/Original/BookTaskDetail/DatatableGenerateImage.vue')['default'] + DatatableGenerateImageAction: typeof import('./src/components/Original/BookTaskDetail/DatatableGenerateImageAction.vue')['default'] + DataTableGptPrompt: typeof import('./src/components/Original/BookTaskDetail/DataTableGptPrompt.vue')['default'] + DatatableHeaderCharacter: typeof import('./src/components/Original/BookTaskDetail/DatatableHeaderCharacter.vue')['default'] + DatatableHeaderImage: typeof import('./src/components/Original/BookTaskDetail/DatatableHeaderImage.vue')['default'] + DisabledWrapper: typeof import('./src/components/common/DisabledWrapper.vue')['default'] + DocHelp: typeof import('./src/components/DocHelp.vue')['default'] + DownloadRound: typeof import('./src/components/common/Icon/DownloadRound.vue')['default'] + DynamicPromptSortTagsSelect: typeof import('./src/components/Original/BookTaskDetail/DynamicPromptSortTagsSelect.vue')['default'] + EditWord: typeof import('./src/components/Original/Copywriter/EditWord.vue')['default'] + EmptyState: typeof import('./src/components/Original/MainHome/OriginalEmptyState.vue')['default'] + FindReplaceRound: typeof import('./src/components/common/Icon/FindReplaceRound.vue')['default'] + GeneralSettings: typeof import('./src/components/Setting/GeneralSettings.vue')['default'] + HandGroup: typeof import('./src/components/Original/Copywriter/HandGroup.vue')['default'] + InputDialogContent: typeof import('./src/components/common/InputDialogContent.vue')['default'] + JianyingGenerateInformation: typeof import('./src/components/Original/BookTaskDetail/JianyingGenerateInformation.vue')['default'] + JianyingKeyFrameSetting: typeof import('./src/components/Setting/JianyingKeyFrameSetting.vue')['default'] + MenuOpenRound: typeof import('./src/components/common/Icon/MenuOpenRound.vue')['default'] + MessageAndProgress: typeof import('./src/components/Original/BookTaskDetail/MessageAndProgress.vue')['default'] + MJSettings: typeof import('./src/components/Setting/MJSettings.vue')['default'] + MobileHeader: typeof import('./src/components/Original/MainHome/OriginalMobileHeader.vue')['default'] + NAlert: typeof import('naive-ui')['NAlert'] + NAletr: typeof import('naive-ui')['NAletr'] + NButton: typeof import('naive-ui')['NButton'] + NCard: typeof import('naive-ui')['NCard'] + NCheckbox: typeof import('naive-ui')['NCheckbox'] + NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup'] + NColorPicker: typeof import('naive-ui')['NColorPicker'] + NConfigProvider: typeof import('naive-ui')['NConfigProvider'] + NDataTable: typeof import('naive-ui')['NDataTable'] + NDescriptions: typeof import('naive-ui')['NDescriptions'] + NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem'] + NDialogProvider: typeof import('naive-ui')['NDialogProvider'] + NDivider: typeof import('naive-ui')['NDivider'] + NDrawer: typeof import('naive-ui')['NDrawer'] + NDrawerContent: typeof import('naive-ui')['NDrawerContent'] + NDropdown: typeof import('naive-ui')['NDropdown'] + NDynamicTags: typeof import('naive-ui')['NDynamicTags'] + NEmpty: typeof import('naive-ui')['NEmpty'] + NFlex: typeof import('naive-ui')['NFlex'] + NForm: typeof import('naive-ui')['NForm'] + NFormItem: typeof import('naive-ui')['NFormItem'] + NGradientText: typeof import('naive-ui')['NGradientText'] + NGrid: typeof import('naive-ui')['NGrid'] + NGridItem: typeof import('naive-ui')['NGridItem'] + NH3: typeof import('naive-ui')['NH3'] + NIcon: typeof import('naive-ui')['NIcon'] + NInp: typeof import('naive-ui')['NInp'] + NInput: typeof import('naive-ui')['NInput'] + NInputGroup: typeof import('naive-ui')['NInputGroup'] + NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel'] + NInputNumber: typeof import('naive-ui')['NInputNumber'] + NLayout: typeof import('naive-ui')['NLayout'] + NLayoutContent: typeof import('naive-ui')['NLayoutContent'] + NLayoutSider: typeof import('naive-ui')['NLayoutSider'] + NMessageProvider: typeof import('naive-ui')['NMessageProvider'] + NModal: typeof import('naive-ui')['NModal'] + NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] + NotesCollapse: typeof import('./src/components/common/NotesCollapse.vue')['default'] + NPagination: typeof import('naive-ui')['NPagination'] + NPopover: typeof import('naive-ui')['NPopover'] + NProgress: typeof import('naive-ui')['NProgress'] + NScrollbar: typeof import('naive-ui')['NScrollbar'] + NSelect: typeof import('naive-ui')['NSelect'] + NSlider: typeof import('naive-ui')['NSlider'] + NSpace: typeof import('naive-ui')['NSpace'] + NSpin: typeof import('naive-ui')['NSpin'] + NStep: typeof import('naive-ui')['NStep'] + NSteps: typeof import('naive-ui')['NSteps'] + NSwitch: typeof import('naive-ui')['NSwitch'] + NTag: typeof import('naive-ui')['NTag'] + NText: typeof import('naive-ui')['NText'] + NTooltip: typeof import('naive-ui')['NTooltip'] + OpenInBrowserRound: typeof import('./src/components/common/Icon/OpenInBrowserRound.vue')['default'] + OriginalAddBook: typeof import('./src/components/Original/MainHome/OriginalAddBook.vue')['default'] + OriginalAddBookTask: typeof import('./src/components/Original/MainHome/OriginalAddBookTask.vue')['default'] + OriginalBookTaskCard: typeof import('./src/components/Original/MainHome/OriginalBookTaskCard.vue')['default'] + OriginalEmptyState: typeof import('./src/components/Original/MainHome/OriginalEmptyState.vue')['default'] + OriginalMobileHeader: typeof import('./src/components/Original/MainHome/OriginalMobileHeader.vue')['default'] + OriginalModifyBookTask: typeof import('./src/components/Original/MainHome/OriginalModifyBookTask.vue')['default'] + OriginalProjectItem: typeof import('./src/components/Original/MainHome/OriginalProjectItem.vue')['default'] + OriginalProjectSidebar: typeof import('./src/components/Original/MainHome/OriginalProjectSidebar.vue')['default'] + OriginalSearchBook: typeof import('./src/components/Original/MainHome/OriginalSearchBook.vue')['default'] + OriginalTaskCard: typeof import('./src/components/Original/MainHome/OriginalTaskCard.vue')['default'] + OriginalTaskList: typeof import('./src/components/Original/MainHome/OriginalTaskList.vue')['default'] + OriginalViewBookInfo: typeof import('./src/components/Original/MainHome/OriginalViewBookInfo.vue')['default'] + OriginalViewBookTaskInfo: typeof import('./src/components/Original/MainHome/OriginalViewBookTaskInfo.vue')['default'] + PresetShowCard: typeof import('./src/components/Preset/PresetShowCard.vue')['default'] + ProjectItem: typeof import('./src/components/Original/MainHome/ProjectItem.vue')['default'] + ProjectSidebar: typeof import('./src/components/Original/MainHome/OriginalProjectSidebar.vue')['default'] + QuickGroup: typeof import('./src/components/Original/Copywriter/QuickGroup.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + SceneAnalysis: typeof import('./src/components/Original/Analysis/SceneAnalysis.vue')['default'] + ScenePreset: typeof import('./src/components/Preset/ScenePreset.vue')['default'] + SDSetting: typeof import('./src/components/Setting/SDSetting.vue')['default'] + SearchBook: typeof import('./src/components/Original/MainHome/OriginalSearchBook.vue')['default'] + SearchPresetArea: typeof import('./src/components/Preset/SearchPresetArea.vue')['default'] + SelectRegionImage: typeof import('./src/components/Original/Image/SelectRegionImage.vue')['default'] + SelectStylePreset: typeof import('./src/components/Preset/SelectStylePreset.vue')['default'] + StylePreset: typeof import('./src/components/Preset/StylePreset.vue')['default'] + TaskCard: typeof import('./src/components/Original/MainHome/OriginalTaskCard.vue')['default'] + TaskList: typeof import('./src/components/Original/MainHome/OriginalTaskList.vue')['default'] + TextEllipsis: typeof import('./src/components/common/TextEllipsis.vue')['default'] + TooltipButton: typeof import('./src/components/common/TooltipButton.vue')['default'] + TooltipDropdown: typeof import('./src/components/common/TooltipDropdown.vue')['default'] + TopMenuButtons: typeof import('./src/components/Original/BookTaskDetail/TopMenuButtons.vue')['default'] + UploadRound: typeof import('./src/components/common/Icon/UploadRound.vue')['default'] + UserAnalysis: typeof import('./src/components/Original/Analysis/UserAnalysis.vue')['default'] + Versions: typeof import('./src/components/Versions.vue')['default'] + ViewBookInfo: typeof import('./src/components/Original/MainHome/ViewBookInfo.vue')['default'] + WechatGroup: typeof import('./src/components/SoftHome/WechatGroup.vue')['default'] + WordGroup: typeof import('./src/components/Original/Copywriter/WordGroup.vue')['default'] + } +} diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..329c3e9 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,17 @@ + + + + + LaiTool PRO + + + + + +
+ + + diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue new file mode 100644 index 0000000..5dbb345 --- /dev/null +++ b/src/renderer/src/App.vue @@ -0,0 +1,361 @@ + + + + + diff --git a/src/renderer/src/assets/base.css b/src/renderer/src/assets/base.css new file mode 100644 index 0000000..4c63469 --- /dev/null +++ b/src/renderer/src/assets/base.css @@ -0,0 +1,85 @@ +c:root { + --ev-c-white: #ffffff; + --ev-c-white-soft: #f8f8f8; + --ev-c-white-mute: #f2f2f2; + + --ev-c-black: #1b1b1f; + --ev-c-black-soft: #222222; + --ev-c-black-mute: #282828; + + --ev-c-gray-1: #e0e0e0; + --ev-c-gray-2: #c8c8c8; + --ev-c-gray-3: #f5f5f5; + + --ev-c-text-1: rgba(30, 30, 30, 0.86); + --ev-c-text-2: rgba(60, 60, 60, 0.7); + --ev-c-text-3: rgba(80, 80, 80, 0.5); + + --ev-button-alt-border: transparent; + --ev-button-alt-text: var(--ev-c-text-1); + --ev-button-alt-bg: var(--ev-c-gray-3); + --ev-button-alt-hover-border: transparent; + --ev-button-alt-hover-text: var(--ev-c-text-1); + --ev-button-alt-hover-bg: var(--ev-c-gray-2); + + /* 设置为白色主题 */ + --color-background: var(--ev-c-white); + --color-background-soft: var(--ev-c-white-soft); + --color-background-mute: var(--ev-c-white-mute); + --color-text: var(--ev-c-text-1); +} + +/* 添加深色模式CSS变量 */ +.dark-theme { + --ev-c-white: #252529; + --ev-c-white-soft: #222222; + --ev-c-white-mute: #2c2c30; + + --ev-c-black: #ffffff; + --ev-c-black-soft: #f9f9f9; + --ev-c-black-mute: #f1f1f1; + + --ev-c-text-1: rgba(255, 255, 255, 0.86); + --ev-c-text-2: rgba(235, 235, 235, 0.7); + --ev-c-text-3: rgba(235, 235, 235, 0.5); + + --color-background: var(--ev-c-black); + --color-background-soft: var(--ev-c-black-soft); + --color-background-mute: var(--ev-c-black-mute); + --color-text: var(--ev-c-text-1); +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +ul { + list-style: none; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} \ No newline at end of file diff --git a/src/renderer/src/assets/dashboard.png b/src/renderer/src/assets/dashboard.png new file mode 100644 index 0000000..8357681 Binary files /dev/null and b/src/renderer/src/assets/dashboard.png differ diff --git a/src/renderer/src/assets/electron.svg b/src/renderer/src/assets/electron.svg new file mode 100644 index 0000000..45ef09c --- /dev/null +++ b/src/renderer/src/assets/electron.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/renderer/src/assets/logo.png b/src/renderer/src/assets/logo.png new file mode 100644 index 0000000..f930d2a Binary files /dev/null and b/src/renderer/src/assets/logo.png differ diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css new file mode 100644 index 0000000..3db6189 --- /dev/null +++ b/src/renderer/src/assets/main.css @@ -0,0 +1,181 @@ +@import './base.css'; + +body { + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + background-size: cover; + user-select: none; +} + +code { + font-weight: 600; + padding: 3px 5px; + border-radius: 2px; + background-color: var(--color-background-mute); + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 85%; +} + +#app { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + margin-bottom: 80px; +} + +.logo { + margin-bottom: 20px; + -webkit-user-drag: none; + height: 128px; + width: 128px; + will-change: filter; + transition: filter 300ms; +} + +.logo:hover { + filter: drop-shadow(0 0 1.2em #6988e6aa); +} + +.creator { + font-size: 14px; + line-height: 16px; + color: var(--ev-c-text-2); + font-weight: 600; + margin-bottom: 10px; +} + +.text { + font-size: 28px; + color: var(--ev-c-text-1); + font-weight: 700; + line-height: 32px; + text-align: center; + margin: 0 10px; + padding: 16px 0; +} + +.tip { + font-size: 16px; + line-height: 24px; + color: var(--ev-c-text-2); + font-weight: 600; +} + +.vue { + background: -webkit-linear-gradient(315deg, #42d392 25%, #647eff); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-weight: 700; +} + +.ts { + background: -webkit-linear-gradient(315deg, #3178c6 45%, #f0dc4e); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-weight: 700; +} + +.actions { + display: flex; + padding-top: 32px; + margin: -6px; + flex-wrap: wrap; + justify-content: flex-start; +} + +.action { + flex-shrink: 0; + padding: 6px; +} + +.action a { + cursor: pointer; + text-decoration: none; + display: inline-block; + border: 1px solid transparent; + text-align: center; + font-weight: 600; + white-space: nowrap; + border-radius: 20px; + padding: 0 20px; + line-height: 38px; + font-size: 14px; + border-color: var(--ev-button-alt-border); + color: var(--ev-button-alt-text); + background-color: var(--ev-button-alt-bg); +} + +.action a:hover { + border-color: var(--ev-button-alt-hover-border); + color: var(--ev-button-alt-hover-text); + background-color: var(--ev-button-alt-hover-bg); +} + +.versions { + position: absolute; + bottom: 30px; + margin: 0 auto; + padding: 15px 0; + font-family: 'Menlo', 'Lucida Console', monospace; + display: inline-flex; + overflow: hidden; + align-items: center; + border-radius: 22px; + background-color: #202127; + backdrop-filter: blur(24px); +} + +.versions li { + display: block; + float: left; + border-right: 1px solid var(--ev-c-gray-1); + padding: 0 20px; + font-size: 14px; + line-height: 14px; + opacity: 0.8; + &:last-child { + border: none; + } +} + +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +/* 在全局样式中添加 */ +.n-spin-container { + z-index: 100 !important; /* 确保加载遮罩在最上层 */ +} + +@media (max-width: 720px) { + .text { + font-size: 20px; + } +} + +@media (max-width: 620px) { + .versions { + display: none; + } +} + +@media (max-width: 350px) { + .tip, + .actions { + display: none; + } +} diff --git a/src/renderer/src/assets/null.png b/src/renderer/src/assets/null.png new file mode 100644 index 0000000..2894d7c Binary files /dev/null and b/src/renderer/src/assets/null.png differ diff --git a/src/renderer/src/assets/qq-qr.jpg b/src/renderer/src/assets/qq-qr.jpg new file mode 100644 index 0000000..389d45d Binary files /dev/null and b/src/renderer/src/assets/qq-qr.jpg differ diff --git a/src/renderer/src/assets/qq-user.jpg b/src/renderer/src/assets/qq-user.jpg new file mode 100644 index 0000000..9112565 Binary files /dev/null and b/src/renderer/src/assets/qq-user.jpg differ diff --git a/src/renderer/src/assets/wechat-user.jpg b/src/renderer/src/assets/wechat-user.jpg new file mode 100644 index 0000000..6662532 Binary files /dev/null and b/src/renderer/src/assets/wechat-user.jpg differ diff --git a/src/renderer/src/assets/wechat-xiangbei.jpg b/src/renderer/src/assets/wechat-xiangbei.jpg new file mode 100644 index 0000000..1a9da5c Binary files /dev/null and b/src/renderer/src/assets/wechat-xiangbei.jpg differ diff --git a/src/renderer/src/common/book.ts b/src/renderer/src/common/book.ts new file mode 100644 index 0000000..88126d2 --- /dev/null +++ b/src/renderer/src/common/book.ts @@ -0,0 +1,154 @@ +import { PresetCategory } from '@/define/data/presetData' +import { Book } from '@/define/model/book/book' +import { ErrorItem, SuccessItem } from '@/define/model/generalResponse' +import { PresetModel } from '@/define/model/preset' +import { errorMessage, successMessage } from '@/public/generalTools' +import { usePresetStore } from '@renderer/stores' +import { isEmpty } from 'lodash' +import { checkImageExists } from './image' +const presetStore = usePresetStore() + +/** + * 获取并处理预设列表数据 + * + * 该方法根据指定条件从数据库获取预设数据,并进行适当处理后按类别分组返回。 + * 处理内容包括: + * - 确保图片路径格式正确(添加file://前缀) + * - 添加时间戳避免缓存问题 + * - 初始化选中状态标记 + * - 将预设按类型分组(角色、风格、场景) + * + * @param {PresetModel.QueryPresetCondition} [condition] - 查询条件,不提供则使用预设仓库的当前条件 + * + * @returns {Promise} 按类别分组的预设集合 + * - character: 角色类预设数组 + * - style: 风格类预设数组 + * - scene: 场景类预设数组 + * + * @throws {Error} 当获取预设数据失败时抛出错误 + * + * @example + * // 使用默认条件获取预设 + * const presets = await getShowTagsData(); + * + * // 使用自定义条件获取预设 + * const presets = await getShowTagsData({ + * keyword: '自然', + * category: PresetCategory.Scene + * }); + */ +export async function getShowTagsData( + condition?: PresetModel.QueryPresetCondition +): Promise { + + if (!condition) { + condition = { + ...presetStore.queryPresetCondition + } + } + let res = await window.preset.GetPresetByCondition(condition) + + if (res.code != 1) { + throw new Error(res.message) + } + + let characterShowPreset: PresetModel.Preset[] = [] + let styleShowPreset: PresetModel.Preset[] = [] + let sceneShowPreset: PresetModel.Preset[] = [] + + for (let i = 0; res.data.presetArray && i < res.data.presetArray.length; i++) { + const element = res.data.presetArray[i] as PresetModel.Preset + if (element.showImage && element.showImage.length > 0) { + // 处理路径中的特殊字符和中文 + let imagePath = element.showImage[0] + // 确保本地文件路径有file://前缀 + if ( + !imagePath.startsWith('file://') && + (imagePath.startsWith('/') || imagePath.includes(':\\') || imagePath.startsWith('..')) + ) { + imagePath = `file://${imagePath}`.replaceAll(/\\/g, '/') + } + + element.coverImage = imagePath + `?${Date.now()}` // 添加时间戳以避免缓存问题 + element.checked = false // 初始化checked属性 + } else { + element.coverImage = '' + element.checked = false // 初始化checked属性 + } + + if (element.type == PresetCategory.Character) { + characterShowPreset.push(element) + } else if (element.type == PresetCategory.Style) { + styleShowPreset.push(element) + } else if (element.type == PresetCategory.Scene) { + sceneShowPreset.push(element) + } + } + return { + character: characterShowPreset, + style: styleShowPreset, + scene: sceneShowPreset + } +} + +/** + * 检查分镜图片是否全部存在 + * + * 该函数检查所有分镜的图片是否已生成且文件存在于本地文件系统中。 + * 检查过程包括两步: + * 1. 验证所有分镜是否都设置了有效的outImagePath + * 2. 调用checkImageExists函数确认图片文件在本地实际存在 + * + * @param {Book.SelectBookTaskDetail[]} bookTaskDetails - 需要检查的分镜数组 + * + * @returns {Promise} 检查结果 + * - 成功:返回SuccessItem,表示所有图片都存在 + * - 失败:返回ErrorItem,包含具体的错误信息,如缺少图片的分镜名称 + * + * @example + * // 检查所有选中分镜的图片是否存在 + * const result = await checBookTaskDetailImageExist(bookStore.selectBookTaskDetail); + * + * if (result.code === 1) { + * // 所有图片存在,可以继续处理 + * await processHDImages(); + * } else { + * // 显示错误信息 + * message.error(result.message); + * } + */ +export async function checBookTaskDetailImageExist( + bookTaskDetails: Book.SelectBookTaskDetail[] +): Promise { + let hasAllImage = bookTaskDetails.findIndex((item) => { + return ( + isEmpty(item.outImagePath) || + item.outImagePath == null || + item.outImagePath == '' || + item.outImagePath == undefined + ) + }) + if (hasAllImage != -1) { + return errorMessage('分镜的图片没有全部出完,不能继续该操作!!') + } + // 检查所有的图片是否存在 + let imageCheck = false + let notFoundImage: string[] = [] + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i] + if (element.outImagePath == undefined || element.outImagePath == null) { + return errorMessage('分镜的图片没有全部出完,不能继续该操作!!') + } + let c = await checkImageExists(element.outImagePath.split('?t')[0]) + if (!c) { + imageCheck = true + notFoundImage.push(element.name as string) + } + } + if (imageCheck) { + return errorMessage( + `分镜 ${notFoundImage.join(',')} 图片在本地未找到,不能继续该操作,请检查对应分镜的图片路径是否正确` + ) + } + return successMessage(null, '分镜的图片全部存在,可以进行高清处理') +} diff --git a/src/renderer/src/common/color.ts b/src/renderer/src/common/color.ts new file mode 100644 index 0000000..d51921f --- /dev/null +++ b/src/renderer/src/common/color.ts @@ -0,0 +1,124 @@ +/** + * 将任意格式的颜色转换为带有指定透明度的RGBA格式 + * @param color 原始颜色值 (十六进制、RGB或RGBA格式) + * @param alpha 目标透明度 (0-1之间的数值) + * @returns RGBA格式的颜色字符串 + */ +export function toRGBA(color: string, alpha: number): string { + // 无效颜色值处理 + if (!color) return `rgba(0, 0, 0, ${alpha})` + + // 处理十六进制颜色 (#RRGGBB 或 #RGB) + if (color.startsWith('#')) { + let hex = color.slice(1) + + // 处理简写的十六进制颜色 (#RGB) + if (hex.length === 3) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + } + + // 解析RGB值 + const r = parseInt(hex.slice(0, 2), 16) + const g = parseInt(hex.slice(2, 4), 16) + const b = parseInt(hex.slice(4, 6), 16) + + return `rgba(${r}, ${g}, ${b}, ${alpha})` + } + // 处理RGB格式: rgb(r, g, b) + else if (color.startsWith('rgb(')) { + return color.replace('rgb(', 'rgba(').replace(')', `, ${alpha})`) + } + // 处理已有的RGBA格式: rgba(r, g, b, a) + else if (color.startsWith('rgba(')) { + return color.replace(/rgba\((.+?),\s*[\d.]+\)/, `rgba($1, ${alpha})`) + } + + // 处理其他格式或无法识别的颜色 + return color +} + +/** + * 从颜色字符串中提取当前透明度 + * @param color 颜色字符串 + * @returns 提取的透明度值,如果无法提取则返回1 + */ +function extractAlpha(color: string): number { + if (color.startsWith('rgba')) { + const match = color.match(/rgba\(.*?,\s*([\d.]+)\)/) + if (match && match[1]) { + return parseFloat(match[1]) + } + } + return 1 // 默认为不透明 +} + +/** + * 智能调整透明度以创建视觉差异 + * @param currentAlpha 当前透明度 + * @param isHover 是否为悬停状态(true)或选中状态(false) + * @returns 调整后的透明度 + */ +function getAdaptiveAlpha(currentAlpha: number, isHover: boolean = true): number { + // 对于悬停状态 + if (isHover) { + if (currentAlpha < 0.5) { + // 如果原透明度较低,则增加透明度(颜色变淡) + return Math.min(currentAlpha + 0.2, 0.9) + } else { + // 如果原透明度较高,则减少透明度(颜色变深) + return Math.max(currentAlpha - 0.2, 0.3) + } + } + // 对于选中/激活状态,效果更强烈一些 + else { + if (currentAlpha < 0.5) { + return Math.min(currentAlpha + 0.3, 0.95) + } else { + return Math.max(currentAlpha - 0.3, 0.25) + } + } +} + +/** + * 创建一个颜色的淡化版本 + * @param color 原始颜色 + * @param factor 淡化因子 (0-1之间,0为完全透明,1为原始颜色) + * @returns 淡化后的RGBA颜色 + */ +export function createLighterColor(color: string, factor: number = 0.8): string { + return toRGBA(color, factor) +} + +/** + * 智能创建悬停状态的颜色 + * @param color 原始颜色 + * @returns 适合悬停状态的颜色 + */ +export function createHoverColor(color: string): string { + const currentAlpha = extractAlpha(color) + const hoverAlpha = getAdaptiveAlpha(currentAlpha, true) + + // 先提取RGB部分,然后应用新的透明度 + const rgbPart = color.startsWith('rgba') + ? color.replace(/rgba\((.*?),\s*[\d.]+\)/, '$1') + : toRGBA(color, 1).replace(/rgba\((.*?),\s*[\d.]+\)/, '$1') + + return `rgba(${rgbPart}, ${hoverAlpha})` +} + +/** + * 智能创建激活/选中状态的颜色 + * @param color 原始颜色 + * @returns 适合激活状态的颜色 + */ +export function createActiveColor(color: string): string { + const currentAlpha = extractAlpha(color) + const activeAlpha = getAdaptiveAlpha(currentAlpha, false) + + // 先提取RGB部分,然后应用新的透明度 + const rgbPart = color.startsWith('rgba') + ? color.replace(/rgba\((.*?),\s*[\d.]+\)/, '$1') + : toRGBA(color, 1).replace(/rgba\((.*?),\s*[\d.]+\)/, '$1') + + return `rgba(${rgbPart}, ${activeAlpha})` +} diff --git a/src/renderer/src/common/file.ts b/src/renderer/src/common/file.ts new file mode 100644 index 0000000..f8c0e75 --- /dev/null +++ b/src/renderer/src/common/file.ts @@ -0,0 +1,236 @@ +/** + * 全面的文件路径处理工具库 + * 适用于浏览器环境,处理多种格式的文件路径和URL + */ + +/** + * 获取路径中的文件名部分(包含扩展名) + * @param filepath - 文件路径或URL + * @returns 文件名(含扩展名) + */ +export function getBasename(filepath: string): string { + if (!filepath) return '' + + // 移除查询参数(?后面的部分)和锚点(#后面的部分) + let path = removeQueryAndHash(filepath) + + // 处理URL编码 + try { + // 只解码路径部分,避免解码整个URL可能导致的错误 + path = decodePathComponent(path) + } catch (e) { + console.warn('解码路径失败,使用原始路径:', e) + } + + // 如果是URL,提取路径部分 + if (isUrl(path)) { + try { + const url = new URL(path) + path = url.pathname + } catch (e) { + // URL解析失败,继续使用原路径 + console.warn('URL解析失败:', e) + } + } + + // 获取最后一个斜杠或反斜杠后的部分 + const match = path.match(/[^\\/]+$/) + return match ? match[0] : '' +} + +/** + * 获取不含扩展名的文件名 + * @param filepath - 文件路径或URL + * @returns 不含扩展名的文件名 + */ +export function getBasenameWithoutExtension(filepath: string): string { + const basename = getBasename(filepath) + const dotIndex = basename.lastIndexOf('.') + // 如果没找到点或点是第一个字符(隐藏文件),返回整个文件名 + return dotIndex <= 0 ? basename : basename.substring(0, dotIndex) +} + +/** + * 获取文件扩展名(不含点) + * @param filepath - 文件路径或URL + * @returns 文件扩展名(不含点) + */ +export function getExtension(filepath: string): string { + const basename = getBasename(filepath) + const dotIndex = basename.lastIndexOf('.') + return dotIndex === -1 ? '' : basename.substring(dotIndex + 1).toLowerCase() +} + +/** + * 获取文件所在目录路径 + * @param filepath - 文件路径或URL + * @returns 文件所在目录路径 + */ +export function getDirname(filepath: string): string { + if (!filepath) return '' + + // 移除查询参数和锚点 + let path = removeQueryAndHash(filepath) + + // 如果是URL,保留协议和主机名部分 + let prefix = '' + if (isUrl(path)) { + try { + const url = new URL(path) + prefix = `${url.protocol}//${url.host}` + path = url.pathname + } catch (e) { + // URL解析失败,继续使用原路径 + } + } + + // 标准化路径分隔符 + path = path.replace(/\\/g, '/') + + // 去掉最后一个斜杠后的部分 + const lastSlashIndex = path.lastIndexOf('/') + if (lastSlashIndex === -1) return '' + + // 组合前缀和目录路径 + const dirname = path.substring(0, lastSlashIndex) + return prefix ? `${prefix}${dirname}` : dirname +} + +/** + * 组合路径片段 + * @param paths - 路径片段数组 + * @returns 组合后的路径 + */ +export function joinPath(...paths: string[]): string { + if (paths.length === 0) return '' + + // 检查第一个路径是否是URL + const isFirstUrl = isUrl(paths[0]) + let result = '' + + // 如果第一个是URL,保留协议和主机名 + if (isFirstUrl) { + try { + const url = new URL(paths[0]) + result = `${url.protocol}//${url.host}` + paths[0] = url.pathname + } catch (e) { + // URL解析失败,继续使用原路径 + } + } + + // 标准化并组合路径 + const normalizedPaths = paths.map((path, index) => { + path = path.replace(/\\/g, '/') // 统一使用 / 作为分隔符 + path = path.replace(/^\/+|\/+$/g, '') // 移除首尾的 / + + // 第一个路径如果是绝对路径,保留其开头的 / + if (index === 0 && (isFirstUrl || path.startsWith('/'))) { + path = '/' + path + } + + return path + }) + + // 组合处理后的路径 + return result + normalizedPaths.filter((p) => p.length > 0).join('/') +} + +/** + * 判断路径是否是URL + * @param path - 要检查的路径 + * @returns 是否为URL + */ +export function isUrl(path: string): boolean { + if (!path) return false + return /^(https?|file|ftp|data):/i.test(path) +} + +/** + * 移除路径中的查询参数和锚点 + * @param path - 输入路径 + * @returns 清理后的路径 + */ +export function removeQueryAndHash(path: string): string { + if (!path) return '' + return path.split(/[?#]/)[0] +} + +/** + * 智能解码路径组件,避免重复解码或解码错误 + * @param path - 可能编码的路径 + * @returns 解码后的路径 + */ +export function decodePathComponent(path: string): string { + if (!path) return '' + + // 检查是否含有编码字符 + if (/%[0-9a-f]{2}/i.test(path)) { + try { + return decodeURIComponent(path) + } catch (e) { + // 解码失败,返回原路径 + return path + } + } + + return path +} + +/** + * 为本地文件路径添加file://协议 + * @param path - 本地文件路径 + * @returns 带有file://协议的URL + */ +export function pathToFileUrl(path: string): string { + if (!path) return '' + if (isUrl(path)) return path + + // 标准化路径分隔符 + path = path.replace(/\\/g, '/') + + // 确保路径以 / 开头 + if (!path.startsWith('/')) { + path = '/' + path + } + + // Windows路径处理 + if (/^\/[A-Za-z]:/.test(path)) { + // 确保Windows盘符后有三个斜杠 + return `file:///${path.substring(1)}` + } + + return `file://${path}` +} + +/** + * 规范化文件路径 + * @param path - 文件路径 + * @returns 规范化后的路径 + */ +export function normalizePath(path: string): string { + if (!path) return '' + + // 统一分隔符 + path = path.replace(/\\/g, '/') + + // 处理 ../ 和 ./ + const parts = path.split('/') + const result: string[] = [] + + for (const part of parts) { + if (part === '..') { + result.pop() + } else if (part !== '.' && part !== '') { + result.push(part) + } + } + + // 如果原始路径以 / 开头,确保结果也以 / 开头 + let normalized = result.join('/') + if (path.startsWith('/')) { + normalized = '/' + normalized + } + + return normalized +} diff --git a/src/renderer/src/common/image.ts b/src/renderer/src/common/image.ts new file mode 100644 index 0000000..581b1f6 --- /dev/null +++ b/src/renderer/src/common/image.ts @@ -0,0 +1,37 @@ +// 通过加载图片的方式检查文件是否存在 +export function checkImageExists(imagePath: string) { + return new Promise((resolve) => { + // 创建新的Image对象 + const img = new Image() + + // 设置成功加载的处理函数 + img.onload = () => { + resolve(true) + } + + // 设置加载失败的处理函数 + img.onerror = () => { + resolve(false) + } + + // 确保路径格式正确,因为是本地文件需要添加file://前缀 + let imgSrc = imagePath + + // 如果路径没有协议前缀,则添加file://前缀 + if ( + !imgSrc.startsWith('file://') && + !imgSrc.startsWith('http://') && + !imgSrc.startsWith('https://') + ) { + // 确保路径格式正确,windows路径需要处理 + imgSrc = 'file:///' + imgSrc.replace(/\\/g, '/') + } + + // 添加时间戳避免缓存 + imgSrc += `?t=${new Date().getTime()}` + + // 设置图片源 + img.src = imgSrc + }) +} + diff --git a/src/renderer/src/common/initialData.ts b/src/renderer/src/common/initialData.ts new file mode 100644 index 0000000..dd07e75 --- /dev/null +++ b/src/renderer/src/common/initialData.ts @@ -0,0 +1,382 @@ +import { getAPIOptions } from '@/define/data/apiData' +import { ImageCategory, ImageToVideoCategory } from '@/define/data/imageData' +import { getMJSpeedOptions, ImageGenerateMode, MJRobotType } from '@/define/data/mjData' +import { JianyingKeyFrameEnum } from '@/define/enum/jianyingEnum' +import { OptionKeyName, OptionType } from '@/define/enum/option' +import { SettingModal } from '@/define/model/setting' +import { ValidateJson, ValidateJsonAndParse } from '@/define/Tools/validate' +import { isEmpty } from 'lodash' + +//#region 初始化通用设置 + +export const defaultGeneralSetting: SettingModal.GeneralSettings = { + draftPath: '', // 剪映草稿箱路径 + projectPath: '', // 项目保存路径 + concurrency: 1, // 系统并发数 (1-16) + defaultImgGenMethod: ImageCategory.Midjourney, // 默认生图方式 + hdScale: 2, // 高清倍数 (1-4) + defaultImg2Video: ImageToVideoCategory.RUNWAY, // 默认图转视频方式 + language: 'zh-CN' // 系统语言 +} + +/** + * 初始化通用设置信息 + */ +export async function InitGeneralSetting() { + try { + let GeneralSettingOption = await window.option.GetOptionByKey( + OptionKeyName.Software.GeneralSetting + ) + let newGeneralSetting = Object.assign({}, defaultGeneralSetting) + if ( + !( + GeneralSettingOption.data == null || + GeneralSettingOption.data.value == null || + isEmpty(GeneralSettingOption.data.value) || + !ValidateJson(GeneralSettingOption.data.value) + ) + ) { + // 不需要初始化,检查各项设置是否存在 + let generalSetting = ValidateJsonAndParse( + GeneralSettingOption.data.value + ) + newGeneralSetting = Object.assign({}, newGeneralSetting, generalSetting) + } + + // 设置部分默认值 + let projectPathOption = await window.option.GetOptionByKey(OptionKeyName.Software.ProjectPath) + if ( + projectPathOption != null && + projectPathOption.data != null && + projectPathOption.data.value != null && + !isEmpty(projectPathOption.data.value) + ) { + newGeneralSetting.projectPath = projectPathOption.data.value + } + + let draftPathOption = await window.setting.GetDefaultJianyingDraftPath() + if (draftPathOption.code == 1) { + newGeneralSetting.draftPath = draftPathOption.data + } + + // 开始保存 + let res = await window.option.ModifyOptionByKey( + OptionKeyName.Software.GeneralSetting, + JSON.stringify(newGeneralSetting), + OptionType.JSON + ) + if (res.code != 1) { + throw new Error('初始化通用设置失败') + } + } catch (error) { + throw error + } +} + +//#endregion + +//#region 初始化MJ设置 + +// 初始化通用设置 +export const mjGeneralSettings: SettingModal.MJGeneralSettings = { + outputMode: ImageGenerateMode.MJ_API, + robot: MJRobotType.MJ, + model: '3e6473ab-9a64-4574-9a38-f5c75af552b6', + size: '3e2772f2-041c-49c6-ba13-d0ed120310b8', + commandSuffix: ' --niji 6 --ar 1:1', + taskCount: 3, + interval: 5 +} + +// 初始化API设置 +export const mjApiSettings: SettingModal.MJApiSettings = { + apiUrl: getAPIOptions('mj')[0].value, + apiKey: '', + apiSpeed: getMJSpeedOptions()[1].value +} + +/** + * 初始化MJ相关设置 + */ +export async function InitMJSetting() { + try { + let generalSettingOption = await window.option.GetOptionByKey( + OptionKeyName.Midjourney.GeneralSetting + ) + let newGeneralSetting = Object.assign({}, mjGeneralSettings) + // 判断是不是有数据 + if ( + !( + generalSettingOption == null || + generalSettingOption.data == null || + generalSettingOption.data.value == null || + isEmpty(generalSettingOption.data.value) || + !ValidateJson(generalSettingOption.data.value) + ) + ) { + // 不需要初始化,检查各项设置是否存在 + let mjGeneralSetting = ValidateJsonAndParse( + generalSettingOption.data.value + ) + newGeneralSetting = Object.assign({}, newGeneralSetting, mjGeneralSetting) + } + // 直接覆盖旧的值 + let res = await window.option.ModifyOptionByKey( + OptionKeyName.Midjourney.GeneralSetting, + JSON.stringify(newGeneralSetting), + OptionType.JSON + ) + if (res.code != 1) { + throw new Error('初始化MJ通用设置失败') + } + + // 初始化API设置 + let apiSettingOption = await window.option.GetOptionByKey(OptionKeyName.Midjourney.ApiSetting) + let newApiSetting = Object.assign({}, mjApiSettings) + + // 判断是不是有数据 + if ( + !( + apiSettingOption == null || + apiSettingOption.data == null || + apiSettingOption.data.value == null || + isEmpty(apiSettingOption.data.value) || + !ValidateJson(apiSettingOption.data.value) + ) + ) { + // 不需要初始化,检查各项设置是否存在 + let mjApiSetting = ValidateJsonAndParse( + apiSettingOption.data.value + ) + newApiSetting = Object.assign({}, newApiSetting, mjApiSetting) + } + // 直接覆盖旧的值 + res = await window.option.ModifyOptionByKey( + OptionKeyName.Midjourney.ApiSetting, + JSON.stringify(newApiSetting), + OptionType.JSON + ) + if (res.code != 1) { + throw new Error('初始化MJ API设置失败') + } + } catch (error) { + throw error + } +} + +//#endregion + +//#region 初始化推理设置 + +/** + * 推理设置默认值 + */ +export const inferenceAISettings: SettingModal.InferenceAISettings = { + apiProvider: getAPIOptions('gpt')[0].value, + apiToken: '', + inferenceModel: 'deepseek-chat', + translationModel: 'Doubao-lite-32k', + aiPromptValue: 'NanFengStoryboardMasterSpecialEffects' +} + +/** + * 初始化推理设置 + */ +export async function InitInferenceAISetting() { + try { + let inferenceAISettingOption = await window.option.GetOptionByKey( + OptionKeyName.InferenceAI.InferenceSetting + ) + let newInferenceAISetting = Object.assign({}, inferenceAISettings) + // 判断是不是有数据 + if ( + !( + inferenceAISettingOption == null || + inferenceAISettingOption.data == null || + inferenceAISettingOption.data.value == null || + isEmpty(inferenceAISettingOption.data.value) || + !ValidateJson(inferenceAISettingOption.data.value) + ) + ) { + // 不需要初始化,检查各项设置是否存在 + let inferenceAISetting = ValidateJsonAndParse( + inferenceAISettingOption.data.value + ) + newInferenceAISetting = Object.assign({}, newInferenceAISetting, inferenceAISetting) + } + // 直接覆盖旧的值 + let res = await window.option.ModifyOptionByKey( + OptionKeyName.InferenceAI.InferenceSetting, + JSON.stringify(newInferenceAISetting), + OptionType.JSON + ) + if (res.code != 1) { + throw new Error('初始化推理设置失败') + } + } catch (error) { + throw error + } +} +//#endregion + +//#region 初始化SD设置 + +/** + * SD设置默认值 + */ +export const initialSDSettings: SettingModal.SDSettings = { + requestUrl: 'http://127.0.0.1:7860', + generationMethod: 'txt2img', + positivePrompt: 'masterpiece, best quality, high resolution', + negativePrompt: 'EasyNegative, nsfw, (worst quality:2), (low quality:2)', + cfgScale: 9.5, + batchCount: 1, + seed: -1, + denoisingStrength: 0.7, + sampler: '', + steps: 20, + width: 768, + height: 768, + faceHandFix: false +} + +/** + * 修手/修脸模型默认值 + */ +export const initialADetailerSettings: SettingModal.SDADetailerModel[] = [ + { + id: crypto.randomUUID(), + model: 'face_yolov8n.pt', + threshold: 0.7 + }, + { + id: crypto.randomUUID(), + model: 'hand_yolov8n.pt', + threshold: 0.7 + } +] + +/** + * 初始化SD设置和修手/修脸模型设置 + * @description 该函数会检查SD设置和修手/修脸模型设置是否存在,如果不存在则初始化为默认值 + */ +export async function InitSDSettingAndADetailerSetting() { + try { + let sdSettingsOption = await window.option.GetOptionByKey(OptionKeyName.SD.SDImageSetting) + let newSdSettings = Object.assign({}, initialSDSettings) + if ( + !( + sdSettingsOption == null || + sdSettingsOption.data == null || + sdSettingsOption.data.value == null || + isEmpty(sdSettingsOption.data.value) || + !ValidateJson(sdSettingsOption.data.value) + ) + ) { + let sdSettings = ValidateJsonAndParse(sdSettingsOption.data.value) + newSdSettings = Object.assign({}, newSdSettings, sdSettings) + } + // 直接覆盖旧的值 + let res = await window.option.ModifyOptionByKey( + OptionKeyName.SD.SDImageSetting, + JSON.stringify(newSdSettings), + OptionType.JSON + ) + if (res.code != 1) { + throw new Error('初始化SD设置失败') + } + + // 修手/修脸模型设置 + let aDetailerOption = await window.option.GetOptionByKey(OptionKeyName.SD.SDADetailerSetting) + if ( + aDetailerOption == null || + aDetailerOption.data == null || + aDetailerOption.data.value == null || + isEmpty(aDetailerOption.data.value) || + !ValidateJson(aDetailerOption.data.value) + ) { + // 数据不存在,直接初始化 + let res = await window.option.ModifyOptionByKey( + OptionKeyName.SD.SDADetailerSetting, + JSON.stringify(initialADetailerSettings), + OptionType.JSON + ) + if (res.code != 1) { + throw new Error('初始化修手/修脸模型设置失败') + } + } + } catch (error) { + throw error + } +} + +//#endregion +/** + * 初始化剪映关键帧设置 + */ +export const defaultJianyingKeyFrameSetting: SettingModal.JianyingKeyFrameSettings = { + keyFrame: JianyingKeyFrameEnum.KFTypePositionY, + isFixedSpeed: true, + keyFrameTime: 4, + upDownKeyFrame: { + defaultScale: 134, + startPosition: 275, + endPosition: 275 + }, + leftRightKeyFrame: { + defaultScale: 134, + startPosition: 275, + endPosition: 275 + }, + scaleKeyFrame: { + defaultScale: 134, + startPosition: 134, + endPosition: 150 + } +} + +/** + * 初始化剪映关键帧设置 + * @description 该函数会检查剪映关键帧设置是否存在,如果不存在则初始化为默认值 + */ +export async function InitJianyingKeyFrameSetting() { + try { + let jianyingKeyFrameSettingOption = await window.option.GetOptionByKey( + OptionKeyName.Software.JianyingKeyFrameSetting + ) + let newJianyingKeyFrameSettingOption = Object.assign({}, defaultJianyingKeyFrameSetting) + if ( + !( + jianyingKeyFrameSettingOption == null || + jianyingKeyFrameSettingOption.data == null || + jianyingKeyFrameSettingOption.data.value == null || + isEmpty(jianyingKeyFrameSettingOption.data.value) || + !ValidateJson(jianyingKeyFrameSettingOption.data.value) + ) + ) { + let sdSettings = ValidateJsonAndParse( + jianyingKeyFrameSettingOption.data.value + ) + newJianyingKeyFrameSettingOption = Object.assign( + {}, + newJianyingKeyFrameSettingOption, + sdSettings + ) + } + // 直接覆盖旧的值 + let res = await window.option.ModifyOptionByKey( + OptionKeyName.Software.JianyingKeyFrameSetting, + JSON.stringify(newJianyingKeyFrameSettingOption), + OptionType.JSON + ) + if (res.code != 1) { + throw new Error('初始化剪映关键帧设置失败') + } + } catch (error) { + throw error + } +} + +//#region 初始化剪映关键帧设置 + +//#endregion diff --git a/src/renderer/src/common/task.ts b/src/renderer/src/common/task.ts new file mode 100644 index 0000000..3f77d44 --- /dev/null +++ b/src/renderer/src/common/task.ts @@ -0,0 +1,223 @@ +import { ImageCategory } from '@/define/data/imageData' +import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum' +import { DEFINE_STRING } from '@/define/ipcDefineString' +import { Book } from '@/define/model/book/book' +import { ErrorItem, SuccessItem } from '@/define/model/generalResponse' +import { errorMessage, successMessage } from '@/public/generalTools' +import { useBookStore } from '@renderer/stores' +const bookStore = useBookStore() + +/** + * 批量添加图片生成任务 + * + * 该函数根据指定的图像生成平台类型,为多个分镜创建相应的图片生成任务。 + * 支持多种图像生成平台,包括Midjourney、Stable Diffusion、Flux Forge/API、 + * ComfyUI和DALL-E 3等。函数会根据平台类型构建适合的任务配置,并交由任务系统执行。 + * + * @param {Book.SelectBookTaskDetail[]} bookTaskDetails - 需要生成图片的分镜数组, + * 每个元素必须包含id属性 + * @param {ImageCategory} imageCategory - 图像生成平台类型,如ImageCategory.Midjourney + * + * @returns {Promise} 任务添加结果 + * - 成功时返回任务添加成功的信息 + * - 失败时返回错误信息,包括无可用分镜或不支持的图像类型等场景 + * + * @example + * // 为选中的分镜使用Midjourney生成图片 + * const result = await AddMultiImageTask( + * selectedFrames, + * ImageCategory.Midjourney + * ); + * + * if (result.code === 1) { + * console.log('成功添加任务'); + * } else { + * console.error(result.message); + * } + */ +export async function AddMultiImageTask( + bookTaskDetails: Book.SelectBookTaskDetail[], + imageCategory: ImageCategory +): Promise { + let res: SuccessItem | ErrorItem + if (bookTaskDetails.length <= 0) { + return errorMessage('添加出图任务失败,未找到可生成图片的分镜,或者分镜都被锁定') + } + + if (imageCategory == ImageCategory.Midjourney) { + let tasksList: any[] = [] + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i] + tasksList.push({ + bookId: bookStore.selectBook.id, + type: BookBackTaskType.MJ_IMAGE, + executeType: TaskExecuteType.AUTO, + bookTaskId: bookStore.selectBookTask.id, + bookTaskDetailId: element.id, + messageName: DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN + }) + } + + res = await window.task.AddMultiTask(tasksList) + } else if (bookStore.selectBookTask.imageCategory == ImageCategory.Stable_Diffusion) { + // 添加SD出图任务 + let tasksList: any[] = [] + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i] + tasksList.push({ + bookId: bookStore.selectBook.id, + type: BookBackTaskType.SD_IMAGE, + executeType: TaskExecuteType.AUTO, + bookTaskId: bookStore.selectBookTask.id, + bookTaskDetailId: element.id, + messageName: DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN + }) + } + res = await window.task.AddMultiTask(tasksList) + } else if (bookStore.selectBookTask.imageCategory == ImageCategory.Flux_Forge) { + // 本地的forge flux + let tasksList: any[] = [] + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i] + tasksList.push({ + bookId: bookStore.selectBook.id, + type: BookBackTaskType.FLUX_FORGE_IMAGE, + executeType: TaskExecuteType.AUTO, + bookTaskId: bookStore.selectBookTask.id, + bookTaskDetailId: element.id, + messageName: DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN + }) + } + res = await window.task.AddMultiTask(tasksList) + } else if (bookStore.selectBookTask.imageCategory == ImageCategory.Flux_API) { + // 本地的forge flux + let tasksList: any[] = [] + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i] + tasksList.push({ + bookId: bookStore.selectBook.id, + type: BookBackTaskType.FLUX_API_IMAGE, + executeType: TaskExecuteType.AUTO, + bookTaskId: bookStore.selectBookTask.id, + bookTaskDetailId: element.id, + messageName: DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN + }) + } + res = await window.task.AddMultiTask(tasksList) + } else if (bookStore.selectBookTask.imageCategory == ImageCategory.Comfy_UI) { + let tasksList: any[] = [] + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i] + tasksList.push({ + bookId: bookStore.selectBook.id, + type: BookBackTaskType.ComfyUI_IMAGE, + executeType: TaskExecuteType.AUTO, + bookTaskId: bookStore.selectBookTask.id, + bookTaskDetailId: element.id, + messageName: DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN + }) + } + res = await window.task.AddMultiTask(tasksList) + } else if (bookStore.selectBookTask.imageCategory == ImageCategory.DALL_E_3) { + let tasksList: any[] = [] + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i] + tasksList.push({ + bookId: bookStore.selectBook.id, + type: BookBackTaskType.D3_IMAGE, + executeType: TaskExecuteType.AUTO, + bookTaskId: bookStore.selectBookTask.id, + bookTaskDetailId: element.id, + messageName: DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN + }) + } + res = await window.task.AddMultiTask(tasksList) + } else { + return errorMessage('添加出图任务失败,不支持的出图类型') + } + return res +} + +/** + * 停止图片生成任务 + * + * 该函数用于停止当前批次或所有批次的图片生成任务。支持停止多种图像生成平台的任务, + * 包括Midjourney、Stable Diffusion、Flux Forge/API、ComfyUI和DALL-E 3等。 + * 函数会查询符合条件的等待中任务,并将其状态更新为失败。 + * + * @param {string} type - 停止的范围类型 + * - 'this': 仅停止当前书籍任务的图片生成任务 + * - 'all': 停止系统中所有等待中的图片生成任务 + * + * @returns {Promise} 操作结果 + * - 成功时返回停止任务的成功信息 + * - 失败时返回错误信息,包括无任务可停止、参数错误等场景 + * + * @example + * // 停止当前批次的图片生成任务 + * const result = await StopImageTask('this'); + * + * // 停止所有批次的图片生成任务 + * const result = await StopImageTask('all'); + */ +export async function StopImageTask(type: string): Promise { + if (type != 'all' && type != 'this') { + return errorMessage('停止出图任务失败,参数错误') + } + let taskTypes = [ + BookBackTaskType.MJ_IMAGE, + BookBackTaskType.FLUX_API_IMAGE, + BookBackTaskType.FLUX_FORGE_IMAGE, + BookBackTaskType.SD_IMAGE, + BookBackTaskType.ComfyUI_IMAGE, + BookBackTaskType.D3_IMAGE + ] + + let queryCondition = { + bookTaskName: bookStore.selectBookTask.name, + taskStatus: BookBackTaskStatus.WAIT, + page: 1, + pageSize: 10000 + } + if (type == 'all') { + delete queryCondition.bookTaskName + } + + let stopTasks: any[] = [] + + for (let i = 0; i < taskTypes.length; i++) { + const element = taskTypes[i] + let taskRes = await window.task.GetTaskCollection({ + ...queryCondition, + taskType: element + }) + if (taskRes.code != 1) { + return taskRes + } + if (taskRes.data?.data.length > 0) { + stopTasks.push(...taskRes.data?.data) + } + } + + if (stopTasks.length <= 0) { + return errorMessage('停止出图任务失败,没有需要停止的任务') + } + + // 开始执行停止的任务 + for (let i = 0; i < stopTasks.length; i++) { + const element = stopTasks[i] + let res = await window.task.UpdateTaskStatus({ + id: element.id, + status: BookBackTaskStatus.FAIL, + errorMessage: '用户手动取消了任务' + }) + if (res.code != 1) { + return res + } + } + if (type == 'all') { + return successMessage(null, '停止所有批次的出图任务成功', 'StopImageTask') + } else { + return successMessage(null, '停止当前批次的出图任务成功', 'StopImageTask') + } +} diff --git a/src/renderer/src/common/themeOverrides.ts b/src/renderer/src/common/themeOverrides.ts new file mode 100644 index 0000000..b5ccde2 --- /dev/null +++ b/src/renderer/src/common/themeOverrides.ts @@ -0,0 +1,53 @@ +import { useThemeStore } from '@renderer/stores' +const themeStore = useThemeStore() + +import { computed } from 'vue' +import { createActiveColor, createHoverColor } from './color' +export const themeOverrides = computed(() => ({ + common: { + primaryColor: themeStore.menuPrimaryColor, + primaryColorHover: createHoverColor(themeStore.menuPrimaryColor), + primaryColorPressed: createActiveColor(themeStore.menuPrimaryColor), + borderRadius: '6px' + }, + Message: { + // 修改成功消息的颜色 + iconColorSuccess: themeStore.menuPrimaryColor // 这里使用您想要的颜色代码,例如紫色 + }, + Progress: { + fillColor: themeStore.menuPrimaryColor, // 进度条填充颜色 + railColor: 'var(--n-divider-color)', // 进度条轨道颜色 + iconColor: themeStore.menuPrimaryColor, // 图标颜色 + textColorLineOuter: themeStore.menuPrimaryColor, // 外部文字颜色 + textColorLineInner: '#ffffff' // 内部文字颜色 + }, + Slider: { + primaryColor: themeStore.menuPrimaryColor + }, + Button: { + textColorPrimary: themeStore.isDarkMode ? '#ffffff' : undefined, // 主要按钮的文本颜色 + textColorInfo: themeStore.isDarkMode ? '#ffffff' : undefined, // 信息按钮的文本颜色 + textColorSuccess: themeStore.isDarkMode ? '#ffffff' : undefined, // 成功按钮的文本颜色 + textColorWarning: themeStore.isDarkMode ? '#ffffff' : undefined, // 警告按钮的文本颜色 + textColorError: themeStore.isDarkMode ? '#ffffff' : undefined, // 错误按钮的文本颜色 + + // 添加悬停状态文字颜色 + textColorHoverPrimary: themeStore.isDarkMode ? '#eeeeee' : undefined, + textColorHoverInfo: themeStore.isDarkMode ? '#eeeeee' : undefined, + textColorHoverSuccess: themeStore.isDarkMode ? '#eeeeee' : undefined, + textColorHoverWarning: themeStore.isDarkMode ? '#eeeeee' : undefined, + textColorHoverError: themeStore.isDarkMode ? '#eeeeee' : undefined, + + // 添加按下状态文字颜色 + textColorFocusPrimary: themeStore.isDarkMode ? '#dddddd' : undefined, + textColorPressedInfo: themeStore.isDarkMode ? '#dddddd' : undefined, + textColorPressedSuccess: themeStore.isDarkMode ? '#dddddd' : undefined, + textColorPressedWarning: themeStore.isDarkMode ? '#dddddd' : undefined, + textColorPressedError: themeStore.isDarkMode ? '#dddddd' : undefined, + + // 可以根据需要添加其他按钮状态的颜色 + borderHover: themeStore.isDarkMode ? 'rgba(255, 255, 255, 0.2)' : undefined, + borderFocus: themeStore.isDarkMode ? 'rgba(255, 255, 255, 0.25)' : undefined, + borderPressed: themeStore.isDarkMode ? 'rgba(255, 255, 255, 0.3)' : undefined + } +})) diff --git a/src/renderer/src/common/time.ts b/src/renderer/src/common/time.ts new file mode 100644 index 0000000..f1f66f0 --- /dev/null +++ b/src/renderer/src/common/time.ts @@ -0,0 +1,75 @@ +/** + * 格式化日期 + * @param {Date|string|number} date - 日期对象、日期字符串或时间戳 + * @param {Intl.DateTimeFormatOptions} options - 格式化选项 + * @returns {string} 格式化后的日期字符串 + */ +export const formatDateToString = ( + date: Date | string | number | undefined | null, + options: Intl.DateTimeFormatOptions = {} +): string => { + if (!date) return '' // 处理空值 + + // 默认格式化选项 + const formatOptions: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: '2-digit', + day: '2-digit', + ...options + } + + try { + // 转换日期为 Date 对象 + let dateObj + if (date instanceof Date) { + dateObj = date + } else if (typeof date === 'number') { + dateObj = new Date(date) + } else if (typeof date === 'string') { + // 处理ISO格式或其他格式的字符串 + if (date.indexOf('T') > -1 || date.indexOf('-') > -1) { + dateObj = new Date(date) + } else { + // 尝试将纯数字字符串解析为时间戳 + dateObj = new Date(parseInt(date, 10)) + } + } else { + return '' + } + + // 检查日期是否有效 + if (isNaN(dateObj.getTime())) { + return '' + } + + return new Intl.DateTimeFormat('zh-CN', formatOptions).format(dateObj) + } catch (error) { + console.error('日期格式化错误:', error) + return '' + } +} + +/** + * 将毫秒转换为 hh:mm:ss xxx 格式 + * @param {number} milliseconds - 毫秒数 + * @returns {string} 格式化后的时间字符串,如 "01:23:45 678" + */ +export const formatMillisecondsToTimeString = (milliseconds: number): string => { + if (typeof milliseconds !== 'number' || milliseconds < 0) { + return '00:00:00 000' + } + + // 计算各个时间单位 + const hours = Math.floor(milliseconds / (1000 * 60 * 60)) + const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)) + const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000) + const remainingMs = milliseconds % 1000 + + // 格式化为两位数(小时、分钟、秒)和三位数(毫秒) + const formattedHours = hours.toString().padStart(2, '0') + const formattedMinutes = minutes.toString().padStart(2, '0') + const formattedSeconds = seconds.toString().padStart(2, '0') + const formattedMs = Math.floor(remainingMs).toString().padStart(3, '0') + + return `${formattedHours}:${formattedMinutes}:${formattedSeconds} ${formattedMs}` +} diff --git a/src/renderer/src/components/DocHelp.vue b/src/renderer/src/components/DocHelp.vue new file mode 100644 index 0000000..59d2112 --- /dev/null +++ b/src/renderer/src/components/DocHelp.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/src/renderer/src/components/Original/Analysis/SceneAnalysis.vue b/src/renderer/src/components/Original/Analysis/SceneAnalysis.vue new file mode 100644 index 0000000..7bf9166 --- /dev/null +++ b/src/renderer/src/components/Original/Analysis/SceneAnalysis.vue @@ -0,0 +1,464 @@ + + + + + diff --git a/src/renderer/src/components/Original/Analysis/UserAnalysis.vue b/src/renderer/src/components/Original/Analysis/UserAnalysis.vue new file mode 100644 index 0000000..366905d --- /dev/null +++ b/src/renderer/src/components/Original/Analysis/UserAnalysis.vue @@ -0,0 +1,464 @@ + + + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/AllImagePreview.vue b/src/renderer/src/components/Original/BookTaskDetail/AllImagePreview.vue new file mode 100644 index 0000000..adc775a --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/AllImagePreview.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/BookTaskDetailTable.vue b/src/renderer/src/components/Original/BookTaskDetail/BookTaskDetailTable.vue new file mode 100644 index 0000000..1fe4ffc --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/BookTaskDetailTable.vue @@ -0,0 +1,342 @@ + + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/DataTableAction.vue b/src/renderer/src/components/Original/BookTaskDetail/DataTableAction.vue new file mode 100644 index 0000000..685b6a5 --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/DataTableAction.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/DataTableGptPrompt.vue b/src/renderer/src/components/Original/BookTaskDetail/DataTableGptPrompt.vue new file mode 100644 index 0000000..b74510a --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/DataTableGptPrompt.vue @@ -0,0 +1,300 @@ + + + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/DatatableAfterGpt.vue b/src/renderer/src/components/Original/BookTaskDetail/DatatableAfterGpt.vue new file mode 100644 index 0000000..6636805 --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/DatatableAfterGpt.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/DatatableCharacterAndSceneAndStyle.vue b/src/renderer/src/components/Original/BookTaskDetail/DatatableCharacterAndSceneAndStyle.vue new file mode 100644 index 0000000..dc729e9 --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/DatatableCharacterAndSceneAndStyle.vue @@ -0,0 +1,428 @@ + + + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/DatatableGenerateImage.vue b/src/renderer/src/components/Original/BookTaskDetail/DatatableGenerateImage.vue new file mode 100644 index 0000000..fe0ecf2 --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/DatatableGenerateImage.vue @@ -0,0 +1,404 @@ + + + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/DatatableGenerateImageAction.vue b/src/renderer/src/components/Original/BookTaskDetail/DatatableGenerateImageAction.vue new file mode 100644 index 0000000..f1ef93d --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/DatatableGenerateImageAction.vue @@ -0,0 +1,226 @@ + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/DatatableHeaderCharacter.vue b/src/renderer/src/components/Original/BookTaskDetail/DatatableHeaderCharacter.vue new file mode 100644 index 0000000..5d0e4e6 --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/DatatableHeaderCharacter.vue @@ -0,0 +1,125 @@ + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/DatatableHeaderImage.vue b/src/renderer/src/components/Original/BookTaskDetail/DatatableHeaderImage.vue new file mode 100644 index 0000000..2c44b20 --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/DatatableHeaderImage.vue @@ -0,0 +1,274 @@ + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/DynamicPromptSortTagsSelect.vue b/src/renderer/src/components/Original/BookTaskDetail/DynamicPromptSortTagsSelect.vue new file mode 100644 index 0000000..a0099ef --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/DynamicPromptSortTagsSelect.vue @@ -0,0 +1,274 @@ + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/JianyingGenerateInformation.vue b/src/renderer/src/components/Original/BookTaskDetail/JianyingGenerateInformation.vue new file mode 100644 index 0000000..fe8fed7 --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/JianyingGenerateInformation.vue @@ -0,0 +1,595 @@ + + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/MessageAndProgress.vue b/src/renderer/src/components/Original/BookTaskDetail/MessageAndProgress.vue new file mode 100644 index 0000000..ab13fff --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/MessageAndProgress.vue @@ -0,0 +1,263 @@ + + + + + diff --git a/src/renderer/src/components/Original/BookTaskDetail/TopMenuButtons.vue b/src/renderer/src/components/Original/BookTaskDetail/TopMenuButtons.vue new file mode 100644 index 0000000..632ba4b --- /dev/null +++ b/src/renderer/src/components/Original/BookTaskDetail/TopMenuButtons.vue @@ -0,0 +1,801 @@ + + + + + diff --git a/src/renderer/src/components/Original/Copywriter/AIGroup.vue b/src/renderer/src/components/Original/Copywriter/AIGroup.vue new file mode 100644 index 0000000..3a5f139 --- /dev/null +++ b/src/renderer/src/components/Original/Copywriter/AIGroup.vue @@ -0,0 +1,515 @@ + + + + + diff --git a/src/renderer/src/components/Original/Copywriter/EditWord.vue b/src/renderer/src/components/Original/Copywriter/EditWord.vue new file mode 100644 index 0000000..33a6274 --- /dev/null +++ b/src/renderer/src/components/Original/Copywriter/EditWord.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/src/renderer/src/components/Original/Copywriter/HandGroup.vue b/src/renderer/src/components/Original/Copywriter/HandGroup.vue new file mode 100644 index 0000000..fe4fbae --- /dev/null +++ b/src/renderer/src/components/Original/Copywriter/HandGroup.vue @@ -0,0 +1,694 @@ + + + + + diff --git a/src/renderer/src/components/Original/Copywriter/WordGroup.vue b/src/renderer/src/components/Original/Copywriter/WordGroup.vue new file mode 100644 index 0000000..aa13317 --- /dev/null +++ b/src/renderer/src/components/Original/Copywriter/WordGroup.vue @@ -0,0 +1,300 @@ + + + + + diff --git a/src/renderer/src/components/Original/Copywriter/composables/useWordGroupBase.js b/src/renderer/src/components/Original/Copywriter/composables/useWordGroupBase.js new file mode 100644 index 0000000..e4a5f29 --- /dev/null +++ b/src/renderer/src/components/Original/Copywriter/composables/useWordGroupBase.js @@ -0,0 +1,950 @@ +import { ref, computed, nextTick } from 'vue' +import { useMessage, useDialog } from 'naive-ui' +import { v4 as uuidv4 } from 'uuid' +import { useBookStore, useSoftwareStore } from '@/renderer/src/stores' +import { OperateBookType } from '@/define/enum/bookEnum' +import { TimeDelay } from '@/define/Tools/time' +import { formatMillisecondsToTimeString } from '@/renderer/src/common/time' +import { toRaw } from 'vue' +import EditWord from '../EditWord.vue' +import { isEmpty } from 'lodash' + +/** + * WordGroup 基础功能的组合式函数 + * @param {Array} initData 初始数据 + * @returns {Object} 包含所有基础功能的对象 + */ +export function useWordGroupBase(initData) { + const message = useMessage() + const dialog = useDialog() + const bookStore = useBookStore() + const softwareStore = useSoftwareStore() + + // 基础状态 + const data = ref(initData) + const selectKey = ref([]) + + // 基础方法 + function handleCheck(rowKeys) { + selectKey.value = rowKeys + } + + function rowKey(row) { + return row.id + } + + /** + * 重新计算指定索引值的时间 + * @param {int} index + */ + function modifyTime(index) { + // 判断下一个行或者是上一行是不是存在,存在才会计算时间 + if (data.value[index]) { + let s_v = data.value[index].subValue + let len = s_v.length + if (len > 0) { + let s_t = s_v[0].start_time + let e_t = s_v[len - 1].end_time + data.value[index].startTime = s_t + data.value[index].endTime = e_t + data.value[index].timeLimit = `${s_t} -- ${e_t}` + } else { + data.value[index].startTime = null + data.value[index].endTime = null + data.value[index].timeLimit = `` + } + } + } + + /** + * 自动向上补位功能 + * 当某一行的字幕为空时,将下面所有行的字幕依次向上移动 + * @param {number} startIndex 开始检查的行索引 + */ + function autoMoveUpSubtitles(startIndex) { + // 从指定索引开始,检查每一行是否为空 + for (let i = startIndex; i < data.value.length; i++) { + const currentRow = data.value[i] + + // 如果当前行字幕为空 + if (!currentRow.subValue || currentRow.subValue.length === 0) { + // 查找下面第一个有字幕的行 + let nextValidIndex = -1 + for (let j = i + 1; j < data.value.length; j++) { + if (data.value[j].subValue && data.value[j].subValue.length > 0) { + nextValidIndex = j + break + } + } + + // 如果找到了有字幕的行,将其字幕移动到当前空行 + if (nextValidIndex !== -1) { + const nextRow = data.value[nextValidIndex] + + // 将下面行的字幕移动到当前行 + currentRow.subValue = [...nextRow.subValue] + nextRow.subValue = [] + + // 重新计算时间 + modifyTime(i) + modifyTime(nextValidIndex) + + console.log(`自动补位:将第${nextValidIndex + 1}行的字幕移动到第${i + 1}行`) + } else { + // 如果后面没有字幕了,检查当前行是否可以删除 + if (isRowEmpty(currentRow)) { + // 删除空行 + data.value.splice(i, 1) + + // 重新编号后续所有行 + for (let k = i; k < data.value.length; k++) { + data.value[k].no = k + 1 + } + + console.log(`删除空行:第${i + 1}行`) + i-- // 由于删除了一行,索引需要回退 + } + break + } + } + } + } + + /** + * 检查行是否为空(没有字幕且没有文案) + * @param {Object} row 要检查的行数据 + * @returns {boolean} 如果行为空返回true + */ + function isRowEmpty(row) { + const hasSubtitles = row.subValue && row.subValue.length > 0 + const hasAfterGpt = row.afterGpt && row.afterGpt.trim() !== '' + + return !hasSubtitles && !hasAfterGpt + } + + /** + * 清理空行并将后面的数据向上移动 + * 从后面开始检查删除,避免索引变化问题 + */ + function cleanEmptyRowsAndMoveUp() { + console.log('开始清理空行...') + + // 从后往前遍历,避免删除时索引变化的问题 + for (let i = data.value.length - 1; i >= 0; i--) { + const currentRow = data.value[i] + if (i == 64) { + debugger + } + + // 检查当前行是否为空 + if (isRowEmpty(currentRow)) { + console.log(`发现空行:第${i + 1}行`) + + // 检查当前行后面是否还有非空数据 + let hasDataAfter = false + for (let j = i + 1; j < data.value.length; j++) { + if (!isRowEmpty(data.value[j])) { + hasDataAfter = true + break + } + } + + if (hasDataAfter) { + // 如果后面有数据,删除当前空行,让后面的数据自动向上 + console.log(`删除第${i + 1}行,后面的数据将向上移动`) + data.value.splice(i, 1) + } else { + // 如果后面没有数据,也删除当前空行 + console.log(`删除末尾空行:第${i + 1}行`) + data.value.splice(i, 1) + } + } + } + + // 重新编号所有行 + for (let i = 0; i < data.value.length; i++) { + data.value[i].no = i + 1 + } + + // 重新计算所有行的时间 + for (let i = 0; i < data.value.length; i++) { + modifyTime(i) + } + + console.log(`清理完成,当前总行数:${data.value.length}`) + message.success('空行清理完成') + } + + /** + * 字幕向上合并 - 重写版本 + * 将当前字幕项及其之前的所有字幕项合并到上一行的字幕列表中 + * @param {Object} row 当前行数据 + * @param {number} rowIndex 当前行索引 + * @param {Object} item 当前字幕项 + * @param {number} itemIndex 当前字幕项索引 + */ + async function Upper(row, rowIndex, item, itemIndex) { + // 防止重复操作 + if (isOperating.value) { + message.warning('操作进行中,请稍后...') + return + } + message.info(`当前行索引:${rowIndex}, 当前字幕项索引:${itemIndex}`) + + // 判断是否为第一行 + if (rowIndex === 0) { + message.error('当前是第一行,不能向上合并') + return + } + + // 获取上一行的索引 + const previousRowIndex = rowIndex - 1 + const previousRow = data.value[previousRowIndex] + + if (!previousRow) { + message.error('未找到上一行数据') + return + } + + isOperating.value = true + + try { + // 确保上一行有subValue数组 + if (!previousRow.subValue) { + previousRow.subValue = [] + } + + // 获取当前字幕项在当前行中的索引 + if (itemIndex < 0) { + message.error('未找到指定的字幕项') + return + } + + // 将当前字幕项及其之前的所有字幕项添加到上一行的字幕列表中 + for (let i = 0; i <= itemIndex; i++) { + const itemToMove = { ...row.subValue[i] } + previousRow.subValue.push(itemToMove) + } + + // 从当前行移除这些字幕项(从开头删除到当前项) + row.subValue.splice(0, itemIndex + 1) + + // 检查当前行是否还有字幕,如果没有则自动向上补位 + if (row.subValue.length === 0) { + autoMoveUpSubtitles(rowIndex) + } + + // 等待DOM更新完成 + await nextTick() + + // 重新计算时间 + modifyTime(previousRowIndex) + modifyTime(rowIndex) + if (rowIndex + 1 < data.value.length) { + modifyTime(rowIndex + 1) + } + + message.success(`字幕向上合并成功,移动了 ${itemIndex + 1} 个字幕项`) + } catch (error) { + message.error('向上合并失败:' + error.message) + } finally { + // 延迟解锁,确保操作完全完成 + setTimeout(() => { + isOperating.value = false + }, 100) + } + } + + /** + * 字幕向下合并 - 重写版本 + * 将当前字幕项及其之后的所有字幕项合并到下一行的字幕列表中,如果下一行不存在则自动创建 + * @param {Object} row 当前行数据 + * @param {Object} item 当前字幕项 + */ + async function Down(row, item) { + // 防止重复操作 + if (isOperating.value) { + message.warning('操作进行中,请稍后...') + return + } + + // 获取当前行在表格中的索引 + const currentRowIndex = data.value.findIndex((dataRow) => dataRow.id === row.id) + + if (currentRowIndex < 0) { + message.error('未找到当前行数据') + return + } + + isOperating.value = true + + try { + // 获取当前字幕项在当前行中的索引 + const itemIndex = row.subValue.findIndex((subItem) => subItem.id === item.id) + + if (itemIndex < 0) { + message.error('未找到指定的字幕项') + return + } + + let nextRowIndex = currentRowIndex + 1 + let nextRow = data.value[nextRowIndex] + + // 如果下一行不存在,则创建新行 + if (!nextRow) { + const newRowData = { + no: data.value.length + 1, + id: uuidv4(), + lastId: row.id, + word: '', + afterGpt: '', + startTime: item.startTime || 0, + endTime: item.endTime || 0, + timeLimit: `${item.startTime || 0} -- ${item.endTime || 0}`, + subValue: [] + } + + // 在当前行后面插入新行 + data.value.splice(nextRowIndex, 0, newRowData) + nextRow = newRowData + + // 重新编号后续所有行 + for (let i = nextRowIndex; i < data.value.length; i++) { + data.value[i].no = i + 1 + } + + message.info('已自动创建新行') + } + + // 确保下一行有subValue数组 + if (!nextRow.subValue) { + nextRow.subValue = [] + } + + // 计算要移动的字幕项数量 + const itemsToMoveCount = row.subValue.length - itemIndex + + // 将当前字幕项及其之后的所有字幕项添加到下一行的字幕列表开头 + // 为了保持顺序,需要倒序插入到开头 + for (let i = row.subValue.length - 1; i >= itemIndex; i--) { + const itemToMove = { ...row.subValue[i] } + nextRow.subValue.unshift(itemToMove) + } + + // 从当前行移除这些字幕项(从当前项到末尾) + row.subValue.splice(itemIndex) + + // 检查当前行是否还有字幕,如果没有则自动向上补位 + if (row.subValue.length === 0) { + autoMoveUpSubtitles(currentRowIndex) + } + + // 等待DOM更新完成 + await nextTick() + + // 重新计算时间 + modifyTime(currentRowIndex) + modifyTime(nextRowIndex) + + message.success(`字幕向下合并成功,移动了 ${itemsToMoveCount} 个字幕项`) + } catch (error) { + message.error('向下合并失败:' + error.message) + } finally { + // 延迟解锁,确保操作完全完成 + setTimeout(() => { + isOperating.value = false + }, 100) + } + } + + /** + * 打开添加字幕信息的窗口 + */ + async function OpenOneDialog() { + let da = dialog.create({ + showIcon: false, + title: '添加分镜字幕', + closeOnEsc: false, + content: () => + h(EditWord, { + initData: data, + onSaveWord: (newWords) => { + da?.destroy() + + for (let i = 0; i < data.value.length; i++) { + const element = data.value[i] + element.afterGpt = '' + + if (newWords[i]) { + element.afterGpt = newWords[i] + } + } + + if (data.value.length < newWords.length) { + // 如果新的文案数量大于当前数据长度,补充新的行 + for (let i = data.value.length; i < newWords.length; i++) { + let newValue = { + no: i + 1, + id: crypto.randomUUID(), + afterGpt: newWords[i], + subValue: [], + startTime: 0, + endTime: 0 + } + data.value.push(newValue) + } + } + } + }), + style: `width : 900px; max-height : 90vh`, + maskClosable: false, + contentStyle: { + marginTop: '30px', + maxHeight: '80vh', + overflowY: 'auto' + } + }) + } + + /** + * 保存字幕文案信息,用于自动生成视频 + */ + async function SaveCopywritingInformation() { + // 判断是不是没有对齐(就是所有的数据 文案 时间 srt 都要有,不然就报错) + for (let i = 0; i < data.value.length; i++) { + const element = data.value[i] + if ( + isEmpty(element.afterGpt) || + !element.hasOwnProperty('startTime') || + !element.hasOwnProperty('endTime') || + !element.hasOwnProperty('subValue') || + element.subValue.length == 0 + ) { + message.error("保存数据失败,数据不完整,请检查'文案'、'时间'、'字幕' 等,数据是否完整") + return + } + } + let da = dialog.warning({ + title: '提示', + content: + '点击保存后,会在之前的分镜数据上进行修改,多余的分镜会自动创建,超出的分镜会自动删除,可能会导致数据丢失或者是提示词和出图数据对不上,是否继续?', + positiveText: '确定', + negativeText: '取消', + onPositiveClick: async () => { + try { + da?.destroy() + softwareStore.spin.spinning = true + softwareStore.spin.tip = '正在保存文案信息...' + let res = await window.book.SaveCopywritingInfo( + bookStore.selectBookTask.id, + toRaw(data.value), + OperateBookType.BOOKTASK + ) + if (res.code != 1) { + message.error(res.message) + return + } + + bookStore.selectBookTaskDetail = [] + await nextTick() + await TimeDelay(10000) + bookStore.selectBookTaskDetail = [...res.data] + + dialog.success({ + title: '保存成功', + content: '文案信息已成功保存', + duration: 2000 + }) + } catch (error) { + message.error('保存操作失败,' + error.message) + } finally { + softwareStore.spin.spinning = false + } + } + }) + } + + /** + * 检查行是否为空(没有字幕且没有文案) + * @param {Object} row 要检查的行数据 + * @returns {boolean} 如果行为空返回true + */ + function isRowEmpty(row) { + const hasSubtitles = row.subValue && row.subValue.length > 0 + const hasAfterGpt = row.afterGpt && row.afterGpt.trim() !== '' + + return !hasSubtitles && !hasAfterGpt + } + + /** + * 智能批量合并功能 - 重写版本 + * 将所有字幕按照分镜文案内容智能合并到对应的行中 + * @param {number} startFromIndex 可选参数,指定从哪个行索引开始执行(0-based) + */ + function handleAutoMerge(startFromIndex = 0) { + if (data.value.length === 0) { + message.warning('没有数据可以处理') + return + } + + // 验证起始索引的有效性 + if (startFromIndex < 0 || startFromIndex >= data.value.length) { + message.error(`起始索引 ${startFromIndex} 无效,有效范围是 0 到 ${data.value.length - 1}`) + return + } + + dialog.create({ + type: 'warning', + title: '智能批量合并', + showIcon: true, + content: `确定要进行智能批量合并吗?${startFromIndex > 0 ? `将从第${startFromIndex + 1}行开始处理。` : ''}此操作会根据分镜文案内容自动将字幕合并到对应的行中。`, + style: 'width: 500px', + maskClosable: false, + positiveText: '确定', + negativeText: '取消', + onPositiveClick: async () => { + try { + // 防止重复操作 + if (isOperating.value) { + message.warning('操作进行中,请稍后...') + return + } + + isOperating.value = true + + // 第一步:收集字幕项,只从指定行开始收集 + const allSubtitles = [] + const originalRows = [] + + // 遍历所有行,收集字幕和原始行信息 + for (let i = 0; i < data.value.length; i++) { + const row = data.value[i] + originalRows.push({ + index: i, + id: row.id, + afterGpt: row.afterGpt ? row.afterGpt.trim() : '', + originalSubValues: [...(row.subValue || [])] + }) + + // 只从指定的起始行开始收集字幕项 + if (i >= startFromIndex && row.subValue && row.subValue.length > 0) { + for (const subtitle of row.subValue) { + allSubtitles.push({ + ...subtitle, + originalRowIndex: i, + processed: false + }) + } + } + } + + console.log(`从第${startFromIndex + 1}行开始收集,字幕总数:`, allSubtitles.length) + console.log('原始行数据:', originalRows) + + // 清空指定行及之后的字幕 + for (let i = startFromIndex; i < data.value.length; i++) { + data.value[i].subValue = [] + } + + // 第二步:从指定行开始按顺序处理每个有文案的行 + let currentSubtitleIndex = 0 + const unmatchedSubtitles = [] + let processingError = null + + for (let rowIndex = startFromIndex; rowIndex < originalRows.length; rowIndex++) { + const rowInfo = originalRows[rowIndex] + + // 跳过没有文案的行 + if (!rowInfo.afterGpt) { + console.log(`第${rowIndex + 1}行没有文案,跳过`) + continue + } + + console.log(`处理第${rowIndex + 1}行,文案: "${rowInfo.afterGpt}"`) + + // 去除标点符号的文案用于匹配 + const cleanAfterGpt = removePunctuationIncludingEllipsis(rowInfo.afterGpt) + let accumulatedText = '' + let matchedSubtitles = [] + + // 从当前位置开始尝试匹配字幕,严格按顺序 + let tempIndex = currentSubtitleIndex + let foundMatch = false + + while (tempIndex < allSubtitles.length) { + const subtitle = allSubtitles[tempIndex] + + if (subtitle.processed) { + tempIndex++ + continue + } + + // 获取字幕文本 + const subtitleText = subtitle.srt_value ? subtitle.srt_value.trim() : '' + if (!subtitleText) { + // 遇到空字幕,立即报错停止 + processingError = { + message: `第${tempIndex + 1}个字幕为空,无法继续处理`, + position: tempIndex, + rowIndex: rowIndex + 1 + } + break + } + + // 尝试添加当前字幕到累积文本 + const testText = accumulatedText + subtitleText + const cleanTestText = removePunctuationIncludingEllipsis(testText) + + console.log(` 测试字幕[${tempIndex}]: "${subtitleText}"`) + console.log(` 累积文本: "${testText}"`) + console.log(` 清理后文案: "${cleanAfterGpt}"`) + console.log(` 清理后测试: "${cleanTestText}"`) + + // 检查是否匹配 + if (cleanAfterGpt.startsWith(cleanTestText)) { + // 匹配成功,添加到当前行 + accumulatedText = testText + matchedSubtitles.push(subtitle) + subtitle.processed = true + foundMatch = true + + console.log(` ✓ 匹配成功,添加字幕: "${subtitleText}"`) + + // 检查是否完全匹配 + if (cleanAfterGpt === cleanTestText) { + console.log(` ✓ 完全匹配,第${rowIndex + 1}行处理完成`) + break + } + + tempIndex++ + } else { + // 不匹配,严格模式下立即报错停止 + if (matchedSubtitles.length === 0) { + // 第一个字幕就不匹配 + processingError = { + message: `第${rowIndex + 1}行的第一个字幕"${subtitleText}"与文案"${rowInfo.afterGpt}"不匹配`, + subtitle: subtitleText, + expectedText: rowInfo.afterGpt, + position: tempIndex, + rowIndex: rowIndex + 1, + isFirstSubtitleMismatch: true // 新增属性标识第一个字幕不匹配 + } + } else { + // 部分匹配后遇到不匹配 + processingError = { + message: `第${rowIndex + 1}行在处理字幕"${subtitleText}"时无法匹配,已累积文本"${accumulatedText}"与目标文案"${rowInfo.afterGpt}"不匹配`, + subtitle: subtitleText, + accumulatedText: accumulatedText, + expectedText: rowInfo.afterGpt, + position: tempIndex, + rowIndex: rowIndex + 1, + isFirstSubtitleMismatch: false // 不是第一个字幕不匹配 + } + } + break + } + } + + // 如果遇到错误,先保存已匹配的字幕,然后停止处理 + if (processingError) { + console.error('处理错误:', processingError) + + // 重要:即使遇到错误,也要保存已经匹配的字幕 + if (matchedSubtitles.length > 0) { + data.value[rowIndex].subValue = matchedSubtitles.map((s) => ({ + id: s.id, + srt_value: s.srt_value, + start_time: s.start_time, + end_time: s.end_time + })) + + console.log( + `第${rowIndex + 1}行在遇到错误前已合并了 ${matchedSubtitles.length} 个字幕` + ) + + // 更新当前字幕索引到错误位置 + currentSubtitleIndex = processingError.position + } else { + // 特殊情况:第一个字幕就不匹配 + // 将当前不匹配的字幕直接写入当前行 + if (processingError.isFirstSubtitleMismatch && tempIndex < allSubtitles.length) { + const unmatchedSubtitle = allSubtitles[tempIndex] + data.value[rowIndex].subValue = [{ + id: unmatchedSubtitle.id, + srt_value: unmatchedSubtitle.srt_value, + start_time: unmatchedSubtitle.start_time, + end_time: unmatchedSubtitle.end_time + }] + + // 标记这个字幕为已处理 + unmatchedSubtitle.processed = true + + console.log(`第${rowIndex + 1}行第一个字幕不匹配,将字幕"${unmatchedSubtitle.srt_value}"直接写入当前行`) + + // 更新当前字幕索引 + currentSubtitleIndex = tempIndex + 1 + } + } + + break + } + + // 将匹配的字幕添加到当前行(只在没有错误的情况下) + if (matchedSubtitles.length > 0 && !processingError) { + data.value[rowIndex].subValue = matchedSubtitles.map((s) => ({ + id: s.id, + srt_value: s.srt_value, + start_time: s.start_time, + end_time: s.end_time + })) + + console.log(`第${rowIndex + 1}行合并了 ${matchedSubtitles.length} 个字幕`) + + // 更新当前字幕索引 + currentSubtitleIndex = tempIndex + } else if (matchedSubtitles.length === 0 && !processingError) { + // 没有找到任何匹配的字幕 + processingError = { + message: `第${rowIndex + 1}行没有找到任何匹配的字幕`, + expectedText: rowInfo.afterGpt, + position: currentSubtitleIndex, + rowIndex: rowIndex + 1 + } + break + } + } + + // 第三步:重新计算所有行的时间 + for (let i = 0; i < data.value.length; i++) { + modifyTime(i) + } + + // 等待DOM更新完成 + await nextTick() + + // 第四步:处理剩余未匹配的字幕(无论成功还是失败都要处理) + const remainingSubtitles = allSubtitles.filter((s) => !s.processed) + + if (remainingSubtitles.length > 0) { + console.log(`还有 ${remainingSubtitles.length} 个字幕未处理,开始写入后续行`) + + // 确定写入起始位置 + let writeStartIndex + if (processingError) { + // 检查是否是第一个字幕不匹配的特殊情况 + if (processingError.isFirstSubtitleMismatch) { + // 第一个字幕不匹配时,已经写入当前行,从下一行开始写入剩余字幕 + writeStartIndex = processingError.rowIndex // processingError.rowIndex 已经是基于1的,直接使用作为下一行的索引 + } else { + // 其他错误情况,从错误发生的行的下一行开始写入 + writeStartIndex = processingError.rowIndex + } + } else { + // 如果没有错误,从下一个可用行开始写入 + // 找到最后一个处理过的行 + let lastProcessedRowIndex = -1 + for (let i = 0; i < originalRows.length; i++) { + if ( + originalRows[i].afterGpt && + data.value[i].subValue && + data.value[i].subValue.length > 0 + ) { + lastProcessedRowIndex = i + } + } + writeStartIndex = lastProcessedRowIndex + 1 + } + + // 将剩余字幕按顺序写入 + for (let i = 0; i < remainingSubtitles.length; i++) { + const subtitle = remainingSubtitles[i] + const targetRowIndex = writeStartIndex + i + + // 如果目标行不存在,创建新行 + if (targetRowIndex >= data.value.length) { + const newRowData = { + no: targetRowIndex + 1, + id: uuidv4(), + lastId: data.value.length > 0 ? data.value[data.value.length - 1].id : null, + word: '', + afterGpt: '', // 新增行的文案设置为空 + startTime: subtitle.startTime, + endTime: subtitle.endTime, + timeLimit: `${subtitle.start_time} -- ${subtitle.end_time}`, + subValue: [] + } + data.value.push(newRowData) + console.log(`创建新行 ${targetRowIndex + 1}`) + } + + // 清空目标行原有的字幕(避免重复) + if (!data.value[targetRowIndex].subValue) { + data.value[targetRowIndex].subValue = [] + } else { + // 如果目标行已有字幕,将当前字幕添加到末尾 + // 但通常在这种情况下,我们希望每行只有一个字幕,所以先清空 + if (data.value[targetRowIndex].subValue.length === 0) { + // 如果目标行为空,直接添加 + } else { + // 如果目标行已有字幕,可能需要合并或替换 + console.log(`目标行 ${targetRowIndex + 1} 已有字幕,追加到末尾`) + } + } + + // 将字幕添加到目标行 + data.value[targetRowIndex].subValue.push({ + id: subtitle.id, + srt_value: subtitle.srt_value, + start_time: subtitle.start_time, + end_time: subtitle.end_time + }) + + // 重新计算时间 + modifyTime(targetRowIndex) + } + + // 重新编号所有行 + for (let i = 0; i < data.value.length; i++) { + data.value[i].no = i + 1 + } + + console.log(`已将 ${remainingSubtitles.length} 个剩余字幕写入后续行`) + } + + // 第五步:报告结果 + if (processingError) { + console.error('智能合并遇到错误,停止处理:', processingError) + + const totalProcessed = allSubtitles.filter((s) => s.processed).length + const totalRemaining = remainingSubtitles.length + + // 显示详细错误信息 + dialog.create({ + type: 'warning', + title: '智能合并部分完成', + content: `处理过程中遇到错误,已停止智能匹配:\n\n${processingError.message}\n\n已处理字幕:${totalProcessed} 个\n剩余字幕:${totalRemaining} 个\n\n剩余字幕已按顺序写入后续行中,请手动调整文案匹配。`, + style: 'width: 600px; white-space: pre-line;', + positiveText: '确定' + }) + } else { + if (remainingSubtitles.length > 0) { + message.warning(`智能合并完成,${remainingSubtitles.length} 个剩余字幕已写入后续行`) + } else { + message.success('智能批量合并完成,所有字幕都已正确匹配') + } + } + cleanEmptyRowsAndMoveUp() + } catch (error) { + console.error('智能合并失败:', error) + message.error('智能批量合并失败:' + error.message) + } finally { + setTimeout(() => { + isOperating.value = false + }, 100) + } + }, + onNegativeClick: () => { + message.info('取消操作') + } + }) + } + + /** + * 对齐当前列即下面的数据 + */ + async function AlignNextWord(row, index) { + // 判断是不是已经到到导入过字幕了 + handleAutoMerge(index) + } + + /** + * 去除标点符号(包括省略号) + */ + function removePunctuationIncludingEllipsis(sentence) { + // 扩展正则表达式以包含中文标点符号和省略号 + // 注意英文省略号可能由三个连续点表示,也可能直接使用特殊的省略号字符 + const punctuationRegExp = /[., \/#!$%\^&\*;:{}=\-_`~()\[\],。、;:?!''""()【】《》…]+/g + + // 使用正则表达式的replace方法替换掉所有匹配到的标点符号为空字符串 + return sentence.replace(punctuationRegExp, '') + } + + /** + * 强制对齐字幕的时间轴 + */ + function CheckTimeLime() { + dialog.create({ + type: 'warning', + title: '警告', + showIcon: true, + content: '确定要检查时间轴吗?该操作会更具对应的字幕进行时间的强制对齐,是不是继续操作?', + style: `width : 400px;`, + maskClosable: false, + positiveText: '确定', + negativeText: '取消', + onPositiveClick: async () => { + for (let i = 0; i < data.value.length; i++) { + const element = data.value[i] + if (!element.subValue || element.subValue.length <= 0) { + message.error(`第${i + 1}行字幕为空,请检查`) + return + } + let startTime = element.subValue[0].start_time ?? 0 + let endTime = element.subValue[element.subValue.length - 1].end_time ?? 0 + if (endTime == 0) { + message.error(`第${i + 1}行字幕结束时间为空,请检查`) + return + } + + // 开始写入时间 + data.value[i].startTime = startTime + data.value[i].endTime = endTime + data.value[i].timeLimit = `${startTime} -- ${endTime}` + } + // 提示 + message.success('时间轴检查和改写完成,请手动保存!!!') + }, + onNegativeClick: () => { + message.info('取消操作') + } + }) + } + + // 操作锁定状态 + const isOperating = ref(false) + + return { + // 数据 + data, + selectKey, + isOperating, + + // 基础方法 + handleCheck, + rowKey, + modifyTime, + + // 字幕操作 + Upper, + Down, + AlignNextWord, + + // 文案操作 + OpenOneDialog, + SaveCopywritingInformation, + + cleanEmptyRowsAndMoveUp, + handleAutoMerge, + autoMoveUpSubtitles, + isRowEmpty, + + // 工具方法 + removePunctuationIncludingEllipsis, + CheckTimeLime, + + // 外部依赖 + message, + dialog, + bookStore + } +} diff --git a/src/renderer/src/components/Original/Image/BookTaskImageCache.vue b/src/renderer/src/components/Original/Image/BookTaskImageCache.vue new file mode 100644 index 0000000..dc577cc --- /dev/null +++ b/src/renderer/src/components/Original/Image/BookTaskImageCache.vue @@ -0,0 +1,407 @@ + + + + + diff --git a/src/renderer/src/components/Original/Image/SelectRegionImage.vue b/src/renderer/src/components/Original/Image/SelectRegionImage.vue new file mode 100644 index 0000000..ecfa641 --- /dev/null +++ b/src/renderer/src/components/Original/Image/SelectRegionImage.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalAddBook.vue b/src/renderer/src/components/Original/MainHome/OriginalAddBook.vue new file mode 100644 index 0000000..b3e503b --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalAddBook.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalAddBookTask.vue b/src/renderer/src/components/Original/MainHome/OriginalAddBookTask.vue new file mode 100644 index 0000000..2ca532b --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalAddBookTask.vue @@ -0,0 +1,179 @@ + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalBookTaskCard.vue b/src/renderer/src/components/Original/MainHome/OriginalBookTaskCard.vue new file mode 100644 index 0000000..59f4d57 --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalBookTaskCard.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalEmptyState.vue b/src/renderer/src/components/Original/MainHome/OriginalEmptyState.vue new file mode 100644 index 0000000..194717d --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalEmptyState.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalMobileHeader.vue b/src/renderer/src/components/Original/MainHome/OriginalMobileHeader.vue new file mode 100644 index 0000000..c797ab9 --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalMobileHeader.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalModifyBookTask.vue b/src/renderer/src/components/Original/MainHome/OriginalModifyBookTask.vue new file mode 100644 index 0000000..599e1e0 --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalModifyBookTask.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalProjectItem.vue b/src/renderer/src/components/Original/MainHome/OriginalProjectItem.vue new file mode 100644 index 0000000..7d2b488 --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalProjectItem.vue @@ -0,0 +1,401 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalProjectSidebar.vue b/src/renderer/src/components/Original/MainHome/OriginalProjectSidebar.vue new file mode 100644 index 0000000..9aef345 --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalProjectSidebar.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalSearchBook.vue b/src/renderer/src/components/Original/MainHome/OriginalSearchBook.vue new file mode 100644 index 0000000..540ce97 --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalSearchBook.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalTaskCard.vue b/src/renderer/src/components/Original/MainHome/OriginalTaskCard.vue new file mode 100644 index 0000000..8d2013d --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalTaskCard.vue @@ -0,0 +1,712 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalTaskList.vue b/src/renderer/src/components/Original/MainHome/OriginalTaskList.vue new file mode 100644 index 0000000..5f35a9a --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalTaskList.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalViewBookInfo.vue b/src/renderer/src/components/Original/MainHome/OriginalViewBookInfo.vue new file mode 100644 index 0000000..c86b0d8 --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalViewBookInfo.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/OriginalViewBookTaskInfo.vue b/src/renderer/src/components/Original/MainHome/OriginalViewBookTaskInfo.vue new file mode 100644 index 0000000..6d54cd8 --- /dev/null +++ b/src/renderer/src/components/Original/MainHome/OriginalViewBookTaskInfo.vue @@ -0,0 +1,297 @@ + + + + + diff --git a/src/renderer/src/components/Original/MainHome/ProjectItem.vue b/src/renderer/src/components/Original/MainHome/ProjectItem.vue new file mode 100644 index 0000000..e69de29 diff --git a/src/renderer/src/components/Original/MainHome/ViewBookInfo.vue b/src/renderer/src/components/Original/MainHome/ViewBookInfo.vue new file mode 100644 index 0000000..e69de29 diff --git a/src/renderer/src/components/Preset/AddOrModifyPreset.vue b/src/renderer/src/components/Preset/AddOrModifyPreset.vue new file mode 100644 index 0000000..8c4f415 --- /dev/null +++ b/src/renderer/src/components/Preset/AddOrModifyPreset.vue @@ -0,0 +1,392 @@ + + + + + diff --git a/src/renderer/src/components/Preset/CharacterPreset.vue b/src/renderer/src/components/Preset/CharacterPreset.vue new file mode 100644 index 0000000..17932d5 --- /dev/null +++ b/src/renderer/src/components/Preset/CharacterPreset.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/src/renderer/src/components/Preset/PresetShowCard.vue b/src/renderer/src/components/Preset/PresetShowCard.vue new file mode 100644 index 0000000..85c46dc --- /dev/null +++ b/src/renderer/src/components/Preset/PresetShowCard.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/src/renderer/src/components/Preset/ScenePreset.vue b/src/renderer/src/components/Preset/ScenePreset.vue new file mode 100644 index 0000000..d931c78 --- /dev/null +++ b/src/renderer/src/components/Preset/ScenePreset.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/renderer/src/components/Preset/SearchPresetArea.vue b/src/renderer/src/components/Preset/SearchPresetArea.vue new file mode 100644 index 0000000..47714b6 --- /dev/null +++ b/src/renderer/src/components/Preset/SearchPresetArea.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/src/renderer/src/components/Preset/SelectStylePreset.vue b/src/renderer/src/components/Preset/SelectStylePreset.vue new file mode 100644 index 0000000..2d5de76 --- /dev/null +++ b/src/renderer/src/components/Preset/SelectStylePreset.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/src/renderer/src/components/Preset/StylePreset.vue b/src/renderer/src/components/Preset/StylePreset.vue new file mode 100644 index 0000000..b7f1350 --- /dev/null +++ b/src/renderer/src/components/Preset/StylePreset.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/renderer/src/components/Setting/AISetting.vue b/src/renderer/src/components/Setting/AISetting.vue new file mode 100644 index 0000000..1d11b09 --- /dev/null +++ b/src/renderer/src/components/Setting/AISetting.vue @@ -0,0 +1,279 @@ + + + + + diff --git a/src/renderer/src/components/Setting/AppearanceSettings.vue b/src/renderer/src/components/Setting/AppearanceSettings.vue new file mode 100644 index 0000000..4bd59c3 --- /dev/null +++ b/src/renderer/src/components/Setting/AppearanceSettings.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/src/renderer/src/components/Setting/GeneralSettings.vue b/src/renderer/src/components/Setting/GeneralSettings.vue new file mode 100644 index 0000000..69aa271 --- /dev/null +++ b/src/renderer/src/components/Setting/GeneralSettings.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/src/renderer/src/components/Setting/JianyingKeyFrameSetting.vue b/src/renderer/src/components/Setting/JianyingKeyFrameSetting.vue new file mode 100644 index 0000000..1d2d7a0 --- /dev/null +++ b/src/renderer/src/components/Setting/JianyingKeyFrameSetting.vue @@ -0,0 +1,221 @@ + + diff --git a/src/renderer/src/components/Setting/MJSettings.vue b/src/renderer/src/components/Setting/MJSettings.vue new file mode 100644 index 0000000..e741cef --- /dev/null +++ b/src/renderer/src/components/Setting/MJSettings.vue @@ -0,0 +1,377 @@ + + + + + diff --git a/src/renderer/src/components/Setting/SDSetting.vue b/src/renderer/src/components/Setting/SDSetting.vue new file mode 100644 index 0000000..08aac8d --- /dev/null +++ b/src/renderer/src/components/Setting/SDSetting.vue @@ -0,0 +1,904 @@ + + + + + diff --git a/src/renderer/src/components/SoftHome/ContactDeveloper.vue b/src/renderer/src/components/SoftHome/ContactDeveloper.vue new file mode 100644 index 0000000..2296443 --- /dev/null +++ b/src/renderer/src/components/SoftHome/ContactDeveloper.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/src/renderer/src/components/SoftHome/WechatGroup.vue b/src/renderer/src/components/SoftHome/WechatGroup.vue new file mode 100644 index 0000000..76bce6f --- /dev/null +++ b/src/renderer/src/components/SoftHome/WechatGroup.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/src/renderer/src/components/Versions.vue b/src/renderer/src/components/Versions.vue new file mode 100644 index 0000000..49a5e08 --- /dev/null +++ b/src/renderer/src/components/Versions.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/renderer/src/components/common/CommonDialog.vue b/src/renderer/src/components/common/CommonDialog.vue new file mode 100644 index 0000000..1ac826b --- /dev/null +++ b/src/renderer/src/components/common/CommonDialog.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/src/renderer/src/components/common/DisabledWrapper.vue b/src/renderer/src/components/common/DisabledWrapper.vue new file mode 100644 index 0000000..1b9d10d --- /dev/null +++ b/src/renderer/src/components/common/DisabledWrapper.vue @@ -0,0 +1,170 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/src/components/common/Icon/APIIcon.vue b/src/renderer/src/components/common/Icon/APIIcon.vue new file mode 100644 index 0000000..8354005 --- /dev/null +++ b/src/renderer/src/components/common/Icon/APIIcon.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/renderer/src/components/common/Icon/BackTaskIcon.vue b/src/renderer/src/components/common/Icon/BackTaskIcon.vue new file mode 100644 index 0000000..8c85634 --- /dev/null +++ b/src/renderer/src/components/common/Icon/BackTaskIcon.vue @@ -0,0 +1,16 @@ + diff --git a/src/renderer/src/components/common/Icon/DownloadRound.vue b/src/renderer/src/components/common/Icon/DownloadRound.vue new file mode 100644 index 0000000..4efa19b --- /dev/null +++ b/src/renderer/src/components/common/Icon/DownloadRound.vue @@ -0,0 +1,12 @@ + diff --git a/src/renderer/src/components/common/Icon/FindReplaceRound.vue b/src/renderer/src/components/common/Icon/FindReplaceRound.vue new file mode 100644 index 0000000..6d97567 --- /dev/null +++ b/src/renderer/src/components/common/Icon/FindReplaceRound.vue @@ -0,0 +1,12 @@ + diff --git a/src/renderer/src/components/common/Icon/MenuOpenRound.vue b/src/renderer/src/components/common/Icon/MenuOpenRound.vue new file mode 100644 index 0000000..79d0f07 --- /dev/null +++ b/src/renderer/src/components/common/Icon/MenuOpenRound.vue @@ -0,0 +1,12 @@ + diff --git a/src/renderer/src/components/common/Icon/OpenInBrowserRound.vue b/src/renderer/src/components/common/Icon/OpenInBrowserRound.vue new file mode 100644 index 0000000..c8a279e --- /dev/null +++ b/src/renderer/src/components/common/Icon/OpenInBrowserRound.vue @@ -0,0 +1,12 @@ + diff --git a/src/renderer/src/components/common/Icon/UploadRound.vue b/src/renderer/src/components/common/Icon/UploadRound.vue new file mode 100644 index 0000000..a26b615 --- /dev/null +++ b/src/renderer/src/components/common/Icon/UploadRound.vue @@ -0,0 +1,14 @@ + diff --git a/src/renderer/src/components/common/InputDialogContent.vue b/src/renderer/src/components/common/InputDialogContent.vue new file mode 100644 index 0000000..ab4c281 --- /dev/null +++ b/src/renderer/src/components/common/InputDialogContent.vue @@ -0,0 +1,71 @@ + + + diff --git a/src/renderer/src/components/common/NotesCollapse.vue b/src/renderer/src/components/common/NotesCollapse.vue new file mode 100644 index 0000000..66b7a66 --- /dev/null +++ b/src/renderer/src/components/common/NotesCollapse.vue @@ -0,0 +1,274 @@ + + + + + + + + diff --git a/src/renderer/src/components/common/TextEllipsis.vue b/src/renderer/src/components/common/TextEllipsis.vue new file mode 100644 index 0000000..687f12f --- /dev/null +++ b/src/renderer/src/components/common/TextEllipsis.vue @@ -0,0 +1,289 @@ + + + + + diff --git a/src/renderer/src/components/common/TooltipButton.vue b/src/renderer/src/components/common/TooltipButton.vue new file mode 100644 index 0000000..dabbdec --- /dev/null +++ b/src/renderer/src/components/common/TooltipButton.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/src/renderer/src/components/common/TooltipButton.vue.d.ts b/src/renderer/src/components/common/TooltipButton.vue.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/renderer/src/components/common/TooltipDropdown.vue b/src/renderer/src/components/common/TooltipDropdown.vue new file mode 100644 index 0000000..2160603 --- /dev/null +++ b/src/renderer/src/components/common/TooltipDropdown.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/renderer/src/env.d.ts b/src/renderer/src/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/renderer/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/renderer/src/main.ts b/src/renderer/src/main.ts new file mode 100644 index 0000000..b9e1764 --- /dev/null +++ b/src/renderer/src/main.ts @@ -0,0 +1,14 @@ +import './assets/main.css' +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import router from './router' + +// 创建pinia实例 +const pinia = createPinia() + +// 创建应用并使用路由和状态管理 +const app = createApp(App) +app.use(router) +app.use(pinia) // 使用pinia +app.mount('#app') \ No newline at end of file diff --git a/src/renderer/src/router/index.ts b/src/renderer/src/router/index.ts new file mode 100644 index 0000000..c20e323 --- /dev/null +++ b/src/renderer/src/router/index.ts @@ -0,0 +1,59 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import Home from '../views/Home.vue' + +// 创建404页面组件(可以是单独文件,这里用组件选项简化) +const NotFound = { + template: '

404

页面不存在

', + style: '.not-found { text-align: center; padding: 40px; }' +} + +const routes = [ + { + path: '/', + component: Home, + children: [ + { + path: '', // 默认子路由(首页内容) + name: 'Dashboard', + component: () => import('../views/SoftwareHome.vue') // 假设有这个组件 + }, + { + path: 'original', + name: 'Original', + component: () => import('../views/OriginalHome.vue') // 假设有这个组件 + }, + { + path: 'original-book-detail/:id', + name: 'OriginalBookDetail', + component: () => import('../views/OriginalBookDetailHome.vue') // 假设有这个组件 + }, + { + path: 'preset-library', + name: 'presetLibrary', + component: () => import('../views/PresetLibraryHome.vue') // 假设有这个组件 + }, + { + path: 'docs', + name: 'Docs', + component: () => import('../views/About.vue') // 假设有这个组件 + }, + { + path: 'settings', + name: 'Settings', + component: () => import('../views/Setting.vue') // 假设有这个组件 + }, + { + path: ':pathMatch(.*)*', // 捕获所有未匹配的路由 + name: 'NotFound', + component: NotFound // 使用上面定义的404组件 + } + ] + } +] + +const router = createRouter({ + history: createWebHashHistory(), + routes +}) + +export default router diff --git a/src/renderer/src/stores/index.ts b/src/renderer/src/stores/index.ts new file mode 100644 index 0000000..3e91336 --- /dev/null +++ b/src/renderer/src/stores/index.ts @@ -0,0 +1,6 @@ +// 统一导出所有stores +export * from './subStore/theme' +export * from './subStore/menu' +export * from './subStore/software' +export * from './subStore/book' +export * from './subStore/preset' diff --git a/src/renderer/src/stores/subStore/book.ts b/src/renderer/src/stores/subStore/book.ts new file mode 100644 index 0000000..7d2347c --- /dev/null +++ b/src/renderer/src/stores/subStore/book.ts @@ -0,0 +1,51 @@ +import { BookTaskStatus } from '@/define/enum/bookEnum' +import { Book } from '@/define/model/book/book' +import { defineStore } from 'pinia' + +// 系统相关设置 +export const useBookStore = defineStore('software', { + state: () => ({ + selectBook: { + id: undefined, + name: undefined, + bookFolderPath: undefined, + type: undefined, + oldVideoPath: undefined, + srtPath: undefined, + audioPath: undefined, + imageFolder: undefined, + subtitlePosition: undefined + } as Book.SelectBook, // 当前选中的小说 + + queryBookCondition: { + id: undefined, + name: undefined, + page: undefined, + pageSize: undefined, + type: undefined + } as Book.QueryBookCondition, // 查询小说的条件 + + selectBookTask: { + no: undefined, + id: undefined, + bookId: undefined, + name: undefined, + generateVideoPath: undefined, + srtPath: undefined, + audioPath: undefined, + draftSrtStyle: undefined, // 草稿字幕样式 + backgroundMusic: undefined, // 背景音乐ID + friendlyReminder: undefined, // 友情提示 + imageFolder: undefined, + styleList: undefined, + prefix: undefined, + imageCategory: undefined, + status: BookTaskStatus.WAIT, + errorMsg: undefined, + openVideoGenerate: false + } as Book.SelectBookTask, // 当前选中的小说任务 + selectBookTaskDetail: [] as Book.SelectBookTaskDetail[] // 当前选中的小说任务的详细数据 + }), + getters: {}, + actions: {} +}) diff --git a/src/renderer/src/stores/subStore/menu.ts b/src/renderer/src/stores/subStore/menu.ts new file mode 100644 index 0000000..6b4c2ad --- /dev/null +++ b/src/renderer/src/stores/subStore/menu.ts @@ -0,0 +1,141 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import type { Component } from 'vue' +import { + BookOutline, + PricetagsOutline, + HomeOutline as HomeIcon, + SettingsOutline as SettingsIcon, + DocumentTextOutline as DocIcon, + InformationCircleOutline as AboutIcon +} from '@vicons/ionicons5' +import { h } from 'vue' +import { NIcon } from 'naive-ui' + +let taskIcon = `` + +export const useMenuStore = defineStore('menu', () => { + // 菜单折叠状态 + const collapsed = ref(false) + // 当前活跃菜单项 + const activeKey = ref('home') + + // 渲染图标 + /** + * 渲染图标函数 - 支持Component组件和SVG字符串 + * + * @param {Component | string} icon - Vue组件或SVG字符串 + * @returns 返回一个渲染函数,用于在模板中显示图标 + */ + function renderIcon(icon: Component | string) { + // 如果是SVG字符串 + if (typeof icon === 'string') { + return () => + h(NIcon, null, { + default: () => + h('div', { + innerHTML: icon, + style: 'display: flex; align-items: center; justify-content: center;' + }) + }) + } + // 如果是Vue组件 + return () => h(NIcon, null, { default: () => h(icon) }) + } + // 菜单项定义 + const menuItems = [ + { + label: '首页', + key: 'home', + icon: renderIcon(HomeIcon), + route: '/', + isFooter: false, + isDialog: false + }, + { + label: '原创', + key: 'original', + icon: renderIcon(BookOutline), + route: '/original', + isFooter: false, + isDialog: false + }, + { + label: '预设库', + key: 'preset-library', + icon: renderIcon(PricetagsOutline), + route: '/preset-library', + isFooter: false, + isDialog: false + }, + { + label: '任务', + key: 'task', + icon: renderIcon(taskIcon), + route: '/task', + isFooter: false, + isDialog: true, + dialogTitle: null, + dialogWidth: 0.9, + dialogHeight: 0.8, + dialogComponent: () => import('@renderer/views/TaskDataTable.vue') + }, + { + label: '文档', + key: 'docs', + icon: renderIcon(DocIcon), + route: '/docs', + isFooter: true, + isDialog: true, + dialogTitle: null, + dialogWidth: 1000, + dialogHeight: 500, + dialogComponent: () => import('../../components/DocHelp.vue') + }, + { + label: '关于', + key: 'about', + icon: renderIcon(AboutIcon), + isFooter: true, + isDialog: true, + dialogTitle: null, + dialogWidth: 800, + dialogHeight: 600, + dialogComponent: () => import('@renderer/views/About.vue') + }, + { + label: '设置', + key: 'settings', + icon: renderIcon(SettingsIcon), + route: '/settings', + isFooter: true, + isDialog: false + } + ] + + // 计算属性:主菜单项 + const mainItems = computed(() => menuItems.filter((item) => !item.isFooter)) + // 计算属性:底部菜单项 + const footerItems = computed(() => menuItems.filter((item) => item.isFooter)) + + // 切换折叠状态 + function toggleCollapsed() { + collapsed.value = !collapsed.value + } + + // 设置活跃菜单 + function setActiveMenu(key: string) { + activeKey.value = key + } + + return { + collapsed, + activeKey, + menuItems, + mainItems, + footerItems, + toggleCollapsed, + setActiveMenu, + renderIcon + } +}) diff --git a/src/renderer/src/stores/subStore/preset.ts b/src/renderer/src/stores/subStore/preset.ts new file mode 100644 index 0000000..39c3cbb --- /dev/null +++ b/src/renderer/src/stores/subStore/preset.ts @@ -0,0 +1,74 @@ +import { PresetCategory } from '@/define/data/presetData' +import { PresetModel } from '@/define/model/preset' +import { defineStore } from 'pinia' + +// 系统相关设置 +export const usePresetStore = defineStore('preset', { + state: () => ({ + selectPreset: { + id: undefined, + label: '预设-' + Date.now(), + type: PresetCategory.Style, + showImage: undefined, + prompt: '', + chinesePrompt: undefined, + imageUrl: undefined, + srefSw: undefined, + crefCw: undefined, + lora: undefined, + loraWeight: undefined, + isShow: true, + aliases: undefined, + createTime: undefined + } as PresetModel.Preset, // 选中的预设 + + queryPresetCondition: { + id: undefined, + label: undefined, + type: undefined, + isShow: undefined, + page: 1, + pageSize: 10 + } as PresetModel.QueryPresetCondition, // 查询预设的条件 + + presetArray: [] as PresetModel.Preset[], // 预设列表 + + showCharacterPresetArray: [] as PresetModel.Preset[], // 角色预设列表 + showStylePresetArray: [] as PresetModel.Preset[], // 风格预设列表 + showScenePresetArray: [] as PresetModel.Preset[], // 场景预设列表 + presetChangeCount: 0, // 预设列表变化的次数 + + totalItems: 0 // 预设列表总条数 + }), + getters: {}, + actions: { + ResetSelectPreset() { + const now = new Date() + const formattedDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}` + + this.selectPreset.id = undefined + this.selectPreset.label = '预设-' + formattedDate + this.selectPreset.type = PresetCategory.Style + this.selectPreset.showImage = undefined + this.selectPreset.prompt = '' + this.selectPreset.chinesePrompt = undefined + this.selectPreset.imageUrl = undefined + this.selectPreset.srefSw = 50 + this.selectPreset.crefCw = 20 + this.selectPreset.lora = null + this.selectPreset.loraWeight = 1.0 + this.selectPreset.isShow = true + this.selectPreset.aliases = undefined + this.selectPreset.createTime = undefined + }, + + ResetQueryPresetCondition() { + this.queryPresetCondition.id = undefined + this.queryPresetCondition.isShow = undefined + this.queryPresetCondition.label = '' + this.queryPresetCondition.page = 1 + this.queryPresetCondition.pageSize = 10 + this.queryPresetCondition.type = null + } + } +}) diff --git a/src/renderer/src/stores/subStore/software.ts b/src/renderer/src/stores/subStore/software.ts new file mode 100644 index 0000000..c600831 --- /dev/null +++ b/src/renderer/src/stores/subStore/software.ts @@ -0,0 +1,62 @@ +import { defineStore } from 'pinia' + +// 系统相关设置 +export const useSoftwareStore = defineStore('software', { + state: () => ({ + // 加载弹窗 + spin: { + spinning: false, + tip: '加载中...' + }, + // 原创表格高度,数字 用于计算的 + originalDatatableHeight: 220, + // 原创表格高度,字符串 用于展示的 + originalDatatableHeightPx: '220px', + // 原创 是否显示全部提示词列 + showCompletePrompt: false, + + // 拖拽的目标元素 + dragImageTarget: null as HTMLElement | null, + + /** 授权信息 */ + authorization: { + // 机器码ID + machineId: '', + // 软件授权码 + authorizationCode: '', + isPro: false, + authorizationMessage: { + machineID: '', + type: 0, + useType: 1, + expiryTime: 1, + authorizedDate: new Date(), + expiryDate: new Date(), + authorizationCode: '' + } as SoftwareModal.SoftwareAuthorizationMessage + }, + versionInfo: { + currentVersion: 'v1.0.0', + latestVersion: 'v1.0.0', + updateInfo: { + latestVersion: 'v1.0.0', + updateDate: '2023-10-01', + updateInfo: [ + { + version: 'v1.0.0', + updateDate: '2023-09-01', + changes: [ + { + type: 'improvement', + description: '改进了性能' + } + ] + } + ] + }, + canUpdate: false + } as SoftwareModal.VersionInfo + }), + getters: {}, + actions: {} +}) diff --git a/src/renderer/src/stores/subStore/theme.ts b/src/renderer/src/stores/subStore/theme.ts new file mode 100644 index 0000000..542d9ae --- /dev/null +++ b/src/renderer/src/stores/subStore/theme.ts @@ -0,0 +1,28 @@ +import { defineStore } from 'pinia' + +export interface ThemeState { + isDarkMode: boolean + menuPrimaryColor: string + menuPrimaryShadow: string +} + +export const useThemeStore = defineStore('theme', { + state: () => + ({ + isDarkMode: true, + menuPrimaryColor: 'rgba(152, 152, 219, 0.6)', + menuPrimaryShadow: 'rgba(152, 152, 21, 0.3)' + }) as ThemeState, + getters: {}, + actions: { + // 切换暗黑模式 + toggleDarkMode() { + this.isDarkMode = !this.isDarkMode + }, + // 设置主题颜色 + setMenuColor(color, shadowColor) { + this.menuPrimaryColor = color + this.menuPrimaryShadow = shadowColor + } + } +}) diff --git a/src/renderer/src/views/About.vue b/src/renderer/src/views/About.vue new file mode 100644 index 0000000..457cee2 --- /dev/null +++ b/src/renderer/src/views/About.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/src/renderer/src/views/Authorization.vue b/src/renderer/src/views/Authorization.vue new file mode 100644 index 0000000..62e42c0 --- /dev/null +++ b/src/renderer/src/views/Authorization.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/src/renderer/src/views/Home.vue b/src/renderer/src/views/Home.vue new file mode 100644 index 0000000..9bcd0cf --- /dev/null +++ b/src/renderer/src/views/Home.vue @@ -0,0 +1,410 @@ + + + + + diff --git a/src/renderer/src/views/LoadingScreen.vue b/src/renderer/src/views/LoadingScreen.vue new file mode 100644 index 0000000..2636ee1 --- /dev/null +++ b/src/renderer/src/views/LoadingScreen.vue @@ -0,0 +1,449 @@ + + + + + diff --git a/src/renderer/src/views/NotFound.vue b/src/renderer/src/views/NotFound.vue new file mode 100644 index 0000000..c80b6a3 --- /dev/null +++ b/src/renderer/src/views/NotFound.vue @@ -0,0 +1,41 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/src/views/OriginalBookDetailHome.vue b/src/renderer/src/views/OriginalBookDetailHome.vue new file mode 100644 index 0000000..15c56bb --- /dev/null +++ b/src/renderer/src/views/OriginalBookDetailHome.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/renderer/src/views/OriginalHome copy.vue b/src/renderer/src/views/OriginalHome copy.vue new file mode 100644 index 0000000..d36e4cb --- /dev/null +++ b/src/renderer/src/views/OriginalHome copy.vue @@ -0,0 +1,868 @@ + + + + + diff --git a/src/renderer/src/views/OriginalHome.vue b/src/renderer/src/views/OriginalHome.vue new file mode 100644 index 0000000..b6eaab7 --- /dev/null +++ b/src/renderer/src/views/OriginalHome.vue @@ -0,0 +1,471 @@ + + + + + diff --git a/src/renderer/src/views/PresetLibraryHome.vue b/src/renderer/src/views/PresetLibraryHome.vue new file mode 100644 index 0000000..be11957 --- /dev/null +++ b/src/renderer/src/views/PresetLibraryHome.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/src/renderer/src/views/Setting.vue b/src/renderer/src/views/Setting.vue new file mode 100644 index 0000000..040793d --- /dev/null +++ b/src/renderer/src/views/Setting.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/src/renderer/src/views/SoftwareHome.vue b/src/renderer/src/views/SoftwareHome.vue new file mode 100644 index 0000000..5588d6e --- /dev/null +++ b/src/renderer/src/views/SoftwareHome.vue @@ -0,0 +1,1387 @@ + + + + + diff --git a/src/renderer/src/views/TaskDataTable.vue b/src/renderer/src/views/TaskDataTable.vue new file mode 100644 index 0000000..6334804 --- /dev/null +++ b/src/renderer/src/views/TaskDataTable.vue @@ -0,0 +1,249 @@ + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2f9c149 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }], + "compilerOptions": { + "module": "commonjs", + "strict": false, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "dist", // 输出目录 + "sourceMap": true, // 生成源码映射 + "declaration": true, // 生成声明文件 + "moduleResolution": "node", // 模块解析策略 + "allowJs": true, // 允许编译 JS 文件 + "target": "es2021", // 你可以设置目标版本, + "baseUrl": ".", // 设置模块解析的根目录为项目根目录 + "paths": { + "@/*": ["src/*"] // 使用 @ 作为 src 目录的别名 + } + }, + "include": [ + "src/**/*" + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..1195819 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,15 @@ +{ + "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", + "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*","src/db/**/*","src/define/**/*","src/public/**/*"], + "compilerOptions": { + "composite": true, + "types": ["electron-vite/node"], + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@renderer/*": ["src/renderer/src/*"] + }, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + } +} diff --git a/tsconfig.web.json b/tsconfig.web.json new file mode 100644 index 0000000..bb736f2 --- /dev/null +++ b/tsconfig.web.json @@ -0,0 +1,22 @@ +{ + "extends": "@electron-toolkit/tsconfig/tsconfig.web.json", + "include": [ + "src/renderer/src/env.d.ts", + "src/renderer/src/**/*", + "src/renderer/src/**/*.tsx", + "src/preload/*.d.ts", + "src/define/**/*", + "src/public/**/*", + "src/enum/**/*", + "src/store/**/*", + ], + "compilerOptions": { + "composite": true, + "baseUrl": ".", + "paths": { + "@renderer/*": [ + "src/renderer/src/*" + ],"@/*": ["src/*"], + } + } +}