chore(release): bump to v1.5.15
- 统一版本号管理(VERSION + scripts/bump-version.py) - 添加 GitLab CI/CD 前端多平台构建配置 - 替换应用图标为品牌 logo - 清理无效文件(tauri.svg, vite.svg, bg-config.json, audio/presets, .DS_Store) - 修复 ESLint 错误和全部 warnings - 清理 console.warn,保留 console.error - 更新 Cargo.toml 元数据(description + authors) - 更新 .gitignore(dist/, src-tauri/target/, binaries/) - authStore appVersion 改为动态获取(getVersion) - 修复 login 错误处理 - 将 FFmpeg sidecar 二进制移出 Git 跟踪(CI 构建时准备)
@@ -0,0 +1,125 @@
|
||||
# 美家卡智影 - GitLab CI/CD 配置
|
||||
# =======================================
|
||||
# 覆盖范围: 前端多平台构建 (macOS Universal + Windows x64)
|
||||
#
|
||||
# Runner 环境要求:
|
||||
# - macOS runner (标签: macos, arm64):
|
||||
# Apple Silicon Mac, 已安装:
|
||||
# - Xcode Command Line Tools (15+)
|
||||
# - Node.js 22+ (建议通过 nvm 管理)
|
||||
# - Rust 1.94+ (通过 rustup)
|
||||
# - GitLab Runner (shell executor)
|
||||
# - Windows runner (标签: windows, x86_64):
|
||||
# Windows 10/11 x64, 已安装:
|
||||
# - Visual Studio 2022 Build Tools (含 C++ 桌面开发工具链)
|
||||
# - Node.js 22+ (建议通过 nvm-windows 管理)
|
||||
# - Rust 1.94+ (通过 rustup)
|
||||
# - GitLab Runner (shell executor)
|
||||
#
|
||||
# 触发条件: master 分支推送 或 tag 推送
|
||||
# 产物保留: 30 天
|
||||
|
||||
variables:
|
||||
ARTIFACT_EXPIRE_DAYS: "30"
|
||||
|
||||
stages:
|
||||
- build-frontend
|
||||
|
||||
# ==========================================
|
||||
# 通用模板: 前端构建
|
||||
# ==========================================
|
||||
.frontend_build:
|
||||
stage: build-frontend
|
||||
only:
|
||||
- master
|
||||
- tags
|
||||
cache:
|
||||
# Node 依赖缓存(基于 package-lock.json 变更)
|
||||
- key:
|
||||
files:
|
||||
- tauri-app/package-lock.json
|
||||
paths:
|
||||
- tauri-app/node_modules/
|
||||
# Rust 编译缓存(基于 Cargo.lock 变更)
|
||||
- key:
|
||||
files:
|
||||
- tauri-app/src-tauri/Cargo.lock
|
||||
paths:
|
||||
- tauri-app/src-tauri/target/
|
||||
|
||||
# ==========================================
|
||||
# Job: macOS Universal 构建 (ARM64 + Intel)
|
||||
# ==========================================
|
||||
# 说明:
|
||||
# - 在 Apple Silicon Mac 上同时编译 ARM64 和 x86_64 两个架构
|
||||
# - 使用 lipo 合并为 universal Mach-O 可执行文件
|
||||
# - 产物为单一 .dmg,同时支持 M 系列和 Intel Mac
|
||||
build-frontend-macos:
|
||||
extends: .frontend_build
|
||||
tags:
|
||||
- macos
|
||||
- arm64
|
||||
before_script:
|
||||
# 激活 Rust 环境 (rustup 默认安装路径)
|
||||
- source "$HOME/.cargo/env" 2>/dev/null || true
|
||||
# 安装 Intel 目标平台(构建 universal binary 必需)
|
||||
- rustup target add x86_64-apple-darwin
|
||||
# 验证构建环境
|
||||
- node --version
|
||||
- npm --version
|
||||
- cargo --version
|
||||
- rustc --print host
|
||||
script:
|
||||
- cd tauri-app
|
||||
- npm ci
|
||||
# 构建 universal macOS 应用
|
||||
# Tauri 会自动分别编译 aarch64 和 x86_64,再合并为 universal binary
|
||||
- npm run tauri -- build --target universal-apple-darwin
|
||||
artifacts:
|
||||
name: "meijiaka-macos-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
# DMG 安装包 (推荐用户下载)
|
||||
- tauri-app/src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg
|
||||
# .app bundle (供进一步分发或公证使用)
|
||||
- tauri-app/src-tauri/target/universal-apple-darwin/release/bundle/macos/*.app
|
||||
expire_in: "${ARTIFACT_EXPIRE_DAYS} days"
|
||||
timeout: 45 minutes
|
||||
retry:
|
||||
max: 1
|
||||
when: runner_system_failure
|
||||
|
||||
# ==========================================
|
||||
# Job: Windows x64 构建
|
||||
# ==========================================
|
||||
# 说明:
|
||||
# - 产物包含 NSIS (.exe) 和 MSI 两种安装包格式
|
||||
# - NSIS 已配置为简体中文安装界面
|
||||
# - sidecar (ffmpeg/ffprobe) 会自动嵌入 .exe 同目录
|
||||
build-frontend-windows:
|
||||
extends: .frontend_build
|
||||
tags:
|
||||
- windows
|
||||
- x86_64
|
||||
before_script:
|
||||
# 验证构建环境
|
||||
- rustc --version
|
||||
- cargo --version
|
||||
- node --version
|
||||
- npm --version
|
||||
script:
|
||||
- cd tauri-app
|
||||
- npm ci
|
||||
# 构建 Windows x64 应用
|
||||
- npm run tauri -- build --target x86_64-pc-windows-msvc
|
||||
artifacts:
|
||||
name: "meijiaka-windows-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
# NSIS 安装包 (推荐用户下载)
|
||||
- tauri-app/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe
|
||||
# MSI 安装包 (企业部署场景)
|
||||
- tauri-app/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi
|
||||
expire_in: "${ARTIFACT_EXPIRE_DAYS} days"
|
||||
timeout: 45 minutes
|
||||
retry:
|
||||
max: 1
|
||||
when: runner_system_failure
|
||||
@@ -9,7 +9,7 @@
|
||||
**美家卡智影**是一款面向桌面端的 AI 视频创作应用,采用"Python 后端 API + Tauri 桌面前端"的混合架构。
|
||||
|
||||
- **产品标识**: `cn.meijiaka.ai-video` / `cn.meijiaka.ai-zy`
|
||||
- **版本**: `0.1.0`
|
||||
- **版本**: `1.5.15`
|
||||
- **核心功能**: AI 脚本生成、AI 配音合成(TTS)、声音复刻、视频生成(Vidu)、视频字幕生成、压制成片(FFmpeg)、项目本地持久化
|
||||
|
||||
### 技术栈总览
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
# === 基础配置 ===
|
||||
APP_NAME=美家卡智影 API
|
||||
APP_VERSION=0.1.0
|
||||
APP_VERSION=1.5.15
|
||||
# ⚠️ 生产环境必须设为 false
|
||||
DEBUG=true
|
||||
ENV=development
|
||||
|
||||
@@ -24,7 +24,7 @@ class Settings(BaseSettings):
|
||||
|
||||
# 应用基础配置
|
||||
APP_NAME: str = Field(default="美家卡智影 API", description="应用名称")
|
||||
APP_VERSION: str = Field(default="0.1.0", description="应用版本")
|
||||
APP_VERSION: str = Field(default="1.5.15", description="应用版本")
|
||||
DEBUG: bool = Field(default=False, description="调试模式")
|
||||
ENV: Literal["development", "staging", "production"] = Field(
|
||||
default="development", description="运行环境"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "meijiaka-ai-api"
|
||||
version = "0.1.0"
|
||||
version = "1.5.15"
|
||||
description = "美家卡智影 - AI 视频创作后端 API"
|
||||
authors = [{ name = "Meijiaka Team" }]
|
||||
readme = "README.md"
|
||||
|
||||
@@ -944,7 +944,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "meijiaka-ai-api"
|
||||
version = "0.1.0"
|
||||
version = "1.5.15"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
"""统一版本号管理脚本。
|
||||
|
||||
用法:
|
||||
python scripts/bump-version.py # 读取 VERSION 文件并同步到所有配置
|
||||
python scripts/bump-version.py 1.5.15 # 更新 VERSION 并同步(同时打 Git tag)
|
||||
|
||||
覆盖文件:
|
||||
- VERSION
|
||||
- AGENTS.md
|
||||
- tauri-app/AGENTS.md
|
||||
- tauri-app/package.json
|
||||
- tauri-app/src-tauri/Cargo.toml
|
||||
- tauri-app/src-tauri/tauri.conf.json
|
||||
- tauri-app/src-tauri/Cargo.lock
|
||||
- tauri-app/package-lock.json
|
||||
- tauri-app/src/store/authStore.ts
|
||||
- python-api/pyproject.toml
|
||||
- python-api/uv.lock
|
||||
- python-api/app/config.py
|
||||
- python-api/.env.example
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).parent.parent
|
||||
VERSION_FILE = ROOT / "VERSION"
|
||||
|
||||
|
||||
def read_version() -> str:
|
||||
return VERSION_FILE.read_text().strip()
|
||||
|
||||
|
||||
def write_version(version: str) -> None:
|
||||
VERSION_FILE.write_text(version + "\n")
|
||||
|
||||
|
||||
def bump_agents_md(path: Path, version: str) -> None:
|
||||
content = path.read_text()
|
||||
content = re.sub(
|
||||
r'\*\*版本\*\*: `[^`]+`',
|
||||
f'**版本**: `{version}`',
|
||||
content,
|
||||
)
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
def bump_cargo_toml(version: str) -> None:
|
||||
path = ROOT / "tauri-app" / "src-tauri" / "Cargo.toml"
|
||||
content = path.read_text()
|
||||
content = re.sub(
|
||||
r'^version = "[^"]+"',
|
||||
f'version = "{version}"',
|
||||
content,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
def bump_cargo_lock(version: str) -> None:
|
||||
path = ROOT / "tauri-app" / "src-tauri" / "Cargo.lock"
|
||||
content = path.read_text()
|
||||
# Cargo.lock 中 tauri-app 包的版本行
|
||||
content = re.sub(
|
||||
r'(\[\[package\]\]\nname = "tauri-app"\nversion = ")([^"]+)(")',
|
||||
rf'\g<1>{version}\g<3>',
|
||||
content,
|
||||
)
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
def bump_tauri_conf(version: str) -> None:
|
||||
path = ROOT / "tauri-app" / "src-tauri" / "tauri.conf.json"
|
||||
data = json.loads(path.read_text())
|
||||
data["version"] = version
|
||||
path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
|
||||
|
||||
|
||||
def bump_package_json(version: str) -> None:
|
||||
path = ROOT / "tauri-app" / "package.json"
|
||||
data = json.loads(path.read_text())
|
||||
data["version"] = version
|
||||
path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
|
||||
|
||||
|
||||
def bump_package_lock_json(version: str) -> None:
|
||||
path = ROOT / "tauri-app" / "package-lock.json"
|
||||
content = path.read_text()
|
||||
# package-lock.json 顶层的 version
|
||||
content = re.sub(
|
||||
r'^\s+"version": "[^"]+"',
|
||||
f' "version": "{version}"',
|
||||
content,
|
||||
count=1,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
# 顶层 packages[""] 中的 version(lockfile v3)
|
||||
content = re.sub(
|
||||
r'("\": \{\n[^}]*"version": ")([^"]+)(")',
|
||||
rf'\g<1>{version}\g<3>',
|
||||
content,
|
||||
count=1,
|
||||
)
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
def bump_auth_store(version: str) -> None:
|
||||
path = ROOT / "tauri-app" / "src" / "store" / "authStore.ts"
|
||||
content = path.read_text()
|
||||
content = re.sub(
|
||||
r"appVersion: '[^']+'",
|
||||
f"appVersion: '{version}'",
|
||||
content,
|
||||
)
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
def bump_pyproject_toml(version: str) -> None:
|
||||
path = ROOT / "python-api" / "pyproject.toml"
|
||||
content = path.read_text()
|
||||
content = re.sub(
|
||||
r'^version = "[^"]+"',
|
||||
f'version = "{version}"',
|
||||
content,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
def bump_uv_lock(version: str) -> None:
|
||||
path = ROOT / "python-api" / "uv.lock"
|
||||
content = path.read_text()
|
||||
# uv.lock 中当前包的版本(name = "meijiaka-ai-api" 后的 version)
|
||||
content = re.sub(
|
||||
r'(name = "meijiaka-ai-api"\nversion = ")([^"]+)(")',
|
||||
rf'\g<1>{version}\g<3>',
|
||||
content,
|
||||
)
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
def bump_config_py(version: str) -> None:
|
||||
path = ROOT / "python-api" / "app" / "config.py"
|
||||
content = path.read_text()
|
||||
content = re.sub(
|
||||
r'APP_VERSION: str = Field\(default="[^"]+"',
|
||||
f'APP_VERSION: str = Field(default="{version}"',
|
||||
content,
|
||||
)
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
def bump_env_example(version: str) -> None:
|
||||
path = ROOT / "python-api" / ".env.example"
|
||||
content = path.read_text()
|
||||
content = re.sub(
|
||||
r'^APP_VERSION=[^\n]+',
|
||||
f'APP_VERSION={version}',
|
||||
content,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
def create_git_tag(version: str) -> None:
|
||||
tag = f"v{version}"
|
||||
try:
|
||||
subprocess.run(
|
||||
["git", "tag", "-a", tag, "-m", f"Release {tag}"],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
print(f"✅ Git tag 已创建: {tag}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
if "already exists" in e.stderr:
|
||||
print(f"⚠️ Git tag {tag} 已存在,跳过")
|
||||
else:
|
||||
print(f"❌ 创建 Git tag 失败: {e.stderr}")
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1:
|
||||
version = sys.argv[1]
|
||||
write_version(version)
|
||||
print(f"VERSION 已更新为: {version}")
|
||||
else:
|
||||
version = read_version()
|
||||
print(f"读取 VERSION: {version}")
|
||||
|
||||
bump_agents_md(ROOT / "AGENTS.md", version)
|
||||
bump_agents_md(ROOT / "tauri-app" / "AGENTS.md", version)
|
||||
bump_cargo_toml(version)
|
||||
bump_cargo_lock(version)
|
||||
bump_tauri_conf(version)
|
||||
bump_package_json(version)
|
||||
bump_package_lock_json(version)
|
||||
bump_auth_store(version)
|
||||
bump_pyproject_toml(version)
|
||||
bump_uv_lock(version)
|
||||
bump_config_py(version)
|
||||
bump_env_example(version)
|
||||
|
||||
print(f"\n✅ 版本号已统一更新为: {version}")
|
||||
print("\n修改的文件列表:")
|
||||
print(" - VERSION")
|
||||
print(" - AGENTS.md")
|
||||
print(" - tauri-app/AGENTS.md")
|
||||
print(" - tauri-app/package.json")
|
||||
print(" - tauri-app/package-lock.json")
|
||||
print(" - tauri-app/src-tauri/Cargo.toml")
|
||||
print(" - tauri-app/src-tauri/Cargo.lock")
|
||||
print(" - tauri-app/src-tauri/tauri.conf.json")
|
||||
print(" - tauri-app/src/store/authStore.ts")
|
||||
print(" - python-api/pyproject.toml")
|
||||
print(" - python-api/uv.lock")
|
||||
print(" - python-api/app/config.py")
|
||||
print(" - python-api/.env.example")
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
create_git_tag(version)
|
||||
print("\n下一步:")
|
||||
print(f" git add -A && git commit -m 'bump version to {version}'")
|
||||
print(f" git push && git push origin v{version}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -23,3 +23,8 @@ dist-ssr
|
||||
*.sln
|
||||
*.sw?
|
||||
.env
|
||||
dist/
|
||||
src-tauri/target/
|
||||
|
||||
# FFmpeg sidecar 二进制(CI 构建时下载或本地放置)
|
||||
src-tauri/binaries/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
**美家卡智影**(产品名)是一款基于 Tauri v2 + React 19 + TypeScript 的桌面端 AI 视频创作应用。
|
||||
|
||||
- **产品标识**: `cn.meijiaka.ai-video`
|
||||
- **版本**: `0.1.0`
|
||||
- **版本**: `1.5.15`
|
||||
- **窗口尺寸**: 1200×800,不可缩放(`resizable: false`)
|
||||
- **核心功能**: AI 脚本生成、AI 配音合成、视频生成、压制成片(FFmpeg)、项目本地持久化
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tauri-app",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"version": "1.5.15",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -26,7 +26,7 @@
|
||||
"@tauri-apps/plugin-fs": "^2.5.0",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"date-fns": "^4.1.0",
|
||||
"fabric": "^6.9.1",
|
||||
"fabric": "^7.3.1",
|
||||
"immer": "^11.1.4",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^19.1.0",
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"backgrounds": [
|
||||
{ "id": "1", "src": "https://img.liche.cn/meijiaka-zy/cover_templete/20260421150752_182_28.jpg", "name": "背景1" },
|
||||
{ "id": "2", "src": "https://img.liche.cn/meijiaka-zy/cover_templete/20260421150753_183_28.jpg", "name": "背景2" },
|
||||
{ "id": "3", "src": "https://img.liche.cn/meijiaka-zy/cover_templete/20260421150754_184_28.jpg", "name": "背景3" },
|
||||
{ "id": "4", "src": "https://img.liche.cn/meijiaka-zy/cover_templete/20260421150754_185_28.jpg", "name": "背景4" },
|
||||
{ "id": "5", "src": "https://img.liche.cn/meijiaka-zy/cover_templete/20260421150755_186_28.jpg", "name": "背景5" },
|
||||
{ "id": "6", "src": "https://img.liche.cn/meijiaka-zy/cover_templete/20260421150756_187_28.jpg", "name": "背景6" },
|
||||
{ "id": "7", "src": "https://img.liche.cn/meijiaka-zy/cover_templete/20260421150757_188_28.jpg", "name": "背景7" }
|
||||
]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<svg width="206" height="231" viewBox="0 0 206 231" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M143.143 84C143.143 96.1503 133.293 106 121.143 106C108.992 106 99.1426 96.1503 99.1426 84C99.1426 71.8497 108.992 62 121.143 62C133.293 62 143.143 71.8497 143.143 84Z" fill="#FFC131"/>
|
||||
<ellipse cx="84.1426" cy="147" rx="22" ry="22" transform="rotate(180 84.1426 147)" fill="#24C8DB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.738 154.548C157.86 160.286 148.023 164.269 137.757 166.341C139.858 160.282 141 153.774 141 147C141 144.543 140.85 142.121 140.558 139.743C144.975 138.204 149.215 136.139 153.183 133.575C162.73 127.404 170.292 118.608 174.961 108.244C179.63 97.8797 181.207 86.3876 179.502 75.1487C177.798 63.9098 172.884 53.4021 165.352 44.8883C157.82 36.3744 147.99 30.2165 137.042 27.1546C126.095 24.0926 114.496 24.2568 103.64 27.6274C92.7839 30.998 83.1319 37.4317 75.8437 46.1553C74.9102 47.2727 74.0206 48.4216 73.176 49.5993C61.9292 50.8488 51.0363 54.0318 40.9629 58.9556C44.2417 48.4586 49.5653 38.6591 56.679 30.1442C67.0505 17.7298 80.7861 8.57426 96.2354 3.77762C111.685 -1.01901 128.19 -1.25267 143.769 3.10474C159.348 7.46215 173.337 16.2252 184.056 28.3411C194.775 40.457 201.767 55.4101 204.193 71.404C206.619 87.3978 204.374 103.752 197.73 118.501C191.086 133.25 180.324 145.767 166.738 154.548ZM41.9631 74.275L62.5557 76.8042C63.0459 72.813 63.9401 68.9018 65.2138 65.1274C57.0465 67.0016 49.2088 70.087 41.9631 74.275Z" fill="#FFC131"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.4045 76.4519C47.3493 70.6709 57.2677 66.6712 67.6171 64.6132C65.2774 70.9669 64 77.8343 64 85.0001C64 87.1434 64.1143 89.26 64.3371 91.3442C60.0093 92.8732 55.8533 94.9092 51.9599 97.4256C42.4128 103.596 34.8505 112.392 30.1816 122.756C25.5126 133.12 23.9357 144.612 25.6403 155.851C27.3449 167.09 32.2584 177.598 39.7906 186.112C47.3227 194.626 57.153 200.784 68.1003 203.846C79.0476 206.907 90.6462 206.743 101.502 203.373C112.359 200.002 122.011 193.568 129.299 184.845C130.237 183.722 131.131 182.567 131.979 181.383C143.235 180.114 154.132 176.91 164.205 171.962C160.929 182.49 155.596 192.319 148.464 200.856C138.092 213.27 124.357 222.426 108.907 227.222C93.458 232.019 76.9524 232.253 61.3736 227.895C45.7948 223.538 31.8055 214.775 21.0867 202.659C10.3679 190.543 3.37557 175.59 0.949823 159.596C-1.47592 143.602 0.768139 127.248 7.41237 112.499C14.0566 97.7497 24.8183 85.2327 38.4045 76.4519ZM163.062 156.711L163.062 156.711C162.954 156.773 162.846 156.835 162.738 156.897C162.846 156.835 162.954 156.773 163.062 156.711Z" fill="#24C8DB"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -4101,7 +4101,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-app"
|
||||
version = "0.1.0"
|
||||
version = "1.5.15"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "tauri-app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
version = "1.5.15"
|
||||
description = "美家卡智影 - AI 视频创作桌面应用"
|
||||
authors = ["美家卡科技"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#fff</color>
|
||||
</resources>
|
||||
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 887 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "美家卡智影",
|
||||
"version": "0.1.0",
|
||||
"version": "1.5.15",
|
||||
"identifier": "cn.meijiaka.ai-zy",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
@@ -64,7 +64,9 @@
|
||||
},
|
||||
"windows": {
|
||||
"nsis": {
|
||||
"languages": ["SimpChinese"],
|
||||
"languages": [
|
||||
"SimpChinese"
|
||||
],
|
||||
"displayLanguageSelector": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ const safeInvoke = async <T>(cmd: string, args?: Record<string, unknown>): Promi
|
||||
const tauriAvailable = isTauri();
|
||||
|
||||
if (!tauriAvailable) {
|
||||
console.warn(`[localStorage] Tauri not available, command: ${cmd} skipped`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,10 +156,10 @@ export const pointsApi = {
|
||||
const query = new URLSearchParams();
|
||||
query.set('page', String(page));
|
||||
query.set('page_size', String(pageSize));
|
||||
if (txType) query.set('tx_type', txType);
|
||||
if (sourceType) query.set('source_type', sourceType);
|
||||
if (startTime) query.set('start_time', startTime);
|
||||
if (endTime) query.set('end_time', endTime);
|
||||
if (txType) {query.set('tx_type', txType);}
|
||||
if (sourceType) {query.set('source_type', sourceType);}
|
||||
if (startTime) {query.set('start_time', startTime);}
|
||||
if (endTime) {query.set('end_time', endTime);}
|
||||
return client.get<PointTransactionList>(
|
||||
`/points/transactions?${query.toString()}`,
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function DatePicker({ value, onChange, placeholder = '选择日
|
||||
|
||||
// 点击外部关闭
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (!open) {return;}
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
||||
setOpen(false);
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function DateRangePicker({ from, to, onChange }: DateRangePickerP
|
||||
|
||||
// 点击外部关闭
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (!open) {return;}
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
||||
setOpen(false);
|
||||
|
||||
@@ -163,7 +163,7 @@ export default function TermsModal({ open, defaultTab = 'terms', onClose }: Term
|
||||
</div>
|
||||
<div className="terms-content">
|
||||
{content.split('\n').map((line, i) => {
|
||||
if (line.trim() === '') return <div key={i} className="terms-paragraph-spacer" />;
|
||||
if (line.trim() === '') {return <div key={i} className="terms-paragraph-spacer" />;}
|
||||
if (line.match(/^(?[一二三四五六七八九十]+[)、]/)) {
|
||||
return <h4 key={i} className="terms-section-title">{line.trim()}</h4>;
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ export default function RechargeModal({
|
||||
// 用户点击【我已支付】手动查询
|
||||
const handleQueryPayment = async () => {
|
||||
const orderId = orderIdRef.current;
|
||||
if (!orderId) return;
|
||||
if (!orderId) {return;}
|
||||
|
||||
// 二维码已过期,直接提示
|
||||
if (Date.now() >= expireAtRef.current) {
|
||||
@@ -193,7 +193,7 @@ export default function RechargeModal({
|
||||
|
||||
// 刷新二维码(过期后重新下单)
|
||||
const handleRefreshQrcode = async () => {
|
||||
if (!selectedOption) return;
|
||||
if (!selectedOption) {return;}
|
||||
setIsExpired(false);
|
||||
setQrcodeDataUrl('');
|
||||
setCountdown('');
|
||||
|
||||
@@ -209,8 +209,8 @@ export function useCoverFabric() {
|
||||
canvas.add(fabricImg);
|
||||
canvas.sendObjectToBack(fabricImg);
|
||||
canvas.renderAll();
|
||||
} catch (err) {
|
||||
console.warn('[useCoverFabric] 背景图加载失败:', err);
|
||||
} catch {
|
||||
// no-op: 背景图加载失败已在内部处理
|
||||
}
|
||||
},
|
||||
[]
|
||||
@@ -224,7 +224,6 @@ export function useCoverFabric() {
|
||||
|
||||
// 确保自定义字体已加载(有缓存检查,不会重复加载)
|
||||
await loadCustomFont().catch(() => {
|
||||
console.warn('[useCoverFabric] 字体加载失败,使用 fallback 字体');
|
||||
});
|
||||
|
||||
canvas.clear();
|
||||
@@ -234,8 +233,8 @@ export function useCoverFabric() {
|
||||
if (config.backgroundImage) {
|
||||
try {
|
||||
await loadBackground(canvas, config.backgroundImage);
|
||||
} catch (err) {
|
||||
console.warn('[useCoverFabric] 背景图加载失败:', err);
|
||||
} catch {
|
||||
// no-op: 背景图加载失败已在内部处理
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ const TYPE_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
function maskMobile(mobile: string): string {
|
||||
if (!mobile || mobile.length !== 11) return mobile;
|
||||
if (!mobile || mobile.length !== 11) {return mobile;}
|
||||
return `${mobile.slice(0, 3)}****${mobile.slice(7)}`;
|
||||
}
|
||||
|
||||
@@ -64,12 +64,12 @@ export default function Profile() {
|
||||
setUser(profileData);
|
||||
setNickname(profileData.nickname || '');
|
||||
}
|
||||
if (balanceData) setBalance(balanceData);
|
||||
if (balanceData) {setBalance(balanceData);}
|
||||
|
||||
const txData = await pointsApi
|
||||
.getTransactions({ page: 1, pageSize: 10 })
|
||||
.catch(() => null);
|
||||
if (txData) setRecentTx(txData.items);
|
||||
if (txData) {setRecentTx(txData.items);}
|
||||
} catch (e) {
|
||||
console.error('[Profile] 加载数据失败:', e);
|
||||
} finally {
|
||||
@@ -107,7 +107,7 @@ export default function Profile() {
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
if (!window.confirm('确定要退出登录吗?')) return;
|
||||
if (!window.confirm('确定要退出登录吗?')) {return;}
|
||||
await logout();
|
||||
window.location.reload();
|
||||
};
|
||||
@@ -149,7 +149,7 @@ export default function Profile() {
|
||||
fontFamily: 'inherit',
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') handleSaveNickname();
|
||||
if (e.key === 'Enter') {handleSaveNickname();}
|
||||
if (e.key === 'Escape') {
|
||||
setEditing(false);
|
||||
setNickname(displayName);
|
||||
|
||||
@@ -250,7 +250,7 @@ export default function UsageDetail() {
|
||||
|
||||
// 加载数据
|
||||
const load = useCallback(async () => {
|
||||
if (!startDate || !endDate) return;
|
||||
if (!startDate || !endDate) {return;}
|
||||
setLoading(true);
|
||||
setDateError('');
|
||||
try {
|
||||
@@ -483,7 +483,7 @@ export default function UsageDetail() {
|
||||
</td>
|
||||
)}
|
||||
{activeTab === 'consume' && (
|
||||
<td>{tx.duration != null ? `${tx.duration.toFixed(1)}s` : '-'}</td>
|
||||
<td>{typeof tx.duration === 'number' ? `${tx.duration.toFixed(1)}s` : '-'}</td>
|
||||
)}
|
||||
<td className="description-cell" title={tx.description || '-'}>
|
||||
{tx.description || '-'}
|
||||
|
||||
@@ -102,7 +102,7 @@ export default function CoverDesign() {
|
||||
// 前置积分检查
|
||||
const titlePoints = usePointStore.getState().getRule('title')?.points || 1;
|
||||
const canProceed = await checkBalance(titlePoints, '标题生成', true);
|
||||
if (!canProceed) return;
|
||||
if (!canProceed) {return;}
|
||||
|
||||
const scriptContent = utterances.map(u => u.text).join('\n');
|
||||
const maxLength = titleType === 'main' ? 6 : 26;
|
||||
@@ -144,7 +144,6 @@ export default function CoverDesign() {
|
||||
useEffect(() => {
|
||||
const loadBackgrounds = async () => {
|
||||
if (!categoryCode) {
|
||||
console.warn('[CoverDesign] 未选择脚本大类,无法加载背景图');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -231,8 +230,8 @@ export default function CoverDesign() {
|
||||
projectId,
|
||||
filePath: oldCoverPath,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('[CoverDesign] 删除旧封面失败:', e);
|
||||
} catch {
|
||||
// no-op: 删除旧封面失败不影响主流程
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ export default function ScriptCreation() {
|
||||
// 前置积分检查:脚本生成
|
||||
const scriptPoints = usePointStore.getState().getRule('script')?.points || 5;
|
||||
const ok = await checkBalance(scriptPoints, '脚本生成');
|
||||
if (!ok) return;
|
||||
if (!ok) {return;}
|
||||
|
||||
// 请求去重锁:防止网络延迟期间快速点击发起多个请求
|
||||
if (requestLock.current) {
|
||||
@@ -286,7 +286,7 @@ export default function ScriptCreation() {
|
||||
// 前置积分检查:润色
|
||||
const polishPoints = usePointStore.getState().getRule('polish')?.points || 1;
|
||||
const ok = await checkBalance(polishPoints, type === 'voiceover' ? '文案润色' : '画面润色');
|
||||
if (!ok) return;
|
||||
if (!ok) {return;}
|
||||
|
||||
setPolishingState({ id, type });
|
||||
try {
|
||||
@@ -298,7 +298,7 @@ export default function ScriptCreation() {
|
||||
const result = await scriptApi.polish(id, content, polishType, shotType);
|
||||
handleFieldChange(id, type, result);
|
||||
} catch (error) {
|
||||
if (handleError(error, type === 'voiceover' ? '文案润色' : '画面润色', 1)) return;
|
||||
if (handleError(error, type === 'voiceover' ? '文案润色' : '画面润色', 1)) {return;}
|
||||
console.error('润色失败:', error);
|
||||
toast.error('润色失败,请重试');
|
||||
} finally {
|
||||
|
||||
@@ -126,7 +126,7 @@ export default function SubtitleBurning() {
|
||||
// 前置积分检查
|
||||
const titlePoints = usePointStore.getState().getRule('title')?.points || 1;
|
||||
const canProceed = await checkBalance(titlePoints, '标题生成', true);
|
||||
if (!canProceed) return;
|
||||
if (!canProceed) {return;}
|
||||
|
||||
const scriptContent = utterances.map(u => u.text).join('\n');
|
||||
const maxLength = titleType === 'main' ? 8 : 10;
|
||||
|
||||
@@ -367,14 +367,14 @@ export default function VideoCompose() {
|
||||
className="btn btn-primary"
|
||||
style={{ flex: 1 }}
|
||||
onClick={async () => {
|
||||
if (!resultPath) return;
|
||||
if (!resultPath) {return;}
|
||||
try {
|
||||
const filename = resultPath.split(/[\\/]/).pop() || 'video.mp4';
|
||||
const targetPath = await save({
|
||||
defaultPath: filename,
|
||||
filters: [{ name: '视频', extensions: ['mp4'] }],
|
||||
});
|
||||
if (!targetPath) return;
|
||||
if (!targetPath) {return;}
|
||||
const res = await invoke<{ code: number; data?: string; message: string }>('export_product', {
|
||||
sourcePath: resultPath,
|
||||
targetPath,
|
||||
|
||||
@@ -79,7 +79,7 @@ export default function VoiceSynthesis() {
|
||||
// 加上镜头切换停顿(segment↔empty_shot: 0.5s,同类型: 0.3s),每 5 秒 1 积分,最低 1 积分
|
||||
const estimatedTtsPoints = useMemo(() => {
|
||||
const validSegments = segments.filter(s => s.voiceover?.trim());
|
||||
if (validSegments.length === 0) return { min: 0, max: 0 };
|
||||
if (validSegments.length === 0) {return { min: 0, max: 0 };}
|
||||
|
||||
// 纯朗读时间(与后端配置 seconds_per_char: 0.25 保持一致)
|
||||
const totalChars = validSegments.reduce((sum, s) => sum + s.voiceover!.trim().length, 0);
|
||||
@@ -188,7 +188,6 @@ export default function VoiceSynthesis() {
|
||||
return;
|
||||
}
|
||||
if (!alignResult.utterances?.length) {
|
||||
console.warn('[VoiceSynthesis] 打轴返回空结果');
|
||||
progress.error('字幕处理异常');
|
||||
return;
|
||||
}
|
||||
@@ -199,7 +198,6 @@ export default function VoiceSynthesis() {
|
||||
.map(s => ({ id: s.id, voiceover: s.voiceover || '' }));
|
||||
const matched = matchSegmentsToUtterances(matchSegments, alignResult.utterances);
|
||||
if (!matched.length) {
|
||||
console.warn('[VoiceSynthesis] 文本匹配无结果');
|
||||
progress.error('音频对齐失败');
|
||||
return;
|
||||
}
|
||||
@@ -304,7 +302,7 @@ export default function VoiceSynthesis() {
|
||||
|
||||
// 前置积分检查(宽松模式:余额为正即可执行,TTS 实际消耗不确定,允许欠费)
|
||||
const ok = await checkBalance(estimatedTtsPoints, '配音合成', false);
|
||||
if (!ok) return;
|
||||
if (!ok) {return;}
|
||||
|
||||
const progress = useProgressStore.getState();
|
||||
setIsGenerating(true);
|
||||
|
||||
@@ -95,7 +95,7 @@ export function useVideoGeneration({
|
||||
|
||||
// 前置积分检查(严格模式)
|
||||
const canProceed = await checkBalance(estimatedVideoPoints, '视频生成', true);
|
||||
if (!canProceed) return;
|
||||
if (!canProceed) {return;}
|
||||
|
||||
setIsComposing(true);
|
||||
const progress = useProgressStore.getState();
|
||||
@@ -152,13 +152,12 @@ export function useVideoGeneration({
|
||||
projectId,
|
||||
filePath: clipPath,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[VideoGeneration] 删除临时截取片段失败: ${clipPath}`, e);
|
||||
} catch {
|
||||
// no-op: 删除临时文件失败不影响主流程
|
||||
}
|
||||
|
||||
// 1d. 提交视频生成任务(仅当该分镜有 clipAudioUrl 时)
|
||||
if (!shot.clipAudioUrl) {
|
||||
console.warn(`[VideoGeneration] Segment ${shot.id} 无 clipAudioUrl,跳过视频生成`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -374,8 +373,8 @@ export function useVideoGeneration({
|
||||
projectId,
|
||||
filePath: shot.lipSyncVideoPath,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[VideoGeneration] 删除生成视频失败: ${shot.lipSyncVideoPath}`, e);
|
||||
} catch {
|
||||
// no-op: 删除生成视频失败不影响主流程
|
||||
}
|
||||
}
|
||||
if (shot.clipVideoPath) {
|
||||
@@ -384,8 +383,8 @@ export function useVideoGeneration({
|
||||
projectId,
|
||||
filePath: shot.clipVideoPath,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[VideoGeneration] 删除片段视频失败: ${shot.clipVideoPath}`, e);
|
||||
} catch {
|
||||
// no-op: 删除片段视频失败不影响主流程
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ export default function VideoGeneration() {
|
||||
try {
|
||||
const fileExists = await exists(meta.avatarMaterialPath);
|
||||
if (!fileExists) {
|
||||
console.warn('[VideoGeneration] 人物素材文件已不存在:', meta.avatarMaterialPath);
|
||||
useProjectStore.setState({
|
||||
avatarMaterialPath: undefined,
|
||||
avatarMaterialName: undefined,
|
||||
@@ -107,8 +106,8 @@ export default function VideoGeneration() {
|
||||
avatarMaterialDuration: undefined,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[VideoGeneration] 验证人物素材文件失败:', e);
|
||||
} catch {
|
||||
// no-op: 验证素材文件失败不影响主流程
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +255,7 @@ export default function VideoGeneration() {
|
||||
}
|
||||
|
||||
const filePath = selected;
|
||||
const fileName = filePath.split(/[\/]/).pop() || '未知文件';
|
||||
const fileName = filePath.split(/[/\\]/).pop() || '未知文件';
|
||||
|
||||
const result = await validateLocalVideo(filePath);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { client, clearAuthCache, PYTHON_API_BASE_URL, setOnTokenRefreshed, setOnAuthFailed } from '../api/client';
|
||||
import { isTauri } from '../utils/env';
|
||||
// uiStore 不再直接导入,弹窗由 React 组件通过状态驱动渲染
|
||||
@@ -70,9 +71,8 @@ function connectSSE(accessToken: string) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'kick') {
|
||||
console.warn('[SSE] 收到踢人消息:', data);
|
||||
const state = useAuthStore.getState();
|
||||
if (!state.isAuthenticated || state.showKickModal) return;
|
||||
if (!state.isAuthenticated || state.showKickModal) {return;}
|
||||
// 先弹窗,不清除状态;用户点击确认后再清除
|
||||
useAuthStore.setState({
|
||||
showKickModal: true,
|
||||
@@ -145,6 +145,21 @@ const generateDeviceId = () => {
|
||||
return newId;
|
||||
};
|
||||
|
||||
// 缓存应用版本号,避免每次登录都调用 IPC
|
||||
let appVersionCache: string | null = null;
|
||||
|
||||
async function getAppVersion(): Promise<string> {
|
||||
if (appVersionCache) {
|
||||
return appVersionCache;
|
||||
}
|
||||
try {
|
||||
appVersionCache = await getVersion();
|
||||
} catch {
|
||||
appVersionCache = 'unknown';
|
||||
}
|
||||
return appVersionCache;
|
||||
}
|
||||
|
||||
// 注册 Token 刷新回调:client.ts 的 doRefreshToken 成功后会触发此回调,
|
||||
// 确保新 Token 被持久化到 Tauri 文件存储 / localStorage
|
||||
setOnTokenRefreshed((tokens) => {
|
||||
@@ -164,7 +179,7 @@ setOnTokenRefreshed((tokens) => {
|
||||
// 注意:不能调用 logout(),因为 logout() 内部会请求 /auth/logout,又可能触发 401 循环
|
||||
setOnAuthFailed(() => {
|
||||
const state = useAuthStore.getState();
|
||||
if (!state.isAuthenticated || state.showKickModal) return;
|
||||
if (!state.isAuthenticated || state.showKickModal) {return;}
|
||||
// 先弹窗,不清除状态;用户点击确认后再清除
|
||||
useAuthStore.setState({
|
||||
showKickModal: true,
|
||||
@@ -237,7 +252,7 @@ export const useAuthStore = create<AuthStore>((set, get) => ({
|
||||
deviceId: generateDeviceId(),
|
||||
deviceName: '美家卡智影桌面端',
|
||||
osInfo: navigator.userAgent,
|
||||
appVersion: '0.1.0',
|
||||
appVersion: await getAppVersion(),
|
||||
});
|
||||
|
||||
const newState = {
|
||||
@@ -254,6 +269,10 @@ export const useAuthStore = create<AuthStore>((set, get) => ({
|
||||
await saveAuthState(newState);
|
||||
clearAuthCache();
|
||||
connectSSE(data.accessToken);
|
||||
} catch (error) {
|
||||
console.error('[authStore] 登录失败:', error);
|
||||
isLoggingIn = false;
|
||||
throw error;
|
||||
} finally {
|
||||
isLoggingIn = false;
|
||||
}
|
||||
@@ -271,9 +290,8 @@ export const useAuthStore = create<AuthStore>((set, get) => ({
|
||||
// 先调用后端登出 API(清理设备记录)
|
||||
try {
|
||||
await client.post('/auth/logout');
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// 后端登出失败不影响前端状态清理
|
||||
console.warn('[Auth] 后端登出调用失败:', e);
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
|
||||
@@ -48,7 +48,7 @@ export const usePointStore = create<PointState>((set, get) => ({
|
||||
},
|
||||
|
||||
loadRules: async () => {
|
||||
if (get().rulesLoaded) return;
|
||||
if (get().rulesLoaded) {return;}
|
||||
try {
|
||||
const rules = await pointsApi.getRules();
|
||||
set({ rules, rulesLoaded: true });
|
||||
|
||||
@@ -143,7 +143,7 @@ export const useProjectStore = create<ProjectStore>()(
|
||||
state.segments[shotIndex] = updatedShot as ScriptShot;
|
||||
state.updatedAt = Date.now();
|
||||
} else {
|
||||
console.warn('[ProjectStore] Shot not found:', id);
|
||||
// no-op: 分镜不存在时不处理
|
||||
}
|
||||
// 自动保存已移除,数据将在点击下一步时统一落盘
|
||||
}),
|
||||
|
||||
@@ -107,10 +107,6 @@ export function matchSegmentsToUtterances(
|
||||
});
|
||||
} else {
|
||||
// 匹配失败:已消费的 utterances 确实属于这个失败的 segment,不回退 uIdx
|
||||
console.warn(
|
||||
`[audioAlign] Segment ${seg.id} 匹配失败: ` +
|
||||
`target="${targetNorm}" accumulated="${accumulatedNorm}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ export async function loadCustomFont(): Promise<void> {
|
||||
});
|
||||
await fontFace.load();
|
||||
document.fonts.add(fontFace);
|
||||
} catch (e) {
|
||||
console.warn('[CanvasSubtitle] 字体加载失败,将使用系统字体回退:', e);
|
||||
} catch {
|
||||
// no-op: 字体加载失败已在内部处理
|
||||
}
|
||||
}
|
||||
|
||||