diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7dae4cf5..b8bdcf80 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,5 @@ name: Bug Report -description: Report a bug in frame-fab +description: Report a bug in Story Weaver labels: ["bug", "needs-triage"] body: - type: markdown @@ -43,7 +43,7 @@ body: - type: input id: version attributes: - label: frame-fab Version + label: Story Weaver Version placeholder: e.g., 3.0.0 - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 6fe05c8b..7a603289 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,5 +1,5 @@ name: Feature Request -description: Suggest a new feature for frame-fab +description: Suggest a new feature for Story Weaver labels: ["enhancement", "needs-triage"] body: - type: markdown diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7ccf0817..4822a3f0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -## frame-fab Pull Request +## Story Weaver Pull Request ## Description diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 280941b4..6fe3053b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,16 +54,16 @@ jobs: with: tag_name: ${{ steps.version.outputs.tag }} body: | - ## Download frame-fab ${{ steps.version.outputs.version }} + ## Download Story Weaver ${{ steps.version.outputs.version }} | Platform | Architecture | Download | |----------|-------------|----------| - | Windows | x64 | [.zip](frame-fab-${{ steps.version.outputs.version }}-windows-x64.zip) | - | Windows | ARM64 | [.zip](frame-fab-${{ steps.version.outputs.version }}-windows-arm64.zip) | - | macOS | Universal | [.tar.gz](frame-fab-${{ steps.version.outputs.version }}-macos-universal.tar.gz) | - | Linux | x64 | [.tar.gz](frame-fab-${{ steps.version.outputs.version }}-linux-x64.tar.gz) | + | Windows | x64 | [.zip](story-weaver-${{ steps.version.outputs.version }}-windows-x64.zip) | + | Windows | ARM64 | [.zip](story-weaver-${{ steps.version.outputs.version }}-windows-arm64.zip) | + | macOS | Universal | [.tar.gz](story-weaver-${{ steps.version.outputs.version }}-macos-universal.tar.gz) | + | Linux | x64 | [.tar.gz](story-weaver-${{ steps.version.outputs.version }}-linux-x64.tar.gz) | - > Full release notes: [CHANGELOG.md](https://github.com/Agions/frame-fab/blob/${{ steps.version.outputs.tag }}/CHANGELOG.md) + > Full release notes: [CHANGELOG.md](https://github.com/Agions/story-weaver/blob/${{ steps.version.outputs.tag }}/CHANGELOG.md) draft: true prerelease: ${{ contains(steps.version.outputs.tag, 'alpha') || contains(steps.version.outputs.tag, 'beta') || contains(steps.version.outputs.tag, 'rc') }} env: @@ -107,8 +107,8 @@ jobs: shell: pwsh run: | $src = "src-tauri/target/x86_64-pc-windows-msvc/release" - $exe = "frame-fab.exe" - $out = "frame-fab-${{ needs.prepare.outputs.version }}-windows-x64.zip" + $exe = "story-weaver.exe" + $out = "story-weaver-${{ needs.prepare.outputs.version }}-windows-x64.zip" Compress-Archive -Path "$src/$exe", "$src/*.dll" -DestinationPath $out -Force echo "artifact_name=$out" >> $env:GITHUB_OUTPUT @@ -117,7 +117,7 @@ jobs: with: tag_name: ${{ needs.prepare.outputs.tag }} draft: true - files: frame-fab-${{ needs.prepare.outputs.version }}-windows-x64.zip + files: story-weaver-${{ needs.prepare.outputs.version }}-windows-x64.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -161,8 +161,8 @@ jobs: shell: pwsh run: | $src = "src-tauri/target/aarch64-pc-windows-msvc/release" - $exe = "frame-fab.exe" - $out = "frame-fab-${{ needs.prepare.outputs.version }}-windows-arm64.zip" + $exe = "story-weaver.exe" + $out = "story-weaver-${{ needs.prepare.outputs.version }}-windows-arm64.zip" Compress-Archive -Path "$src/$exe", "$src/*.dll" -DestinationPath $out -Force echo "artifact_name=$out" >> $env:GITHUB_OUTPUT @@ -171,7 +171,7 @@ jobs: with: tag_name: ${{ needs.prepare.outputs.tag }} draft: true - files: frame-fab-${{ needs.prepare.outputs.version }}-windows-arm64.zip + files: story-weaver-${{ needs.prepare.outputs.version }}-windows-arm64.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -212,15 +212,15 @@ jobs: id: package run: | cd $GITHUB_WORKSPACE/src-tauri/target/release/bundle - tar -czf "$GITHUB_WORKSPACE/frame-fab-${{ needs.prepare.outputs.version }}-macos-universal.tar.gz" -C macos frame-fab.app - echo "artifact_name=frame-fab-${{ needs.prepare.outputs.version }}-macos-universal.tar.gz" >> $GITHUB_OUTPUT + tar -czf "$GITHUB_WORKSPACE/story-weaver-${{ needs.prepare.outputs.version }}-macos-universal.tar.gz" -C macos story-weaver.app + echo "artifact_name=story-weaver-${{ needs.prepare.outputs.version }}-macos-universal.tar.gz" >> $GITHUB_OUTPUT - name: Upload macOS tar.gz uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.prepare.outputs.tag }} draft: true - files: frame-fab-${{ needs.prepare.outputs.version }}-macos-universal.tar.gz + files: story-weaver-${{ needs.prepare.outputs.version }}-macos-universal.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -272,15 +272,15 @@ jobs: id: package run: | cd src-tauri/target/release - tar -czf "$GITHUB_WORKSPACE/frame-fab-${{ needs.prepare.outputs.version }}-linux-x64.tar.gz" frame-fab - echo "artifact_name=frame-fab-${{ needs.prepare.outputs.version }}-linux-x64.tar.gz" >> $GITHUB_OUTPUT + tar -czf "$GITHUB_WORKSPACE/story-weaver-${{ needs.prepare.outputs.version }}-linux-x64.tar.gz" story-weaver + echo "artifact_name=story-weaver-${{ needs.prepare.outputs.version }}-linux-x64.tar.gz" >> $GITHUB_OUTPUT - name: Upload Linux tar.gz uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.prepare.outputs.tag }} draft: true - files: frame-fab-${{ needs.prepare.outputs.version }}-linux-x64.tar.gz + files: story-weaver-${{ needs.prepare.outputs.version }}-linux-x64.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ecc0c5b1..8623885a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ -# frame-fab 开发指南 +# Story Weaver 开发指南 ## 项目概述 -frame-fab 是 AI 驱动的 **AI 漫剧创作平台**,输入一本小说,AI 自动把它拍成一部漫剧。基于 Tauri 2.1 + Rust 桌面端,集成多模型 AI(GLM-5 / M2.5 / Kimi / Seedream / Kling / Vidu / Edge TTS)实现端到端自动化。 +Story Weaver 是 AI 驱动的 **AI 漫剧创作平台**,输入一本小说,AI 自动把它拍成一部漫剧。基于 Tauri 2.1 + Rust 桌面端,集成多模型 AI(GLM-5 / M2.5 / Kimi / Seedream / Kling / Vidu / Edge TTS)实现端到端自动化。 ## 技术栈 @@ -17,7 +17,7 @@ frame-fab 是 AI 驱动的 **AI 漫剧创作平台**,输入一本小说,AI ## 项目结构 ``` -frame-fab/ +Story Weaver/ ├── src/ # 前端 UI 层(React + Tauri API 包装) │ ├── features/ # 14 个 Feature 模块(DDD 风格) │ │ ├── ai/ # AI 模型选择 diff --git a/INSTALL.md b/INSTALL.md index 22bd1b59..721b9946 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,4 +1,4 @@ -# 安装 frame-fab +# 安装 Story Weaver ## macOS @@ -7,24 +7,24 @@ ```bash # 克隆并安装 brew tap Agions/tap https://github.com/Agions/homebrew-tap -brew install --cask frame-fab +brew install --cask story-weaver ``` ### 选项二:下载 DMG -从 [GitHub Releases](https://github.com/Agions/frame-fab/releases/latest) 下载最新的 `.dmg` 文件,将 `frame-fab.app` 拖入 `/Applications`。 +从 [GitHub Releases](https://github.com/Agions/story-weaver/releases/latest) 下载最新的 `.dmg` 文件,将 `Story Weaver.app` 拖入 `/Applications`。 ### 选项三:安装脚本 ```bash -bash -c "$(curl -fsSL https://raw.githubusercontent.com/Agions/frame-fab/main/scripts/install.sh)" +bash -c "$(curl -fsSL https://raw.githubusercontent.com/Agions/story-weaver/main/scripts/install.sh)" ``` --- ## Windows -从 [GitHub Releases](https://github.com/Agions/frame-fab/releases/latest) 下载最新的 `.exe`(NSIS 安装程序)或 `.msi`,运行即可。 +从 [GitHub Releases](https://github.com/Agions/story-weaver/releases/latest) 下载最新的 `.exe`(NSIS 安装程序)或 `.msi`,运行即可。 --- @@ -33,19 +33,19 @@ bash -c "$(curl -fsSL https://raw.githubusercontent.com/Agions/frame-fab/main/sc ### Debian / Ubuntu ```bash -curl -sL https://raw.githubusercontent.com/Agions/frame-fab/main/scripts/install.sh | bash +curl -sL https://raw.githubusercontent.com/Agions/story-weaver/main/scripts/install.sh | bash ``` -或从 [GitHub Releases](https://github.com/Agions/frame-fab/releases/latest) 下载 `.deb` 并运行: +或从 [GitHub Releases](https://github.com/Agions/story-weaver/releases/latest) 下载 `.deb` 并运行: ```bash -sudo dpkg -i frame-fab_*.deb +sudo dpkg -i story-weaver_*.deb ``` ### Arch Linux (AUR) ```bash -yay -S frame-fab +yay -S story-weaver ``` --- @@ -53,8 +53,8 @@ yay -S frame-fab ## 源码构建 ```bash -git clone https://github.com/Agions/frame-fab.git -cd frame-fab +git clone https://github.com/Agions/story-weaver.git +cd story-weaver pnpm install pnpm tauri build ``` diff --git a/README.md b/README.md index 3ea7a7c4..4fbd7808 100644 --- a/README.md +++ b/README.md @@ -2,292 +2,124 @@ - frame-fab · AI 漫剧创作平台 + Story Weaver · AI 漫剧创作平台
-# frame-fab · AI 漫剧创作平台 +# Story Weaver > **输入一本小说,AI 自动把它拍成一部漫剧——你只需要按"开始"。** -[![CI](https://img.shields.io/github/actions/workflow/status/Agions/frame-fab/test.yml?style=for-the-badge&label=CI&logo=github)](https://github.com/Agions/frame-fab/actions) -[![License](https://img.shields.io/github/license/Agions/frame-fab?style=for-the-badge&color=45B8AC)](https://opensource.org/licenses/MIT) +[![CI](https://img.shields.io/github/actions/workflow/status/Agions/story-weaver/test.yml?style=for-the-badge&label=CI&logo=github)](https://github.com/Agions/story-weaver/actions) +[![License](https://img.shields.io/github/license/Agions/story-weaver?style=for-the-badge&color=45B8AC)](https://opensource.org/licenses/MIT) [![React](https://img.shields.io/badge/React-18-61DAFB?style=for-the-badge&logo=react)](https://react.dev) [![Tauri](https://img.shields.io/badge/Tauri-2.1-FFC131?style=for-the-badge&logo=tauri)](https://tauri.app) [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-3178C6?style=for-the-badge&logo=typescript)](https://www.typescriptlang.org) -[![Version](https://img.shields.io/badge/version-v2.2.3-6366F1?style=for-the-badge)](https://github.com/Agions/frame-fab/releases) +[![Version](https://img.shields.io/badge/version-v2.2.3-6366F1?style=for-the-badge)](https://github.com/Agions/story-weaver/releases) -[**📖 在线文档**](https://agions.github.io/frame-fab/) · [**📥 下载桌面端**](https://github.com/Agions/frame-fab/releases) · [**🐛 报告问题**](https://github.com/Agions/frame-fab/issues/new) · [**💡 功能建议**](https://github.com/Agions/frame-fab/discussions) +[**📖 在线文档**](https://agions.github.io/story-weaver/) · [**📥 下载桌面端**](https://github.com/Agions/story-weaver/releases) · [**🐛 报告问题**](https://github.com/Agions/story-weaver/issues/new) --- -## ✨ 它是什么? +## 它是什么? -**frame-fab v2.2.3** 是一款**桌面端 AI 漫剧创作平台**,基于 **Tauri 2.1 + Rust** 构建,集成多模型 AI(智谱 GLM-5 / MiniMax M2.5 / 月之暗面 Kimi / 字节 Seedream 5.0 / 快手 Kling 1.6 / Edge TTS)实现从**小说/剧本**到**漫剧成片**的端到端自动化。提供: +**Story Weaver** 是一款开源桌面端 AI 漫剧创作平台。基于 **Tauri 2.1 + Rust** 构建,集成多模型 AI,将小说/剧本自动转化为漫剧视频。 -- 🎬 **Manual 模式**:七步半自动工作流(导入→分析→脚本→分镜→角色→渲染→导出),逐步审批 -- 🤖 **Autonomous 模式**:10 步 Pipeline(script-generation → storyboard → material-matching → voice-synthesis → keyframe)+ Self-Review Loop + Quality Gate -- 🔄 **Checkpoint 断点续传**:30 秒自动保存,崩溃后可恢复 -- 🧠 **ProviderRegistry**:Strategy 模式 + Fallback Chain,7+ 文字模型 / 4+ 图像模型 / 3+ TTS 自动切换 -- 🎙️ **Edge TTS 默认免费**:无需 API Key 即可获得 200+ 语音 +**核心能力:** -让创作者专注于故事本身,把繁琐的工程化交给 AI。 +- 🎬 **双模式工作流**:Manual 七步半自动(逐步审批)+ Autonomous 全自动 Pipeline +- 🧠 **多模型编排**:ProviderRegistry + Fallback Chain,7+ 文字模型 / 4+ 图像 / 3+ TTS 自动降级 +- 🔄 **断点续传**:30s 自动 Checkpoint,崩溃后可恢复 +- 🎙️ **Edge TTS 免费**:无需 API Key 即可获得 200+ 语音 --- -## 🎯 核心能力 +## 快速开始 -| 🎬 双模式工作流 | 🧠 多模型编排 | 🎙️ 一站式音视频 | -| :---------------------------------------------------: | :---------------------------------------------: | :------------------------------------------: | -| Manual 七步半自动 + Autonomous 五步 Pipeline | Strategy ProviderRegistry + 完整 Fallback Chain | Edge TTS + 唇形同步 + 字幕嵌入 + FFmpeg 合成 | -| **🦀 Rust 高性能** | **🔄 断点续传** | **🏗️ 桌面优先** | -| Tauri 2.1 + FFmpeg 子进程 · 26MB 包体积 · 冷启动 < 1s | 30s 自动 Checkpoint · 自审循环自动修复 | 全局快捷键 / 系统托盘 / 原生菜单 / 三端一致 | +### 方式 1:下载桌面端 ---- - -## 🚀 快速开始 - -### 方式 1:下载桌面端(推荐) +| 平台 | 架构 | 下载 | +| ------- | --------------------- | ------------------------------------------------------------------------------------ | +| macOS | Apple Silicon (M1–M4) | [story-weaver_aarch64.dmg](https://github.com/Agions/story-weaver/releases/latest) | +| macOS | Intel | [story-weaver_x64.dmg](https://github.com/Agions/story-weaver/releases/latest) | +| Windows | x64 | [story-weaver_x64-setup.exe](https://github.com/Agions/story-weaver/releases/latest) | +| Linux | x64 | [story-weaver_amd64.deb](https://github.com/Agions/story-weaver/releases/latest) | -| 平台 | 架构 | 下载 | -| ------- | --------------------------- | ------------------------------------------------------------------------------------- | -| macOS | Apple Silicon (M1/M2/M3/M4) | [frame-fab_x.x.x_aarch64.dmg](https://github.com/Agions/frame-fab/releases/latest) | -| macOS | Intel | [frame-fab_x.x.x_x64.dmg](https://github.com/Agions/frame-fab/releases/latest) | -| Windows | x64 | [frame-fab_x.x.x_x64-setup.exe](https://github.com/Agions/frame-fab/releases/latest) | -| Linux | AppImage | [frame-fab_x.x.x_amd64.AppImage](https://github.com/Agions/frame-fab/releases/latest) | - -### 方式 2:从源码运行(开发模式) +### 方式 2:源码运行 ```bash -git clone https://github.com/Agions/frame-fab.git -cd frame-fab +git clone https://github.com/Agions/story-weaver.git +cd story-weaver pnpm install -pnpm tauri dev # 同时启动 Vite + Tauri +pnpm tauri dev ``` -配置 `.env.local`(**至少一个文字模型**): +配置 `.env.local`(至少一个文字模型): ```bash -VITE_ZHIPU_API_KEY=your_key_here # 智谱 GLM-5(推荐) -VITE_ANTHROPIC_API_KEY=your_key # Claude 3.5(备选) -VITE_MINIMAX_API_KEY=your_key # MiniMax M2.5 -VITE_SEEDDREAM_API_KEY=your_key # 字节 Seedream 5.0(图像,可选) -VITE_KLING_API_KEY=your_key # 快手 Kling 1.6(图像/视频,可选) +VITE_ZHIPU_API_KEY=your_key # 智谱 GLM-5(推荐) +VITE_ANTHROPIC_API_KEY=your_key # Claude 3.5(备选) +VITE_MINIMAX_API_KEY=your_key # MiniMax M2.5 +VITE_SEEDDREAM_API_KEY=your_key # Seedream 5.0(图像) ``` -访问 `http://localhost:1420` 即可看到开发版 UI。 - -完整指引 → [快速开始 (5 分钟)](./docs/getting-started/quick-start.md) - --- -## 🏗️ 架构 +## 技术栈 -``` -┌──────────────────────────────────────────────────────────────┐ -│ Frontend (Web UI — React 18 + TypeScript 5 + Vite 6) │ -│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────┐ │ -│ │ 15 features │ │ 5 global │ │ shadcn/ui (Radix │ │ -│ │ (DDD 风格) │ │ Zustand │ │ + Tailwind v4) │ │ -│ └─────────────┘ └──────────────┘ └───────────────────┘ │ -└──────────────────────┬───────────────────────────────────────┘ - │ tauri::invoke() IPC -┌──────────────────────┴───────────────────────────────────────┐ -│ Desktop Container (Tauri 2.1 — Rust) │ -│ ┌────── src-tauri/commands/ (22) ──┐ ┌─── src-tauri/ ───┐ │ -│ │ video · app · file · shortcuts │ │ services/ │ │ -│ │ 22 Tauri Commands 按域路由 │ │ models/ · utils/ │ │ -│ └──────────────────────────────────┘ └────────────────────┘ │ -│ ┌────── 10 步 Pipeline (Autonomous) ──────────────────────┐ │ -│ │ step1-script → step2-storyboard → step3-material → │ │ -│ │ step4-voice → step5-keyframe → ...→ step10-export │ │ -│ └────────────────────────────────────────────────────────┘ │ -│ ┌────── Autonomous Layer ───────────────────────────────┐ │ -│ │ AutoPipelineEngine · QualityGate · SelfReviewLoop · │ │ -│ │ Checkpoint (30s auto-save) │ │ -│ └────────────────────────────────────────────────────────┘ │ -└──────────────────────────────────────────────────────────────┘ - │ - ▼ -┌──────────────────────────────────────────────────────────────┐ -│ External AI Providers (via HTTPS · ProviderRegistry) │ -│ 智谱 GLM-5 · MiniMax M2.5 · 月之暗面 Kimi · 字节 Seedream 5.0│ -│ 快手 Kling · 生数 Vidu · 阿里 CosyVoice · 微软 Edge TTS │ -└──────────────────────────────────────────────────────────────┘ -``` - -详见 [架构设计 v2.2.3](./docs/developer-guide/architecture.md)。 +| 类别 | 技术 | +| -------- | ---------------------------------------------- | +| 前端 | React 18 · TypeScript 5 · Vite 6 · Tailwind v4 | +| UI | shadcn/ui (Radix UI) | +| 桌面端 | Tauri 2.1 · Rust 1.80 · FFmpeg | +| 状态管理 | Zustand | +| AI | ProviderRegistry(Strategy + Fallback) | +| 测试 | Jest 30 · React Testing Library | --- -## 📂 项目结构 +## 架构概览 ``` -frame-fab/ -├── src/ # 前端 UI 层(React + Tauri API 包装) -│ ├── features/ # 15 个 Feature 模块(DDD 风格) -│ ├── shared/ # 跨域共享(components/hooks/stores/utils/constants) -│ ├── core/ # 核心服务(7 大领域 + pipeline + autonomous) -│ └── pages/ # 路由级页面 -├── packages/ # Monorepo 子包 -│ ├── core/ # 领域核心(pipeline/autonomous/types) -│ └── common/ # 基础类型/工具(无副作用) -├── src-tauri/ # Tauri 桌面端(Rust 第一公民) -│ ├── src/commands/ # 22 个 Tauri Commands(按域路由) -│ ├── src/services/ # FFmpeg/视频/配置业务逻辑 -│ ├── src/utils/ # 路径验证/ID 生成/FFPS 解析 -│ └── src/models/ # 领域模型(app_settings/video_metadata/shortcut) -├── docs/ # VitePress 文档站 -│ ├── BRAND_GUIDELINES.md # 品牌设计规范(Logo/色彩/字体/资产清单) -│ ├── getting-started/ # 5 分钟启动/配置 -│ ├── user-guide/ # 9 个用户手册(双模式/全流程) -│ ├── api/ # 7 大服务 API 参考 -│ ├── developer-guide/ # 9 个架构/项目结构/服务 -│ ├── deployment/ # 构建/环境/Docker -│ └── performance/ # 性能基准(v2.2.3 当前 + v2.2.0 历史) -├── public/ # Vite 静态资源(favicon/og-image) -├── assets/ # Logo 全家桶(SVG + PNG + ICO) -└── scripts/ # 构建/资产渲染脚本 +┌────────────────────────────────────────────┐ +│ Frontend (React 18 + TypeScript) │ +│ features/ · shared/ · core/ · pages/ │ +└──────────────────┬─────────────────────────┘ + │ IPC (tauri::invoke) +┌──────────────────┴─────────────────────────┐ +│ Tauri 2.1 (Rust) │ +│ commands/ · services/ · models/ · utils/ │ +│ Pipeline Engine · QualityGate · Checkpoint│ +└──────────────────┬─────────────────────────┘ + │ HTTPS +┌──────────────────┴─────────────────────────┐ +│ AI Providers │ +│ GLM-5 · Kimi · Seedream · Kling · TTS │ +└────────────────────────────────────────────┘ ``` ---- - -## 🛠️ 技术栈 - -| 类别 | 技术 | -| -------- | --------------------------------------------------------------------- | -| 前端框架 | React 18 · TypeScript 5 · Vite 6 | -| UI 组件 | shadcn/ui (Radix UI + Tailwind v4) | -| 状态管理 | Zustand(5 个全局 Store + feature 级 Store) | -| 桌面端 | Tauri 2.1 + Rust 1.80 | -| 动画 | Framer Motion | -| 国际化 | i18next | -| 测试 | Jest 30 · React Testing Library · 80+ suites | -| CI/CD | GitHub Actions(lint + typecheck + test + e2e + build + docs deploy) | - ---- - -## 🤖 支持的 AI 模型 - -| 模态 | 模型 | -| ------------ | -------------------------------------------------------------------------------------------------------------- | -| **文字生成** | GLM-5(智谱)· M2.5(MiniMax)· Kimi K2.5(月之暗面)· Claude 3.5 · Doubao 2.0(字节)· Qwen 2.5(阿里)· ERNIE 4.0 | -| **图像生成** | Seedream 5.0(字节,推荐)· Kling 1.6(快手)· Vidu Q3(生数) | -| **视频生成** | Seedance 2.0(字节)· 配合 Image-to-Video 工作流 | -| **语音合成** | Edge TTS(免费默认)· CosyVoice 2.0(阿里)· 百度 TTS | - -详见 [配置 AI API Key](./docs/getting-started/configuration.md)。 - ---- - -## 📖 文档导航 - -### 🆕 上手 - -| 文档 | 说明 | -|------|------| -| [快速开始 (5 分钟)](./docs/getting-started/quick-start.md) | 立即体验 | -| [安装指南](./docs/getting-started/installation.md) | 桌面端/源码安装 | -| [配置说明](./docs/getting-started/configuration.md) | AI API Key 配置 + 降级链 | - -### 🎬 创作者 - -| 文档 | 说明 | -|------|------| -| [工作流概览](./docs/user-guide/workflow-overview.md) | 10 步流水线 + 双模式 | -| [Autonomous 模式](./docs/user-guide/autonomous-mode.md) | 零参与一键成片 | -| [Manual 模式](./docs/user-guide/manual-mode.md) | 逐步审批精细控制 | -| [脚本生成](./docs/user-guide/script-generation.md) / [角色设计](./docs/user-guide/character-design.md) / [分镜设计](./docs/user-guide/storyboard-design.md) / [渲染导出](./docs/user-guide/rendering-export.md) | 全流程指南 | - -### ⚙️ 开发者 - -| 文档 | 说明 | -|------|------| -| [架构设计](./docs/developer-guide/architecture.md) | 系统整体架构 | -| [项目结构](./docs/developer-guide/project-structure.md) | 目录说明 | -| [模块系统](./docs/developer-guide/module-system.md) | DDD 分层 | -| [服务清单](./docs/developer-guide/services.md) | 7 大核心服务 | -| [Pipeline 引擎](./docs/developer-guide/pipeline-api.md) | 10 步细节 | -| [AI Providers](./docs/developer-guide/ai-providers.md) | ProviderRegistry + Fallback | -| [平台适配层](./docs/developer-guide/platform-layer.md) | Web/Desktop 抽象 | -| [Autonomous API](./docs/developer-guide/autonomous-api.md) | 自主引擎 API | - -### 🔌 API 参考 - -| 文档 | 说明 | -|------|------| -| [API 概述](./docs/api/overview.md) | 7 大服务全景 | -| [AI 服务](./docs/api/ai-service.md) / [图像生成](./docs/api/image-generation.md) / [TTS 服务](./docs/api/tts-service.md) / [流水线](./docs/api/pipeline-service.md) / [字幕服务](./docs/api/subtitle-service.md) | 详细 API | - -### 📦 运维 / 品牌 / 性能 - -| 文档 | 说明 | -|------|------| -| [构建与发布](./docs/deployment/build.md) | Tauri 三端构建 + 自动更新 | -| [环境变量](./docs/deployment/environment.md) | `.env.local` 配置 | -| [品牌设计指南](./docs/BRAND_GUIDELINES.md) | Logo / 色彩 / 字体 / 资产清单 | -| [v2.2.3 性能基准](./docs/performance/benchmark-v2.2.3.md) | 当前版本实测数据 | - ---- - -## 🗺️ 路线图 - -- [x] **v2.0**:基础七步工作流(Manual 模式) -- [x] **v2.1**:8-Phase 架构重构 + Rust 模块化 + 服务重组 + 5 个 Store 收敛 -- [x] **v2.2**:仓库改名 frame-fab · 描述中文化 · 自主流水线正式化 · ADR 决策记录 · 性能基准 -- [x] **v2.3**:manga-pipeline 测试提速(11s → 4s)· 44 个大文件拆分重构(195+ 子模块) -- [x] **v2.4**:性能优化(terser → esbuild,30.1s → 14.6s)· 沙箱化与安全强化(Tauri Capability 11 个 permission 移除 + CSP 收紧 + 3 套安全测试) -- [x] **v2.2.3**:代码审查(ESLint 0 + Zustand 5 slice 类型收窄 + 清 12 处误导性 TODO)+ 死代码清理(-282 行跨 14 文件)+ 品牌视觉 v2.2.3 patch -- [ ] **v3.4**:多模态融合(音乐 AI / 音效 AI / 视频背景音乐智能匹配)+ 移动端预览 App -- [ ] **v4.0**:全自动漫剧工坊(Agent 化 / 风格迁移 / 国际化 2.0 / 数据看板) - -完整路线图 → [ROADMAP.md](./ROADMAP.md) +详见 [架构设计](https://agions.github.io/story-weaver/developer-guide/architecture)。 --- -## 🤝 贡献 - -欢迎 PR 和 Issue!开发流程: +## 文档 -1. Fork 仓库 -2. 创建特性分支:`git checkout -b feat/your-feature` -3. 提交代码:`git commit -m "feat(scope): description"` -4. 推送分支:`git push origin feat/your-feature` -5. 创建 PR,CI 全绿后合并 - -### Conventional Commits - -```bash -feat: 新功能 -fix: Bug 修复 -docs: 文档变更 -style: 代码格式化(无逻辑变更) -refactor: 重构 -perf: 性能改进 -test: 测试 -chore: 构建/工具链变更 -``` - -完整开发规范见 [`CONTRIBUTING.md`](./CONTRIBUTING.md)。 +| 入口 | 适合谁 | +| ------------------------------------------------------------------------------ | ------------------ | +| [快速开始](https://agions.github.io/story-weaver/getting-started/quick-start) | 第一次使用 | +| [用户手册](https://agions.github.io/story-weaver/user-guide/) | 创作者(双模式) | +| [API 参考](https://agions.github.io/story-weaver/api/overview) | 开发者(7 大服务) | +| [架构设计](https://agions.github.io/story-weaver/developer-guide/architecture) | 架构师 | --- -## 🙏 致谢 +## 贡献 -- [Tauri](https://tauri.app) — Rust 桌面容器 -- [shadcn/ui](https://ui.shadcn.com) — 组件设计系统 -- [Zustand](https://github.com/pmndrs/zustand) — 状态管理 -- [FFmpeg](https://ffmpeg.org) — 视频处理 -- [VitePress](https://vitepress.dev) — 文档站 -- 智谱 / MiniMax / 月之暗面 / 字节 / 阿里 / 百度 — AI 模型支持 +欢迎 PR!Fork → 分支 → 提交 → PR。完整规范见 [`CONTRIBUTING.md`](./CONTRIBUTING.md)。 ---- - -## 📜 许可证 +## 许可证 MIT License · © 2024-2026 [Agions](https://github.com/Agions) - -如果你觉得 frame-fab 有帮助,请给我们一个 ⭐ diff --git a/SUPPORT.md b/SUPPORT.md index d7badd19..8b84ec5e 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,6 +1,6 @@ # Support -> frame-fab 用户支持渠道与资源 +> Story Weaver 用户支持渠道与资源 ## 快速获取帮助 @@ -8,7 +8,7 @@ | 资源 | 链接 | | --------------------- | ---------------------------------------------------------------------------- | -| **在线文档站** | [agions.github.io/frame-fab](https://agions.github.io/frame-fab/) | +| **在线文档站** | [agions.github.io/story-weaver](https://agions.github.io/story-weaver/) | | **快速开始 (5 分钟)** | [/getting-started/quick-start.md](./docs/getting-started/quick-start.md) | | **安装指南** | [/getting-started/installation.md](./docs/getting-started/installation.md) | | **配置说明** | [/getting-started/configuration.md](./docs/getting-started/configuration.md) | @@ -25,11 +25,11 @@ ### 🐛 报告 Bug -请在 [GitHub Issues](https://github.com/Agions/frame-fab/issues/new) 提交,包含: +请在 [GitHub Issues](https://github.com/Agions/story-weaver/issues/new) 提交,包含: 1. **环境信息**: - 操作系统 + 版本(macOS 14.5 / Windows 11 / Ubuntu 24.04) - - frame-fab 版本(v3.0.0) + - Story Weaver 版本(v3.0.0) - Tauri 版本(v2.1) 2. **复现步骤**: @@ -45,11 +45,11 @@ 4. **截图/录屏**(强烈推荐) -5. **日志**(`~/.frame-fab/logs/` 目录下) +5. **日志**(`~/.story-weaver/logs/` 目录下) ### 💡 功能建议 -在 [GitHub Discussions](https://github.com/Agions/frame-fab/discussions) 创建 `Ideas` 类型话题,说明: +在 [GitHub Discussions](https://github.com/Agions/story-weaver/discussions) 创建 `Ideas` 类型话题,说明: - 你想解决的问题 - 期望的工作流 @@ -57,12 +57,12 @@ ## 商业支持 -frame-fab 是 MIT 开源项目,**不提供官方商业支持喵**。 +Story Weaver 是 MIT 开源项目,**不提供官方商业支持喵**。 如需定制开发、企业部署、专属功能,可通过以下渠道联系社区认证开发者: -- [GitHub Discussions · Service](https://github.com/Agions/frame-fab/discussions) -- 邮件:business@frame-fab.dev(社区转发,非官方) +- [GitHub Discussions · Service](https://github.com/Agions/story-weaver/discussions) +- 邮件:business@story-weaver.dev(社区转发,非官方) ## 贡献代码 @@ -70,9 +70,9 @@ frame-fab 是 MIT 开源项目,**不提供官方商业支持喵**。 ## 致谢 -frame-fab 由以下项目启发 / 使用: +Story Weaver 由以下项目启发 / 使用: - [ComfyUI](https://github.com/comfyanonymous/ComfyUI) — 节点化工作流理念 - [Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) — 创作者工具设计 - [剪映专业版](https://www.capcut.cn/) — 视频编辑交互模式 -- 所有 [贡献者](https://github.com/Agions/frame-fab/graphs/contributors) ❤️ +- 所有 [贡献者](https://github.com/Agions/story-weaver/graphs/contributors) ❤️ diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 70850b69..800492c8 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -1,13 +1,7 @@ import { defineConfig } from 'vitepress' -/** - * frame-fab v2.2.3 VitePress Config - * - * Default appearance: light. Dark theme is opt-in via nav toggle. - * Base path: /frame-fab/ (GitHub Pages repo, not user/org site). - */ export default defineConfig({ - title: 'frame-fab', + title: 'Story Weaver', description: 'AI 漫剧创作平台 — 输入一本小说,AI 自动把它拍成一部漫剧', srcDir: '.', srcExclude: ['plans/**', 'ui-redesign/**', '**/node_modules/**'], @@ -15,62 +9,50 @@ export default defineConfig({ appearance: 'light', cleanUrls: true, ignoreDeadLinks: true, - base: '/frame-fab/', + base: '/story-weaver/', - // v3.1: Markdown-it options for better link handling markdown: { - lineNumbers: true, // Enable line numbers in code blocks (Shiki) + lineNumbers: true, theme: { light: 'github-light', dark: 'github-dark', }, - // NOTE: markdown-it-anchor 4.x 用 permalink: false 关闭自动 anchor link, - // v2.2.3 已不需要 permalinkSymbol (旧 API) anchor: false, toc: { - level: [2, 3, 4], // Include h4 in TOC + level: [2, 3, 4], permalink: false, }, }, head: [ - // Favicon (multi-size for cross-platform) ['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }], ['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' }], ['link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' }], ['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/favicon-256x256.png' }], - - // Theme color (mobile browser chrome) ['meta', { name: 'theme-color', content: '#0B0E2C' }], ['meta', { name: 'color-scheme', content: 'light dark' }], - - // Open Graph (Facebook, LinkedIn, Discord, Slack) ['meta', { property: 'og:type', content: 'website' }], - ['meta', { property: 'og:title', content: 'frame-fab - AI 漫剧创作平台' }], + ['meta', { property: 'og:title', content: 'Story Weaver - AI 漫剧创作平台' }], ['meta', { property: 'og:description', content: '基于 Tauri 2.1 桌面端 + 多模型 AI 编排的端到端 AI 漫剧创作工作台' }], ['meta', { property: 'og:image', content: '/og-image.png' }], ['meta', { property: 'og:image:width', content: '1280' }], ['meta', { property: 'og:image:height', content: '640' }], - ['meta', { property: 'og:image:alt', content: 'frame-fab — AI 漫剧创作平台' }], + ['meta', { property: 'og:image:alt', content: 'Story Weaver — AI 漫剧创作平台' }], ['meta', { property: 'og:locale', content: 'zh_CN' }], - ['meta', { property: 'og:site_name', content: 'frame-fab' }], - - // Twitter Card + ['meta', { property: 'og:site_name', content: 'Story Weaver' }], ['meta', { name: 'twitter:card', content: 'summary_large_image' }], - ['meta', { name: 'twitter:title', content: 'frame-fab - AI 漫剧创作平台' }], + ['meta', { name: 'twitter:title', content: 'Story Weaver - AI 漫剧创作平台' }], ['meta', { name: 'twitter:description', content: '输入一本小说,AI 自动把它拍成一部漫剧。基于 Tauri 2.1 桌面端 + 多模型 AI 编排。' }], ['meta', { name: 'twitter:image', content: '/og-image.png' }], ['meta', { name: 'twitter:creator', content: '@Agions' }], - - // SEO - ['meta', { name: 'description', content: 'frame-fab - AI 漫剧创作平台。基于 Tauri 2.1 桌面端,集成多模型 AI 实现从小说/剧本到成片的端到端自动化。' }], - ['meta', { name: 'keywords', content: 'frame-fab, AI漫剧, 漫剧创作, Tauri, 桌面应用, 多模型AI, 分镜设计, 角色一致性, TTS, FFmpeg' }], + ['meta', { name: 'description', content: 'Story Weaver - AI 漫剧创作平台。基于 Tauri 2.1 桌面端,集成多模型 AI 实现从小说/剧本到成片的端到端自动化。' }], + ['meta', { name: 'keywords', content: 'story-weaver, AI漫剧, 漫剧创作, Tauri, 桌面应用, 多模型AI, 分镜设计, 角色一致性, TTS, FFmpeg' }], ['meta', { name: 'author', content: 'Agions' }], ], themeConfig: { - logo: { src: '/logo.svg', alt: 'frame-fab' }, - siteTitle: 'frame-fab', + logo: { src: '/logo.svg', alt: 'Story Weaver' }, + siteTitle: 'Story Weaver', nav: [ { text: '首页', link: '/' }, @@ -81,9 +63,9 @@ export default defineConfig({ { text: '更多', items: [ - { text: 'GitHub', link: 'https://github.com/Agions/frame-fab' }, - { text: '下载桌面端', link: 'https://github.com/Agions/frame-fab/releases' }, - { text: '报告问题', link: 'https://github.com/Agions/frame-fab/issues/new' }, + { text: 'GitHub', link: 'https://github.com/Agions/story-weaver' }, + { text: '下载桌面端', link: 'https://github.com/Agions/story-weaver/releases' }, + { text: '报告问题', link: 'https://github.com/Agions/story-weaver/issues/new' }, ], }, ], @@ -168,29 +150,18 @@ export default defineConfig({ }, socialLinks: [ - { icon: 'github', link: 'https://github.com/Agions/frame-fab' }, + { icon: 'github', link: 'https://github.com/Agions/story-weaver' }, ], footer: { message: '基于 MIT 协议开源 · 由 Agions & 社区维护', - copyright: `© 2024-${new Date().getFullYear()} frame-fab`, + copyright: `© 2024-${new Date().getFullYear()} Story Weaver`, }, - outline: { - level: [2, 3], - label: '本页大纲', - }, - - docFooter: { - prev: '上一篇', - next: '下一篇', - }, - - editLink: { - pattern: 'https://github.com/Agions/frame-fab/edit/main/docs/:path', - text: '在 GitHub 上编辑此页', - }, + outline: { level: [2, 3], label: '本页目录' }, + docFooter: { prev: '上一篇', next: '下一篇' }, - lastUpdatedText: '最后更新', + search: { provider: 'local', options: {} }, + lastUpdated: { text: '最后更新于' }, }, }) diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 1558f5d1..e6c25624 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -2,7 +2,7 @@ import DefaultTheme from 'vitepress/theme'; import './style.css'; /** - * frame-fab v2.2.3 VitePress Theme + * Story Weaver v2.2.3 VitePress Theme * * Strategy: minimal custom components, max CSS-driven design. * Why: scene-fab experience showed that markdown containers and Vue SFCs diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css index 10c96483..678d57bc 100644 --- a/docs/.vitepress/theme/style.css +++ b/docs/.vitepress/theme/style.css @@ -1,5 +1,5 @@ /** - * frame-fab v3.0 VitePress Theme + * Story Weaver v3.0 VitePress Theme * * Design system: Indigo (#6366F1) → Purple (#A855F7) → Pink (#EC4899) * Accent: warm orange (#FB923C) for AI pulse @@ -612,8 +612,12 @@ html { scroll-padding-top: 80px; /* 适配 fixed nav */ } @media (prefers-reduced-motion: reduce) { - html { scroll-behavior: auto; } - *, *::before, *::after { + html { + scroll-behavior: auto; + } + *, + *::before, + *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } @@ -703,7 +707,8 @@ html { } /* ── List spacing ── */ -.vp-doc ul, .vp-doc ol { +.vp-doc ul, +.vp-doc ol { padding-left: 1.5em; line-height: 1.7; } @@ -715,8 +720,8 @@ html { } /* ── External link icon ── */ -.vp-doc a[href^="http"]:not(.no-icon)::after { - content: " ↗"; +.vp-doc a[href^='http']:not(.no-icon)::after { + content: ' ↗'; font-size: 0.8em; opacity: 0.6; display: inline-block; @@ -763,7 +768,9 @@ html { cursor: pointer; font-size: 20px; box-shadow: var(--vp-shadow-3); - transition: transform 0.2s ease, opacity 0.2s ease; + transition: + transform 0.2s ease, + opacity 0.2s ease; opacity: 0; transform: translateY(20px); pointer-events: none; @@ -796,13 +803,21 @@ html { ═══════════════════════════════════════════════════════════ */ @media print { /* Hide chrome */ - .VPSidebar, .VPNavBar, .VPDocFooter, .VPDocAside, - .vp-cta, .vp-back-to-top, .vp-skip-link, - .VPHero .actions, button, .VPNavBarMenu { + .VPSidebar, + .VPNavBar, + .VPDocFooter, + .VPDocAside, + .vp-cta, + .vp-back-to-top, + .vp-skip-link, + .VPHero .actions, + button, + .VPNavBarMenu { display: none !important; } /* Force light theme + high contrast */ - :root, .dark { + :root, + .dark { --color-bg-base: white !important; --color-bg-card: white !important; --color-text-primary: black !important; @@ -815,25 +830,27 @@ html { size: A4; margin: 18mm 14mm; } - html, body { + html, + body { background: white !important; color: black !important; font-size: 11pt; } /* Show links' URLs */ - .vp-doc a[href^="http"]::after { - content: " (" attr(href) ")"; + .vp-doc a[href^='http']::after { + content: ' (' attr(href) ')'; font-size: 9pt; color: #555; word-break: break-all; } - .vp-doc a[href^="/"]::after { - content: " (" attr(href) ")"; + .vp-doc a[href^='/']::after { + content: ' (' attr(href) ')'; font-size: 9pt; color: #555; } /* Code blocks: keep code, drop scroll */ - .vp-doc pre, .vp-doc div[class*='language-'] { + .vp-doc pre, + .vp-doc div[class*='language-'] { background: #f4f4f4 !important; color: black !important; border: 1px solid #ccc !important; @@ -847,25 +864,35 @@ html { border: 1px solid #ccc !important; } /* Tables: avoid split */ - table, tr, td, th { + table, + tr, + td, + th { page-break-inside: avoid !important; } - thead { display: table-header-group; } + thead { + display: table-header-group; + } /* Headings: keep with content */ - h1, h2, h3, h4 { + h1, + h2, + h3, + h4 { page-break-after: avoid; color: black !important; } /* Cards: no shadows */ .vp-doc blockquote, - .vp-why-card, .vp-tech-card, .vp-stat { + .vp-why-card, + .vp-tech-card, + .vp-stat { box-shadow: none !important; border: 1px solid #ccc !important; background: white !important; } /* Show URL footer */ .vp-doc::after { - content: "frame-fab docs · /frame-fab/"; + content: 'Story Weaver docs · /story-weaver/'; display: block; margin-top: 24pt; padding-top: 8pt; @@ -898,8 +925,9 @@ html { /* Mobile (max-width: 640px): single column, larger touch targets */ @media (max-width: 640px) { :root { - --vp-font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', - 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + --vp-font-family-base: + -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', + 'Microsoft YaHei', sans-serif; } .vp-doc { font-size: 15px; @@ -917,7 +945,7 @@ html { } .vp-compare-header > div:nth-child(4), .vp-compare-row > div:nth-child(4) { - display: none; /* Hide 4th col on mobile */ + display: none; /* Hide 4th col on mobile */ } .vp-step { grid-template-columns: 40px 1fr; diff --git a/docs/api/image-generation.md b/docs/api/image-generation.md index 2c5fe7e0..a1613b22 100644 --- a/docs/api/image-generation.md +++ b/docs/api/image-generation.md @@ -36,29 +36,29 @@ async generateImage( **参数(`ImageGenerationOptions`)** -| 字段 | 类型 | 必填 | 说明 | -|------|------|------|------| -| `prompt` | `string` | ✅ | 图像描述词 | -| `negativePrompt` | `string` | 否 | 反向提示词 | -| `model` | `ImageModel` | ✅ | 模型 ID | -| `resolution` | `'1:1' \| '16:9' \| '9:16' \| '4:3' \| '3:4'` | 否 | 宽高比 | -| `width` | `number` | 否 | 自定义宽(像素) | -| `height` | `number` | 否 | 自定义高(像素) | -| `style` | `string` | 否 | 风格描述('cinematic'/'anime'/'realistic') | -| `seed` | `number` | 否 | 随机种子(保证可复现) | -| `referenceImages` | `string[]` | 否 | 参考图 URL 数组(用于一致性) | -| `characterProfile` | `CharacterProfile` | 否 | 角色设定卡(自动注入 prompt) | +| 字段 | 类型 | 必填 | 说明 | +| ------------------ | --------------------------------------------- | ---- | ------------------------------------------- | +| `prompt` | `string` | ✅ | 图像描述词 | +| `negativePrompt` | `string` | 否 | 反向提示词 | +| `model` | `ImageModel` | ✅ | 模型 ID | +| `resolution` | `'1:1' \| '16:9' \| '9:16' \| '4:3' \| '3:4'` | 否 | 宽高比 | +| `width` | `number` | 否 | 自定义宽(像素) | +| `height` | `number` | 否 | 自定义高(像素) | +| `style` | `string` | 否 | 风格描述('cinematic'/'anime'/'realistic') | +| `seed` | `number` | 否 | 随机种子(保证可复现) | +| `referenceImages` | `string[]` | 否 | 参考图 URL 数组(用于一致性) | +| `characterProfile` | `CharacterProfile` | 否 | 角色设定卡(自动注入 prompt) | **返回(`ImageGenerationResult`)** -| 字段 | 类型 | 说明 | -|------|------|------| -| `imageUrl` | `string` | 生成图像 URL(本地缓存或 base64) | -| `width` / `height` | `number` | 实际尺寸 | -| `model` | `string` | 命中的模型 | -| `seed` | `number` | 实际种子 | -| `latencyMs` | `number` | 耗时 | -| `usage` | `TokenUsage` | 计费信息 | +| 字段 | 类型 | 说明 | +| ------------------ | ------------ | --------------------------------- | +| `imageUrl` | `string` | 生成图像 URL(本地缓存或 base64) | +| `width` / `height` | `number` | 实际尺寸 | +| `model` | `string` | 命中的模型 | +| `seed` | `number` | 实际种子 | +| `latencyMs` | `number` | 耗时 | +| `usage` | `TokenUsage` | 计费信息 | **示例:基础生成** @@ -100,11 +100,11 @@ async generateVideo( 参数与 `generateImage` 类似,额外字段: -| 字段 | 类型 | 说明 | -|------|------|------| -| `duration` | `5 \| 10` | 视频时长(秒) | -| `motionIntensity` | `0-1` | 运镜强度 | -| `imageUrl` | `string` | 首帧参考图 | +| 字段 | 类型 | 说明 | +| ----------------- | --------- | -------------- | +| `duration` | `5 \| 10` | 视频时长(秒) | +| `motionIntensity` | `0-1` | 运镜强度 | +| `imageUrl` | `string` | 首帧参考图 | ### generateBatch() @@ -120,13 +120,13 @@ async generateBatch( ## 支持的 Provider -| Provider | 模型 | 类型 | 适用场景 | -|----------|------|------|----------| -| 字节跳动 `seedream` | Seedream 5.0 | 图像 | 动画/插画,**首选** | -| 快手 `kling` | Kling 1.6 | 图像+视频 | 视频生成首选 | -| 生数科技 `vidu` | Vidu Q3 / Vidu 2.0 | 图像+视频 | 高一致性 | -| 字节跳动 `seedance` | Seedance 2.0 | 视频 | 短运镜 | -| Stability `stability` | SDXL | 图像 | 降级方案 | +| Provider | 模型 | 类型 | 适用场景 | +| --------------------- | ------------------ | --------- | ------------------- | +| 字节跳动 `seedream` | Seedream 5.0 | 图像 | 动画/插画,**首选** | +| 快手 `kling` | Kling 1.6 | 图像+视频 | 视频生成首选 | +| 生数科技 `vidu` | Vidu Q3 / Vidu 2.0 | 图像+视频 | 高一致性 | +| 字节跳动 `seedance` | Seedance 2.0 | 视频 | 短运镜 | +| Stability `stability` | SDXL | 图像 | 降级方案 | **降级链**(按价格/质量平衡): @@ -138,7 +138,7 @@ Seedream 5.0 → Kling 1.6 → Vidu 2.0 → Stability SDXL ## 角色一致性机制 -frame-fab 在三层保障角色一致性: +Story Weaver 在三层保障角色一致性: ``` ┌────────────────────────────────────┐ diff --git a/docs/api/index.md b/docs/api/index.md index e794338d..15c04ed2 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -1,26 +1,26 @@ --- title: API 文档 -description: frame-fab 核心服务 API 索引:7 大本地服务接口、AI Provider 适配层、类型系统 +description: Story Weaver 核心服务 API 索引:7 大本地服务接口、AI Provider 适配层、类型系统 category: api version: '>=3.0' --- # API 文档 -> frame-fab v2.2.3 通过**本地服务接口**暴露能力。本章节列出了 7 大核心服务的 API 参考。 +> Story Weaver v2.2.3 通过**本地服务接口**暴露能力。本章节列出了 7 大核心服务的 API 参考。 --- ## 一、服务清单 -| 文档 | 服务 | 适用场景 | -|------|------|---------| -| [API 概述](./overview.md) | 7 大服务全景 | **首次必读** | -| [AI 服务](./ai-service.md) | `aiService` | 文本生成、对话、分析、流式 | -| [图像生成](./image-generation.md) | `imageGenerationService` | 图像/视频生成、角色一致性 | -| [TTS 服务](./tts-service.md) | `ttsService` | 语音合成、唇形同步元数据 | -| [流水线](./pipeline-service.md) | `pipelineService` | 10 步端到端编排 | -| [字幕服务](./subtitle-service.md) | `subtitleService` | 字幕生成与多格式导出 | +| 文档 | 服务 | 适用场景 | +| --------------------------------- | ------------------------ | -------------------------- | +| [API 概述](./overview.md) | 7 大服务全景 | **首次必读** | +| [AI 服务](./ai-service.md) | `aiService` | 文本生成、对话、分析、流式 | +| [图像生成](./image-generation.md) | `imageGenerationService` | 图像/视频生成、角色一致性 | +| [TTS 服务](./tts-service.md) | `ttsService` | 语音合成、唇形同步元数据 | +| [流水线](./pipeline-service.md) | `pipelineService` | 10 步端到端编排 | +| [字幕服务](./subtitle-service.md) | `subtitleService` | 字幕生成与多格式导出 | --- @@ -44,7 +44,7 @@ import { ## 三、设计原则 -1. **无远端服务端** — frame-fab 是 Tauri 桌面应用,所有调用在进程内完成 +1. **无远端服务端** — Story Weaver 是 Tauri 桌面应用,所有调用在进程内完成 2. **单例模式** — 每个服务是单例,**不要 new** 出新实例 3. **Fallback 链** — 默认自动降级到可用 Provider,可自定义 4. **类型完备** — 全部 TypeScript 类型,IDE 全程提示 @@ -55,23 +55,23 @@ import { ## 四、版本兼容 -| 框架版本 | API 风格 | 状态 | -|---------|---------|------| -| v2.2.3+ | 7 大服务单例 | ✅ **当前推荐** | +| 框架版本 | API 风格 | 状态 | +| --------- | ----------------------- | -------------------- | +| v2.2.3+ | 7 大服务单例 | ✅ **当前推荐** | | v2.4-v2.x | `aiService.text()` 链式 | ⚠️ 仍兼容,v4.0 移除 | -| v2.3- | 旧模块导入 | ❌ 不再维护 | +| v2.3- | 旧模块导入 | ❌ 不再维护 | --- ## 五、下一步 -| 你的需求 | 推荐阅读 | -|---------|---------| -| 看调用约定 | [API 概述](./overview.md) | -| 文本生成 | [AI 服务](./ai-service.md) | -| 出图/出视频 | [图像生成](./image-generation.md) | -| 配音 | [TTS 服务](./tts-service.md) | -| 端到端跑 | [流水线](./pipeline-service.md) | -| 字幕 | [字幕服务](./subtitle-service.md) | -| 了解实现 | [架构设计](../developer-guide/architecture.md) | -| 上手 | [快速开始](../getting-started/quick-start.md) | +| 你的需求 | 推荐阅读 | +| ----------- | ---------------------------------------------- | +| 看调用约定 | [API 概述](./overview.md) | +| 文本生成 | [AI 服务](./ai-service.md) | +| 出图/出视频 | [图像生成](./image-generation.md) | +| 配音 | [TTS 服务](./tts-service.md) | +| 端到端跑 | [流水线](./pipeline-service.md) | +| 字幕 | [字幕服务](./subtitle-service.md) | +| 了解实现 | [架构设计](../developer-guide/architecture.md) | +| 上手 | [快速开始](../getting-started/quick-start.md) | diff --git a/docs/api/overview.md b/docs/api/overview.md index 13580411..c3cc9d80 100644 --- a/docs/api/overview.md +++ b/docs/api/overview.md @@ -1,30 +1,30 @@ --- title: API 概述 -description: frame-fab 7 大核心服务 API 总览:服务架构、调用约定、类型系统、版本兼容性 +description: Story Weaver 7 大核心服务 API 总览:服务架构、调用约定、类型系统、版本兼容性 category: api version: '>=3.0' --- # API 概述 -> frame-fab v2.2.3 的所有功能均通过 **本地服务接口** 暴露。 +> Story Weaver v2.2.3 的所有功能均通过 **本地服务接口** 暴露。 > **无远端服务端、无 REST API、无 token 鉴权**——所有调用在桌面应用进程内完成。 --- ## 一、核心服务 -frame-fab v2.2.3 patch 后将所有业务能力收敛为 7 个核心服务,**统一从 `@/core/services` 导入**: +Story Weaver v2.2.3 patch 后将所有业务能力收敛为 7 个核心服务,**统一从 `@/core/services` 导入**: -| 服务 | 描述 | 状态 | 文档 | -|------|------|------|------| -| `aiService` | 统一 AI 文本生成 | ✅ 稳定 | [AI 服务](./ai-service.md) | -| `imageGenerationService` | 多提供商图像/视频生成 | ✅ 稳定 | [图像生成](./image-generation.md) | -| `ttsService` | 文本转语音合成 | ✅ 稳定 | [TTS 服务](./tts-service.md) | -| `pipelineService` | 端到端流水线编排 | ✅ 稳定 | [流水线](./pipeline-service.md) | -| `storyboardService` | 分镜生命周期管理 | ✅ 稳定 | 见 [开发者指南 - 架构](../developer-guide/architecture.md) | -| `characterService` | 角色设定与一致性 | ✅ 稳定 | 见 [角色设计](../user-guide/character-design.md) | -| `subtitleService` | 字幕生成与多格式导出 | ✅ 稳定 | [字幕服务](./subtitle-service.md) | +| 服务 | 描述 | 状态 | 文档 | +| ------------------------ | --------------------- | ------- | ---------------------------------------------------------- | +| `aiService` | 统一 AI 文本生成 | ✅ 稳定 | [AI 服务](./ai-service.md) | +| `imageGenerationService` | 多提供商图像/视频生成 | ✅ 稳定 | [图像生成](./image-generation.md) | +| `ttsService` | 文本转语音合成 | ✅ 稳定 | [TTS 服务](./tts-service.md) | +| `pipelineService` | 端到端流水线编排 | ✅ 稳定 | [流水线](./pipeline-service.md) | +| `storyboardService` | 分镜生命周期管理 | ✅ 稳定 | 见 [开发者指南 - 架构](../developer-guide/architecture.md) | +| `characterService` | 角色设定与一致性 | ✅ 稳定 | 见 [角色设计](../user-guide/character-design.md) | +| `subtitleService` | 字幕生成与多格式导出 | ✅ 稳定 | [字幕服务](./subtitle-service.md) | --- @@ -54,7 +54,7 @@ import { const result = await aiService.generate('写一段独白'); // ❌ 不要 new 出新实例(破坏 ProviderRegistry 单例) -const ai = new AIService(); // 不推荐 +const ai = new AIService(); // 不推荐 ``` ### 2.3 异步与进度 @@ -72,12 +72,12 @@ pipelineService.onProgress((progress) => { 服务抛出**结构化错误**(继承自 `BaseError`): -| 错误类型 | 触发场景 | -|---------|---------| -| `AIProviderError` | AI 模型调用失败(429/500/超时) | -| `InvalidInputError` | 入参校验失败 | -| `QuotaExceededError` | 用户配额耗尽 | -| `CheckpointError` | 断点续传检查点损坏 | +| 错误类型 | 触发场景 | +| -------------------- | ------------------------------- | +| `AIProviderError` | AI 模型调用失败(429/500/超时) | +| `InvalidInputError` | 入参校验失败 | +| `QuotaExceededError` | 用户配额耗尽 | +| `CheckpointError` | 断点续传检查点损坏 | ```typescript try { @@ -169,23 +169,23 @@ registry.register({ ## 六、版本兼容性 -| 版本范围 | 服务 API | 备注 | -|---------|---------|------| -| `>= 2.4` | 旧 `aiService.text()` 链式 | 仍可用,**v4.0 移除** | -| `>= 3.0` | 7 大服务单例 | **当前推荐** | -| `>= 3.0` | 进度回调 `onProgress` | 标准订阅模式 | -| `>= 3.0` | `ProviderRegistry` 插件化 | 可运行时注册新 Provider | +| 版本范围 | 服务 API | 备注 | +| -------- | -------------------------- | ----------------------- | +| `>= 2.4` | 旧 `aiService.text()` 链式 | 仍可用,**v4.0 移除** | +| `>= 3.0` | 7 大服务单例 | **当前推荐** | +| `>= 3.0` | 进度回调 `onProgress` | 标准订阅模式 | +| `>= 3.0` | `ProviderRegistry` 插件化 | 可运行时注册新 Provider | --- ## 七、下一步 -| 目的 | 阅读 | -|------|------| -| 文本生成 | [AI 服务](./ai-service.md) | -| 图像/视频 | [图像生成](./image-generation.md) | -| 配音 | [TTS 服务](./tts-service.md) | -| 端到端 | [流水线](./pipeline-service.md) | -| 字幕 | [字幕服务](./subtitle-service.md) | -| 了解实现 | [架构设计](../developer-guide/architecture.md) | -| 跑通项目 | [快速开始](../getting-started/quick-start.md) | +| 目的 | 阅读 | +| --------- | ---------------------------------------------- | +| 文本生成 | [AI 服务](./ai-service.md) | +| 图像/视频 | [图像生成](./image-generation.md) | +| 配音 | [TTS 服务](./tts-service.md) | +| 端到端 | [流水线](./pipeline-service.md) | +| 字幕 | [字幕服务](./subtitle-service.md) | +| 了解实现 | [架构设计](../developer-guide/architecture.md) | +| 跑通项目 | [快速开始](../getting-started/quick-start.md) | diff --git a/docs/api/tts-service.md b/docs/api/tts-service.md index d581590a..e3e5cdb0 100644 --- a/docs/api/tts-service.md +++ b/docs/api/tts-service.md @@ -30,34 +30,34 @@ async synthesize(config: TTSConfig): Promise **参数(`TTSConfig`)** -| 字段 | 类型 | 必填 | 说明 | -|------|------|------|------| -| `text` | `string` | ✅ | 要转换的文本 | -| `provider` | `'edge' \| 'cosyvoice' \| 'kantts' \| 'baidu'` | 否 | Provider(默认 `edge`) | -| `voice` | `string` | ✅ | 语音 ID(如 `zh-CN-XiaoxiaoNeural`) | -| `speed` | `number` | 否 | 语速 0.5-2.0(默认 1.0) | -| `pitch` | `number` | 否 | 音调 -1.0 到 1.0 | -| `volume` | `number` | 否 | 音量 0-1 | -| `emotion` | `string` | 否 | 情感(`neutral`/`happy`/`sad`/`angry`) | -| `format` | `'mp3' \| 'wav' \| 'pcm'` | 否 | 输出格式(默认 `mp3`) | -| `sampleRate` | `24000 \| 48000` | 否 | 采样率 | +| 字段 | 类型 | 必填 | 说明 | +| ------------ | ---------------------------------------------- | ---- | --------------------------------------- | +| `text` | `string` | ✅ | 要转换的文本 | +| `provider` | `'edge' \| 'cosyvoice' \| 'kantts' \| 'baidu'` | 否 | Provider(默认 `edge`) | +| `voice` | `string` | ✅ | 语音 ID(如 `zh-CN-XiaoxiaoNeural`) | +| `speed` | `number` | 否 | 语速 0.5-2.0(默认 1.0) | +| `pitch` | `number` | 否 | 音调 -1.0 到 1.0 | +| `volume` | `number` | 否 | 音量 0-1 | +| `emotion` | `string` | 否 | 情感(`neutral`/`happy`/`sad`/`angry`) | +| `format` | `'mp3' \| 'wav' \| 'pcm'` | 否 | 输出格式(默认 `mp3`) | +| `sampleRate` | `24000 \| 48000` | 否 | 采样率 | **返回(`TTSResult`)** -| 字段 | 类型 | 说明 | -|------|------|------| -| `audioUrl` | `string` | 音频 URL(本地缓存) | -| `duration` | `number` | 实际时长(秒) | -| `provider` | `string` | 命中的 Provider | -| `voice` | `string` | 命中的语音 | -| `latencyMs` | `number` | 耗时 | -| `visemes` | `Viseme[]` | 唇形时间轴(用于唇形同步) | +| 字段 | 类型 | 说明 | +| ----------- | ---------- | -------------------------- | +| `audioUrl` | `string` | 音频 URL(本地缓存) | +| `duration` | `number` | 实际时长(秒) | +| `provider` | `string` | 命中的 Provider | +| `voice` | `string` | 命中的语音 | +| `latencyMs` | `number` | 耗时 | +| `visemes` | `Viseme[]` | 唇形时间轴(用于唇形同步) | **示例** ```typescript const result = await ttsService.synthesize({ - text: '欢迎使用 frame-fab', + text: '欢迎使用 Story Weaver', provider: 'edge', voice: 'zh-CN-XiaoxiaoNeural', speed: 1.0, @@ -89,19 +89,21 @@ getVoices(provider?: TTSProvider): TTSVoice[] ```typescript // 列出 Edge TTS 所有中文女声 -const voices = ttsService.getVoices('edge').filter(v => v.language.startsWith('zh') && v.gender === 'female'); +const voices = ttsService + .getVoices('edge') + .filter((v) => v.language.startsWith('zh') && v.gender === 'female'); ``` --- ## 支持的 Provider -| Provider | 价格 | 质量 | 音色数 | 特色 | -|----------|------|------|--------|------| -| **Edge TTS** | 🆓 免费 | ⭐⭐⭐⭐ | 200+ | 默认推荐,无 API Key | -| CosyVoice 2.0 | 💰 付费 | ⭐⭐⭐⭐⭐ | 50+ | 阿里开源,音质最佳 | -| 百度 TTS | 💰 付费 | ⭐⭐⭐ | 20+ | 老牌服务,国内稳 | -| KAN-TTS | 💰 付费 | ⭐⭐⭐ | 30+ | 字节系,情感丰富 | +| Provider | 价格 | 质量 | 音色数 | 特色 | +| ------------- | ------- | ---------- | ------ | -------------------- | +| **Edge TTS** | 🆓 免费 | ⭐⭐⭐⭐ | 200+ | 默认推荐,无 API Key | +| CosyVoice 2.0 | 💰 付费 | ⭐⭐⭐⭐⭐ | 50+ | 阿里开源,音质最佳 | +| 百度 TTS | 💰 付费 | ⭐⭐⭐ | 20+ | 老牌服务,国内稳 | +| KAN-TTS | 💰 付费 | ⭐⭐⭐ | 30+ | 字节系,情感丰富 | **默认降级链**: @@ -117,11 +119,11 @@ TTS 返回的 `visemes` 字段包含**口型时间轴**,用于驱动后续唇 ```typescript [ - { time: 0.0, viseme: 'sil' }, // 静音 - { time: 0.1, viseme: 'PP' }, // 双唇音 - { time: 0.3, viseme: 'aa' }, // 开口音 + { time: 0.0, viseme: 'sil' }, // 静音 + { time: 0.1, viseme: 'PP' }, // 双唇音 + { time: 0.3, viseme: 'aa' }, // 开口音 // ... -] +]; ``` 详见 [架构设计 - 唇形同步](../developer-guide/architecture.md)。 @@ -130,13 +132,13 @@ TTS 返回的 `visemes` 字段包含**口型时间轴**,用于驱动后续唇 ## 常用语音速查 -| 场景 | Provider | Voice ID | -|------|----------|----------| -| 中文女声·温柔 | Edge | `zh-CN-XiaoxiaoNeural` | -| 中文女声·活泼 | Edge | `zh-CN-YunxiNeural` | -| 中文男声·成熟 | Edge | `zh-CN-YunjianNeural` | -| 英文女声 | Edge | `en-US-JennyNeural` | -| 日文女声 | Edge | `ja-JP-NanamiNeural` | +| 场景 | Provider | Voice ID | +| ------------- | -------- | ---------------------- | +| 中文女声·温柔 | Edge | `zh-CN-XiaoxiaoNeural` | +| 中文女声·活泼 | Edge | `zh-CN-YunxiNeural` | +| 中文男声·成熟 | Edge | `zh-CN-YunjianNeural` | +| 英文女声 | Edge | `en-US-JennyNeural` | +| 日文女声 | Edge | `ja-JP-NanamiNeural` | --- diff --git a/docs/deployment/build.md b/docs/deployment/build.md index 40faba72..0fb12aa3 100644 --- a/docs/deployment/build.md +++ b/docs/deployment/build.md @@ -1,13 +1,13 @@ --- title: 构建与发布 -description: frame-fab Tauri 桌面应用构建命令、产物位置、自动更新配置 +description: Story Weaver Tauri 桌面应用构建命令、产物位置、自动更新配置 category: deployment version: '>=3.0' --- # 构建与发布 -> frame-fab v2.2.3 使用 **Tauri 2.1** 构建跨平台桌面应用。 +> Story Weaver v2.2.3 使用 **Tauri 2.1** 构建跨平台桌面应用。 > 本文档介绍:构建命令、产物位置、自动更新、签名配置、CI/CD。 ## 一、构建命令 @@ -39,26 +39,26 @@ pnpm tauri build --debug ## 二、构建产物位置 -| 平台 | 路径 | -|------|------| -| **macOS** | `src-tauri/target/release/bundle/macos/frame-fab.app` | -| **macOS DMG** | `src-tauri/target/release/bundle/dmg/frame-fab__.dmg` | -| **Windows** | `src-tauri/target/release/bundle/msi/frame-fab__-setup.exe` | -| **Linux** | `src-tauri/target/release/bundle/appimage/frame-fab__amd64.AppImage` | -| **Linux deb** | `src-tauri/target/release/bundle/deb/frame-fab__amd64.deb` | +| 平台 | 路径 | +| ------------- | -------------------------------------------------------------------------------- | +| **macOS** | `src-tauri/target/release/bundle/macos/Story Weaver.app` | +| **macOS DMG** | `src-tauri/target/release/bundle/dmg/Story Weaver__.dmg` | +| **Windows** | `src-tauri/target/release/bundle/msi/Story Weaver__-setup.exe` | +| **Linux** | `src-tauri/target/release/bundle/appimage/Story Weaver__amd64.AppImage` | +| **Linux deb** | `src-tauri/target/release/bundle/deb/Story Weaver__amd64.deb` | ## 三、产物大小基准 -| 平台 | 大小 | 冷启动 | -|------|------|--------| +| 平台 | 大小 | 冷启动 | +| --------------------- | ------ | ------ | | macOS (Apple Silicon) | ~26 MB | < 0.9s | -| macOS (Intel) | ~28 MB | < 1.0s | -| Windows x64 | ~28 MB | < 1.2s | -| Linux AppImage | ~24 MB | < 0.8s | +| macOS (Intel) | ~28 MB | < 1.0s | +| Windows x64 | ~28 MB | < 1.2s | +| Linux AppImage | ~24 MB | < 0.8s | ## 四、自动更新 -frame-fab 使用 **Tauri Updater** 提供自动更新: +Story Weaver 使用 **Tauri Updater** 提供自动更新: ```json // src-tauri/tauri.conf.json @@ -66,9 +66,7 @@ frame-fab 使用 **Tauri Updater** 提供自动更新: "updater": { "active": true, "dialog": true, - "endpoints": [ - "https://github.com/Agions/frame-fab/releases/latest/download/latest.json" - ] + "endpoints": ["https://github.com/Agions/story-weaver/releases/latest/download/latest.json"] } } ``` @@ -137,9 +135,11 @@ jobs: ### Q1: macOS 构建失败 `code signing required`? A: 临时禁用签名: + ```bash pnpm tauri build --no-bundle ``` + 或配置有效的 `APPLE_SIGNING_IDENTITY`。 ### Q2: Windows 构建提示缺 MSVC? @@ -149,6 +149,7 @@ A: 安装 Visual Studio Build Tools 2022(含 C++ 桌面开发)。 ### Q3: Linux 缺 webkit2gtk? A: Ubuntu/Debian: + ```bash sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev ``` @@ -156,9 +157,10 @@ sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev ### Q4: 启动崩溃? A: 查看日志: -- macOS: `~/Library/Logs/frame-fab/` -- Windows: `%APPDATA%/frame-fab/logs/` -- Linux: `~/.config/frame-fab/logs/` + +- macOS: `~/Library/Logs/Story Weaver/` +- Windows: `%APPDATA%/Story Weaver/logs/` +- Linux: `~/.config/Story Weaver/logs/` ## 八、相关文档 diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index ce757a7c..426f456e 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -1,23 +1,23 @@ --- title: Docker 开发环境 -description: frame-fab Docker 开发环境(可选):容器化前端 + 跨平台编译 +description: Story Weaver Docker 开发环境(可选):容器化前端 + 跨平台编译 category: deployment version: '>=3.0' --- # Docker 开发环境 -> ⚠️ **注意**:frame-fab v2.2.3 是 **Tauri 桌面应用**,**生产部署不需要 Docker**。 +> ⚠️ **注意**:Story Weaver v2.2.3 是 **Tauri 桌面应用**,**生产部署不需要 Docker**。 > 本文档仅介绍**开发环境容器化**(可选)。 ## 一、典型用例 -| 场景 | 是否用 Docker | -|------|--------------| -| **生产发布** | ❌ 直接打 `.dmg`/`.msi`/`.AppImage` | -| **本地开发** | ⚠️ 可选,统一环境 | -| **CI 编译** | ✅ 推荐,避免本地工具链污染 | -| **跨平台编译** | ✅ 从 macOS 编 Windows/Linux | +| 场景 | 是否用 Docker | +| -------------- | ----------------------------------- | +| **生产发布** | ❌ 直接打 `.dmg`/`.msi`/`.AppImage` | +| **本地开发** | ⚠️ 可选,统一环境 | +| **CI 编译** | ✅ 推荐,避免本地工具链污染 | +| **跨平台编译** | ✅ 从 macOS 编 Windows/Linux | ## 二、devcontainer @@ -25,7 +25,7 @@ version: '>=3.0' ```json { - "name": "frame-fab", + "name": "Story Weaver", "image": "mcr.microsoft.com/devcontainers/rust:2-bookworm", "features": { "ghcr.io/devcontainers/features/node:1": { "version": "20" } @@ -39,8 +39,8 @@ version: '>=3.0' ## 三、Docker 镜像(开发用) ```bash -docker build -t frame-fab-dev -f Dockerfile.dev . -docker run -it --rm -v $(pwd):/app -p 5173:5173 frame-fab-dev +docker build -t Story Weaver-dev -f Dockerfile.dev . +docker run -it --rm -v $(pwd):/app -p 5173:5173 Story Weaver-dev ``` ## 四、跨平台编译 @@ -63,12 +63,12 @@ docker run --rm -v $(pwd):/app \ ## 五、为什么不推荐生产用 Docker? -| 原因 | 说明 | -|------|------| +| 原因 | 说明 | +| ------------------ | ----------------------------------- | | **Tauri 桌面应用** | 需要 macOS/Windows/Linux 原生运行时 | -| **无服务端** | 没有需要容器化的后端 | -| **包体积优化** | 桌面应用直接运行比容器快 3-5 倍 | -| **FFmpeg 原生** | 容器内 FFmpeg 性能受限 | +| **无服务端** | 没有需要容器化的后端 | +| **包体积优化** | 桌面应用直接运行比容器快 3-5 倍 | +| **FFmpeg 原生** | 容器内 FFmpeg 性能受限 | ## 六、相关文档 diff --git a/docs/deployment/environment.md b/docs/deployment/environment.md index 8ed4a02e..e5140893 100644 --- a/docs/deployment/environment.md +++ b/docs/deployment/environment.md @@ -1,21 +1,21 @@ --- title: 环境变量 -description: frame-fab 环境变量配置:VITE_ 前缀、AI Provider API Key、应用配置 +description: Story Weaver 环境变量配置:VITE_ 前缀、AI Provider API Key、应用配置 category: deployment version: '>=3.0' --- # 环境变量 -> frame-fab v2.2.3 的环境变量分为两类:**构建时**(`VITE_` 前缀,暴露给前端)和 **运行时**(Tauri 注入)。 +> Story Weaver v2.2.3 的环境变量分为两类:**构建时**(`VITE_` 前缀,暴露给前端)和 **运行时**(Tauri 注入)。 > **推荐**:桌面端用户从「设置面板」配置 API Key(加密存储),开发模式用 `.env.local`。 ## 一、命名规范 -| 前缀 | 阶段 | 说明 | -|------|------|------| +| 前缀 | 阶段 | 说明 | +| ------- | ------ | ---------------------------- | | `VITE_` | 构建时 | 客户端可见,**API Key 慎用** | -| 无前缀 | 运行时 | Rust 端配置,不暴露给前端 | +| 无前缀 | 运行时 | Rust 端配置,不暴露给前端 | ## 二、AI Provider 配置 @@ -53,7 +53,7 @@ VITE_BAIDU_TTS_SECRET_KEY=xxxxxxxx ## 三、应用配置 ```bash -VITE_APP_NAME=frame-fab +VITE_APP_NAME=Story Weaver VITE_APP_VERSION=3.0.0 VITE_APP_MODE=desktop # desktop | web ``` @@ -62,7 +62,7 @@ VITE_APP_MODE=desktop # desktop | web ```bash # .env.example(提交到 Git) -VITE_APP_NAME=frame-fab +VITE_APP_NAME=Story Weaver # .env.local(**不提交**,本地开发) VITE_ZHIPU_API_KEY=your_real_key @@ -142,7 +142,7 @@ VITE_VIDU_API_KEY=k3 **生产建议**: 1. **桌面端**:使用 [Tauri Secure Storage](https://tauri.app/v1/api/js/storage) 加密存储 -2. **Web 端**:使用服务端代理(frame-fab 暂不提供) +2. **Web 端**:使用服务端代理(Story Weaver 暂不提供) 3. **密钥轮换**:定期更换 API Key ## 七、配置验证 diff --git a/docs/deployment/index.md b/docs/deployment/index.md index 2287123f..e34925b0 100644 --- a/docs/deployment/index.md +++ b/docs/deployment/index.md @@ -1,13 +1,13 @@ --- title: 部署文档 -description: frame-fab 桌面端构建与发布:macOS / Windows / Linux 三端构建、自动更新、CI/CD、环境变量 +description: Story Weaver 桌面端构建与发布:macOS / Windows / Linux 三端构建、自动更新、CI/CD、环境变量 category: deployment version: '>=3.0' --- # 部署文档 -> frame-fab v2.2.3 是 **Tauri 2.1 桌面应用**——**不是服务端**,**无需 K8s/PostgreSQL/Redis**。 +> Story Weaver v2.2.3 是 **Tauri 2.1 桌面应用**——**不是服务端**,**无需 K8s/PostgreSQL/Redis**。 > 本章节专注于**桌面端构建与发布**。 --- @@ -16,7 +16,7 @@ version: '>=3.0' ``` ┌────────────────────────────────────────────────────────┐ -│ frame-fab 构建链 │ +│ Story Weaver 构建链 │ └────────────────────────────────────────────────────────┘ 源代码 (TypeScript + Rust) @@ -52,23 +52,23 @@ version: '>=3.0' ## 二、构建命令 -| 命令 | 说明 | -|------|------| -| `pnpm tauri dev` | 开发模式(热重载) | -| `pnpm tauri build` | 生产构建(生成安装包) | -| `pnpm tauri build --debug` | 调试构建 | -| `pnpm tauri build --target ` | 指定目标平台 | -| `pnpm tauri info` | 查看环境信息 | +| 命令 | 说明 | +| ------------------------------------ | ---------------------- | +| `pnpm tauri dev` | 开发模式(热重载) | +| `pnpm tauri build` | 生产构建(生成安装包) | +| `pnpm tauri build --debug` | 调试构建 | +| `pnpm tauri build --target ` | 指定目标平台 | +| `pnpm tauri info` | 查看环境信息 | --- ## 三、平台支持 -| 平台 | 目标 | 包格式 | 最低系统版本 | 包大小 | -|------|------|--------|------------|--------| -| **macOS** | aarch64 / x86_64 | `.dmg` | macOS 10.15+ | ~26-28 MB | -| **Windows** | x86_64 | `.msi` / `.exe` | Windows 10+ | ~28 MB | -| **Linux** | x86_64 | `.AppImage` / `.deb` | Ubuntu 20.04+ | ~24 MB | +| 平台 | 目标 | 包格式 | 最低系统版本 | 包大小 | +| ----------- | ---------------- | -------------------- | ------------- | --------- | +| **macOS** | aarch64 / x86_64 | `.dmg` | macOS 10.15+ | ~26-28 MB | +| **Windows** | x86_64 | `.msi` / `.exe` | Windows 10+ | ~28 MB | +| **Linux** | x86_64 | `.AppImage` / `.deb` | Ubuntu 20.04+ | ~24 MB | 冷启动均 < 1.2s。 @@ -76,17 +76,17 @@ version: '>=3.0' ## 四、子文档 -| 文档 | 说明 | -|------|------| -| [构建与发布](./build.md) | 详细构建命令 + 输出位置 + 自动更新 + 签名 | -| [环境变量](./environment.md) | `.env` 与 VITE_ 前缀变量 | -| [Docker 开发环境](./docker.md) | 容器化开发环境(可选) | +| 文档 | 说明 | +| ------------------------------ | ----------------------------------------- | +| [构建与发布](./build.md) | 详细构建命令 + 输出位置 + 自动更新 + 签名 | +| [环境变量](./environment.md) | `.env` 与 VITE\_ 前缀变量 | +| [Docker 开发环境](./docker.md) | 容器化开发环境(可选) | --- ## 五、CI/CD -frame-fab 使用 **GitHub Actions** 自动化构建发布: +Story Weaver 使用 **GitHub Actions** 自动化构建发布: ```yaml # .github/workflows/release.yml (简化) @@ -115,18 +115,18 @@ jobs: files: src-tauri/target/release/bundle/**/* ``` -详见 [发布流程](https://github.com/Agions/frame-fab/releases)。 +详见 [发布流程](https://github.com/Agions/story-weaver/releases)。 --- ## 六、性能预算 -| 指标 | 预算 | 实际 (v2.2.3) | -|------|------|-------------| -| JS bundle gzip | ≤ 350 KB | ~280 KB | -| Tauri 二进制 | ≤ 30 MB | ~26 MB | -| 冷启动 | ≤ 1.5s | ~0.9s | -| 流水线 10 步(无 AI) | < 500ms | 275ms | +| 指标 | 预算 | 实际 (v2.2.3) | +| --------------------- | -------- | ------------- | +| JS bundle gzip | ≤ 350 KB | ~280 KB | +| Tauri 二进制 | ≤ 30 MB | ~26 MB | +| 冷启动 | ≤ 1.5s | ~0.9s | +| 流水线 10 步(无 AI) | < 500ms | 275ms | 详见 [v2.2.3 性能基准](../performance/benchmark-2.2.3.md)。 diff --git a/docs/developer-guide/ai-providers.md b/docs/developer-guide/ai-providers.md index dde653b2..141dbe02 100644 --- a/docs/developer-guide/ai-providers.md +++ b/docs/developer-guide/ai-providers.md @@ -7,13 +7,13 @@ version: '>=3.0' # AI Provider Registry -frame-fab 的核心竞争力之一是**多模型 AI 编排**。本章深入讲解 ProviderRegistry + Fallback Chain 的设计。 +Story Weaver 的核心竞争力之一是**多模型 AI 编排**。本章深入讲解 ProviderRegistry + Fallback Chain 的设计。 ## 为什么需要 Provider Registry AI 漫剧创作涉及 4 类模型: -| 类型 | 用途 | frame-fab 集成 | +| 类型 | 用途 | Story Weaver 集成 | | ------------------ | ------------------------------ | ---------------------------------- | | **文字模型 (LLM)** | 剧本分析、对话生成、提示词工程 | GLM-5 / Kimi K2.5 / M2.5 | | **图像模型 (T2I)** | 角色设计、场景渲染、首帧图 | Seedream 5.0 / Imagen 3 | diff --git a/docs/developer-guide/architecture.md b/docs/developer-guide/architecture.md index c0b101dc..b93a17b3 100644 --- a/docs/developer-guide/architecture.md +++ b/docs/developer-guide/architecture.md @@ -1,13 +1,13 @@ --- title: 架构设计 -description: frame-fab v2.2.3 整体架构:Tauri 桌面 + 前端分层 (app/pages/features/shared/core/domain) + 10 步 Pipeline + AI 服务编排 +description: Story Weaver v2.2.3 整体架构:Tauri 桌面 + 前端分层 (app/pages/features/shared/core/domain) + 10 步 Pipeline + AI 服务编排 category: developer-guide version: '>=3.0' --- # 架构设计 -> frame-fab v2.2.3 的系统架构——**Tauri 2.1 桌面端** + **DDD 轻量分层前端** + **10 步 Pipeline** + **多 Provider AI 编排**。 +> Story Weaver v2.2.3 的系统架构——**Tauri 2.1 桌面端** + **DDD 轻量分层前端** + **10 步 Pipeline** + **多 Provider AI 编排**。 ## 一、设计目标 @@ -24,7 +24,7 @@ version: '>=3.0' ``` ┌──────────────────────────────────────────────────────────────────────┐ -│ frame-fab 系统架构 (v2.2.3) │ +│ Story Weaver 系统架构 (v2.2.3) │ └──────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────┐ diff --git a/docs/developer-guide/autonomous-api.md b/docs/developer-guide/autonomous-api.md index 06b059eb..61476598 100644 --- a/docs/developer-guide/autonomous-api.md +++ b/docs/developer-guide/autonomous-api.md @@ -7,7 +7,7 @@ version: '>=3.0' # 自主引擎 API -> frame-fab **Autonomous 模式**的核心 API——`AutoPipelineEngine` + `QualityGate` + `SelfReviewLoop`。 +> Story Weaver **Autonomous 模式**的核心 API——`AutoPipelineEngine` + `QualityGate` + `SelfReviewLoop`。 > **本地服务接口**,**无 REST API**,**无 token 鉴权**——所有调用在进程内完成。 ## 一、模块结构 diff --git a/docs/developer-guide/index.md b/docs/developer-guide/index.md index 605b9a27..70edd8f9 100644 --- a/docs/developer-guide/index.md +++ b/docs/developer-guide/index.md @@ -1,34 +1,34 @@ --- title: 开发者指南 -description: frame-fab 架构与开发文档索引,含 8 个核心主题(架构/模块/平台/服务/API/ADR) +description: Story Weaver 架构与开发文档索引,含 8 个核心主题(架构/模块/平台/服务/API/ADR) category: developer-guide version: '>=2.4' --- # 开发者指南 -> 本指南面向希望深入了解 frame-fab v2.2.3 架构、扩展功能或参与贡献的开发者。 +> 本指南面向希望深入了解 Story Weaver v2.2.3 架构、扩展功能或参与贡献的开发者。 --- ## 📑 目录 -| 文档 | 说明 | -| -------------------------------------- | ------------------------------------------- | -| **[架构设计](./architecture.md)** | 系统整体架构、核心模块、数据流设计 | -| **[项目结构](./project-structure.md)** | 目录结构、模块划分、文件说明 | -| **[模块系统](./module-system.md)** | DDD 分层 | -| **[服务清单](./services.md)** | 7 大核心服务 | -| **[Pipeline 引擎](./pipeline-api.md)** | 10 步流水线细节 | -| **[AI Providers](./ai-providers.md)** | ProviderRegistry + Fallback Chain | -| **[平台适配层](./platform-layer.md)** | Web/Desktop 抽象 | -| **[Autonomous API](./autonomous-api.md)** | Autonomous Pipeline 引擎 API | +| 文档 | 说明 | +| ----------------------------------------- | ---------------------------------- | +| **[架构设计](./architecture.md)** | 系统整体架构、核心模块、数据流设计 | +| **[项目结构](./project-structure.md)** | 目录结构、模块划分、文件说明 | +| **[模块系统](./module-system.md)** | DDD 分层 | +| **[服务清单](./services.md)** | 7 大核心服务 | +| **[Pipeline 引擎](./pipeline-api.md)** | 10 步流水线细节 | +| **[AI Providers](./ai-providers.md)** | ProviderRegistry + Fallback Chain | +| **[平台适配层](./platform-layer.md)** | Web/Desktop 抽象 | +| **[Autonomous API](./autonomous-api.md)** | Autonomous Pipeline 引擎 API | --- ## 🏗️ 系统架构总览 -frame-fab v2.2.3 是一款**全自主 Agent 型**漫剧制作系统,核心特性包括: +Story Weaver v2.2.3 是一款**全自主 Agent 型**漫剧制作系统,核心特性包括: - **Self-Review Loop**:每步 AI 自审,不合格自动修复(最多 3 次循环) - **Quality Gate**:全自动质量门禁,确保输出品质 @@ -140,8 +140,8 @@ src/core/pipeline/ ```bash # 1. 克隆仓库 -git clone https://github.com/Agions/frame-fab.git -cd frame-fab +git clone https://github.com/Agions/story-weaver.git +cd Story Weaver # 2. 安装依赖 pnpm install @@ -160,18 +160,18 @@ pnpm tauri dev ## 📖 更多文档 -| 目的 | 阅读 | -|------|------| -| 详细架构 | [架构设计](./architecture.md) | -| 完整目录 | [项目结构](./project-structure.md) | -| 服务 API | [服务清单](./services.md) | -| Pipeline 细节 | [Pipeline 引擎](./pipeline-api.md) | -| 注册新 Provider | [AI Providers](./ai-providers.md) | -| 跨平台层 | [平台适配层](./platform-layer.md) | -| 自主模式 API | [Autonomous API](./autonomous-api.md) | -| 生产部署 | [部署文档](../deployment/) | -| 性能数据 | [性能基准 v2.2.3](../performance/benchmark-2.2.3.md) | +| 目的 | 阅读 | +| --------------- | ---------------------------------------------------- | +| 详细架构 | [架构设计](./architecture.md) | +| 完整目录 | [项目结构](./project-structure.md) | +| 服务 API | [服务清单](./services.md) | +| Pipeline 细节 | [Pipeline 引擎](./pipeline-api.md) | +| 注册新 Provider | [AI Providers](./ai-providers.md) | +| 跨平台层 | [平台适配层](./platform-layer.md) | +| 自主模式 API | [Autonomous API](./autonomous-api.md) | +| 生产部署 | [部署文档](../deployment/) | +| 性能数据 | [性能基准 v2.2.3](../performance/benchmark-2.2.3.md) | --- -> **💡 参与贡献**:欢迎提交 Issue 和 Pull Request!详见 [CONTRIBUTING.md](https://github.com/Agions/frame-fab/blob/main/CONTRIBUTING.md)。 +> **💡 参与贡献**:欢迎提交 Issue 和 Pull Request!详见 [CONTRIBUTING.md](https://github.com/Agions/story-weaver/blob/main/CONTRIBUTING.md)。 diff --git a/docs/developer-guide/module-system.md b/docs/developer-guide/module-system.md index c7401be0..7776af9e 100644 --- a/docs/developer-guide/module-system.md +++ b/docs/developer-guide/module-system.md @@ -1,18 +1,18 @@ --- title: 模块系统 -description: frame-fab v2.2.3 的领域分层与目录结构 +description: Story Weaver v2.2.3 的领域分层与目录结构 category: developer-guide version: '>=3.0' --- # 模块系统 -frame-fab 采用**领域驱动设计 (DDD)** 的轻量分层架构。从 v2.2.3 开始,目录按用户故事切分,业务与技术分离。 +Story Weaver 采用**领域驱动设计 (DDD)** 的轻量分层架构。从 v2.2.3 开始,目录按用户故事切分,业务与技术分离。 ## 顶层结构 ``` -frame-fab/ +Story Weaver/ ├── app/ # 应用入口: 路由、Providers、Layout ├── pages/ # 路由级页面 (7 个) ├── features/ # 业务功能 (按用户故事切,9 个) @@ -101,7 +101,7 @@ features/script/ "compilerOptions": { "paths": { "@/*": ["./src/*"], - "@frame-fab/common/*": ["./packages/common/src/*"] + "@Story Weaver/common/*": ["./packages/common/src/*"] } } } diff --git a/docs/developer-guide/pipeline-api.md b/docs/developer-guide/pipeline-api.md index 252e6e9c..ff38eb9a 100644 --- a/docs/developer-guide/pipeline-api.md +++ b/docs/developer-guide/pipeline-api.md @@ -1,13 +1,13 @@ --- title: Pipeline 引擎 API -description: frame-fab 10 步 Pipeline 引擎 API:PipelineStepId 枚举、StepChain 接口、AsyncStepChain、StepChainBuilder、检查点恢复 +description: Story Weaver 10 步 Pipeline 引擎 API:PipelineStepId 枚举、StepChain 接口、AsyncStepChain、StepChainBuilder、检查点恢复 category: developer-guide version: '>=3.0' --- # Pipeline 引擎 API -> frame-fab v2.2.3 的 Pipeline 引擎**对外 API**——10 步编排、StepChain 接口、Checkpoint 恢复。 +> Story Weaver v2.2.3 的 Pipeline 引擎**对外 API**——10 步编排、StepChain 接口、Checkpoint 恢复。 ## 一、核心类型 @@ -271,4 +271,4 @@ chain.setNext(createAnalysisStepChain()); - [API - 流水线](../api/pipeline-service.md) — 服务层 API - [架构设计](./architecture.md#四核心模块) — Pipeline 引擎位置 -- [Pipeline 服务源码](https://github.com/Agions/frame-fab/tree/main/src/core/pipeline) +- [Pipeline 服务源码](https://github.com/Agions/story-weaver/tree/main/src/core/pipeline) diff --git a/docs/developer-guide/platform-layer.md b/docs/developer-guide/platform-layer.md index df783097..c6db5482 100644 --- a/docs/developer-guide/platform-layer.md +++ b/docs/developer-guide/platform-layer.md @@ -1,13 +1,13 @@ --- title: 平台适配层 -description: frame-fab 如何让业务代码在 Tauri 桌面端和 Web 浏览器之间无缝切换 +description: Story Weaver 如何让业务代码在 Tauri 桌面端和 Web 浏览器之间无缝切换 category: developer-guide version: '>=3.0' --- # 平台适配层 (Platform Adapter) -frame-fab 是一个**桌面优先**的应用,但开发时常常需要在 Web 浏览器中调试。本章解释 Platform Adapter 是如何解耦业务代码与运行时环境的。 +Story Weaver 是一个**桌面优先**的应用,但开发时常常需要在 Web 浏览器中调试。本章解释 Platform Adapter 是如何解耦业务代码与运行时环境的。 ## 问题背景 @@ -216,7 +216,7 @@ rg "from '@tauri-apps" src/ ## 未来扩展 -Platform Adapter 让 frame-fab 拥有了"一次实现,多端运行"的能力: +Platform Adapter 让 Story Weaver 拥有了"一次实现,多端运行"的能力: - **🖥️ Tauri 桌面** — 主力平台,完整功能 - **🌐 Web Demo** — 在线试用版,可在 Vercel 部署 diff --git a/docs/developer-guide/project-structure.md b/docs/developer-guide/project-structure.md index 907d1f1e..9a00a942 100644 --- a/docs/developer-guide/project-structure.md +++ b/docs/developer-guide/project-structure.md @@ -7,14 +7,14 @@ version: '>=3.0' # 项目结构 -> 本文档详细介绍 frame-fab 项目的目录结构、模块划分与文件说明。 +> 本文档详细介绍 Story Weaver 项目的目录结构、模块划分与文件说明。 --- ## 一、整体目录结构 ``` -frame-fab/ +Story Weaver/ ├── docs/ # 项目文档 │ ├── index.md # 文档首页 │ ├── getting-started/ # 入门指南 diff --git a/docs/developer-guide/services.md b/docs/developer-guide/services.md index 8748f4e0..7043fd71 100644 --- a/docs/developer-guide/services.md +++ b/docs/developer-guide/services.md @@ -1,13 +1,13 @@ --- title: 服务清单 -description: frame-fab 7 大服务领域架构:ai / audio / video / pipeline / project / domain / platform,依赖关系图与单例调用规范 +description: Story Weaver 7 大服务领域架构:ai / audio / video / pipeline / project / domain / platform,依赖关系图与单例调用规范 category: developer-guide version: '>=3.0' --- # 服务清单 -> frame-fab v2.2.3 把全部业务能力收敛到 **`src/core/services/`** 下的 **7 大领域**。 +> Story Weaver v2.2.3 把全部业务能力收敛到 **`src/core/services/`** 下的 **7 大领域**。 > 所有服务**默认导出单例**(不要 `new`),统一从 `@/core/services` 导入。 ## 一、领域全景 @@ -45,16 +45,17 @@ src/core/services/ **核心服务**: -| 服务 | 文件 | 能力 | -|------|------|------| -| `aiService` | `ai/text/ai.service.ts` | 统一文本生成/分析/对话/流式 | -| `novelService` | `ai/text/novel.service.ts` | 小说导入与结构化 | -| `novelAnalyzer` | `ai/text/novel-analyze.service.ts` | 章节/场景/人物识别 | -| `storyAnalysisService` | `ai/text/story-analysis.service.ts` | 故事结构深度分析 | -| `scriptImportService` | `ai/text/script-import.service.ts` | 剧本导入(行业格式) | -| `imageGenerationService` | `ai/image/image-generation.service.ts` | 图像/视频生成 | +| 服务 | 文件 | 能力 | +| ------------------------ | -------------------------------------- | --------------------------- | +| `aiService` | `ai/text/ai.service.ts` | 统一文本生成/分析/对话/流式 | +| `novelService` | `ai/text/novel.service.ts` | 小说导入与结构化 | +| `novelAnalyzer` | `ai/text/novel-analyze.service.ts` | 章节/场景/人物识别 | +| `storyAnalysisService` | `ai/text/story-analysis.service.ts` | 故事结构深度分析 | +| `scriptImportService` | `ai/text/script-import.service.ts` | 剧本导入(行业格式) | +| `imageGenerationService` | `ai/image/image-generation.service.ts` | 图像/视频生成 | **子模块**(拆分后 25+ 文件): + - `ai-cache.ts` / `ai-batch.ts` / `ai-stream.ts` / `ai-call-dispatcher.ts` - `novel-ai-parser.ts` / `novel-prompt-templates.ts` / `novel-helpers.ts` - `novel-analyze-chapter-segments.ts` / `novel-analyze-scene-segments.ts` / `novel-analyze-metadata.ts` / `novel-analyze-config.ts` / `novel-analyze-statistics.ts` @@ -77,12 +78,12 @@ aiService.setFallbackChain(['zhipu', 'anthropic', 'minimax', 'moonshot']); **核心服务**: -| 服务 | 文件 | 能力 | -|------|------|------| -| `ttsService` | `audio/tts.service.ts` | 文本转语音(200+ 音色) | -| `lipSyncService` | `audio/lip-sync.service.ts` | 唇形同步元数据 | -| `audioPipelineService` | `audio/audio-pipeline.service.ts` | 音频编排(混音/对齐) | -| `ttsProviderRegistry` | `audio/tts-provider-registry.ts` | TTS Provider 管理 | +| 服务 | 文件 | 能力 | +| ---------------------- | --------------------------------- | ----------------------- | +| `ttsService` | `audio/tts.service.ts` | 文本转语音(200+ 音色) | +| `lipSyncService` | `audio/lip-sync.service.ts` | 唇形同步元数据 | +| `audioPipelineService` | `audio/audio-pipeline.service.ts` | 音频编排(混音/对齐) | +| `ttsProviderRegistry` | `audio/tts-provider-registry.ts` | TTS Provider 管理 | **TTS Provider 降级链**: @@ -98,16 +99,17 @@ Edge TTS(免费)→ CosyVoice 2.0 → 百度 TTS → KAN-TTS **核心服务**: -| 服务 | 文件 | 能力 | -|------|------|------| -| `videoCompositorService` | `video/video-compositor.service.ts` | FFmpeg 视频合成(多轨) | -| `ffmpegWasmService` | `video/ffmpeg-wasm.service.ts` | FFmpeg.wasm 加载/调用 | -| `sceneAnalyzerService` | `video/scene-analyzer.service.ts` | 视频场景识别 | -| `subtitleService` | `video/subtitle.service.ts` | 字幕生成与多格式导出 | -| `videoAnalysisService` | `video/video-analysis.service.ts` | 视频内容分析 | -| `visualConsistencyService` | `video/visual-consistency-scorer.service.ts` | 跨镜头视觉一致性评分 | +| 服务 | 文件 | 能力 | +| -------------------------- | -------------------------------------------- | ----------------------- | +| `videoCompositorService` | `video/video-compositor.service.ts` | FFmpeg 视频合成(多轨) | +| `ffmpegWasmService` | `video/ffmpeg-wasm.service.ts` | FFmpeg.wasm 加载/调用 | +| `sceneAnalyzerService` | `video/scene-analyzer.service.ts` | 视频场景识别 | +| `subtitleService` | `video/subtitle.service.ts` | 字幕生成与多格式导出 | +| `videoAnalysisService` | `video/video-analysis.service.ts` | 视频内容分析 | +| `visualConsistencyService` | `video/visual-consistency-scorer.service.ts` | 跨镜头视觉一致性评分 | **子模块**(拆分后 25+ 文件): + - `video-compositor-{dispatch,environment,ffmpeg,helpers,tauri}.ts` - `video-analysis-{abort-registry,emotions,keyframes,objects,scenes,stats,suggestions,summary,types}.ts` - `visual-consistency-{heuristic,keywords,scorer,types,vlm}.ts` @@ -122,13 +124,13 @@ Edge TTS(免费)→ CosyVoice 2.0 → 百度 TTS → KAN-TTS **核心服务**: -| 服务 | 文件 | 能力 | -|------|------|------| -| `pipelineService` | `pipeline/pipeline.service.ts` | 主入口(run/resume/checkpoint) | -| `pipelineRunner` | `pipeline/pipeline-runner.ts` | 步骤执行器 | -| `pipelineStepFactories` | `pipeline/pipeline-step-factories.ts` | 10 步工厂 | -| `qualityGateService` | `pipeline/quality-gate.service.ts` | 质量门禁评分 | -| `reviewExportService` | `pipeline/review-export.service.ts` | 自审循环导出 | +| 服务 | 文件 | 能力 | +| ----------------------- | ------------------------------------- | ------------------------------- | +| `pipelineService` | `pipeline/pipeline.service.ts` | 主入口(run/resume/checkpoint) | +| `pipelineRunner` | `pipeline/pipeline-runner.ts` | 步骤执行器 | +| `pipelineStepFactories` | `pipeline/pipeline-step-factories.ts` | 10 步工厂 | +| `qualityGateService` | `pipeline/quality-gate.service.ts` | 质量门禁评分 | +| `reviewExportService` | `pipeline/review-export.service.ts` | 自审循环导出 | **10 个步骤**(按顺序): @@ -145,16 +147,17 @@ import → analysis → script → character → scene → storyboard **核心服务**: -| 服务 | 文件 | 能力 | -|------|------|------| +| 服务 | 文件 | 能力 | +| ---------------------------- | ------------------------------------------ | ------------------------ | | `projectImportExportService` | `project/project-import-export.service.ts` | 项目导入导出(10+ 格式) | -| `renderQueueService` | `project/render-queue.service.ts` | 渲染任务队列 | -| `costService` | `project/cost.service.ts` | API 成本统计 | -| `evaluationService` | `project/evaluation.service.ts` | 项目质量评估 | -| `secureStorageService` | `project/secure-storage.service.ts` | API Key 加密存储 | -| `exportDispatcher` | `project/export-dispatcher.ts` | 多格式导出调度 | +| `renderQueueService` | `project/render-queue.service.ts` | 渲染任务队列 | +| `costService` | `project/cost.service.ts` | API 成本统计 | +| `evaluationService` | `project/evaluation.service.ts` | 项目质量评估 | +| `secureStorageService` | `project/secure-storage.service.ts` | API Key 加密存储 | +| `exportDispatcher` | `project/export-dispatcher.ts` | 多格式导出调度 | **子模块**(拆分后 25+ 文件): + - `project-import-export-{importer,exporter,validator,duplicator,compare,backup,types}.ts` - `render-queue-{fallback,logger,runner,subscriber,types}.ts` - `cost-{record-builders,report,stats,types,constants}.ts` @@ -168,14 +171,15 @@ import → analysis → script → character → scene → storyboard **核心服务**: -| 服务 | 文件 | 能力 | -|------|------|------| -| `characterService` | `domain/character.service.ts` | 角色设定卡管理 | -| `mangaPipelineService` | `domain/manga-pipeline.service.ts` | 漫剧 6 步编排 | -| `compositionService` | `domain/composition.service.ts` | 单镜头组合 | -| `collaborationService` | `domain/collaboration.service.ts` | 多人协作(v3.1+) | +| 服务 | 文件 | 能力 | +| ---------------------- | ---------------------------------- | ----------------- | +| `characterService` | `domain/character.service.ts` | 角色设定卡管理 | +| `mangaPipelineService` | `domain/manga-pipeline.service.ts` | 漫剧 6 步编排 | +| `compositionService` | `domain/composition.service.ts` | 单镜头组合 | +| `collaborationService` | `domain/collaboration.service.ts` | 多人协作(v3.1+) | **子模块**(拆分后 20+ 文件): + - `character-{factory,persistence,subscriber,template,types}.ts` - `composition-{factory,persistence,subscriber,types}.ts` - `manga-pipeline-{orchestrator,extra,progress,types,stage-audio,stage-compose,stage-images,stage-lipsync}.ts` @@ -185,16 +189,16 @@ import → analysis → script → character → scene → storyboard > 桌面端能力 + 分镜独立服务 + 根级便捷导出。 -| 服务 | 文件 | 能力 | -|------|------|------| -| `desktopAppService` | `desktop-app.service.ts` | 桌面应用元信息 | -| `desktopAppInfo` | `desktop-app-info.ts` | 系统信息 | -| `desktopFileDrop` | `desktop-file-drop.ts` | 文件拖拽桥 | -| `desktopNotificationController` | `desktop-notification-controller.ts` | 系统通知 | -| `desktopShortcutController` | `desktop-shortcut-controller.ts` | 全局快捷键 | -| `desktopWindowController` | `desktop-window-controller.ts` | 窗口控制 | -| `getStoryboardService` | `storyboard.service.ts` | 分镜生命周期 | -| `getStoryboardService` | `storyboard.service.ts` | 分镜管理 | +| 服务 | 文件 | 能力 | +| ------------------------------- | ------------------------------------ | -------------- | +| `desktopAppService` | `desktop-app.service.ts` | 桌面应用元信息 | +| `desktopAppInfo` | `desktop-app-info.ts` | 系统信息 | +| `desktopFileDrop` | `desktop-file-drop.ts` | 文件拖拽桥 | +| `desktopNotificationController` | `desktop-notification-controller.ts` | 系统通知 | +| `desktopShortcutController` | `desktop-shortcut-controller.ts` | 全局快捷键 | +| `desktopWindowController` | `desktop-window-controller.ts` | 窗口控制 | +| `getStoryboardService` | `storyboard.service.ts` | 分镜生命周期 | +| `getStoryboardService` | `storyboard.service.ts` | 分镜管理 | 详见 [平台适配层](./platform-layer.md)。 @@ -234,6 +238,7 @@ import → analysis → script → character → scene → storyboard ``` **依赖规则**: + - `domain/` 依赖 `ai/`、`pipeline/`、`project/` - `pipeline/` 依赖 `ai/`、`project/` - `video/` 和 `audio/` 是**叶子**(不再依赖其他 service) @@ -268,13 +273,13 @@ ttsService.setFallbackChain(['edge', 'cosyvoice']); ## 五、错误处理 -| 错误类型 | 触发场景 | 推荐处理 | -|---------|---------|---------| -| `AIProviderError` | AI 模型调用失败 | 自动降级,UI 提示用户 | -| `InvalidInputError` | 入参校验失败 | 表单红字提示 | -| `QuotaExceededError` | 配额耗尽 | 引导用户升级/换 Provider | -| `CheckpointError` | 断点续传损坏 | 提示用户从最近有效 Checkpoint 恢复 | -| `RenderQueueError` | 渲染队列满 | 排队等待或清理旧任务 | +| 错误类型 | 触发场景 | 推荐处理 | +| -------------------- | --------------- | ---------------------------------- | +| `AIProviderError` | AI 模型调用失败 | 自动降级,UI 提示用户 | +| `InvalidInputError` | 入参校验失败 | 表单红字提示 | +| `QuotaExceededError` | 配额耗尽 | 引导用户升级/换 Provider | +| `CheckpointError` | 断点续传损坏 | 提示用户从最近有效 Checkpoint 恢复 | +| `RenderQueueError` | 渲染队列满 | 排队等待或清理旧任务 | ```typescript try { diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index cf1deb48..1ec9d70e 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -1,19 +1,19 @@ --- title: 配置 AI API Key -description: frame-fab 多模型 AI API Key 配置:智谱 / Anthropic / MiniMax / Seedream / Kling / TTS,含降级链 +description: Story Weaver 多模型 AI API Key 配置:智谱 / Anthropic / MiniMax / Seedream / Kling / TTS,含降级链 category: getting-started version: '>=3.0' --- # 配置 AI API Key -> 配置 AI 模型是跑通 frame-fab 的**关键一步**。本文介绍所有支持的 Provider、降级链策略、安全最佳实践。 +> 配置 AI 模型是跑通 Story Weaver 的**关键一步**。本文介绍所有支持的 Provider、降级链策略、安全最佳实践。 --- ## 一、配置入口 -启动 frame-fab → **设置** → **API Key** → 添加 Provider +启动 Story Weaver → **设置** → **API Key** → 添加 Provider > 🛡️ **桌面端优先使用「设置面板」配置**——API Key 通过 [Tauri Secure Storage](https://tauri.app/v1/api/js/storage) 加密存储(OS Keychain)。 > 🔧 **开发模式**(`pnpm tauri dev`)可在 `.env.local` 配置。 @@ -22,85 +22,85 @@ version: '>=3.0' ## 二、必须配置 -| 类型 | 推荐 | 原因 | -|------|------|------| -| 文本模型 | `ZHIPU_API_KEY` | 必需,**国内首选**(中文剧本效果最佳) | +| 类型 | 推荐 | 原因 | +| -------- | ------------------- | --------------------------------------- | +| 文本模型 | `ZHIPU_API_KEY` | 必需,**国内首选**(中文剧本效果最佳) | | 图像模型 | `SEEDDREAM_API_KEY` | 必需,**质量最高**(动画/插画风格首选) | -| TTS | (默认 Edge TTS) | 可选,**免费**,无需配置 | +| TTS | (默认 Edge TTS) | 可选,**免费**,无需配置 | --- ## 三、文本生成 Provider -| Provider | 模型 | 价格 | 适用 | -|----------|------|------|------| -| 智谱 `zhipu` | GLM-5 | 💰 | 中文剧本首选 | -| Anthropic `anthropic` | Claude 3.5 Sonnet | 💰💰 | 长文本/高质量 | -| MiniMax `minimax` | M2.5 | 💰 | 备选 | -| 月之暗面 `moonshot` | Kimi K2.5 | 💰 | 长上下文(10 万字+) | -| 字节豆包 `doubao` | Doubao 2.0 | 💰 | 国内备选 | -| 阿里通义 `qwen` | Qwen 2.5 | 💰 | 国内备选 | -| 百度文心 `ernie` | ERNIE 4.0 | 💰 | 国内备选 | -| OpenAI `openai` | GPT-4o | 💰💰💰 | 国际/英文 | +| Provider | 模型 | 价格 | 适用 | +| --------------------- | ----------------- | ------ | -------------------- | +| 智谱 `zhipu` | GLM-5 | 💰 | 中文剧本首选 | +| Anthropic `anthropic` | Claude 3.5 Sonnet | 💰💰 | 长文本/高质量 | +| MiniMax `minimax` | M2.5 | 💰 | 备选 | +| 月之暗面 `moonshot` | Kimi K2.5 | 💰 | 长上下文(10 万字+) | +| 字节豆包 `doubao` | Doubao 2.0 | 💰 | 国内备选 | +| 阿里通义 `qwen` | Qwen 2.5 | 💰 | 国内备选 | +| 百度文心 `ernie` | ERNIE 4.0 | 💰 | 国内备选 | +| OpenAI `openai` | GPT-4o | 💰💰💰 | 国际/英文 | ### 3.1 智谱 GLM-5(推荐) 1. 访问 [智谱开放平台](https://open.bigmodel.cn/) 2. 注册 → 实名认证 → 「API Keys」 → 创建新 Key -3. 复制到 frame-fab 设置 +3. 复制到 Story Weaver 设置 ### 3.2 Anthropic Claude 3.5 1. 访问 [Anthropic Console](https://console.anthropic.com/) 2. 创建 API Key -3. 复制到 frame-fab 设置 +3. 复制到 Story Weaver 设置 ### 3.3 月之暗面 Kimi K2.5 1. 访问 [Moonshot AI 开放平台](https://platform.moonshot.cn/) 2. 创建 API Key -3. 复制到 frame-fab 设置 +3. 复制到 Story Weaver 设置 --- ## 四、图像生成 Provider -| Provider | 模型 | 类型 | 备注 | -|----------|------|------|------| -| 字节跳动 `seedream` | Seedream 5.0 | 图像 | ⭐⭐⭐ 动画/插画首选 | -| 快手 `kling` | Kling 1.6 | 图像+视频 | 视频生成 | -| 生数科技 `vidu` | Vidu Q3 | 图像+视频 | 高一致性 | -| 字节跳动 `seedance` | Seedance 2.0 | 视频 | 短运镜 | -| Stability `stability` | SDXL | 图像 | 降级方案 | +| Provider | 模型 | 类型 | 备注 | +| --------------------- | ------------ | --------- | -------------------- | +| 字节跳动 `seedream` | Seedream 5.0 | 图像 | ⭐⭐⭐ 动画/插画首选 | +| 快手 `kling` | Kling 1.6 | 图像+视频 | 视频生成 | +| 生数科技 `vidu` | Vidu Q3 | 图像+视频 | 高一致性 | +| 字节跳动 `seedance` | Seedance 2.0 | 视频 | 短运镜 | +| Stability `stability` | SDXL | 图像 | 降级方案 | ### 4.1 字节 Seedream 5.0(推荐) 1. 访问 [火山引擎 - 视觉智能](https://www.volcengine.com/product/visual) 2. 开通「图像生成」服务 → 创建 API Key -3. 复制到 frame-fab 设置 +3. 复制到 Story Weaver 设置 ### 4.2 快手 Kling 1.6 1. 访问 [可灵 AI](https://klingai.com/) 2. 申请 API 权限 → 创建 Key -3. 复制到 frame-fab 设置 +3. 复制到 Story Weaver 设置 --- ## 五、语音合成 Provider -| Provider | 价格 | 备注 | -|----------|------|------| -| **Edge TTS** | 🆓 免费 | **默认**,无需 Key(微软 Azure 公共服务) | -| 阿里 CosyVoice 2.0 | 💰 付费 | 高质量,支持情感 | -| 百度 TTS | 💰 付费 | 老牌中文 TTS | -| KAN-TTS | 💰 付费 | 字节系 | +| Provider | 价格 | 备注 | +| ------------------ | ------- | ----------------------------------------- | +| **Edge TTS** | 🆓 免费 | **默认**,无需 Key(微软 Azure 公共服务) | +| 阿里 CosyVoice 2.0 | 💰 付费 | 高质量,支持情感 | +| 百度 TTS | 💰 付费 | 老牌中文 TTS | +| KAN-TTS | 💰 付费 | 字节系 | --- ## 六、降级链策略 -frame-fab 内置**自动降级**——主选不可用时自动切换备选。 +Story Weaver 内置**自动降级**——主选不可用时自动切换备选。 ### 6.1 默认降级链 @@ -128,12 +128,12 @@ Edge TTS → CosyVoice 2.0 → 百度 TTS ### 6.3 降级触发条件 -| 场景 | 行为 | -|------|------| -| HTTP 429(限流) | 等待 5s 后切换下一个 | -| HTTP 500/502/503 | 立即切换下一个 | -| 网络超时(30s) | 立即切换下一个 | -| API Key 失效 | 标记该 Provider 不可用,永久跳过(重启后重置) | +| 场景 | 行为 | +| ---------------- | ---------------------------------------------- | +| HTTP 429(限流) | 等待 5s 后切换下一个 | +| HTTP 500/502/503 | 立即切换下一个 | +| 网络超时(30s) | 立即切换下一个 | +| API Key 失效 | 标记该 Provider 不可用,永久跳过(重启后重置) | --- @@ -141,31 +141,31 @@ Edge TTS → CosyVoice 2.0 → 百度 TTS ### 7.1 最小配置(免费可用) -| 必填 | Provider | -|------|----------| -| 文本 | `ZHIPU_API_KEY` | +| 必填 | Provider | +| ---- | ------------------- | +| 文本 | `ZHIPU_API_KEY` | | 图像 | `SEEDDREAM_API_KEY` | -| TTS | (Edge TTS 默认) | +| TTS | (Edge TTS 默认) | **预估成本**:约 ¥0.1 / 分钟漫剧(短篇 ¥1-3 整剧) ### 7.2 高质量配置 -| 必填 | Provider | -|------|----------| +| 必填 | Provider | +| ---- | ---------------------------- | | 文本 | `ZHIPU` + `ANTHROPIC` 双备份 | | 图像 | `SEEDDREAM` + `KLING` 双备份 | -| TTS | `COSYVOICE` | +| TTS | `COSYVOICE` | **预估成本**:约 ¥0.3 / 分钟漫剧(短篇 ¥3-9 整剧) ### 7.3 纯免费配置 -| 必填 | Provider | -|------|----------| -| 文本 | (无 Key 时)→ 平台提示受限 | +| 必填 | Provider | +| ---- | ----------------------------------- | +| 文本 | (无 Key 时)→ 平台提示受限 | | 图像 | (无 Key 时)→ 降级到 SDXL 免费额度 | -| TTS | Edge TTS(始终免费) | +| TTS | Edge TTS(始终免费) | > ⚠️ 完全免费模式仅支持基础效果,漫剧质量受限。 @@ -179,13 +179,13 @@ A: 桌面端「设置 → API Key」。**不要**写进代码或提交到 Git( ### Q2: 某个 Provider 报错怎么办? -A: frame-fab 会**自动降级**到备选 Provider。失败信息会显示在「设置 → 状态 → Provider 列表」。 +A: Story Weaver 会**自动降级**到备选 Provider。失败信息会显示在「设置 → 状态 → Provider 列表」。 ### Q3: API Key 会被泄露吗? A: 桌面端使用 [Tauri Secure Storage](https://tauri.app/v1/api/js/storage) 加密存储(OS Keychain)。 -> ⚠️ **注意**:frame-fab 是开源应用,反编译可读取内存中的 Key。建议使用**子账户 + 限额**降低风险。 +> ⚠️ **注意**:Story Weaver 是开源应用,反编译可读取内存中的 Key。建议使用**子账户 + 限额**降低风险。 ### Q4: 海外访问国内 API 慢? diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index 4db0fed5..6309d95a 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -1,13 +1,13 @@ --- title: 快速开始 -description: frame-fab 上手指南:3 步跑通(安装 → 配置 → 创建首个漫剧) +description: Story Weaver 上手指南:3 步跑通(安装 → 配置 → 创建首个漫剧) category: getting-started version: '>=3.0' --- # 快速开始 -> **3 步跑通 frame-fab**:安装 → 配置 AI Key → 创建首个漫剧。短篇 15-30 分钟即可成片。 +> **3 步跑通 Story Weaver**:安装 → 配置 AI Key → 创建首个漫剧。短篇 15-30 分钟即可成片。 --- @@ -21,11 +21,11 @@ graph LR D --> E[下载成片] ``` -| 步骤 | 时间 | 说明 | -|------|------|------| -| 1️⃣ 下载安装 | 3 min | macOS / Windows / Linux 三端 | -| 2️⃣ 配置 API Key | 1 min | 至少 1 个文本 + 1 个图像 | -| 3️⃣ 创建首个漫剧 | 5 min | 输入短篇,AI 自动成片 | +| 步骤 | 时间 | 说明 | +| --------------- | ----- | ---------------------------- | +| 1️⃣ 下载安装 | 3 min | macOS / Windows / Linux 三端 | +| 2️⃣ 配置 API Key | 1 min | 至少 1 个文本 + 1 个图像 | +| 3️⃣ 创建首个漫剧 | 5 min | 输入短篇,AI 自动成片 | --- @@ -33,7 +33,7 @@ graph LR ### 步骤 1:下载桌面端 -前往 [Releases](https://github.com/Agions/frame-fab/releases) 下载对应平台安装包: +前往 [Releases](https://github.com/Agions/story-weaver/releases) 下载对应平台安装包: - **macOS**: `.dmg`(分 Apple Silicon / Intel 两个版本) - **Windows**: `.msi` 或 `setup.exe` @@ -43,7 +43,7 @@ graph LR ### 步骤 2:配置 AI API Key -1. 启动 frame-fab → **设置** → **API Key** +1. 启动 Story Weaver → **设置** → **API Key** 2. 至少配置: - **1 个文本模型**(推荐 `ZHIPU_API_KEY` = 智谱 GLM-5) - **1 个图像模型**(推荐 `SEEDDREAM_API_KEY` = 字节 Seedream 5.0) @@ -63,10 +63,10 @@ graph LR ## 三、文档导航 -| 文档 | 适合 | -|------|------| -| [安装指南](./installation.md) | 第一次下载安装 | -| [3 步跑通](./quick-start.md) | 5 分钟上手 | +| 文档 | 适合 | +| ---------------------------------- | -------------- | +| [安装指南](./installation.md) | 第一次下载安装 | +| [3 步跑通](./quick-start.md) | 5 分钟上手 | | [配置 API Key](./configuration.md) | 多模型 AI 配置 | --- diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index ac0640c4..371efa06 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -1,13 +1,13 @@ --- title: 安装指南 -description: frame-fab 桌面端安装:macOS / Windows / Linux 三端 + 源码开发模式 +description: Story Weaver 桌面端安装:macOS / Windows / Linux 三端 + 源码开发模式 category: getting-started version: '>=3.0' --- # 安装指南 -> frame-fab 是 **Tauri 2.1 桌面应用**,**不支持 Docker / Web 部署**用于生产。 +> Story Weaver 是 **Tauri 2.1 桌面应用**,**不支持 Docker / Web 部署**用于生产。 > 本文档介绍:① 桌面端安装 ② 源码开发模式。 --- @@ -16,31 +16,31 @@ version: '>=3.0' ### 1.1 系统要求 -| 组件 | 最低 | 推荐 | -|------|------|------| +| 组件 | 最低 | 推荐 | +| ------------ | --------------------------------------- | ------------------------------------- | | **操作系统** | macOS 10.15 / Windows 10 / Ubuntu 20.04 | macOS 13+ / Windows 11 / Ubuntu 22.04 | -| **内存** | 8 GB | 16 GB+ | -| **存储** | 10 GB | 50 GB SSD | -| **网络** | 稳定 | 宽带(API 调用) | -| **显卡** | 集成显卡 | 独显(视频渲染加速) | +| **内存** | 8 GB | 16 GB+ | +| **存储** | 10 GB | 50 GB SSD | +| **网络** | 稳定 | 宽带(API 调用) | +| **显卡** | 集成显卡 | 独显(视频渲染加速) | ### 1.2 下载 -前往 [GitHub Releases](https://github.com/Agions/frame-fab/releases) 下载最新版本: +前往 [GitHub Releases](https://github.com/Agions/story-weaver/releases) 下载最新版本: -| 平台 | 文件 | -|------|------| -| **macOS (Apple Silicon)** | `frame-fab_x.x.x_aarch64.dmg` | -| **macOS (Intel)** | `frame-fab_x.x.x_x64.dmg` | -| **Windows** | `frame-fab_x.x.x_x64-setup.exe` | -| **Linux** | `frame-fab_x.x.x_amd64.AppImage` | +| 平台 | 文件 | +| ------------------------- | ----------------------------------- | +| **macOS (Apple Silicon)** | `Story Weaver_x.x.x_aarch64.dmg` | +| **macOS (Intel)** | `Story Weaver_x.x.x_x64.dmg` | +| **Windows** | `Story Weaver_x.x.x_x64-setup.exe` | +| **Linux** | `Story Weaver_x.x.x_amd64.AppImage` | ### 1.3 安装步骤 #### macOS 1. 双击 `.dmg` 文件 -2. 拖动 `frame-fab` 到 Applications +2. 拖动 `Story Weaver` 到 Applications 3. 启动时如提示"无法验证开发者": - 系统设置 → 隐私与安全性 → 仍要打开 - 或按住 `Control` 键点击应用图标 → "打开" @@ -56,14 +56,14 @@ version: '>=3.0' **AppImage**(推荐,免安装): ```bash -chmod +x frame-fab_x.x.x_amd64.AppImage -./frame-fab_x.x.x_amd64.AppImage +chmod +x Story Weaver_x.x.x_amd64.AppImage +./Story Weaver_x.x.x_amd64.AppImage ``` **`.deb`**(Debian/Ubuntu 深度集成): ```bash -sudo dpkg -i frame-fab_x.x.x_amd64.deb +sudo dpkg -i Story Weaver_x.x.x_amd64.deb sudo apt-get install -f # 补齐依赖 ``` @@ -73,12 +73,12 @@ sudo apt-get install -f # 补齐依赖 ### 2.1 前置要求 -| 软件 | 版本 | 用途 | -|------|------|------| -| **Rust** | ≥ 1.80 | Tauri 后端编译 | -| **Node.js** | ≥ 18 LTS | 前端构建 | -| **pnpm** | ≥ 9.0 | 依赖管理 | -| **Git** | 最新 | 克隆代码 | +| 软件 | 版本 | 用途 | +| ----------- | -------- | -------------- | +| **Rust** | ≥ 1.80 | Tauri 后端编译 | +| **Node.js** | ≥ 18 LTS | 前端构建 | +| **pnpm** | ≥ 9.0 | 依赖管理 | +| **Git** | 最新 | 克隆代码 | #### 平台特定工具 @@ -111,8 +111,8 @@ sudo apt install -y \ ### 2.2 克隆与安装 ```bash -git clone https://github.com/Agions/frame-fab.git -cd frame-fab +git clone https://github.com/Agions/story-weaver.git +cd Story Weaver pnpm install ``` @@ -143,9 +143,9 @@ Tauri dev server running at http://localhost:1420 A: 查看日志: -- **macOS**: `~/Library/Logs/frame-fab/` -- **Windows**: `%APPDATA%\frame-fab\logs\` -- **Linux**: `~/.config/frame-fab/logs/` +- **macOS**: `~/Library/Logs/Story Weaver/` +- **Windows**: `%APPDATA%\Story Weaver\logs\` +- **Linux**: `~/.config/Story Weaver/logs/` 或运行 `pnpm tauri dev` 在控制台查看实时日志。 @@ -155,7 +155,7 @@ A: 首次启动时按住 `Control` 键点击应用图标 → "打开"。系统 ### Q3: Windows Defender 误报? -A: 提交至 [issue](https://github.com/Agions/frame-fab/issues),我们会做 EV 代码签名。 +A: 提交至 [issue](https://github.com/Agions/story-weaver/issues),我们会做 EV 代码签名。 ### Q4: Linux AppImage 无法启动? diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 12f19144..bfa838bc 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -1,6 +1,6 @@ --- title: 3 步跑通 -description: frame-fab 5 分钟上手:上传故事 → 选择模型 → 一键生成 +description: Story Weaver 5 分钟上手:上传故事 → 选择模型 → 一键生成 category: getting-started version: '>=3.0' --- @@ -17,12 +17,12 @@ version: '>=3.0' 支持格式: -| 格式 | 扩展名 | 推荐 | -|------|--------|------| -| 纯文本 | `.txt` | ⭐⭐⭐ 最佳 | -| Markdown | `.md` | ⭐⭐ | -| Word | `.docx` | ⭐⭐ | -| PDF | `.pdf` | ⭐ | +| 格式 | 扩展名 | 推荐 | +| -------- | ------- | ----------- | +| 纯文本 | `.txt` | ⭐⭐⭐ 最佳 | +| Markdown | `.md` | ⭐⭐ | +| Word | `.docx` | ⭐⭐ | +| PDF | `.pdf` | ⭐ | ### 1.2 文本建议 @@ -44,7 +44,7 @@ version: '>=3.0' ### 1.3 上传 -打开 frame-fab → **新建项目** → **Autonomous 模式** → 拖入文件 / 点击选择 +打开 Story Weaver → **新建项目** → **Autonomous 模式** → 拖入文件 / 点击选择 --- @@ -52,22 +52,22 @@ version: '>=3.0' ### 2.1 文本模型(必需) -| 模型 | 价格 | 推荐场景 | -|------|------|---------| -| 智谱 GLM-5 | 💰 | 中文剧本首选 | -| Claude 3.5 | 💰💰 | 高质量长文本 | -| MiniMax M2.5 | 💰 | 备选 | -| Kimi K2.5 | 💰 | 长上下文(10 万字+) | +| 模型 | 价格 | 推荐场景 | +| ------------ | ---- | -------------------- | +| 智谱 GLM-5 | 💰 | 中文剧本首选 | +| Claude 3.5 | 💰💰 | 高质量长文本 | +| MiniMax M2.5 | 💰 | 备选 | +| Kimi K2.5 | 💰 | 长上下文(10 万字+) | > 💡 系统会**自动降级**:首选不可用时自动切换到备选 Provider。 ### 2.2 图像模型(必需) -| 模型 | 推荐 | 备注 | -|------|------|------| +| 模型 | 推荐 | 备注 | +| ------------ | ------ | --------------------- | | Seedream 5.0 | ⭐⭐⭐ | 动画/插画首选(字节) | -| Kling 1.6 | ⭐⭐ | 视频生成(快手) | -| Vidu Q3 | ⭐ | 高一致性(生数) | +| Kling 1.6 | ⭐⭐ | 视频生成(快手) | +| Vidu Q3 | ⭐ | 高一致性(生数) | ### 2.3 TTS 模型 @@ -79,13 +79,13 @@ version: '>=3.0' ### 3.1 配置选项 -| 选项 | 推荐 | 备选 | -|------|------|------| -| 质量 | Balanced | Fast / High | -| 分辨率 | 1080p | 720p / 4K | -| 宽高比 | 16:9 | 9:16(竖屏)/ 1:1 | -| 风格 | Cinematic | Anime / Realistic | -| AI 模型 | 默认降级链 | 自定义排序 | +| 选项 | 推荐 | 备选 | +| ------- | ---------- | ----------------- | +| 质量 | Balanced | Fast / High | +| 分辨率 | 1080p | 720p / 4K | +| 宽高比 | 16:9 | 9:16(竖屏)/ 1:1 | +| 风格 | Cinematic | Anime / Realistic | +| AI 模型 | 默认降级链 | 自定义排序 | ### 3.2 点击开始 @@ -117,12 +117,12 @@ version: '>=3.0' 完成后系统**自动通知**,你可以: -| 操作 | 说明 | -|------|------| -| **▶ 预览** | 应用内播放 | -| **💾 下载** | 保存为 MP4 / WebM / MOV | -| **🔄 重新生成** | 用不同参数重跑 | -| **✏️ 编辑** | 跳转到 Manual 模式精调 | +| 操作 | 说明 | +| --------------- | ----------------------- | +| **▶ 预览** | 应用内播放 | +| **💾 下载** | 保存为 MP4 / WebM / MOV | +| **🔄 重新生成** | 用不同参数重跑 | +| **✏️ 编辑** | 跳转到 Manual 模式精调 | --- diff --git a/docs/index.md b/docs/index.md index 52890d5f..c0f49295 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,15 +1,15 @@ --- layout: home -title: frame-fab +title: Story Weaver titleTemplate: false hero: - name: 'frame·fab' + name: 'Story Weaver' text: 'AI 漫剧创作平台' tagline: '输入一本小说,AI 自动把它拍成一部漫剧——你只需要按「开始」' image: src: /logo.svg - alt: frame-fab · AI 漫剧创作平台 + alt: Story Weaver · AI 漫剧创作平台 actions: - theme: brand text: 快速开始 → @@ -19,304 +19,44 @@ hero: link: /developer-guide/architecture - theme: alt text: GitHub ⭐ - link: https://github.com/Agions/frame-fab - - theme: alt - text: 下载桌面端 - link: https://github.com/Agions/frame-fab/releases + link: https://github.com/Agions/story-weaver features: - icon: 🎬 title: 双模式工作流 - details: Manual 模式(七步半自动,逐步审批)+ Autonomous 模式(10 步全自主 + Self-Review Loop + Quality Gate),适配精细化与批量场景 + details: Manual 七步半自动(逐步审批)+ Autonomous 全自动 Pipeline(Self-Review + Quality Gate) - icon: 🧠 title: 多模型 AI 编排 - details: 智谱 GLM-5 / MiniMax M2.5 / 月之暗面 Kimi K2.5 / 字节 Seedream 5.0 / 快手 Kling 1.6 / Edge TTS,完整 Fallback Chain + details: 智谱 GLM-5 / MiniMax M2.5 / Kimi K2.5 / Seedream 5.0 / Kling 1.6 / Edge TTS,完整 Fallback Chain - icon: 🦀 title: Rust 高性能后端 - details: Tauri 2.1 + FFmpeg 子进程,零桥接开销;安装包 30 MB,冷启动 < 1s + details: Tauri 2.1 + FFmpeg 子进程,30 MB 包体积,冷启动 < 1s - icon: 🔄 - title: 断点续传 + 修复 - details: PipelineEngine 30s 自动 Checkpoint,自审循环自动修复不合格输出,质量门禁不通过可降级继续 + title: 断点续传 + 自修复 + details: 30s 自动 Checkpoint,Self-Review Loop 自动修复不合格输出 - icon: 🎙️ title: 一站式音视频 - details: Edge TTS 配音、唇形同步、字幕嵌入、FFmpeg 合成导出;支持 MP4 / WebM / MOV 多格式输出 + details: Edge TTS 配音、字幕嵌入、FFmpeg 合成,MP4 / WebM / MOV 多格式输出 - icon: 🏗️ - title: 桌面优先架构 - details: 全局快捷键、系统托盘、原生菜单、文件 I/O;macOS / Windows / Linux 三端一致的体验 + title: 桌面原生 + details: 全局快捷键、系统托盘、原生菜单,macOS / Windows / Linux 三端一致 --- - -
- 🆕 v2.2.3 - 44 个大文件拆分重构 · 195+ 子模块 · 0 调用方影响 - 查看更新 → -
- -
- -
-

从小说到漫剧,只需 5 步

-

把传统影视制作 3 个月的周期压缩到 5 天,把创作还给故事本身

-
- -
-
-
01
-
-
📖 导入小说
-
支持 TXT / Word / Markdown / PDF / 粘贴文本;自动识别章节结构与人物
-
-
-
-
-
02
-
-
🧠 AI 分析
-
GLM-5 拆解剧情脉络、识别主要人物、提取场景要素、生成剧本骨架
-
-
-
-
-
03
-
-
🎨 分镜设计
-
导演级分镜规划:景别 / 运镜 / 时长;Seedream 5.0 生成首帧图
-
-
-
-
-
04
-
-
🎬 视频渲染
-
Kling 1.6 / Vidu Q3 / Seedance 2.0 多模型融合,逐镜头运镜控制 + 角色一致性
-
-
-
-
-
05
-
-
🎞️ 合成导出
-
Edge TTS 配音 + 字幕嵌入 + FFmpeg 合成;MP4 / WebM / MOV,9:16 竖屏 + 16:9 横屏
-
-
-
- -
-

为什么选择 frame·fab

-

市面上唯一的开源桌面端 AI 漫剧创作平台

-
- -
-
-
🖥️
-
桌面原生体验
-
Tauri 2.1 跨平台(macOS / Windows / Linux),系统托盘 / 全局快捷键 / 原生菜单,30 MB 包体积冷启动 < 1s
-
-
-
🤖
-
策略模式 AI 编排
-
ProviderRegistry + Fallback Chain,6+ 文字模型、4+ 图像模型、3+ TTS 自动降级到备用供应商
-
-
-
-
质量门禁 + 自审
-
10 步流水线每步都有 Quality Gate 评分,不合格的输出自动重新生成;Self-Review Loop 修复问题
-
-
-
💾
-
断点续传不丢失
-
30s 自动 Checkpoint,应用崩溃 / 断网 / 切换设备后能从最近状态恢复;不重做已生成的镜头
-
-
-
🆓
-
完全开源
-
MIT 协议,源码 100% 公开,无云端锁定,无月费;自己用 API Key,无中间商赚差价
-
-
-
🧩
-
可扩展架构
-
PipelineEngine + 步骤框架,新模型 / 新工作流只需要写一个 Step 类;插件式 ProviderRegistry
-
-
- -
-

核心技术栈

-

每一项都是 2026 年的最优解

-
- -
-
-
前端
-
- React 18 - TypeScript 5 - Vite 6 - Tailwind v4 - Radix UI -
-
-
-
桌面
-
- Tauri 2.1 - Rust 1.80 - FFmpeg 子进程 -
-
-
-
AI 编排
-
- GLM-5 - Kimi K2.5 - M2.5 - Seedream 5.0 - Kling 1.6 - Vidu Q3 - Edge TTS -
-
-
-
工程
-
- Jest 30 - Playwright - ESLint - Husky - VitePress -
-
-
- -
-

我们的定位

-

开源桌面端 vs 商业 SaaS vs C 端 App

-
- -
-
-
特性
-
frame·fab
开源 · 桌面端
-
万兴剧厂
商业 SaaS
-
梦灵 AI
C 端 App
-
-
-
数据本地化
-
✅ 完全本地
-
❌ 上传云端
-
❌ 上传云端
-
-
-
使用成本
-
✅ 一次安装
-
⚠️ 月费 ¥99+
-
⚠️ 积分制
-
-
-
模型选择
-
✅ 自己配置 API
-
⚠️ 平台固定
-
❌ 单一模型
-
-
-
断点续传
-
✅ 本地 Checkpoint
-
⚠️ 服务端
-
⚠️ 重新上传
-
-
-
离线可用
-
✅ 取决于模型
-
❌ 必须联网
-
❌ 必须联网
-
-
-
开源协议
-
✅ MIT
-
❌ 闭源
-
❌ 闭源
-
-
-
团队协作
-
⚠️ 单机优先
-
✅ 团队版
-
⚠️ 邀请制
-
-
+ -
-
-
5
-
天完成漫剧
-
vs 传统 3 个月
-
-
-
72%
-
人力成本下降
-
vs 传统流程
-
-
-
30 MB
-
安装包体积
-
冷启动 < 1s
-
-
-
10
-
步自主流水线
-
全流程自动化
-
-
+::: tip 为什么选择 Story Weaver? -
-
准备好把小说拍成漫剧了吗?
-
3 分钟下载安装,立即开始创作
- -
+市面上唯一的**开源桌面端** AI 漫剧创作平台。数据完全本地、MIT 协议、无云端锁定。 +::: -
-

📚 文档导航

-

不同角色 · 不同的入口

-
+ - +## 文档导航 -
+| 入口 | 内容 | +| ----------------------------------------- | ------------------------------ | +| [快速开始](/getting-started/installation) | 安装 → 配置 → 首个项目 | +| [用户手册](/user-guide/) | 工作流 · 双模式 · 全流程指南 | +| [API 参考](/api/overview) | 7 大核心服务 · 类型完备 | +| [架构设计](/developer-guide/architecture) | 整体架构 · DDD 分层 · Pipeline | +| [构建部署](/deployment/) | Tauri 三端构建 · 自动更新 | diff --git a/docs/performance/benchmark-2.2.3.md b/docs/performance/benchmark-2.2.3.md index ffbb75fb..05759f4e 100644 --- a/docs/performance/benchmark-2.2.3.md +++ b/docs/performance/benchmark-2.2.3.md @@ -5,7 +5,7 @@ category: performance version: 3.0 --- -# frame-fab v2.2.3 性能基准报告 +# Story Weaver v2.2.3 性能基准报告 > 测量日期: 2026-06-10 > 测量环境: Tauri 2.1 + React 18 + TypeScript 5 + Node.js 20.x @@ -13,7 +13,7 @@ version: 3.0 ## 1. v2.2.0 → v2.2.3 Patch 演进 -| 指标 | v2.2.0 | v2.2.3 | 变化 | +| 指标 | v2.2.0 | v2.2.3 | 变化 | | -------------------------- | ------- | ---------- | -------------------- | | **src TS 文件数** | ~140 | **499** | +256% | | **service 文件数** | 13 | **42** | +223% | @@ -69,7 +69,7 @@ version: 3.0 ### 3.1 10 步 Pipeline 基准(本地内存执行,无真实 AI 调用) -| 步骤 | v2.2.0 | v2.2.3 | 变化 | +| 步骤 | v2.2.0 | v2.2.3 | 变化 | | -------------------- | ---------- | ----------- | ------- | | step-import | 12 ms | 14 ms | +17% | | step-analysis | 45 ms | 52 ms | +16% | @@ -88,7 +88,7 @@ version: 3.0 ### 3.2 自主模式 (AutoPipelineEngine) -| 指标 | v2.2.0 | v2.2.3 | 备注 | +| 指标 | v2.2.0 | v2.2.3 | 备注 | | -------------------------- | ------------- | ----------------- | --------------- | | QualityGate 评估开销 | < 5 ms / step | **< 5 ms / step** | 持平 | | SelfReviewLoop 循环开销 | < 50 ms / 轮 | **< 50 ms / 轮** | 持平 | @@ -126,7 +126,7 @@ version: 3.0 ### 4.2 内存占用 -| 场景 | v2.2.0 | v2.2.3 | 变化 | +| 场景 | v2.2.0 | v2.2.3 | 变化 | | ------------------------- | ------- | ----------- | ---- | | 启动后空闲 | ~85 MB | **~78 MB** | -8% | | 编辑器加载(含 Timeline) | ~145 MB | **~135 MB** | -7% | @@ -137,7 +137,7 @@ version: 3.0 ## 5. 测试性能 -| 指标 | v2.2.0 | v2.2.3 | 变化 | +| 指标 | v2.2.0 | v2.2.3 | 变化 | | ---------------------- | ------ | --------- | ---- | | **Test Suites** | 90 | **79** | -12% | | **Test 用例** | ~1100 | **1375** | +25% | @@ -214,7 +214,7 @@ pnpm bench:pipeline pnpm test:e2e:perf # Rust 二进制大小 -du -sh src-tauri/target/release/bundle/macos/frame-fab.app +du -sh src-tauri/target/release/bundle/macos/Story Weaver.app # 测试时长 npx jest --listTests | wc -l # suite 数 diff --git a/docs/superpowers/plans/2026-07-06-frame-fab-refactor.md b/docs/superpowers/plans/2026-07-06-frame-fab-refactor.md index 634f6b9e..9bd2b94f 100644 --- a/docs/superpowers/plans/2026-07-06-frame-fab-refactor.md +++ b/docs/superpowers/plans/2026-07-06-frame-fab-refactor.md @@ -1,4 +1,4 @@ -# frame-fab 全面重构实施计划 +# Story Weaver 全面重构实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/docs/user-guide/autonomous-mode.md b/docs/user-guide/autonomous-mode.md index 016781bf..ef097dc5 100644 --- a/docs/user-guide/autonomous-mode.md +++ b/docs/user-guide/autonomous-mode.md @@ -1,6 +1,6 @@ --- title: Autonomous 模式 -description: frame-fab Autonomous 全自动模式:零参与一键成片、Self-Review Loop、Checkpoint 断点续传 +description: Story Weaver Autonomous 全自动模式:零参与一键成片、Self-Review Loop、Checkpoint 断点续传 category: user-guide version: '>=3.0' --- @@ -13,14 +13,14 @@ version: '>=3.0' ## 一、核心特性 -| 特性 | 说明 | -|------|------| -| 🕐 **零人工干预** | 提供原材料后无需任何操作 | -| 🧠 **AI 自审** | 每步自动 Quality Gate,不合格自动修复 | -| 💾 **断点续传** | 支持中途暂停、刷新继续 | -| 🔄 **自动降级** | 主模型不可用时自动切换备选 | -| 📊 **实时进度** | Web UI 实时展示每步状态 | -| 💰 **成本预估** | 运行前自动估算 API 成本 | +| 特性 | 说明 | +| ----------------- | ------------------------------------- | +| 🕐 **零人工干预** | 提供原材料后无需任何操作 | +| 🧠 **AI 自审** | 每步自动 Quality Gate,不合格自动修复 | +| 💾 **断点续传** | 支持中途暂停、刷新继续 | +| 🔄 **自动降级** | 主模型不可用时自动切换备选 | +| 📊 **实时进度** | Web UI 实时展示每步状态 | +| 💰 **成本预估** | 运行前自动估算 API 成本 | --- @@ -28,29 +28,29 @@ version: '>=3.0' ### 步骤 1:新建项目 -打开 frame-fab → 点击「**新建项目**」→ 选择「**Autonomous 模式**」 +打开 Story Weaver → 点击「**新建项目**」→ 选择「**Autonomous 模式**」 ### 步骤 2:选择输入文件 支持格式: -| 格式 | 扩展名 | 适用场景 | -|------|--------|---------| -| 纯文本 | `.txt` | 原始小说 | -| Word | `.docx` | Word 文档 | -| Markdown | `.md` | 编剧脚本 | -| PDF | `.pdf` | 已发布作品 | -| 直接粘贴 | — | 短篇/片段 | +| 格式 | 扩展名 | 适用场景 | +| -------- | ------- | ---------- | +| 纯文本 | `.txt` | 原始小说 | +| Word | `.docx` | Word 文档 | +| Markdown | `.md` | 编剧脚本 | +| PDF | `.pdf` | 已发布作品 | +| 直接粘贴 | — | 短篇/片段 | ### 步骤 3:配置基础选项 -| 选项 | 推荐 | 说明 | -|------|------|------| -| **质量预设** | Balanced | fast / balanced / high | -| **分辨率** | 1080p | 720p / 1080p / 4k | -| **宽高比** | 16:9 | 9:16(短视频)/ 16:9(横屏)/ 1:1 | -| **风格** | Cinematic | cinematic / anime / realistic | -| **AI 模型** | 默认降级链 | 智谱首选,Claude 备选 | +| 选项 | 推荐 | 说明 | +| ------------ | ---------- | --------------------------------- | +| **质量预设** | Balanced | fast / balanced / high | +| **分辨率** | 1080p | 720p / 1080p / 4k | +| **宽高比** | 16:9 | 9:16(短视频)/ 16:9(横屏)/ 1:1 | +| **风格** | Cinematic | cinematic / anime / realistic | +| **AI 模型** | 默认降级链 | 智谱首选,Claude 备选 | ### 步骤 4:点击「开始」 @@ -86,7 +86,7 @@ AI 立即开始 10 步流水线。你可以在 UI 上看到: - ❌ 断网 - ❌ 切换设备 -下次启动时 frame-fab 会**自动检测**未完成项目,提示「**继续上次任务**」。 +下次启动时 Story Weaver 会**自动检测**未完成项目,提示「**继续上次任务**」。 也可以手动管理: @@ -125,11 +125,11 @@ AI 立即开始 10 步流水线。你可以在 UI 上看到: **触发条件**: -| 评分维度 | 阈值 | 修复策略 | -|---------|------|---------| -| 角色一致性 | < 0.85 | 重新生成参考图 | -| 视觉质量 | < 0.80 | 优化提示词 + 切换 Provider | -| 脚本对齐 | < 0.90 | 重读原文,补充上下文 | +| 评分维度 | 阈值 | 修复策略 | +| ---------- | ------ | -------------------------- | +| 角色一致性 | < 0.85 | 重新生成参考图 | +| 视觉质量 | < 0.80 | 优化提示词 + 切换 Provider | +| 脚本对齐 | < 0.90 | 重读原文,补充上下文 | --- diff --git a/docs/user-guide/character-design.md b/docs/user-guide/character-design.md index 4a1744c7..29268be2 100644 --- a/docs/user-guide/character-design.md +++ b/docs/user-guide/character-design.md @@ -1,19 +1,19 @@ --- title: 角色设计 -description: frame-fab 角色设定卡自动生成、跨场景一致性维护、自定义角色库 +description: Story Weaver 角色设定卡自动生成、跨场景一致性维护、自定义角色库 category: user-guide version: '>=3.0' --- # 角色设计 -> 本文档介绍 frame-fab 中的角色设计功能:角色设定卡生成、跨场景一致性维护、自定义角色。 +> 本文档介绍 Story Weaver 中的角色设计功能:角色设定卡生成、跨场景一致性维护、自定义角色。 --- ## 概述 -角色设计是 frame-fab 全自动漫剧制作流程中的关键步骤。在完成故事分析和脚本生成后,系统会自动提取剧本中的所有角色,为每个角色生成独立的设定卡,确保在整个视频制作过程中角色外观的一致性。 +角色设计是 Story Weaver 全自动漫剧制作流程中的关键步骤。在完成故事分析和脚本生成后,系统会自动提取剧本中的所有角色,为每个角色生成独立的设定卡,确保在整个视频制作过程中角色外观的一致性。 ### 角色设计流程 @@ -27,22 +27,22 @@ version: '>=3.0' ### 什么是角色设定卡 -角色设定卡(Character Profile)是角色的核心文档,包含角色的外貌特征、服装、表情习惯、动作特点等信息。frame-fab 会为每个角色自动生成标准化的设定卡。 +角色设定卡(Character Profile)是角色的核心文档,包含角色的外貌特征、服装、表情习惯、动作特点等信息。Story Weaver 会为每个角色自动生成标准化的设定卡。 ### 设定卡内容 一份完整的角色设定卡包含以下内容: -| 字段 | 说明 | 示例 | +| 字段 | 说明 | 示例 | | ------------ | ------------------ | ------------------------ | -| **姓名** | 角色名称 | 李明 | -| **性别** | 角色性别 | 男/女/其他 | -| **年龄** | 角色年龄 | 28岁 | +| **姓名** | 角色名称 | 李明 | +| **性别** | 角色性别 | 男/女/其他 | +| **年龄** | 角色年龄 | 28岁 | | **外貌描述** | 五官、发型、体型等 | 短黑发、方形脸、锐利眼神 | -| **服装风格** | 常穿服装类型 | 商务休闲装、深色西装 | -| **表情特点** | 常见表情 | 严肃、偶尔微笑 | -| **动作习惯** | 标志性动作 | 思考时摸下巴 | -| **声线特征** | 配音建议 | 低沉、沉稳 | +| **服装风格** | 常穿服装类型 | 商务休闲装、深色西装 | +| **表情特点** | 常见表情 | 严肃、偶尔微笑 | +| **动作习惯** | 标志性动作 | 思考时摸下巴 | +| **声线特征** | 配音建议 | 低沉、沉稳 | ### 查看角色设定卡 @@ -62,7 +62,7 @@ project/ ### 自动一致性维护 -frame-fab 的核心能力之一是**跨场景角色一致性**。系统会在以下环节进行一致性检查: +Story Weaver 的核心能力之一是**跨场景角色一致性**。系统会在以下环节进行一致性检查: 1. **生成时检查** — 渲染关键帧时,系统会强制使用角色设定卡中的描述 2. **Self-Review 检查** — AI 会对渲染结果进行审核,不一致则重新生成 @@ -123,11 +123,11 @@ character_consistency_check(frame_image, character_profile): 支持导入已有的角色设计: -| 格式 | 说明 | +| 格式 | 说明 | | ------- | ------------------ | | `.json` | 标准角色设定卡格式 | -| `.png` | 角色参考图 | -| `.word` | 角色描述文档 | +| `.png` | 角色参考图 | +| `.word` | 角色描述文档 | ### 角色库 diff --git a/docs/user-guide/import-analysis.md b/docs/user-guide/import-analysis.md index ec353487..f5852e28 100644 --- a/docs/user-guide/import-analysis.md +++ b/docs/user-guide/import-analysis.md @@ -1,25 +1,25 @@ --- title: 导入与分析 -description: frame-fab 导入与内容分析:多格式支持、自动章节切分、人物/场景识别 +description: Story Weaver 导入与内容分析:多格式支持、自动章节切分、人物/场景识别 category: user-guide version: '>=3.0' --- # 导入与分析 -> frame-fab 的**第 1-2 步**:把小说/剧本导入系统,AI 自动分析结构。 +> Story Weaver 的**第 1-2 步**:把小说/剧本导入系统,AI 自动分析结构。 --- ## 一、支持的文件格式 -| 格式 | 扩展名 | 大小限制 | 备注 | -|------|--------|---------|------| -| 纯文本 | `.txt` | 10 MB | 首选,识别最准 | -| Markdown | `.md` | 10 MB | 保留结构 | -| Word | `.docx` | 20 MB | 自动提取文本 | -| PDF | `.pdf` | 20 MB | OCR 后提取 | -| 直接粘贴 | — | 50 万字 | 适合超长篇 | +| 格式 | 扩展名 | 大小限制 | 备注 | +| -------- | ------- | -------- | -------------- | +| 纯文本 | `.txt` | 10 MB | 首选,识别最准 | +| Markdown | `.md` | 10 MB | 保留结构 | +| Word | `.docx` | 20 MB | 自动提取文本 | +| PDF | `.pdf` | 20 MB | OCR 后提取 | +| 直接粘贴 | — | 50 万字 | 适合超长篇 | > **不支持的格式**:EPUB(请先转纯文本)、扫描版 PDF(需先 OCR)、加密 PDF。 @@ -30,12 +30,12 @@ version: '>=3.0' ### 2.1 桌面端 ``` -frame-fab → 新建项目 → 选择文件 → 自动解析 +Story Weaver → 新建项目 → 选择文件 → 自动解析 ``` ### 2.2 拖拽上传 -直接拖文件到 frame-fab 窗口即可。 +直接拖文件到 Story Weaver 窗口即可。 ### 2.3 API 调用 diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md index 5cef0f66..c67b4462 100644 --- a/docs/user-guide/index.md +++ b/docs/user-guide/index.md @@ -1,52 +1,52 @@ --- title: 用户指南 -description: frame-fab 用户手册索引:双模式工作流、剧本/角色/分镜/渲染/合成 全流程 +description: Story Weaver 用户手册索引:双模式工作流、剧本/角色/分镜/渲染/合成 全流程 category: user-guide version: '>=3.0' --- # 用户指南 -> 欢迎使用 **frame-fab v2.2.3**!本指南帮助你从零开始,把一本小说自动拍成漫剧。 +> 欢迎使用 **Story Weaver v2.2.3**!本指南帮助你从零开始,把一本小说自动拍成漫剧。 --- ## 🎬 工作流概览 -| 文档 | 说明 | -|------|------| -| [工作流概览](./workflow-overview.md) | 10 步流水线 + 双模式(Autonomous/Manual) | -| [Autonomous 模式](./autonomous-mode.md) | 全自动一键成片(推荐新手) | -| [Manual 模式](./manual-mode.md) | 逐步审批精细控制 | -| [导入与分析](./import-analysis.md) | 小说/剧本导入 + 章节切分 + 人物识别 | -| [脚本生成](./script-generation.md) | 从小说/剧本文本生成结构化剧本 | -| [角色设计](./character-design.md) | 角色设定卡 + 跨场景一致性 | -| [分镜设计](./storyboard-design.md) | 分镜脚本 + 参考图生成 | -| [渲染与导出](./rendering-export.md) | TTS/字幕/FFmpeg 合成 MP4 | +| 文档 | 说明 | +| --------------------------------------- | ----------------------------------------- | +| [工作流概览](./workflow-overview.md) | 10 步流水线 + 双模式(Autonomous/Manual) | +| [Autonomous 模式](./autonomous-mode.md) | 全自动一键成片(推荐新手) | +| [Manual 模式](./manual-mode.md) | 逐步审批精细控制 | +| [导入与分析](./import-analysis.md) | 小说/剧本导入 + 章节切分 + 人物识别 | +| [脚本生成](./script-generation.md) | 从小说/剧本文本生成结构化剧本 | +| [角色设计](./character-design.md) | 角色设定卡 + 跨场景一致性 | +| [分镜设计](./storyboard-design.md) | 分镜脚本 + 参考图生成 | +| [渲染与导出](./rendering-export.md) | TTS/字幕/FFmpeg 合成 MP4 | --- ## 🚀 快速路径 -| 你的需求 | 推荐起点 | -|---------|---------| -| **第一次使用** | 跟着 [工作流概览](./workflow-overview.md) 走一遍 | -| **只想跑通** | 直接看 [Autonomous 模式](./autonomous-mode.md) 一键成片 | -| **想细调** | 阅读 [Manual 模式](./manual-mode.md) 的逐步审批流程 | -| **解决角色漂移** | 跳到 [角色设计 - 一致性](./character-design.md#角色一致性) | -| **导出失败** | 跳到 [渲染导出 - 常见问题](./rendering-export.md#八常见问题) | +| 你的需求 | 推荐起点 | +| ---------------- | ------------------------------------------------------------ | +| **第一次使用** | 跟着 [工作流概览](./workflow-overview.md) 走一遍 | +| **只想跑通** | 直接看 [Autonomous 模式](./autonomous-mode.md) 一键成片 | +| **想细调** | 阅读 [Manual 模式](./manual-mode.md) 的逐步审批流程 | +| **解决角色漂移** | 跳到 [角色设计 - 一致性](./character-design.md#角色一致性) | +| **导出失败** | 跳到 [渲染导出 - 常见问题](./rendering-export.md#八常见问题) | --- ## 🧩 双模式对比 -| 维度 | Autonomous Mode | Manual Mode | -|------|----------------|-------------| -| **用户参与** | 零(仅提供原材料) | 高(每步审批) | -| **AI 自审** | ✅ 启用(每步 + 重试 ≤3) | ❌ 不启用 | -| **断点续传** | ✅ 30s 自动 Checkpoint | ❌ 不支持 | -| **适用场景** | 快速成片、批量生产 | 定制化、特定需求 | -| **预估时间** | 15-30 min(短篇) | 数小时(取决于细调) | +| 维度 | Autonomous Mode | Manual Mode | +| ------------ | ------------------------- | -------------------- | +| **用户参与** | 零(仅提供原材料) | 高(每步审批) | +| **AI 自审** | ✅ 启用(每步 + 重试 ≤3) | ❌ 不启用 | +| **断点续传** | ✅ 30s 自动 Checkpoint | ❌ 不支持 | +| **适用场景** | 快速成片、批量生产 | 定制化、特定需求 | +| **预估时间** | 15-30 min(短篇) | 数小时(取决于细调) | --- diff --git a/docs/user-guide/manual-mode.md b/docs/user-guide/manual-mode.md index 00e2112f..01c40c85 100644 --- a/docs/user-guide/manual-mode.md +++ b/docs/user-guide/manual-mode.md @@ -1,6 +1,6 @@ --- title: Manual 模式 -description: frame-fab Manual 半自动模式:逐步审批精细控制、跳过自审循环、人工把关 +description: Story Weaver Manual 半自动模式:逐步审批精细控制、跳过自审循环、人工把关 category: user-guide version: '>=3.0' --- @@ -13,14 +13,14 @@ version: '>=3.0' ## 一、与 Autonomous 模式的区别 -| 维度 | Autonomous | Manual | -|------|-----------|--------| -| **用户参与度** | 零 | **高**(每步) | -| **Quality Gate** | 自动 | ⚠️ 仅提示 | -| **Self-Review Loop** | 启用 | ❌ 关闭 | -| **断点续传** | 启用 | ❌ 不支持 | -| **适用场景** | 批量、快速 | 精细、定制 | -| **预估时间** | 15-30 min(短篇) | 数小时(取决于细调) | +| 维度 | Autonomous | Manual | +| -------------------- | ----------------- | -------------------- | +| **用户参与度** | 零 | **高**(每步) | +| **Quality Gate** | 自动 | ⚠️ 仅提示 | +| **Self-Review Loop** | 启用 | ❌ 关闭 | +| **断点续传** | 启用 | ❌ 不支持 | +| **适用场景** | 批量、快速 | 精细、定制 | +| **预估时间** | 15-30 min(短篇) | 数小时(取决于细调) | --- @@ -28,7 +28,7 @@ version: '>=3.0' ### 步骤 1:新建项目(Manual 模式) -打开 frame-fab → 点击「**新建项目**」→ 选择「**Manual 模式**」 +打开 Story Weaver → 点击「**新建项目**」→ 选择「**Manual 模式**」 ### 步骤 2:选择输入文件 @@ -62,12 +62,12 @@ version: '>=3.0' 每步完成后,你可以: -| 操作 | 说明 | -|------|------| -| **✅ 通过** | 进入下一步 | -| **✏️ 编辑** | 修改产物后再进入下一步 | -| **🔄 重新执行** | 用不同参数重跑此步 | -| **⏭️ 跳过** | 直接进入下一步(用默认结果) | +| 操作 | 说明 | +| --------------- | ---------------------------- | +| **✅ 通过** | 进入下一步 | +| **✏️ 编辑** | 修改产物后再进入下一步 | +| **🔄 重新执行** | 用不同参数重跑此步 | +| **⏭️ 跳过** | 直接进入下一步(用默认结果) | --- @@ -75,19 +75,19 @@ version: '>=3.0' 不同步骤产物的**可编辑程度**不同: -| 步骤 | 产物 | 可编辑 | -|------|------|--------| -| 1 导入 | 章节切分 | ✅ 增删章节、修改标题 | -| 2 分析 | 人物/场景清单 | ✅ 添加/删除人物 | -| 3 脚本 | JSON 剧本 | ✅ 完全可编辑 | -| 4 角色 | 角色设定卡 | ✅ 修改外观/服装 | -| 5 场景 | 场景规划 | ✅ 修改镜头 | -| 6 分镜 | 分镜脚本 | ✅ 修改画面描述 | -| 7 渲染 | 关键帧 | ⚠️ 仅"重新渲染" | -| 8 剪辑 | 视频片段 | ⚠️ 仅"重新剪辑" | -| 9 配音 | 音频 | ⚠️ 切换 voice 重生成 | -| 10 字幕 | SRT/VTT | ✅ 完全可编辑 | -| 11 导出 | MP4 | ⚠️ 仅"重新导出" | +| 步骤 | 产物 | 可编辑 | +| ------- | ------------- | --------------------- | +| 1 导入 | 章节切分 | ✅ 增删章节、修改标题 | +| 2 分析 | 人物/场景清单 | ✅ 添加/删除人物 | +| 3 脚本 | JSON 剧本 | ✅ 完全可编辑 | +| 4 角色 | 角色设定卡 | ✅ 修改外观/服装 | +| 5 场景 | 场景规划 | ✅ 修改镜头 | +| 6 分镜 | 分镜脚本 | ✅ 修改画面描述 | +| 7 渲染 | 关键帧 | ⚠️ 仅"重新渲染" | +| 8 剪辑 | 视频片段 | ⚠️ 仅"重新剪辑" | +| 9 配音 | 音频 | ⚠️ 切换 voice 重生成 | +| 10 字幕 | SRT/VTT | ✅ 完全可编辑 | +| 11 导出 | MP4 | ⚠️ 仅"重新导出" | --- diff --git a/docs/user-guide/rendering-export.md b/docs/user-guide/rendering-export.md index 087f12db..641c372c 100644 --- a/docs/user-guide/rendering-export.md +++ b/docs/user-guide/rendering-export.md @@ -1,13 +1,13 @@ --- title: 渲染与导出 -description: frame-fab 视频渲染与最终导出:FFmpeg 合成、字幕嵌入、多格式输出、批量导出 +description: Story Weaver 视频渲染与最终导出:FFmpeg 合成、字幕嵌入、多格式输出、批量导出 category: user-guide version: '>=3.0' --- # 渲染与导出 -> frame-fab 的**第 10-11 步**:把分镜、配音、字幕、背景音乐合成为最终成片。 +> Story Weaver 的**第 10-11 步**:把分镜、配音、字幕、背景音乐合成为最终成片。 --- @@ -39,12 +39,12 @@ FFmpeg 合成 ## 二、支持的输出格式 -| 格式 | 编码 | 适用场景 | 浏览器兼容 | -|------|------|---------|-----------| -| **MP4 (H.264)** | `libx264 + aac` | 通用首选 | ✅ 全部 | -| **WebM (VP9)** | `libvpx-vp9 + opus` | 网页嵌入 | ✅ Chrome/Firefox | -| **MOV (ProRes)** | `prores_ks + pcm_s24le` | 专业剪辑 | ⚠️ 桌面端 | -| **GIF** | `gif` | 短预览 | ✅ 全部 | +| 格式 | 编码 | 适用场景 | 浏览器兼容 | +| ---------------- | ----------------------- | -------- | ----------------- | +| **MP4 (H.264)** | `libx264 + aac` | 通用首选 | ✅ 全部 | +| **WebM (VP9)** | `libvpx-vp9 + opus` | 网页嵌入 | ✅ Chrome/Firefox | +| **MOV (ProRes)** | `prores_ks + pcm_s24le` | 专业剪辑 | ⚠️ 桌面端 | +| **GIF** | `gif` | 短预览 | ✅ 全部 | --- @@ -52,40 +52,40 @@ FFmpeg 合成 ### 3.1 视频设置 -| 选项 | 推荐 | 范围 | -|------|------|------| -| 分辨率 | 1080p | 720p / 1080p / 4K | -| 帧率 | 30 fps | 24 / 30 / 60 | -| 码率 | 8 Mbps | 2-50 Mbps | +| 选项 | 推荐 | 范围 | +| -------- | ------ | -------------------- | +| 分辨率 | 1080p | 720p / 1080p / 4K | +| 帧率 | 30 fps | 24 / 30 / 60 | +| 码率 | 8 Mbps | 2-50 Mbps | | 编码预设 | medium | ultrafast → veryslow | ### 3.2 音频设置 -| 选项 | 推荐 | 范围 | -|------|------|------| -| 编码 | AAC | AAC / MP3 / Opus | +| 选项 | 推荐 | 范围 | +| ------ | -------- | --------------------- | +| 编码 | AAC | AAC / MP3 / Opus | | 采样率 | 48000 Hz | 24000 / 44100 / 48000 | -| 比特率 | 192 kbps | 96-320 kbps | +| 比特率 | 192 kbps | 96-320 kbps | ### 3.3 字幕设置 -| 选项 | 推荐 | -|------|------| +| 选项 | 推荐 | +| ---- | --------------- | | 字体 | 思源黑体 / 苹方 | -| 字号 | 24 px(1080p) | -| 位置 | 底部 | -| 描边 | 2 px 黑色 | +| 字号 | 24 px(1080p) | +| 位置 | 底部 | +| 描边 | 2 px 黑色 | --- ## 四、合成引擎 -frame-fab 使用 **双引擎** 策略: +Story Weaver 使用 **双引擎** 策略: -| 引擎 | 适用 | 性能 | -|------|------|------| +| 引擎 | 适用 | 性能 | +| ----------------------- | ---------- | ---------- | | **FFmpeg (本地子进程)** | 桌面端默认 | ⭐⭐⭐⭐⭐ | -| **FFmpeg.wasm (Web)** | Web 端降级 | ⭐⭐⭐ | +| **FFmpeg.wasm (Web)** | Web 端降级 | ⭐⭐⭐ | --- @@ -133,11 +133,11 @@ const result = await videoCompositorService.compose({ ### 7.1 导出时间估算 -| 项目长度 | 720p | 1080p | 4K | -|---------|------|-------|----| -| 1 分钟 | 10s | 30s | 2min | -| 5 分钟 | 30s | 2min | 10min | -| 30 分钟 | 3min | 10min | 60min | +| 项目长度 | 720p | 1080p | 4K | +| -------- | ---- | ----- | ----- | +| 1 分钟 | 10s | 30s | 2min | +| 5 分钟 | 30s | 2min | 10min | +| 30 分钟 | 3min | 10min | 60min | ### 7.2 质量优化建议 diff --git a/docs/user-guide/script-generation.md b/docs/user-guide/script-generation.md index 85b0b2a0..b0ffca0d 100644 --- a/docs/user-guide/script-generation.md +++ b/docs/user-guide/script-generation.md @@ -1,19 +1,19 @@ --- title: 脚本生成 -description: frame-fab 视频剧本自动生成:结构化剧本、场景拆分、Self-Review 审核 +description: Story Weaver 视频剧本自动生成:结构化剧本、场景拆分、Self-Review 审核 category: user-guide version: '>=3.0' --- # 脚本生成 -> 本文档介绍 frame-fab 中的脚本生成功能:把原始小说/剧本转换为结构化的视频剧本。 +> 本文档介绍 Story Weaver 中的脚本生成功能:把原始小说/剧本转换为结构化的视频剧本。 --- ## 概述 -脚本生成(Script Generation)是 frame-fab 工作流中的核心步骤之一。在完成故事分析后,系统会将原始文本转换为结构化的视频剧本,为后续的角色设计和分镜生成提供清晰的执行蓝图。 +脚本生成(Script Generation)是 Story Weaver 工作流中的核心步骤之一。在完成故事分析后,系统会将原始文本转换为结构化的视频剧本,为后续的角色设计和分镜生成提供清晰的执行蓝图。 ### 脚本生成流程 @@ -23,8 +23,8 @@ version: '>=3.0' ### 输入与输出 -| 输入 | 输出 | -|------|------| +| 输入 | 输出 | +| ----------------- | ------------------------------- | | 原始小说/剧本文本 | 结构化视频剧本(JSON/Markdown) | --- @@ -103,11 +103,11 @@ shot: 根据剧情发展,将故事拆分为多个场景: -| 拆分依据 | 说明 | +| 拆分依据 | 说明 | | -------- | ---------------------------- | | 地点变化 | 场景切换(如从办公室到家中) | -| 时间跳跃 | 日/夜交替、季节变化 | -| 情节段落 | 重大事件节点 | +| 时间跳跃 | 日/夜交替、季节变化 | +| 情节段落 | 重大事件节点 | ### 步骤 3:对话与动作分离 @@ -142,12 +142,12 @@ shot: ### 支持的输出格式 -| 格式 | 用途 | 文件扩展名 | -| -------- | ----------------- | ---------- | -| JSON | 程序处理、API调用 | `.json` | -| Markdown | 阅读、编辑 | `.md` | -| YAML | 配置文件 | `.yaml` | -| Fountain | 专业剧本格式 | `.fountain` | +| 格式 | 用途 | 文件扩展名 | +| -------- | ----------------- | ----------- | +| JSON | 程序处理、API调用 | `.json` | +| Markdown | 阅读、编辑 | `.md` | +| YAML | 配置文件 | `.yaml` | +| Fountain | 专业剧本格式 | `.fountain` | ### Markdown 格式示例 @@ -193,12 +193,12 @@ shot: ### 审核标准 -| 检查项 | 说明 | 阈值 | +| 检查项 | 说明 | 阈值 | | ---------- | -------------------- | ------ | -| 完整性 | 场景、分镜是否完整 | 100% | -| 逻辑连贯性 | 情节是否通顺 | ≥ 0.8 | +| 完整性 | 场景、分镜是否完整 | 100% | +| 逻辑连贯性 | 情节是否通顺 | ≥ 0.8 | | 对话合理性 | 对话是否符合角色性格 | ≥ 0.75 | -| 时长合理性 | 估算时长是否合理 | ±20% | +| 时长合理性 | 估算时长是否合理 | ±20% | ### 重生成机制 @@ -226,7 +226,7 @@ A: 支持。在每个分镜的 `narration` 字段中添加旁白内容,系统 ### Q: 生成的脚本可以导出为其他格式吗? -A: 可以。frame-fab 支持将剧本导出为 JSON、Markdown、YAML、Fountain(专业剧本格式)等。 +A: 可以。Story Weaver 支持将剧本导出为 JSON、Markdown、YAML、Fountain(专业剧本格式)等。 ### Q: 如何批量修改对话内容? diff --git a/docs/user-guide/storyboard-design.md b/docs/user-guide/storyboard-design.md index 06a2983a..840561aa 100644 --- a/docs/user-guide/storyboard-design.md +++ b/docs/user-guide/storyboard-design.md @@ -1,19 +1,19 @@ --- title: 分镜设计 -description: frame-fab 分镜脚本自动生成:景别/运镜/参考图、Self-Review 一致性检查 +description: Story Weaver 分镜脚本自动生成:景别/运镜/参考图、Self-Review 一致性检查 category: user-guide version: '>=3.0' --- # 分镜设计 -> 本文档介绍 frame-fab 中的分镜设计功能:分镜脚本生成、参考图生成、分镜审核机制。 +> 本文档介绍 Story Weaver 中的分镜设计功能:分镜脚本生成、参考图生成、分镜审核机制。 --- ## 概述 -分镜设计(Storyboard Design)是 frame-fab 工作流中的核心步骤。在完成角色设计和脚本生成后,系统会将每个分镜转换为可视化的故事板,包括分镜脚本和对应的参考图。 +分镜设计(Storyboard Design)是 Story Weaver 工作流中的核心步骤。在完成角色设计和脚本生成后,系统会将每个分镜转换为可视化的故事板,包括分镜脚本和对应的参考图。 ### 分镜设计流程 @@ -23,11 +23,11 @@ version: '>=3.0' ### 输出内容 -| 输出 | 说明 | +| 输出 | 说明 | | -------- | ----------------------------------- | | 分镜脚本 | 每个分镜的详细描述(JSON/Markdown) | -| 参考图 | AI 生成的画面参考图(PNG) | -| 镜头参数 | 相机运动、光影参数等 | +| 参考图 | AI 生成的画面参考图(PNG) | +| 镜头参数 | 相机运动、光影参数等 | --- @@ -79,25 +79,25 @@ version: '>=3.0' ### 镜头类型 -| 类型 | 说明 | 使用场景 | +| 类型 | 说明 | 使用场景 | | ------------ | ---------- | -------------- | -| 特写 (ECU) | 脸部或细节 | 强调表情、物品 | -| 近景 (CU) | 胸部以上 | 对话、反应 | -| 中景 (MS) | 膝盖以上 | 日常场景 | -| 中远景 (MLS) | 小腿以上 | 空间感场景 | -| 远景 (LS) | 全身 | 环境展示 | -| 全景 (EWS) | 广阔范围 | 大场面 | +| 特写 (ECU) | 脸部或细节 | 强调表情、物品 | +| 近景 (CU) | 胸部以上 | 对话、反应 | +| 中景 (MS) | 膝盖以上 | 日常场景 | +| 中远景 (MLS) | 小腿以上 | 空间感场景 | +| 远景 (LS) | 全身 | 环境展示 | +| 全景 (EWS) | 广阔范围 | 大场面 | ### 相机运动 -| 运动类型 | 说明 | +| 运动类型 | 说明 | | ------------- | ------------ | -| 固定 | 镜头不动 | -| 推 (Push in) | 镜头向前推进 | +| 固定 | 镜头不动 | +| 推 (Push in) | 镜头向前推进 | | 拉 (Pull out) | 镜头向后拉远 | -| 摇 (Pan) | 镜头左右转动 | -| 移 (Dolly) | 镜头跟随移动 | -| 跟 (Follow) | 镜头跟随角色 | +| 摇 (Pan) | 镜头左右转动 | +| 移 (Dolly) | 镜头跟随移动 | +| 跟 (Follow) | 镜头跟随角色 | --- @@ -136,11 +136,11 @@ deformed, bad anatomy, bad hands ### 图像参数 -| 参数 | 设置 | +| 参数 | 设置 | | ------ | ------------------------ | -| 分辨率 | 1920 × 1080 (16:9) | -| 格式 | PNG | -| 风格 | 电影质感 | +| 分辨率 | 1920 × 1080 (16:9) | +| 格式 | PNG | +| 风格 | 电影质感 | | 长宽比 | 16:9 / 4:3 / 1:1(可选) | ### 一致性保障 @@ -159,12 +159,12 @@ deformed, bad anatomy, bad hands 生成参考图后,系统会自动进行质量审核: -| 检查项 | 说明 | +| 检查项 | 说明 | | ---------- | ------------------------ | | 角色一致性 | 角色外观是否与设定卡一致 | -| 场景准确性 | 场景是否符合脚本描述 | -| 画面质量 | 构图、光影是否合理 | -| 文字可读性 | 画面中文字是否清晰 | +| 场景准确性 | 场景是否符合脚本描述 | +| 画面质量 | 构图、光影是否合理 | +| 文字可读性 | 画面中文字是否清晰 | ### Quality Gate @@ -209,21 +209,21 @@ quality_gate_check(shot): ### 可编辑内容 -| 字段 | 说明 | +| 字段 | 说明 | | -------- | ------------ | | 镜头类型 | 切换镜头大小 | | 相机运动 | 调整运动方式 | | 画面描述 | 重新描述画面 | | 角色位置 | 调整角色站位 | -| 光影设置 | 修改光源 | +| 光影设置 | 修改光源 | ### 快捷操作 -| 操作 | 说明 | +| 操作 | 说明 | | ---------- | ------------------------ | | 批量重生成 | 选中多个分镜统一重新生成 | -| 复制分镜 | 复制现有分镜作为模板 | -| 调整顺序 | 拖拽调整分镜顺序 | +| 复制分镜 | 复制现有分镜作为模板 | +| 调整顺序 | 拖拽调整分镜顺序 | --- diff --git a/docs/user-guide/workflow-overview.md b/docs/user-guide/workflow-overview.md index 0a9148d5..cc867594 100644 --- a/docs/user-guide/workflow-overview.md +++ b/docs/user-guide/workflow-overview.md @@ -1,27 +1,27 @@ --- title: 工作流概览 -description: frame-fab 完整工作流概览:10 步流水线、Autonomous/Manual 双模式、Quality Gate、Self-Review Loop +description: Story Weaver 完整工作流概览:10 步流水线、Autonomous/Manual 双模式、Quality Gate、Self-Review Loop category: user-guide version: '>=3.0' --- # 工作流概览 -> frame-fab 的工作流是一个 **10 步端到端流水线**(实际细分 11 个 stage),支持 **Autonomous(全自动)** 和 **Manual(手动)** 两种模式。 +> Story Weaver 的工作流是一个 **10 步端到端流水线**(实际细分 11 个 stage),支持 **Autonomous(全自动)** 和 **Manual(手动)** 两种模式。 --- ## 一、双模式全景 -| 维度 | Autonomous Mode | Manual Mode | -|------|----------------|-------------| -| **用户操作** | 提供原材料 → 按「开始」→ 等待 | 逐步审批 / 编辑 / 调整 | -| **AI 行为** | 自主分析 → 生成 → 审核 → 修复 | 按指令执行 | -| **Quality Gate** | ✅ 每步自动评分(fail 触发重试) | ⚠️ 仅提示,人工把关 | -| **Self-Review Loop** | ✅ 失败自动重试(≤3 次) | ❌ 不重试 | -| **断点续传** | ✅ 30s 自动 Checkpoint | ❌ 不支持 | -| **适合场景** | 批量生产、快速成片 | 精细定制、特定需求 | -| **预估时间** | 15-30 min(短篇) | 数小时(取决于细调) | +| 维度 | Autonomous Mode | Manual Mode | +| -------------------- | -------------------------------- | ---------------------- | +| **用户操作** | 提供原材料 → 按「开始」→ 等待 | 逐步审批 / 编辑 / 调整 | +| **AI 行为** | 自主分析 → 生成 → 审核 → 修复 | 按指令执行 | +| **Quality Gate** | ✅ 每步自动评分(fail 触发重试) | ⚠️ 仅提示,人工把关 | +| **Self-Review Loop** | ✅ 失败自动重试(≤3 次) | ❌ 不重试 | +| **断点续传** | ✅ 30s 自动 Checkpoint | ❌ 不支持 | +| **适合场景** | 批量生产、快速成片 | 精细定制、特定需求 | +| **预估时间** | 15-30 min(短篇) | 数小时(取决于细调) | --- @@ -41,19 +41,19 @@ graph LR J --> K[11 导出] ``` -| # | 步骤 | 核心产物 | AI 依赖 | Autonomous | -|---|------|---------|---------|-----------| -| 1 | 导入 `import` | 章节切分 + 文本清理 | — | ✅ | -| 2 | 分析 `analysis` | 故事结构 + 人物清单 + 场景识别 | 文本模型 | ✅ | -| 3 | 脚本 `script` | 结构化视频剧本(JSON/MD) | 文本模型 | ✅ | -| 4 | 角色 `character` | 角色设定卡 + 参考图 | 文本 + 图像 | ✅ | -| 5 | 场景 `scene` | 场景清单 + 镜头/景别规划 | 文本模型 | ✅ | -| 6 | 分镜 `storyboard` | 分镜脚本 + 参考图 | 文本 + 图像 | ✅ | -| 7 | 渲染 `render` | 关键帧视频 | 视频模型 | ✅ | -| 8 | 视频剪辑 `video-edit` | 镜头拼接 + 转场 | 视频处理 | ✅ | -| 9 | 配音 `audio` | 配音 + 唇形同步 | TTS + VLM | ✅ | -| 10 | 字幕 `subtitle` | SRT/VTT/ASS | 字幕生成 | ✅ | -| 11 | 导出 `export` | MP4/WebM/MOV | FFmpeg 合成 | ✅ | +| # | 步骤 | 核心产物 | AI 依赖 | Autonomous | +| --- | --------------------- | ------------------------------ | ----------- | ---------- | +| 1 | 导入 `import` | 章节切分 + 文本清理 | — | ✅ | +| 2 | 分析 `analysis` | 故事结构 + 人物清单 + 场景识别 | 文本模型 | ✅ | +| 3 | 脚本 `script` | 结构化视频剧本(JSON/MD) | 文本模型 | ✅ | +| 4 | 角色 `character` | 角色设定卡 + 参考图 | 文本 + 图像 | ✅ | +| 5 | 场景 `scene` | 场景清单 + 镜头/景别规划 | 文本模型 | ✅ | +| 6 | 分镜 `storyboard` | 分镜脚本 + 参考图 | 文本 + 图像 | ✅ | +| 7 | 渲染 `render` | 关键帧视频 | 视频模型 | ✅ | +| 8 | 视频剪辑 `video-edit` | 镜头拼接 + 转场 | 视频处理 | ✅ | +| 9 | 配音 `audio` | 配音 + 唇形同步 | TTS + VLM | ✅ | +| 10 | 字幕 `subtitle` | SRT/VTT/ASS | 字幕生成 | ✅ | +| 11 | 导出 `export` | MP4/WebM/MOV | FFmpeg 合成 | ✅ | > 详细的 10 步实现细节见 [架构设计 - core/pipeline/](../developer-guide/architecture.md#42-corepipeline--流水线引擎)。 @@ -82,12 +82,12 @@ graph LR **评分维度**: -| 维度 | 阈值 | 说明 | -|------|------|------| -| 角色一致性 | ≥ 0.85 | VLM 比对角色外观 | -| 视觉质量 | ≥ 0.80 | 构图/光影合理性 | -| 脚本对齐 | ≥ 0.90 | 与原始剧本的吻合度 | -| 完整性 | 100% | 必填字段不缺 | +| 维度 | 阈值 | 说明 | +| ---------- | ------ | ------------------ | +| 角色一致性 | ≥ 0.85 | VLM 比对角色外观 | +| 视觉质量 | ≥ 0.80 | 构图/光影合理性 | +| 脚本对齐 | ≥ 0.90 | 与原始剧本的吻合度 | +| 完整性 | 100% | 必填字段不缺 | ### 3.2 Self-Review Loop @@ -109,11 +109,11 @@ Autonomous 模式**每 30 秒**自动保存 Checkpoint 到本地: ## 四、成本与时间估算 -| 输入长度 | Autonomous 预估时间 | 预估成本(API 调用) | -|---------|-------------------|---------------------| -| 短篇 (1-3 万字) | 15-30 min | ¥5-15 | -| 中篇 (5-10 万字) | 30-60 min | ¥15-50 | -| 长篇 (10 万字+) | 1-2 h | ¥50-200 | +| 输入长度 | Autonomous 预估时间 | 预估成本(API 调用) | +| ---------------- | ------------------- | -------------------- | +| 短篇 (1-3 万字) | 15-30 min | ¥5-15 | +| 中篇 (5-10 万字) | 30-60 min | ¥15-50 | +| 长篇 (10 万字+) | 1-2 h | ¥50-200 | > 实际成本取决于**所选 AI Provider**(详见 [配置 AI API Key](../getting-started/configuration.md))。 > 默认 Fallback Chain(GLM-5 + Seedream 5.0 + Edge TTS)一集 5 分钟漫剧约 **¥8-12**。 @@ -122,9 +122,9 @@ Autonomous 模式**每 30 秒**自动保存 Checkpoint 到本地: ## 五、下一步 -| 你的角色 | 推荐阅读 | -|---------|---------| -| 新手 | [Autonomous 模式](./autonomous-mode.md) | -| 高级用户 | [Manual 模式](./manual-mode.md) | -| 创作者 | [脚本生成](./script-generation.md) / [角色设计](./character-design.md) / [分镜设计](./storyboard-design.md) | -| 开发集成 | [API 文档 - 流水线](../api/pipeline-service.md) | +| 你的角色 | 推荐阅读 | +| -------- | ----------------------------------------------------------------------------------------------------------- | +| 新手 | [Autonomous 模式](./autonomous-mode.md) | +| 高级用户 | [Manual 模式](./manual-mode.md) | +| 创作者 | [脚本生成](./script-generation.md) / [角色设计](./character-design.md) / [分镜设计](./storyboard-design.md) | +| 开发集成 | [API 文档 - 流水线](../api/pipeline-service.md) | diff --git a/e2e/basic.spec.ts b/e2e/basic.spec.ts index 03d170a3..c17979dc 100644 --- a/e2e/basic.spec.ts +++ b/e2e/basic.spec.ts @@ -22,7 +22,7 @@ async function screenshot(page: Page, name: string) { test('home page: should load without errors', async ({ page }) => { await page.goto('/'); await waitForSkeleton(page); - await expect(page).toHaveTitle(/frame-fab/i); + await expect(page).toHaveTitle(/Story Weaver/i); }); test('home page: should display empty state', async ({ page }) => { diff --git a/eslint.config.js b/eslint.config.js index 63e17eb3..fee020db 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -22,6 +22,7 @@ export default [ 'src/__mocks__/**', 'packages/**', 'vite.config.ts', + 'tailwind.config.ts', 'jest.config.js', ], }, diff --git a/index.html b/index.html index cf820da7..15b220f5 100644 --- a/index.html +++ b/index.html @@ -10,17 +10,17 @@ - + - + - + - + - frame-fab - AI 漫剧创作平台 + Story Weaver - AI 漫剧创作平台
diff --git a/jest.config.cjs b/jest.config.cjs index ac735d31..cca1232d 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -39,8 +39,7 @@ module.exports = { ], moduleNameMapper: { '^@/(.*)$': '/src/$1', - '^@core/(.*)$': '/src/core/$1', - '^@shared/(.*)$': '/src/shared/$1', + // @core/ and @shared/ aliases removed (unused — all imports use @/ prefix) '\\.(css|less|scss|sass)$': '/src/__tests__/__mocks__/styleMock.js', '\\.(jpg|jpeg|png|gif|webp|svg)$': '/src/__tests__/__mocks__/fileMock.js', '^@tauri-apps/api/notification$': '/src/__mocks__/@tauri-apps/api/notification.ts', diff --git a/package.json b/package.json index 9df2ec28..8de5a0c4 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "frame-fab", + "name": "story-weaver", "private": true, "version": "2.2.3", - "description": "frame-fab - AI 漫剧创作平台。集成多模型 AI、角色一致性、分镜设计与 TTS 配音,将小说一键转化为专业级漫剧视频。", + "description": "Story Weaver - AI 漫剧创作平台。集成多模型 AI、角色一致性、分镜设计与 TTS 配音,将小说一键转化为专业级漫剧视频。", "author": "Agions ", "license": "MIT", "type": "module", diff --git a/public/favicon.svg b/public/favicon.svg index e772ee4e..42d039eb 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,38 +1,31 @@ - - frame-fab - 浏览器 favicon 版 (v2.2.3) + + Story Weaver - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - diff --git a/public/logo-horizontal.svg b/public/logo-horizontal.svg index 97898f50..29f11f91 100644 --- a/public/logo-horizontal.svg +++ b/public/logo-horizontal.svg @@ -1,93 +1,47 @@ - - frame-fab - frame-fab 水平 logo: 图标 + 渐变文字 + 副标 (v2.2.3 patch版) + + Story Weaver - + - - - - - + - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - + + + - + - - frame·fab - - AI 漫剧创作平台 - NOVEL · TO · VIDEO · AI · PIPELINE + Story Weaver + AI 漫剧创作平台 + NOVEL · TO · COMIC · DRAMA diff --git a/public/logo.svg b/public/logo.svg index 0f2f8820..005b36bb 100644 --- a/public/logo.svg +++ b/public/logo.svg @@ -1,129 +1,52 @@ - - frame-fab - AI 漫剧创作平台 — 胶片三帧 + 多模型神经连结 + 中心光圈的视觉锤 (v2.2.3) + + Story Weaver + AI 漫剧创作平台 — 编织故事的概念标志 - - + - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/public/og-image.svg b/public/og-image.svg index d6c7148b..ab842d29 100644 --- a/public/og-image.svg +++ b/public/og-image.svg @@ -1,207 +1,83 @@ - - frame-fab · AI 漫剧创作平台 - GitHub social card / Open Graph share card 1280×640 (v2.2.3 patch版) + + Story Weaver · AI 漫剧创作平台 - + - - + - - - - - - - - - - - - - - + - - - - + + + + - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - v2.2.3 · OPEN SOURCE · MIT LICENSE - - - - frame·fab - + v2.2.3 · OPEN SOURCE · MIT + Story · Weaver + AI 漫剧创作平台 + Novel-to-Comic Drama · AI Pipeline - - AI 漫剧创作平台 - - Novel-to-Video AI Pipeline · v2.2.3 - - - - - - TAURI 2 · RUST - - - - 7+ AI MODELS + + + + TAURI · RUST + + + 7+ AI MODELS - - - QUALITY GATE + + + QUALITY GATE - - - 10-STEP PIPELINE + + + 10-STEP PIPELINE - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + - - - github.com/Agions/frame-fab - - - v2.2.3 · 2026 - + + github.com/Agions/story-weaver + v2.2.3 · 2026 diff --git a/scripts/docs-check-links.mjs b/scripts/docs-check-links.mjs index 255a544d..911401c7 100644 --- a/scripts/docs-check-links.mjs +++ b/scripts/docs-check-links.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node /** - * frame-fab docs 死链检查器 + * Story Weaver docs 死链检查器 * * 扫描 docs/ 下所有 .md 文件,提取内部链接,验证目标存在。 * 支持: diff --git a/scripts/run-evaluation.ts b/scripts/run-evaluation.ts index 1ef0f423..90734c09 100644 --- a/scripts/run-evaluation.ts +++ b/scripts/run-evaluation.ts @@ -86,7 +86,7 @@ const summary = { }; const lines: string[] = []; -lines.push('# frame-fab 回归评测报告'); +lines.push('# Story Weaver 回归评测报告'); lines.push(''); lines.push(`生成时间: ${new Date().toISOString()}`); lines.push(`样例数: ${samples.length}`); diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 93eb98eb..c48de067 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "frame-fab" +name = "story-weaver" version = "2.2.3" -description = "frame-fab v2.2.0 - AI 漫剧创作平台" -authors = ["frame-fab Team "] +description = "Story Weaver - AI 漫剧创作平台" +authors = ["Story Weaver Team "] license = "MIT" -repository = "https://github.com/Agions/frame-fab" +repository = "https://github.com/Agions/story-weaver" edition = "2021" [lib] diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index fc3a15d3..ef5fabb2 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -1,7 +1,7 @@ { "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", - "description": "frame-fab AI 默认权限配置(v3.1 收紧:移除未用 plugin)", + "description": "Story Weaver AI 默认权限配置(v3.1 收紧:移除未用 plugin)", "windows": ["main"], "permissions": [ "core:default", diff --git a/src-tauri/src/constants/allowed_dirs.rs b/src-tauri/src/constants/allowed_dirs.rs index 635e0589..90dac3a0 100644 --- a/src-tauri/src/constants/allowed_dirs.rs +++ b/src-tauri/src/constants/allowed_dirs.rs @@ -4,9 +4,9 @@ //! of least privilege: output files may only be created inside these directories //! (typically the OS temp dir + per-app subdirectories). //! -//! **Naming convention**: Each subdirectory uses the `framefab_*` prefix for -//! brand continuity with the frame-fab product line. No backwards-compatibility -//! aliases are retained — old data in non-`framefab_*` directories will be +//! **Naming convention**: Each subdirectory uses the `storyweaver_*` prefix for +//! brand continuity with the Story Weaver product line. No backwards-compatibility +//! aliases are retained — old data in non-`storyweaver_*` directories will be //! rejected by the path validator. use std::path::PathBuf; @@ -14,19 +14,19 @@ use std::path::PathBuf; /// Directories where FFmpeg output files are allowed to be created. /// Used by `validate_output_path`. pub const ALLOWED_OUTPUT_DIRS: &[&str] = &[ - "framefab_temp", - "framefab_keyframes", - "framefab_thumbnails", - "framefab_preview", + "storyweaver_temp", + "storyweaver_keyframes", + "storyweaver_thumbnails", + "storyweaver_preview", ]; /// Directories where cleanup is allowed (subset of output dirs). /// Used by `clean_temp_file` to verify a path is safe to delete. pub const ALLOWED_TEMP_CLEANUP_DIRS: &[&str] = &[ - "framefab_keyframes", - "framefab_thumbnails", - "framefab_temp", - "framefab_preview", + "storyweaver_keyframes", + "storyweaver_thumbnails", + "storyweaver_temp", + "storyweaver_preview", ]; /// Build the full OS temp dir + subdirectory path for a given subdir. @@ -40,8 +40,8 @@ mod tests { #[test] fn output_dirs_includes_keyframes() { - assert!(ALLOWED_OUTPUT_DIRS.contains(&"framefab_keyframes")); - assert!(ALLOWED_OUTPUT_DIRS.contains(&"framefab_preview")); + assert!(ALLOWED_OUTPUT_DIRS.contains(&"storyweaver_keyframes")); + assert!(ALLOWED_OUTPUT_DIRS.contains(&"storyweaver_preview")); } #[test] @@ -57,7 +57,7 @@ mod tests { #[test] fn temp_subdir_creates_under_temp() { - let p = temp_subdir("framefab_keyframes"); - assert!(p.ends_with("framefab_keyframes")); + let p = temp_subdir("storyweaver_keyframes"); + assert!(p.ends_with("storyweaver_keyframes")); } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index cbba292c..dc286f4f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,4 @@ -//! frame-fab Tauri backend. +//! Story Weaver Tauri backend. //! //! This crate is the Rust-side first citizen of the desktop application. //! It exposes Tauri commands to the JS frontend, and is internally organized @@ -26,7 +26,7 @@ pub fn run() { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) .init(); - info!("frame-fab 启动中..."); + info!("Story Weaver 启动中..."); tauri::Builder::default() .plugin(tauri_plugin_fs::init()) @@ -83,5 +83,5 @@ pub fn run() { commands::shortcuts::get_registered_shortcuts, ]) .run(tauri::generate_context!()) - .expect("启动 frame-fab 时发生错误"); + .expect("启动 Story Weaver 时发生错误"); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 8cc64cbc..5bf4d2c4 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,4 +1,4 @@ -// frame-fab desktop entry point. +// Story Weaver desktop entry point. // // On Windows in release builds, hides the console window. // The actual application logic lives in `lib::run()`. diff --git a/src-tauri/src/models/mod.rs b/src-tauri/src/models/mod.rs index cc94cbae..91dcfe9c 100644 --- a/src-tauri/src/models/mod.rs +++ b/src-tauri/src/models/mod.rs @@ -1,4 +1,4 @@ -//! Data models for the frame-fab Tauri backend. +//! Data models for the Story Weaver Tauri backend. pub mod app_settings; pub mod shortcut; diff --git a/src-tauri/src/services/video/analyzer.rs b/src-tauri/src/services/video/analyzer.rs index ed00c3a3..fbd2cb13 100644 --- a/src-tauri/src/services/video/analyzer.rs +++ b/src-tauri/src/services/video/analyzer.rs @@ -95,7 +95,7 @@ pub fn extract_key_frames(path: &str, count: u32) -> Result, String> let metadata = analyze(path)?; let duration = metadata.duration; - let temp_dir = temp_subdir("framefab_keyframes"); + let temp_dir = temp_subdir("storyweaver_keyframes"); fs::create_dir_all(&temp_dir).map_err(|e| format!("创建临时目录失败: {}", e))?; let mut frame_positions = Vec::new(); @@ -143,7 +143,7 @@ pub fn generate_thumbnail(path: &str) -> Result { return Err("未安装FFmpeg,请先安装FFmpeg后再试".into()); } - let temp_dir = temp_subdir("framefab_thumbnails"); + let temp_dir = temp_subdir("storyweaver_thumbnails"); fs::create_dir_all(&temp_dir).map_err(|e| format!("创建临时目录失败: {}", e))?; let thumbnail_path = temp_dir.join(format!("thumb_{}.jpg", random_id())); diff --git a/src-tauri/src/services/video/cutter.rs b/src-tauri/src/services/video/cutter.rs index 3755201e..908b0bbd 100644 --- a/src-tauri/src/services/video/cutter.rs +++ b/src-tauri/src/services/video/cutter.rs @@ -22,7 +22,7 @@ pub fn cut(params: &CutVideoParams) -> Result { return Err("未安装FFmpeg,请先安装FFmpeg后再试".into()); } - let temp_dir = temp_subdir("framefab_temp"); + let temp_dir = temp_subdir("storyweaver_temp"); fs::create_dir_all(&temp_dir).map_err(|e| format!("创建临时目录失败: {}", e))?; let format = params.format.clone().unwrap_or_else(|| "mp4".to_string()); diff --git a/src-tauri/src/services/video/preview.rs b/src-tauri/src/services/video/preview.rs index 688197fc..06173df7 100644 --- a/src-tauri/src/services/video/preview.rs +++ b/src-tauri/src/services/video/preview.rs @@ -19,7 +19,7 @@ pub fn generate(params: &PreviewParams) -> Result { return Err("未安装FFmpeg,请先安装FFmpeg后再试".into()); } - let temp_dir = temp_subdir("framefab_preview"); + let temp_dir = temp_subdir("storyweaver_preview"); fs::create_dir_all(&temp_dir).map_err(|e| format!("创建临时目录失败: {}", e))?; let preview_file = temp_dir.join(format!("preview_{}.mp4", random_id())); diff --git a/src-tauri/src/utils/path_validator.rs b/src-tauri/src/utils/path_validator.rs index 1e21f4e3..56d1e852 100644 --- a/src-tauri/src/utils/path_validator.rs +++ b/src-tauri/src/utils/path_validator.rs @@ -68,7 +68,7 @@ pub fn validate_output_path(path: &str) -> Result { /// Used for cleanup operations where the file must already exist. /// /// 【v3.1 安全强化】原实现用 `canonical_path.starts_with(allowed)`,其中 `allowed` -/// 来自 `ALLOWED_TEMP_CLEANUP_DIRS = &["framefab_keyframes", ...]`,**这些是相对名** +/// 来自 `ALLOWED_TEMP_CLEANUP_DIRS = &["storyweaver_keyframes", ...]`,**这些是相对名** /// 而 `canonical_path` 是绝对路径——`starts_with` 实际上永远不匹配,**漏洞:永远拒绝**。 /// 修复:把 allowed 拼接成绝对 temp 目录后再 canonicalize 比较。 pub fn validate_temp_path(path: &str) -> Result { @@ -88,7 +88,7 @@ pub fn validate_temp_path(path: &str) -> Result { format!("路径无效: {}", e) })?; - // 【v3.1 修复】allowed_dirs 是子目录名(如 "framefab_keyframes"), + // 【v3.1 修复】allowed_dirs 是子目录名(如 "storyweaver_keyframes"), // 需拼成 OS temp dir 下的绝对路径再 canonicalize 后比较 let is_allowed = ALLOWED_TEMP_CLEANUP_DIRS.iter().any(|allowed| { let allowed_abs = temp_subdir(allowed); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index bff3d171..1c603d79 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,8 +1,8 @@ { "$schema": "../node_modules/@tauri-apps/cli/schema.json", - "productName": "frame-fab", + "productName": "Story Weaver", "version": "2.2.3", - "identifier": "com.framefab.app", + "identifier": "com.storyweaver.app", "build": { "beforeDevCommand": "npm run dev", "beforeBuildCommand": "npm run build", @@ -12,7 +12,7 @@ "app": { "windows": [ { - "title": "frame-fab - AI 漫剧创作平台", + "title": "Story Weaver - AI 漫剧创作平台", "width": 1280, "height": 800, "minWidth": 960, @@ -41,7 +41,7 @@ "bundle": { "active": true, "category": "Video", - "copyright": "Copyright © 2024-2026 frame-fab", + "copyright": "Copyright © 2024-2026 Story Weaver", "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -49,7 +49,7 @@ "icons/icon.icns", "icons/icon.ico" ], - "longDescription": "frame-fab - AI 漫剧创作平台。集成多模型 AI、角色一致性、分镜设计与 TTS 配音,将小说一键转化为专业级漫剧视频。", + "longDescription": "Story Weaver - AI 漫剧创作平台。集成多模型 AI、角色一致性、分镜设计与 TTS 配音,将小说一键转化为专业级漫剧视频。", "shortDescription": "AI 漫剧创作平台", "targets": "all", "linux": { diff --git a/src/__tests__/core/autonomous/auto-pipeline-engine.integration.test.ts b/src/__tests__/core/autonomous/auto-pipeline-engine.integration.test.ts deleted file mode 100644 index c668b87e..00000000 --- a/src/__tests__/core/autonomous/auto-pipeline-engine.integration.test.ts +++ /dev/null @@ -1,232 +0,0 @@ -/** - * AutoPipelineEngine 集成测试(v3.2 性能优化阶段) - * - * 单元测试覆盖:构造、idle 状态、event handler、并发拒绝 → src/core/autonomous/auto-pipeline-engine.test.ts - * 集成测试(本文件)覆盖:端到端 run() 全流程 + 真实 executeStep 路径 - * - * 测试矩阵(5 类场景): - * 1. Happy path:3 个 step 顺序执行,dispatch 事件顺序正确,最终 result 含 stepDurations - * 2. 依赖图:A→B→C,B 的 input 包含 A 的 output - * 3. Step 抛错:execute 抛异常 → 整体失败 + dispatch fail - * 4. Self-Review 失败重试:quality gate 第一次 fail,第二次 pass(验证重试循环) - * 5. disabled step 跳过:enabled=false 的 step 不执行 - * - * 策略:mock 掉 SelfReviewLoop / QualityGate 让行为可控; - * 用工厂 options.steps 注入 fake step 链覆盖默认空数组。 - */ - -import { - AutoPipelineEngine, - createAutoPipelineEngine, -} from '@/core/autonomous/auto-pipeline-engine'; -import type { PipelineStep, StepInput } from '@/core/autonomous/pipeline-types'; -import type { StepOutput } from '@/core/autonomous/types/autonomous.types'; - -// ============================================================================ -// 辅助:构造可控制的 mock step -// ============================================================================ - -interface MockStepOptions { - stepId: string; - name?: string; - enabled?: boolean; - dependencies?: string[]; - output?: StepOutput; - /** 每次 execute 抛错(覆盖 output) */ - throwError?: string; - /** 每次 execute 返回不同 output(按调用次数) */ - outputSequence?: StepOutput[]; - /** execute 调用时记录到 spy */ - onCall?: (callIndex: number, input: StepInput) => void; -} - -function makeMockStep(opts: MockStepOptions): PipelineStep & { _calls: number } { - const step = { - id: opts.stepId, - name: opts.name ?? opts.stepId, - stepId: opts.stepId, - enabled: opts.enabled ?? true, - maxRetries: 3, - timeout: 5000, - dependencies: opts.dependencies, - _calls: 0, - async execute(input: StepInput): Promise { - const idx = this._calls++; - opts.onCall?.(idx, input); - if (opts.throwError) { - throw new Error(opts.throwError); - } - if (opts.outputSequence) { - return opts.outputSequence[idx] ?? opts.outputSequence[opts.outputSequence.length - 1]; - } - return opts.output ?? { ok: true }; - }, - }; - return step; -} - -/** 通过工厂 + 覆盖内部 steps 字段 + 截断 loadSteps() 注入 mock 步骤链 */ -function createEngineWithSteps(steps: PipelineStep[]): AutoPipelineEngine { - const engine = createAutoPipelineEngine({ steps }); - - // 绕过 loadSteps() 空数组:直接覆盖内部 steps 字段,并 stub loadSteps - // @ts-expect-error - 内部字段访问,集成测试需要构造非空步骤链 - engine.steps = steps; - // @ts-expect-error - stub 私有方法 - engine.loadSteps = async () => steps; - return engine; -} - -// ============================================================================ -// 集成测试 -// ============================================================================ - -describe('AutoPipelineEngine — integration (v3.2)', () => { - // 收集所有 dispatch 事件 - function captureEvents(engine: AutoPipelineEngine) { - const events: string[] = []; - engine.onEvents({ - onStepStart: (id) => events.push(`step_start:${id}`), - onStepComplete: (id) => events.push(`step_complete:${id}`), - onStepFail: (id, err) => events.push(`step_fail:${id}:${err}`), - onPipelineStart: () => events.push('pipeline_start'), - onPipelineComplete: () => events.push('pipeline_complete'), - onPipelineFail: (err) => events.push(`pipeline_fail:${err}`), - onPipelineCancel: () => events.push('pipeline_cancel'), - }); - return events; - } - - // ========================================================================== - // 场景 1:Happy path — 3 个 step 顺序执行 - // ========================================================================== - it('runs 3 steps in order and dispatches correct events', async () => { - const stepA = makeMockStep({ stepId: 'step_a', output: { a: 1 } }); - const stepB = makeMockStep({ stepId: 'step_b', output: { b: 2 } }); - const stepC = makeMockStep({ stepId: 'step_c', output: { c: 3 } }); - - const engine = createEngineWithSteps([stepA, stepB, stepC]); - const events = captureEvents(engine); - - const result = await engine.run({ - content: 'test', - mode: 'novel', - style: 'anime', - qualityLevel: 'balanced', - }); - - expect(result.success).toBe(true); - expect(events).toEqual([ - 'pipeline_start', - 'step_start:step_a', - 'step_complete:step_a', - 'step_start:step_b', - 'step_complete:step_b', - 'step_start:step_c', - 'step_complete:step_c', - 'pipeline_complete', - ]); - expect(stepA._calls).toBe(1); - expect(stepB._calls).toBe(1); - expect(stepC._calls).toBe(1); - }); - - // ========================================================================== - // 场景 2:依赖图合并 — 后续 step 的 input 包含前序 step 的 output - // ========================================================================== - it('merges dependency outputs into next step input', async () => { - const stepA = makeMockStep({ stepId: 'step_a', output: { aValue: 'A' } }); - const stepB = makeMockStep({ - stepId: 'step_b', - dependencies: ['step_a'], - output: { bValue: 'B' }, - onCall: (_idx, input) => { - // step_b 应该看到 step_a 的 output - expect(input.aValue).toBe('A'); - }, - }); - - const engine = createEngineWithSteps([stepA, stepB]); - captureEvents(engine); - - const result = await engine.run({ content: 'merge', mode: 'novel' }); - expect(result.success).toBe(true); - expect(stepB._calls).toBe(1); - }); - - // ========================================================================== - // 场景 3:Step 抛错 → 整体失败 + dispatch pipeline_fail - // ========================================================================== - it('aborts pipeline when a step throws', async () => { - const stepA = makeMockStep({ stepId: 'step_a', output: { a: 1 } }); - const stepB = makeMockStep({ stepId: 'step_b', throwError: 'LLM timeout' }); - const stepC = makeMockStep({ stepId: 'step_c', output: { c: 3 } }); // 永远不应执行 - - const engine = createEngineWithSteps([stepA, stepB, stepC]); - const events = captureEvents(engine); - - const result = await engine.run({ content: 'fail', mode: 'novel' }); - expect(result.success).toBe(false); - expect(result.error).toContain('LLM timeout'); - expect(stepC._calls).toBe(0); // 关键:C 不应执行 - // 真实事件是 step_fail:step_b:LLM timeout(带 error 详情) - expect(events.some((e) => e.startsWith('step_fail:step_b'))).toBe(true); - expect(events.some((e) => e.startsWith('pipeline_fail'))).toBe(true); - // pipeline_complete 不应出现 - expect(events).not.toContain('pipeline_complete'); - }); - - // ========================================================================== - // 场景 4:Self-Review 失败重试(outputSequence 模拟修复过程) - // ========================================================================== - it('retries step when output needs improvement (outputSequence)', async () => { - const stepA = makeMockStep({ - stepId: 'step_a', - // 第一次返回不完整,第二次完整 - outputSequence: [{ quality: 'low' }, { quality: 'high' }], - }); - - const engine = createEngineWithSteps([stepA]); - const events = captureEvents(engine); - - const result = await engine.run({ content: 'retry', mode: 'novel' }); - expect(result.success).toBe(true); - // 至少调用一次(具体次数由 quality gate 配置决定) - expect(stepA._calls).toBeGreaterThanOrEqual(1); - expect(events).toContain('pipeline_complete'); - }); - - // ========================================================================== - // 场景 5:disabled step 跳过 - // ========================================================================== - it('skips disabled steps', async () => { - const stepA = makeMockStep({ stepId: 'step_a', enabled: true, output: { a: 1 } }); - const stepB = makeMockStep({ stepId: 'step_b', enabled: false, output: { b: 2 } }); - const stepC = makeMockStep({ stepId: 'step_c', enabled: true, output: { c: 3 } }); - - const engine = createEngineWithSteps([stepA, stepB, stepC]); - const events = captureEvents(engine); - - const result = await engine.run({ content: 'skip', mode: 'novel' }); - expect(result.success).toBe(true); - expect(stepB._calls).toBe(0); // 关键:disabled 不执行 - // step_b 不应有 step_start/Complete - expect(events.some((e) => e.startsWith('step_start:step_b'))).toBe(false); - expect(events).toContain('step_complete:step_a'); - expect(events).toContain('step_complete:step_c'); - }); - - // ========================================================================== - // 场景 6:result 含 stepDurations(每个 step 至少 0ms) - // ========================================================================== - it('returns result with stepDurations on success', async () => { - const stepA = makeMockStep({ stepId: 'step_a', output: { ok: true } }); - const engine = createEngineWithSteps([stepA]); - captureEvents(engine); - - const result = await engine.run({ content: 'duration', mode: 'novel' }); - expect(result.success).toBe(true); - expect(result.stepDurations).toBeDefined(); - expect(result.stepDurations?.step_a).toBeGreaterThanOrEqual(0); - }); -}); diff --git a/src/__tests__/core/autonomous/auto-pipeline-engine.test.ts b/src/__tests__/core/autonomous/auto-pipeline-engine.test.ts deleted file mode 100644 index def005cf..00000000 --- a/src/__tests__/core/autonomous/auto-pipeline-engine.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * AutoPipelineEngine Smoke Test - * 验证:构造、idle 状态、event handler 注册、cancel API - */ -import { AutoPipelineEngine, createAutoPipelineEngine } from '@/core/autonomous/auto-pipeline-engine'; - -describe('AutoPipelineEngine', () => { - describe('factory', () => { - it('createAutoPipelineEngine returns a new instance', () => { - const engine = createAutoPipelineEngine(); - expect(engine).toBeInstanceOf(AutoPipelineEngine); - }); - - it('accepts maxReviewRetries / reviewModel options', () => { - const engine = createAutoPipelineEngine({ maxReviewRetries: 5, reviewModel: 'gpt-4' }); - expect(engine).toBeInstanceOf(AutoPipelineEngine); - }); - }); - - describe('initial state', () => { - it('starts in idle status (not running, not failed)', () => { - const engine = new AutoPipelineEngine(); - // No public getter for status; verify by attempting duplicate run - expect(engine).toBeInstanceOf(AutoPipelineEngine); - }); - }); - - describe('event handlers', () => { - it('onEvents registers a handler without throwing', () => { - const engine = new AutoPipelineEngine(); - const handler = { - onStepProgress: jest.fn(), - onPipelineComplete: jest.fn(), - onPipelineError: jest.fn(), - onSelfReview: jest.fn(), - }; - expect(() => engine.onEvents(handler)).not.toThrow(); - }); - - it('multiple handlers can be registered', () => { - const engine = new AutoPipelineEngine(); - engine.onEvents({ onStepProgress: jest.fn() }); - expect(() => engine.onEvents({ onPipelineComplete: jest.fn() })).not.toThrow(); - }); - }); - - describe('run() guards', () => { - it('rejects concurrent runs (second call rejects first still running)', async () => { - const engine = new AutoPipelineEngine(); - // First call will fail because no steps are loaded; we just test the guard message - const p1 = engine.run({ content: '', mode: 'novel', style: 'anime', qualityLevel: 'balanced' }); - const p2 = engine.run({ content: '', mode: 'novel', style: 'anime', qualityLevel: 'balanced' }); - const results = await Promise.allSettled([p1, p2]); - // One of them must throw "Pipeline already running" (the second) - const messages = results - .filter((r) => r.status === 'rejected') - .map((r) => (r as PromiseRejectedResult).reason?.message ?? ''); - // Either the second run is rejected, or both fail downstream - expect(results.every((r) => r.status === 'fulfilled' || r.status === 'rejected')).toBe(true); - // Loose: just ensure the engine didn't crash silently - expect(messages.length).toBeGreaterThanOrEqual(0); - }); - }); -}); diff --git a/src/__tests__/core/hooks/useProject.reducer.test.ts b/src/__tests__/core/hooks/useProject.reducer.test.ts new file mode 100644 index 00000000..8f58ecda --- /dev/null +++ b/src/__tests__/core/hooks/useProject.reducer.test.ts @@ -0,0 +1,144 @@ +/** + * useProject.reducer 单元测试 + * + * 覆盖 projectReducer + createProjectSetters 的状态转换契约。 + */ + +import { + projectReducer, + initialProjectState, + createProjectSetters, +} from '@/core/hooks/useProject.reducer'; +import type { ProjectAction } from '@/core/hooks/useProject.reducer'; + +describe('projectReducer', () => { + it('应返回初始状态的深拷贝引用', () => { + expect(initialProjectState).toEqual({ + project: null, + projects: [], + isLoading: false, + isSaving: false, + error: null, + hasUnsavedChanges: false, + currentStep: 0, + taskStatus: null, + }); + }); + + it('set action: 直接替换字段值', () => { + const action: ProjectAction = { type: 'set', key: 'isLoading', value: true }; + const next = projectReducer(initialProjectState, action); + + expect(next.isLoading).toBe(true); + expect(next.error).toBeNull(); // 其他字段不变 + }); + + it('set action: 更新 currentStep', () => { + const action: ProjectAction = { type: 'set', key: 'currentStep', value: 3 }; + const next = projectReducer(initialProjectState, action); + + expect(next.currentStep).toBe(3); + }); + + it('update action: 使用函数 updater 修改 projects 数组', () => { + const existing = { id: 'p1', name: 'A' }; + const state = { ...initialProjectState, projects: [existing] as never[] }; + + const newProject = { id: 'p2', name: 'B' }; + const action: ProjectAction = { + type: 'update', + key: 'projects', + updater: (prev) => [...(prev as never[]), newProject], + }; + const next = projectReducer(state, action); + + expect(next.projects).toHaveLength(2); + expect(next.projects[1]).toEqual(newProject); + }); + + it('update action: 基于 prev 的 set 模式 (与 setter factory 同构)', () => { + const action: ProjectAction = { + type: 'update', + key: 'hasUnsavedChanges', + updater: (prev) => !prev, + }; + const next = projectReducer(initialProjectState, action); + + expect(next.hasUnsavedChanges).toBe(true); + }); + + it('未知 action 类型应返回原 state (reducer 幂等)', () => { + const unknown = { type: 'UNKNOWN' } as unknown as ProjectAction; + const next = projectReducer(initialProjectState, unknown); + + expect(next).toBe(initialProjectState); + }); + + it('set taskStatus 应支持 null (接口契约)', () => { + const action: ProjectAction = { type: 'set', key: 'taskStatus', value: null }; + const next = projectReducer(initialProjectState, action); + + expect(next.taskStatus).toBeNull(); + }); +}); + +describe('createProjectSetters', () => { + it('应工厂化生成 7 个 setter 方法', () => { + const dispatched: ProjectAction[] = []; + const setters = createProjectSetters((a) => dispatched.push(a)); + + expect(setters).toHaveProperty('setProject'); + expect(setters).toHaveProperty('setProjects'); + expect(setters).toHaveProperty('setIsLoading'); + expect(setters).toHaveProperty('setIsSaving'); + expect(setters).toHaveProperty('setError'); + expect(setters).toHaveProperty('setHasUnsavedChanges'); + expect(setters).toHaveProperty('setCurrentStep'); + expect(dispatched).toHaveLength(0); + }); + + it('传入值时发出 set action', () => { + const dispatched: ProjectAction[] = []; + const setters = createProjectSetters((a) => dispatched.push(a)); + + setters.setIsLoading(true); + + expect(dispatched).toHaveLength(1); + expect(dispatched[0]).toEqual({ type: 'set', key: 'isLoading', value: true }); + }); + + it('传入函数时发出 update action', () => { + const dispatched: ProjectAction[] = []; + const setters = createProjectSetters((a) => dispatched.push(a)); + + setters.setCurrentStep((prev) => prev + 1); + + expect(dispatched).toHaveLength(1); + expect(dispatched[0].type).toBe('update'); + expect((dispatched[0] as { key: string }).key).toBe('currentStep'); + }); + + it('函数 updater 应接收 prev 值并返回新值', () => { + let capturedPrev: unknown = 'NOT_SET'; + const dispatched: ProjectAction[] = []; + const setters = createProjectSetters((a) => dispatched.push(a)); + + setters.setCurrentStep((prev) => { + capturedPrev = prev; + return 99; + }); + + // dispatch 不会立即执行 updater + expect(capturedPrev).toBe('NOT_SET'); + + // 手动调用 updater 模拟 reducer 行为 + const action = dispatched[0]; + if (action.type === 'update') { + const result = action.updater(42); + expect(capturedPrev).toBe(42); // updater 收到了传入的 prev + expect(result).toBe(99); // 返回新值 + } else { + fail('expected update action'); + } + }); +}); diff --git a/src/__tests__/core/services/composition.service.test.ts b/src/__tests__/core/services/composition.service.test.ts index ef53ccf2..dee79ee3 100644 --- a/src/__tests__/core/services/composition.service.test.ts +++ b/src/__tests__/core/services/composition.service.test.ts @@ -830,7 +830,7 @@ describe('CompositionService', () => { updatedAt: new Date().toISOString(), }, ]; - localStorageMock.data['frame-fab-compositions'] = JSON.stringify(storedData); + localStorageMock.data['storyweaver-compositions'] = JSON.stringify(storedData); const service = new CompositionService(); const composition = service.getById('stored-id'); @@ -840,7 +840,7 @@ describe('CompositionService', () => { }); it('应该处理 localStorage 解析错误', () => { - localStorageMock.data['frame-fab-compositions'] = 'invalid json'; + localStorageMock.data['storyweaver-compositions'] = 'invalid json'; const service = new CompositionService(); diff --git a/src/__tests__/core/services/project-import-export.service.test.ts b/src/__tests__/core/services/project-import-export.service.test.ts index 6a1a7dc2..2bc70921 100644 --- a/src/__tests__/core/services/project-import-export.service.test.ts +++ b/src/__tests__/core/services/project-import-export.service.test.ts @@ -109,7 +109,7 @@ describe('ProjectImportExportService', () => { const project = createMockProject(); const result = await projectImportExportService.exportProject(project, { format: 'json' }); - expect(result.filename).toMatch(/^framefab_测试项目_\d{4}-\d{2}-\d{2}\.json$/); + expect(result.filename).toMatch(/^storyweaver_测试项目_\d{4}-\d{2}-\d{2}\.json$/); expect(typeof result.content).toBe('string'); }); @@ -292,7 +292,7 @@ describe('ProjectImportExportService', () => { const project = createMockProject(); await projectImportExportService.backupProject(project); - const backupContent = localStorage.getItem('framefab_backup_mock-uuid-1'); + const backupContent = localStorage.getItem('storyweaver_backup_mock-uuid-1'); expect(backupContent).not.toBeNull(); const parsed = JSON.parse(backupContent!); @@ -348,7 +348,7 @@ describe('ProjectImportExportService', () => { }); it('should return null for corrupted backup data', async () => { - localStorage.setItem('framefab_backup_corrupted', 'not valid json {'); + localStorage.setItem('storyweaver_backup_corrupted', 'not valid json {'); const result = await projectImportExportService.restoreBackup('corrupted'); expect(result).toBeNull(); @@ -391,7 +391,7 @@ describe('ProjectImportExportService', () => { projectImportExportService.deleteBackup(backupId); - expect(localStorage.getItem('framefab_backup_mock-uuid-1')).toBeNull(); + expect(localStorage.getItem('storyweaver_backup_mock-uuid-1')).toBeNull(); }); it('should remove backup from backup list', async () => { diff --git a/src/__tests__/core/services/storyboard.service.test.ts b/src/__tests__/core/services/storyboard.service.test.ts index ebefa199..f8f5dbdb 100644 --- a/src/__tests__/core/services/storyboard.service.test.ts +++ b/src/__tests__/core/services/storyboard.service.test.ts @@ -666,7 +666,7 @@ describe('StoryboardService', () => { it('应该从 localStorage 加载数据', () => { const storedFrames = [createTestFrame({ id: 'stored-frame' })]; - localStorageMock.data['frame-fab-storyboards'] = JSON.stringify(storedFrames); + localStorageMock.data['storyweaver-storyboards'] = JSON.stringify(storedFrames); const service = new StoryboardService(); @@ -676,7 +676,7 @@ describe('StoryboardService', () => { it('不同 projectId 使用不同的存储键', () => { const storedFrames = [createTestFrame({ id: 'project-frame' })]; - localStorageMock.data['frame-fab-storyboards-project-1'] = JSON.stringify(storedFrames); + localStorageMock.data['storyweaver-storyboards-project-1'] = JSON.stringify(storedFrames); const service = new StoryboardService({ projectId: 'project-1' }); @@ -684,7 +684,7 @@ describe('StoryboardService', () => { }); it('损坏的存储数据不应该抛出错误', () => { - localStorageMock.data['frame-fab-storyboards'] = 'invalid json'; + localStorageMock.data['storyweaver-storyboards'] = 'invalid json'; const service = new StoryboardService(); diff --git a/src/__tests__/packages/autonomous/quality-gate.test.ts b/src/__tests__/packages/autonomous/quality-gate.test.ts deleted file mode 100644 index 4a9937d2..00000000 --- a/src/__tests__/packages/autonomous/quality-gate.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * QualityGate Smoke Test - * 验证:默认审核标准、构造器、eval API 行为 - */ -import { - QualityGate, - createQualityGate, - DEFAULT_REVIEW_CRITERIA, - DEFAULT_QUALITY_GATE_CONFIG, -} from '../../../core/autonomous/evaluator/quality-gate'; - -describe('QualityGate', () => { - describe('default review criteria', () => { - it('exposes DEFAULT_REVIEW_CRITERIA for known stepIds', () => { - expect(DEFAULT_REVIEW_CRITERIA.script).toBeDefined(); - expect(DEFAULT_REVIEW_CRITERIA.character).toBeDefined(); - expect(DEFAULT_REVIEW_CRITERIA.storyboard).toBeDefined(); - expect(DEFAULT_REVIEW_CRITERIA.render).toBeDefined(); - }); - - it('exposes DEFAULT_QUALITY_GATE_CONFIG map', () => { - expect(DEFAULT_QUALITY_GATE_CONFIG).toBeDefined(); - expect(typeof DEFAULT_QUALITY_GATE_CONFIG).toBe('object'); - }); - }); - - describe('factory', () => { - it('createQualityGate returns QualityGate instance', () => { - const gate = createQualityGate('script'); - expect(gate).toBeInstanceOf(QualityGate); - }); - }); - - describe('construction', () => { - it('accepts a QualityGateConfig', () => { - const gate = new QualityGate({ - enabled: true, - threshold: 70, - onFail: 'retry', - }); - expect(gate).toBeInstanceOf(QualityGate); - }); - - it('defaults enabled/threshold/onFail when omitted', () => { - const gate = new QualityGate({} as any); - expect(gate).toBeInstanceOf(QualityGate); - }); - }); - - describe('evaluate()', () => { - it('returns passed=true when gate is disabled (regardless of output)', () => { - const gate = new QualityGate({ enabled: false } as any); - const result = gate.evaluate('script', {} as any); - expect(result.passed).toBe(true); - expect(result.score).toBe(100); - }); - - it('returns a result object with passed + score fields', () => { - const gate = new QualityGate({ enabled: true, threshold: 70 } as any); - const result = gate.evaluate('script', { data: 'hello' } as any); - expect(result).toHaveProperty('passed'); - expect(result).toHaveProperty('score'); - expect(typeof result.score).toBe('number'); - }); - }); -}); diff --git a/src/__tests__/packages/autonomous/self-review-loop.test.ts b/src/__tests__/packages/autonomous/self-review-loop.test.ts deleted file mode 100644 index 31b291a0..00000000 --- a/src/__tests__/packages/autonomous/self-review-loop.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * SelfReviewLoop Smoke Test - * 验证:构造、循环 API、回调触发 - */ -import { - SelfReviewLoop, - createSelfReviewLoop, -} from '../../../core/autonomous/evaluator/self-review-loop'; - -describe('SelfReviewLoop', () => { - describe('factory', () => { - it('createSelfReviewLoop returns instance with default options', () => { - const loop = createSelfReviewLoop(); - expect(loop).toBeInstanceOf(SelfReviewLoop); - }); - - it('accepts maxRetries and model options', () => { - const loop = createSelfReviewLoop({ maxRetries: 5, model: 'gpt-4o' }); - expect(loop).toBeInstanceOf(SelfReviewLoop); - }); - }); - - describe('construction', () => { - it('direct construction works', () => { - const loop = new SelfReviewLoop(); - expect(loop).toBeInstanceOf(SelfReviewLoop); - }); - }); - - describe('review()', () => { - it('returns a result object with passed/feedback fields', async () => { - const loop = createSelfReviewLoop({ maxRetries: 0 }); - // Pass a minimal valid step output (review takes stepId + output, not gate result) - const result = await loop.review('script', { data: 'test' } as any); - expect(result).toBeDefined(); - expect(result).toHaveProperty('passed'); - expect(result).toHaveProperty('score'); - expect(typeof result.score).toBe('number'); - }); - }); -}); diff --git a/src/__tests__/pages/project-detail.test.tsx b/src/__tests__/pages/project-detail.test.tsx index 576bf65a..7033f2ca 100644 --- a/src/__tests__/pages/project-detail.test.tsx +++ b/src/__tests__/pages/project-detail.test.tsx @@ -187,7 +187,7 @@ describe('ProjectDetail page collaboration regression', () => { const writeCall = mockInvoke.mock.calls.find((call) => call[0] === 'write_text_file'); expect(String(writeCall?.[1]?.content ?? '')).toContain('- 综合: 80.0'); const activities = JSON.parse( - localStorage.getItem('frame-fab_review_export_activities') ?? '[]' + localStorage.getItem('storyweaver_review_export_activities') ?? '[]' ); expect(activities[0]).toMatchObject({ projectId: 'p-detail-1', diff --git a/src/__tests__/pages/project-edit.test.tsx b/src/__tests__/pages/project-edit.test.tsx index 0c67b825..4039f82a 100644 --- a/src/__tests__/pages/project-edit.test.tsx +++ b/src/__tests__/pages/project-edit.test.tsx @@ -135,7 +135,7 @@ describe('ProjectEdit collaboration regression', () => { const writeCall = mockInvoke.mock.calls.find((call) => call[0] === 'write_text_file'); expect(String(writeCall?.[1]?.content ?? '')).toContain('- 综合: 82.0'); const activities = JSON.parse( - localStorage.getItem('frame-fab_review_export_activities') ?? '[]' + localStorage.getItem('storyweaver_review_export_activities') ?? '[]' ); expect(activities[0]).toMatchObject({ projectId: 'p-edit-1', diff --git a/src/__tests__/services/cost.service.test.ts b/src/__tests__/services/cost.service.test.ts index 733e3f43..7c8a4441 100644 --- a/src/__tests__/services/cost.service.test.ts +++ b/src/__tests__/services/cost.service.test.ts @@ -110,7 +110,7 @@ describe('costService enhancements', () => { const report = service.exportReport(); expect(typeof report).toBe('string'); - expect(report).toContain('frame-fab'); + expect(report).toContain('Story Weaver'); expect(report).toContain('成本报告'); expect(report).toContain('总计'); }); diff --git a/src/__tests__/services/evaluation.service.test.ts b/src/__tests__/services/evaluation.service.test.ts index 2d8999b0..dbad4b64 100644 --- a/src/__tests__/services/evaluation.service.test.ts +++ b/src/__tests__/services/evaluation.service.test.ts @@ -25,7 +25,7 @@ describe('evaluationService', () => { expect(report.summary.overall).toBeGreaterThan(0); const md = evaluationService.toMarkdown(report); - expect(md).toContain('frame-fab AI 评测回归报告'); + expect(md).toContain('Story Weaver AI 评测回归报告'); expect(md).toContain('| ID | 标题 |'); }); }); diff --git a/src/__tests__/services/review-export.service.test.ts b/src/__tests__/services/review-export.service.test.ts index 03e60a60..973e0c63 100644 --- a/src/__tests__/services/review-export.service.test.ts +++ b/src/__tests__/services/review-export.service.test.ts @@ -77,7 +77,7 @@ describe('reviewExportService', () => { generatedAt: new Date('2026-03-08T12:30:00.000Z'), }); - expect(markdown).toContain('frame-fab AI 评审记录导出'); + expect(markdown).toContain('Story Weaver AI 评审记录导出'); expect(markdown).toContain('- 项目ID: p1'); expect(markdown).toContain('- 评论数量: 1'); expect(markdown).toContain('## 评论时间线'); diff --git a/src/__tests__/stores/project.store.test.ts b/src/__tests__/stores/project.store.test.ts index 95a20b9c..18d268b4 100644 --- a/src/__tests__/stores/project.store.test.ts +++ b/src/__tests__/stores/project.store.test.ts @@ -3,9 +3,8 @@ */ import { useProjectStore } from '@/shared/stores'; -import type { ProjectData } from '@/shared/types'; -// 模拟 storage service (原 shared/services/storage 已迁移到 useProject 内置 localStorage) +// 模拟 storage service jest.mock('@/core/services/project/secure-storage.service', () => ({ secureStorage: { saveSecureConfig: jest.fn(), @@ -15,15 +14,9 @@ jest.mock('@/core/services/project/secure-storage.service', () => ({ describe('Project Store', () => { beforeEach(() => { - // 重置 store useProjectStore.setState({ projects: [], currentProject: null, - searchQuery: '', - filterStatus: 'all', - sortBy: 'updatedAt', - sortOrder: 'desc', - exportHistory: [], }); }); @@ -49,19 +42,11 @@ describe('Project Store', () => { expect(project.name).toBe('新项目'); }); - - it('应该将新项目设为当前项目', () => { - const { createProject, currentProject } = useProjectStore.getState(); - - createProject({ name: '测试' }); - - expect(useProjectStore.getState().currentProject).toBeDefined(); - }); }); describe('updateProject', () => { it('应该更新项目', () => { - const { createProject, updateProject, projects } = useProjectStore.getState(); + const { createProject, updateProject } = useProjectStore.getState(); const project = createProject({ name: '原始名称' }); updateProject(project.id, { name: '新名称' }); @@ -73,102 +58,38 @@ describe('Project Store', () => { describe('deleteProject', () => { it('应该删除项目', () => { - const { createProject, deleteProject, projects } = useProjectStore.getState(); + const { createProject, deleteProject } = useProjectStore.getState(); - const project = createProject({ name: '测试' }); + createProject({ name: '测试' }); expect(useProjectStore.getState().projects.length).toBe(1); + const project = useProjectStore.getState().projects[0]; deleteProject(project.id); expect(useProjectStore.getState().projects.length).toBe(0); }); }); - describe('filteredProjects', () => { - it('应该过滤项目', () => { - const { createProject, setSearchQuery } = useProjectStore.getState(); + describe('recentProjects', () => { + it('应该返回最近更新的项目', () => { + const { createProject, recentProjects } = useProjectStore.getState(); - createProject({ name: '测试项目A' }); - createProject({ name: '另一个项目' }); - createProject({ name: '测试项目B' }); + createProject({ name: '项目A' }); + createProject({ name: '项目B' }); - setSearchQuery('测试'); - - const filtered = useProjectStore.getState().filteredProjects(); - expect(filtered.length).toBe(2); + const recent = recentProjects(); + expect(recent.length).toBe(2); }); - it('应该按状态过滤', () => { - const { createProject, setFilterStatus } = useProjectStore.getState(); - - createProject({ name: '草稿1', status: 'draft' }); - createProject({ name: '完成', status: 'completed' }); - createProject({ name: '草稿2', status: 'draft' }); - - setFilterStatus('draft'); + it('应该限制最多 10 个项目', () => { + const { createProject, recentProjects } = useProjectStore.getState(); - const filtered = useProjectStore.getState().filteredProjects(); - expect(filtered.length).toBe(2); - expect(filtered.every((p) => p.status === 'draft')).toBe(true); - }); - }); + for (let i = 0; i < 15; i++) { + createProject({ name: `项目${i}` }); + } - describe('script 操作', () => { - it('应该添加脚本', () => { - const { createProject, addScript } = useProjectStore.getState(); - - const project = createProject({ name: '测试' }); - - const script = { - id: 'script_1', - title: '测试脚本', - content: '脚本内容', - segments: [], - metadata: { - style: 'professional', - tone: 'neutral', - length: 'medium' as const, - targetAudience: 'general', - language: 'zh-CN', - wordCount: 100, - estimatedDuration: 60, - generatedBy: 'test', - generatedAt: new Date().toISOString(), - }, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - - addScript(project.id, script); - - const updated = useProjectStore.getState().projects.find((p) => p.id === project.id); - expect(updated?.scripts?.length).toBe(1); - }); - }); - - describe('video 操作', () => { - it('应该添加视频', () => { - const { createProject, addVideo } = useProjectStore.getState(); - - const project = createProject({ name: '测试' }); - - const video = { - id: 'video_1', - path: '/test/video.mp4', - name: '测试视频', - duration: 120, - width: 1920, - height: 1080, - fps: 30, - format: 'mp4', - size: 1024000, - createdAt: new Date().toISOString(), - }; - - addVideo(project.id, video); - - const updated = useProjectStore.getState().projects.find((p) => p.id === project.id); - expect(updated?.videos?.length).toBe(1); + const recent = recentProjects(); + expect(recent.length).toBe(10); }); }); }); diff --git a/src/app/components/ErrorBoundary.tsx b/src/app/components/ErrorBoundary.tsx index 9d2bbf2b..c29c50aa 100644 --- a/src/app/components/ErrorBoundary.tsx +++ b/src/app/components/ErrorBoundary.tsx @@ -6,7 +6,7 @@ * 3. 局部错误捕获 Hook */ -import React, { Component, ErrorInfo, ReactNode } from 'react'; +import { Component, ErrorInfo, ReactNode } from 'react'; import { logger } from '@/core/utils/logger'; import { telemetry } from '@/infrastructure/telemetry/telemetry'; diff --git a/src/app/index.tsx b/src/app/index.tsx index 753cac0d..9c693885 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -23,7 +23,6 @@ const WorkflowPage = lazy(importers.workflow); const ProjectEditPage = lazy(importers.projectEdit); const ProjectDetailPage = lazy(importers.projectDetail); const SettingsPage = lazy(importers.settings); -const AutoPipelinePage = lazy(importers.autoPipeline); // 加载时的占位组件 const PageLoader = () => ( @@ -97,16 +96,6 @@ const router = createBrowserRouter([ ), }, - { - path: '/auto-pipeline', - element: ( - - }> - - - - ), - }, { path: '*', element: , diff --git a/src/app/providers/SettingsContext.tsx b/src/app/providers/SettingsContext.tsx index fb698eba..54d42687 100644 --- a/src/app/providers/SettingsContext.tsx +++ b/src/app/providers/SettingsContext.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, ReactNode } from 'react'; +import { createContext, useContext, ReactNode } from 'react'; import { useSettingsStore } from '@/shared/stores/settings.store'; import type { AppSettings } from '@/shared/types'; @@ -6,76 +6,17 @@ import type { AppSettings } from '@/shared/types'; interface SettingsContextType { settings: AppSettings; updateSettings: (newSettings: Partial) => void; - resetSettings: () => void; - addRecentProject: (projectId: string) => void; } // 创建Context const SettingsContext = createContext(undefined); -// Provider组件 +// Provider组件 — 主题 DOM 操作已统一由 ThemeContext 处理 export function SettingsProvider({ children }: { children: ReactNode }) { - const { settings, updateSettings, resetSettings, addRecentProject } = useSettingsStore(); - - // 初始化时应用主题设置 - useEffect(() => { - const applyTheme = () => { - // 获取主题设置 - const { theme } = settings; - - // 根据主题设置和系统偏好确定是否使用暗色主题 - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - const shouldUseDarkTheme = theme === 'dark' || (theme === 'system' && prefersDark); - - // 应用主题 - document.documentElement.classList.toggle('dark', shouldUseDarkTheme); - document.documentElement.classList.toggle('dark-theme', shouldUseDarkTheme); - - // 更新标签的data-theme属性,供CSS变量使用 - document.documentElement.setAttribute('data-theme', shouldUseDarkTheme ? 'dark' : 'light'); - - // 更新背景色和文字颜色 - if (shouldUseDarkTheme) { - document.body.style.backgroundColor = '#141414'; - document.body.style.color = 'rgba(255, 255, 255, 0.85)'; - } else { - document.body.style.backgroundColor = '#fff'; - document.body.style.color = 'rgba(0, 0, 0, 0.85)'; - } - }; - - // 应用主题 - applyTheme(); - - // 监听系统主题变化 - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const handleChange = () => { - if (settings.theme === 'system') { - applyTheme(); - } - }; - - // 添加监听器 - if (mediaQuery.addEventListener) { - mediaQuery.addEventListener('change', handleChange); - } else { - // 兼容旧版浏览器 - mediaQuery.addListener(handleChange); - } - - // 清理函数 - return () => { - if (mediaQuery.removeEventListener) { - mediaQuery.removeEventListener('change', handleChange); - } else { - // 兼容旧版浏览器 - mediaQuery.removeListener(handleChange); - } - }; - }, [settings.theme, settings]); + const { settings, updateSettings } = useSettingsStore(); return ( - + {children} ); diff --git a/src/app/providers/ThemeContext.tsx b/src/app/providers/ThemeContext.tsx index 65df2293..2ea2548b 100644 --- a/src/app/providers/ThemeContext.tsx +++ b/src/app/providers/ThemeContext.tsx @@ -1,56 +1,95 @@ /** - * Theme Context — canonical implementation - * Provides theme state (light/dark/system) via React Context. + * Theme Context — 主题状态管理 + * + * 主题状态由 settingsStore (Zustand + persist) 作为唯一来源, + * 通过 useSyncExternalStore 同步到 ThemeProvider。 */ -import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react' +import { createContext, useContext, useEffect, ReactNode, useSyncExternalStore } from 'react'; -type Theme = 'light' | 'dark' | 'system' +import { useSettingsStore } from '@/shared/stores/settings.store'; + +type Theme = 'light' | 'dark' | 'system'; interface ThemeContextValue { - theme: Theme - setTheme: (t: Theme) => void - toggleTheme: () => void - resolvedTheme: 'light' | 'dark' - isDarkMode: boolean + theme: Theme; + setTheme: (t: Theme) => void; + toggleTheme: () => void; + resolvedTheme: 'light' | 'dark'; + isDarkMode: boolean; +} + +const ThemeContext = createContext(null); + +function getSystemTheme(): 'light' | 'dark' { + if (typeof window === 'undefined') return 'light'; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +} + +function resolveTheme(theme: Theme): 'light' | 'dark' { + if (theme === 'system') return getSystemTheme(); + return theme; } -const ThemeContext = createContext(null) +function applyThemeToDOM(resolved: 'light' | 'dark'): void { + if (typeof document === 'undefined') return; + const root = document.documentElement; + root.classList.remove('light', 'dark'); + root.classList.add(resolved); + root.setAttribute('data-theme', resolved); + document.body.style.backgroundColor = resolved === 'dark' ? '#141414' : '#fff'; + document.body.style.color = + resolved === 'dark' ? 'rgba(255, 255, 255, 0.85)' : 'rgba(0, 0, 0, 0.85)'; +} -export const ThemeProvider: React.FC<{ children: ReactNode; defaultTheme?: Theme }> = ({ - children, - defaultTheme = 'light', -}) => { - const [theme, setTheme] = useState(defaultTheme) - const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light') +export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const settingsTheme = useSyncExternalStore( + (cb) => + useSettingsStore.subscribe((s, prev) => { + if (s.settings.theme !== prev.settings.theme) cb(); + }), + () => useSettingsStore.getState().settings.theme as Theme, + () => useSettingsStore.getState().settings.theme as Theme + ); useEffect(() => { - const root = document.documentElement - if (theme === 'system') { - const mq = window.matchMedia('(prefers-color-scheme: dark)') - setResolvedTheme(mq.matches ? 'dark' : 'light') - } else { - setResolvedTheme(theme) - } - root.classList.remove('light', 'dark') - root.classList.add(resolvedTheme) - }, [theme, resolvedTheme]) - - const toggleTheme = () => setTheme((t) => (t === 'dark' ? 'light' : 'dark')) + const resolved = resolveTheme(settingsTheme); + applyThemeToDOM(resolved); + + // 监听系统主题变化 (仅 system 模式) + if (settingsTheme !== 'system') return; + const mq = window.matchMedia('(prefers-color-scheme: dark)'); + const handler = () => applyThemeToDOM(getSystemTheme()); + mq.addEventListener('change', handler); + return () => mq.removeEventListener('change', handler); + }, [settingsTheme]); + + const updateSettings = useSettingsStore.getState().updateSettings; + const setTheme = (t: Theme) => updateSettings({ theme: t }); + const toggleTheme = () => + updateSettings({ theme: resolveTheme(settingsTheme) === 'dark' ? 'light' : 'dark' }); + const resolvedTheme = resolveTheme(settingsTheme); + return ( {children} - ) -} + ); +}; export const useTheme = (): ThemeContextValue => { - const ctx = useContext(ThemeContext) + const ctx = useContext(ThemeContext); if (!ctx) { - throw new Error('useTheme must be used within ThemeProvider') + throw new Error('useTheme must be used within ThemeProvider'); } - return ctx -} + return ctx; +}; -export default ThemeProvider +export default ThemeProvider; diff --git a/src/app/router/page-preload.ts b/src/app/router/page-preload.ts index e784dff4..dbf4d882 100644 --- a/src/app/router/page-preload.ts +++ b/src/app/router/page-preload.ts @@ -6,7 +6,6 @@ const pageImporters = { projectEdit: () => import('@/pages/project-edit/ProjectEditPage'), projectDetail: () => import('@/pages/project-detail/ProjectDetailPage'), settings: () => import('@/pages/settings/SettingsPage'), - autoPipeline: () => import('@/pages/auto-pipeline/AutoPipelinePage'), } as const; const routeImporterMap: Array<{ prefix: string; importer: Importer }> = [ diff --git a/src/core/api/client.ts b/src/core/api/client.ts index f65fdbe1..5a8036c3 100644 --- a/src/core/api/client.ts +++ b/src/core/api/client.ts @@ -25,10 +25,8 @@ interface ApiResponse { class ApiClient { private client: AxiosInstance; - private baseURL: string; constructor(baseURL: string = '') { - this.baseURL = baseURL; this.client = axios.create({ baseURL, timeout: 30000, diff --git a/src/core/autonomous/auto-pipeline-engine.ts b/src/core/autonomous/auto-pipeline-engine.ts deleted file mode 100644 index 64e180c7..00000000 --- a/src/core/autonomous/auto-pipeline-engine.ts +++ /dev/null @@ -1,460 +0,0 @@ -/** - * AutoPipelineEngine — 全自动流水线引擎(Facade) - * - * 核心能力(保持不变): - * 1. 端到端自动化:输入原材料 → 输出成片,无需用户介入 - * 2. Self-Review Loop:每个 Step 配备自审,不合格自动修复重做 - * 3. Quality Gate:关键节点设置质量门禁,不达标不推进 - * 4. 断点续传:支持暂停/恢复,中断后可继续 - * 5. 降级策略:主模型不可用时自动切换备选 - * - * 重构思路:原 634 行单类混合了6类职责,现拆为5个子模块: - * - pipeline-types PipelineStep / StepInput / StepCheckpoint - * - pipeline-step-state applyStepStateTransition / collectStepDurations / computeProgressPercent - * - pipeline-checkpoint buildPipelineId / save/load + applyCheckpointToEngine + interval 常量 - * - pipeline-event-dispatcher 13 个 dispatchXxx 替代原 emit 大 switch - * - pipeline-executor executeStepWithTimeout / buildStepInput - * - * 主类保留对外 API:run / pause / resume / cancel / getStatus / getStepStates - * / onEvents / isRunning + 工厂函数 createAutoPipelineEngine。 - */ - -import { logger } from '@/core/utils/logger'; - -import { createQualityGate } from './evaluator/quality-gate'; -import { SelfReviewLoop, createSelfReviewLoop } from './evaluator/self-review-loop'; -import { - buildCheckpointSnapshot, - buildPipelineId, - CHECKPOINT_INTERVAL_MS, - loadCheckpointFromStorage, - saveCheckpointToStorage, -} from './pipeline-checkpoint'; -import { PipelineEventDispatcher } from './pipeline-event-dispatcher'; -import { buildStepInput, executeStepWithTimeout } from './pipeline-executor'; -import { - applyStepStateTransition, - collectStepDurations, - computeProgressPercent, -} from './pipeline-step-state'; -import type { PipelineStep } from './pipeline-types'; -import type { - AutoPipelineInput, - AutoPipelineResult, - PipelineEventHandler, - PipelineStatus, - StepOutput, -} from './types/autonomous.types'; - -/** - * 步骤默认实现集合(占位)。 - * 实际步骤从 core/pipeline/steps/ 动态导入。 - */ -const AUTONOMOUS_STEPS: PipelineStep[] = []; - -export class AutoPipelineEngine { - private status: PipelineStatus = 'idle'; - private currentStepId: string | null = null; - private stepStates: Map = new Map(); - private selfReview: SelfReviewLoop; - private handlers: PipelineEventHandler[]; - private events: PipelineEventDispatcher; - private abortController: AbortController | null = null; - private checkpointInterval: ReturnType | null = null; - private context: Map = new Map(); - private steps: PipelineStep[] = []; - - constructor( - options: { - selfReview?: SelfReviewLoop; - maxReviewRetries?: number; - reviewModel?: string; - } = {} - ) { - this.selfReview = - options.selfReview ?? - createSelfReviewLoop({ - maxRetries: options.maxReviewRetries ?? 3, - model: options.reviewModel ?? 'glm-5', - }); - this.handlers = []; - this.events = new PipelineEventDispatcher(this.handlers); - } - - // ============================================================================ - // 公共 API - // ============================================================================ - - async run(input: AutoPipelineInput): Promise { - if (this.status === 'running') { - throw new Error('Pipeline already running'); - } - - this.status = 'running'; - this.abortController = new AbortController(); - - this.steps = await this.loadSteps(); - - // 如果 context 中已有 __input__(resume 场景),保留它;否则设置新 input - if (!this.context.has('__input__')) { - this.context.set('__input__', input as unknown as StepOutput); - } - - const startTime = Date.now(); - this.events.dispatchPipelineStart(); - this.startCheckpointInterval(); - - try { - for (const step of this.steps) { - if (!step.enabled) { - applyStepStateTransition(this.stepStates, step.stepId, 'skipped'); - continue; - } - - // 跳过已完成的步骤(resume 场景) - const currentState = this.stepStates.get(step.stepId); - if (currentState?.status === 'completed') { - continue; - } - - if (this.abortController.signal.aborted) { - this.status = 'cancelled'; - this.events.dispatchPipelineCancel(); - return { success: false, error: 'Pipeline cancelled by user' }; - } - - this.currentStepId = step.stepId; - const stepResult = await this.executeStep(step); - - if (!stepResult.success) { - this.status = 'failed'; - this.events.dispatchPipelineFail(`Step ${step.name} failed: ${stepResult.error}`); - return { - success: false, - error: `Step ${step.name} failed: ${stepResult.error}`, - }; - } - } - - this.status = 'completed'; - const duration = Date.now() - startTime; - const result: AutoPipelineResult = { - success: true, - outputPath: this.context.get('step_export')?.outputPath as string | undefined, - duration: this.context.get('step_export')?.duration as number | undefined, - resolution: '1080p', - fileSize: this.context.get('step_export')?.fileSize as number | undefined, - stepDurations: collectStepDurations(this.stepStates), - sceneCount: (this.context.get('step_script') as unknown as { scenes?: { length: number } }) - ?.scenes?.length as number | undefined, - characterCount: ( - this.context.get('step_character') as unknown as { characters?: { length: number } } - )?.characters?.length as number | undefined, - renderedFrames: ( - this.context.get('step_render') as unknown as { renderedFrames?: { length: number } } - )?.renderedFrames?.length as number | undefined, - }; - - this.events.dispatchPipelineComplete({ ...result, duration }); - return result; - } catch (error) { - this.status = 'failed'; - const errorMessage = error instanceof Error ? error.message : String(error); - this.events.dispatchPipelineFail(errorMessage); - return { success: false, error: errorMessage }; - } finally { - this.stopCheckpointInterval(); - } - } - - pause(): boolean { - if (this.status !== 'running') return false; - this.status = 'paused'; - this.events.dispatchPipelinePause(); - return true; - } - - async resume(_input?: AutoPipelineInput): Promise { - if (this.status !== 'paused') { - throw new Error('Pipeline is not paused'); - } - - this.status = 'running'; - this.events.dispatchPipelineResume(); - - const pipelineId = buildPipelineId( - this.context.get('__input__') as unknown as AutoPipelineInput | undefined - ); - const checkpoint = loadCheckpointFromStorage(pipelineId); - if (checkpoint) { - this.restoreFromCheckpoint(checkpoint); - } - - // 直接从中断点继续,不再调用 run() 重新初始化 - if (_input) { - this.context.set('__input__', _input as unknown as StepOutput); - } - - this.abortController = new AbortController(); - const startTime = Date.now(); - this.startCheckpointInterval(); - - try { - for (const step of this.steps) { - if (!step.enabled) { - applyStepStateTransition(this.stepStates, step.stepId, 'skipped'); - continue; - } - - const currentState = this.stepStates.get(step.stepId); - if (currentState?.status === 'completed') { - continue; - } - - if (this.abortController.signal.aborted) { - this.status = 'cancelled'; - this.events.dispatchPipelineCancel(); - return { success: false, error: 'Pipeline cancelled by user' }; - } - - this.currentStepId = step.stepId; - const stepResult = await this.executeStep(step); - - if (!stepResult.success) { - this.status = 'failed'; - this.events.dispatchPipelineFail(`Step ${step.name} failed: ${stepResult.error}`); - return { - success: false, - error: `Step ${step.name} failed: ${stepResult.error}`, - }; - } - } - - this.status = 'completed'; - const duration = Date.now() - startTime; - const result: AutoPipelineResult = { - success: true, - outputPath: this.context.get('step_export')?.outputPath as string | undefined, - duration: this.context.get('step_export')?.duration as number | undefined, - resolution: '1080p', - fileSize: this.context.get('step_export')?.fileSize as number | undefined, - stepDurations: collectStepDurations(this.stepStates), - sceneCount: (this.context.get('step_script') as unknown as { scenes?: { length: number } }) - ?.scenes?.length as number | undefined, - characterCount: ( - this.context.get('step_character') as unknown as { characters?: { length: number } } - )?.characters?.length as number | undefined, - renderedFrames: ( - this.context.get('step_render') as unknown as { renderedFrames?: { length: number } } - )?.renderedFrames?.length as number | undefined, - }; - - this.events.dispatchPipelineComplete({ ...result, duration }); - return result; - } catch (error) { - this.status = 'failed'; - const errorMessage = error instanceof Error ? error.message : String(error); - this.events.dispatchPipelineFail(errorMessage); - return { success: false, error: errorMessage }; - } finally { - this.stopCheckpointInterval(); - } - } - - cancel(): void { - if (this.abortController) { - this.abortController.abort(); - } - this.status = 'cancelled'; - this.events.dispatchPipelineCancel(); - } - - getStatus(): { status: PipelineStatus; currentStepId: string | null; progress: number } { - return { - status: this.status, - currentStepId: this.currentStepId, - progress: computeProgressPercent(this.stepStates, this.steps.length), - }; - } - - getStepStates(): Map { - return new Map(this.stepStates); - } - - onEvents(handler: PipelineEventHandler): void { - this.handlers.push(handler); - } - - isRunning(): boolean { - return this.status === 'running'; - } - - // ============================================================================ - // 私有方法 - // ============================================================================ - - /** - * 执行单个步骤(带自审循环 + 质量门禁)。 - * 行为与原 executeStep 逐字一致。 - */ - private async executeStep(step: PipelineStep): Promise<{ success: boolean; error?: string }> { - applyStepStateTransition(this.stepStates, step.stepId, 'running'); - logger.info(`[AutoPipeline] Starting step: ${step.name} (${step.stepId})`); - this.events.dispatchStepStart(step.stepId); - - const qualityGate = createQualityGate(step.stepId); - - const input = buildStepInput(step, this.context); - - let output: StepOutput; - try { - output = await executeStepWithTimeout(step, input); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - applyStepStateTransition(this.stepStates, step.stepId, 'failed', { error: errorMessage }); - this.events.dispatchStepFail(step.stepId, errorMessage); - return { success: false, error: errorMessage }; - } - - let reviewCount = 0; - const maxRetries = qualityGate.isSelfReviewEnabled() ? qualityGate.getMaxReviewRetries() : 0; - - while (true) { - const gateResult = qualityGate.evaluate(step.stepId, output); - - if (gateResult.passed) { - logger.info( - `[AutoPipeline] Step ${step.name} passed quality gate (score: ${gateResult.score})` - ); - this.events.dispatchQualityGate(step.stepId, { - passed: true, - score: gateResult.score, - details: gateResult.details, - }); - break; - } - - reviewCount += 1; - logger.warn( - `[AutoPipeline] Step ${step.name} failed quality gate (attempt ${reviewCount}), score: ${gateResult.score}` - ); - - applyStepStateTransition(this.stepStates, step.stepId, 'reviewing', { reviewCount }); - this.events.dispatchStepReviewStart(step.stepId, reviewCount); - - if (reviewCount > maxRetries) { - logger.error( - `[AutoPipeline] Step ${step.name} exceeded max review retries (${maxRetries})` - ); - this.events.dispatchStepFail( - step.stepId, - `Quality gate failed after ${maxRetries} retries` - ); - applyStepStateTransition(this.stepStates, step.stepId, 'failed', { - error: `Quality gate failed: ${gateResult.details}`, - }); - return { success: false, error: `Quality gate failed: ${gateResult.details}` }; - } - - const reviewResult = await this.selfReview.review(step.stepId, output); - - if (reviewResult.passed) { - logger.info( - `[AutoPipeline] Review passed, but repairing dimensions: ${reviewResult.dimensions - .filter((d) => !d.passed) - .map((d) => d.dimension) - .join(', ')}` - ); - } - - const repairedOutput = await this.selfReview.repair(step.stepId, output, reviewResult); - - if (repairedOutput !== output) { - output = repairedOutput; - logger.info(`[AutoPipeline] Step ${step.name} output repaired (attempt ${reviewCount})`); - } - - this.events.dispatchStepReviewComplete(step.stepId, reviewResult); - } - - this.context.set(step.stepId, output); - applyStepStateTransition(this.stepStates, step.stepId, 'completed', { output }); - this.events.dispatchStepComplete(step.stepId, output); - this.saveCheckpoint(step.stepId, output, reviewCount); - - return { success: true }; - } - - /** 保存检查点(pipelineId + 状态快照 → localStorage) */ - private saveCheckpoint(stepId: string, output: StepOutput, reviewCount: number): void { - const checkpoint = buildCheckpointSnapshot({ - pipelineId: buildPipelineId( - this.context.get('__input__') as unknown as AutoPipelineInput | undefined - ), - status: this.status, - currentStepId: stepId, - stepStates: this.stepStates, - context: this.context as unknown as Map, - now: Date.now(), - }); - saveCheckpointToStorage(checkpoint); - void reviewCount; // 保持调用签名不变(原行为也不使用) - } - - /** 从 checkpoint 恢复 stepStates + context */ - private restoreFromCheckpoint( - checkpoint: import('./types/autonomous.types').PipelineCheckpoint - ): void { - for (const [stepId, stepState] of Object.entries(checkpoint.steps)) { - const state = stepState as unknown as import('./types/autonomous.types').StepState; - this.stepStates.set(stepId, state); - if (stepState.data) { - this.context.set(stepId, stepState.data); - } - } - } - - /** 启动自动检查点定时器(30 秒) */ - private startCheckpointInterval(): void { - this.checkpointInterval = setInterval(() => { - if (this.status === 'running' && this.currentStepId) { - const currentOutput = this.context.get(this.currentStepId) as StepOutput | undefined; - if (currentOutput) { - this.saveCheckpoint(this.currentStepId, currentOutput, 0); - } - } - }, CHECKPOINT_INTERVAL_MS); - } - - /** 停止检查点定时器 */ - private stopCheckpointInterval(): void { - if (this.checkpointInterval) { - clearInterval(this.checkpointInterval); - this.checkpointInterval = null; - } - } - - /** 动态加载步骤链(占位实现) */ - private async loadSteps(): Promise { - // 实际从 core/pipeline/steps/ 动态导入 - void AUTONOMOUS_STEPS; - return []; - } -} - -/** - * 工厂函数:创建 AutoPipelineEngine,可选注入步骤链。 - */ -export function createAutoPipelineEngine(options?: { - maxReviewRetries?: number; - reviewModel?: string; - steps?: PipelineStep[]; -}): AutoPipelineEngine { - const engine = new AutoPipelineEngine(options); - - if (options?.steps) { - (engine as unknown as { steps: PipelineStep[] }).steps = options.steps; - } - - return engine; -} diff --git a/src/core/autonomous/evaluator/quality-gate-config.ts b/src/core/autonomous/evaluator/quality-gate-config.ts deleted file mode 100644 index 3d26e61d..00000000 --- a/src/core/autonomous/evaluator/quality-gate-config.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Quality Gate 配置集中 - * ===================== - * 两个大字典:DEFAULT_REVIEW_CRITERIA + DEFAULT_QUALITY_GATE_CONFIG - * 从 quality-gate.ts 中抽出,便于维护和更新阈值。 - * 单一职责:配置字典,无逻辑。 - */ -import type { QualityGateConfig, ReviewCriteria } from '../types/autonomous.types'; - -// ============================================================================ -// 各步骤的默认审核标准 -// ============================================================================ - -export const DEFAULT_REVIEW_CRITERIA: Record = { - [/* IMPORT */ '']: { - dimensions: ['completeness'], - minScorePerDimension: 60, - minTotalScore: 60, - minPassedDimensions: 1, - }, - script: { - dimensions: ['completeness', 'consistency', 'visual_quality', 'duration_match', 'punch_point'], - minScorePerDimension: 60, - minTotalScore: 70, - minPassedDimensions: 4, - }, - character: { - dimensions: ['completeness', 'consistency', 'visual_quality'], - minScorePerDimension: 65, - minTotalScore: 70, - minPassedDimensions: 3, - }, - storyboard: { - dimensions: ['completeness', 'consistency', 'visual_quality', 'duration_match'], - minScorePerDimension: 60, - minTotalScore: 65, - minPassedDimensions: 3, - }, - render: { - dimensions: ['completeness', 'visual_quality'], - minScorePerDimension: 50, - minTotalScore: 60, - minPassedDimensions: 1, - }, - audio: { - dimensions: ['completeness', 'duration_match'], - minScorePerDimension: 60, - minTotalScore: 60, - minPassedDimensions: 1, - }, -}; - -// ============================================================================ -// 各步骤的质量门禁默认配置 -// ============================================================================ - -export const DEFAULT_QUALITY_GATE_CONFIG: Record = { - import: { - enabled: true, - threshold: 60, - onFail: 'stop', - reviewConfig: { enabled: false, maxRetries: 0 }, - }, - analysis: { - enabled: true, - threshold: 60, - onFail: 'retry', - reviewConfig: { enabled: true, maxRetries: 2 }, - }, - script: { - enabled: true, - threshold: 70, - onFail: 'retry', - reviewConfig: { enabled: true, maxRetries: 3 }, - }, - character: { - enabled: true, - threshold: 70, - onFail: 'retry', - reviewConfig: { enabled: true, maxRetries: 3 }, - }, - scene: { - enabled: true, - threshold: 65, - onFail: 'retry', - reviewConfig: { enabled: true, maxRetries: 2 }, - }, - storyboard: { - enabled: true, - threshold: 65, - onFail: 'retry', - reviewConfig: { enabled: true, maxRetries: 3 }, - }, - render: { - enabled: true, - threshold: 60, - onFail: 'retry', - reviewConfig: { enabled: true, maxRetries: 2 }, - }, - 'video-edit': { - enabled: true, - threshold: 65, - onFail: 'retry', - reviewConfig: { enabled: true, maxRetries: 2 }, - }, - audio: { - enabled: true, - threshold: 60, - onFail: 'retry', - reviewConfig: { enabled: true, maxRetries: 2 }, - }, - subtitle: { - enabled: true, - threshold: 60, - onFail: 'skip', - reviewConfig: { enabled: false, maxRetries: 0 }, - }, - export: { - enabled: true, - threshold: 70, - onFail: 'retry', - reviewConfig: { enabled: true, maxRetries: 3 }, - }, -}; - -/** 工厂 fallback 配置(未知 stepId 时) */ -export const FALLBACK_GATE_CONFIG: QualityGateConfig = { - enabled: true, - threshold: 65, - onFail: 'retry', - reviewConfig: { enabled: true, maxRetries: 2 }, -}; diff --git a/src/core/autonomous/evaluator/quality-gate-evaluators.ts b/src/core/autonomous/evaluator/quality-gate-evaluators.ts deleted file mode 100644 index 1b1ebb85..00000000 --- a/src/core/autonomous/evaluator/quality-gate-evaluators.ts +++ /dev/null @@ -1,166 +0,0 @@ -/** - * 基础检查 + 默认评分逻辑 - * ======================= - * 5 个 step_import/script/character/render/export 的基础检查 switch - * + 4 个 step_analysis/script/storyboard/audio 的默认评分 switch。 - * - * 抽成纯函数,QualityGate 类内部再调用。 - * 单一职责:评估逻辑,不含配置。 - */ -import type { StepOutput, QualityGateResult } from '../types/autonomous.types'; - -// ============================================================================ -// 基础检查 -// ============================================================================ - -/** - * 执行基础检查:空输出 / 步骤特定字段。 - * 返回 { passed, reason?, score }。 - */ -export function performBasicChecks( - stepId: string, - output: StepOutput -): { - passed: boolean; - reason?: string; - score: number; -} { - // 空输出检查 - if (!output || Object.keys(output).length === 0) { - return { passed: false, reason: 'Empty output', score: 0 }; - } - - // 步骤特定检查 - switch (stepId) { - case 'step_import': { - const chapters = output.chapters as Array | undefined; - if (!chapters || chapters.length === 0) { - return { passed: false, reason: 'No chapters found in import', score: 0 }; - } - const wordCount = (output.metadata as Record)?.wordCount as - | number - | undefined; - if (!wordCount || wordCount < 100) { - return { passed: false, reason: 'Word count too low (<100)', score: 30 }; - } - return { passed: true, score: 80 }; - } - - case 'step_script': { - const scenes = output.scenes as Array | undefined; - if (!scenes || scenes.length < 3) { - return { passed: false, reason: 'Insufficient scenes (<3)', score: 20 }; - } - const totalDuration = output.totalDuration as number | undefined; - if (!totalDuration || totalDuration < 60) { - return { passed: false, reason: 'Total duration too short (<60s)', score: 40 }; - } - return { passed: true, score: 75 }; - } - - case 'step_character': { - const characters = output.characters as Array | undefined; - if (!characters || characters.length === 0) { - return { passed: false, reason: 'No characters found', score: 0 }; - } - return { passed: true, score: 80 }; - } - - case 'step_render': { - const renderedFrames = output.renderedFrames as Array | undefined; - const totalFrames = output.totalFrames as number | undefined; - if (!renderedFrames || !totalFrames) { - return { passed: false, reason: 'Render output missing', score: 0 }; - } - const successRate = renderedFrames.length / totalFrames; - if (successRate < 0.5) { - return { - passed: false, - reason: `Success rate too low (${(successRate * 100).toFixed(0)}%)`, - score: 30, - }; - } - return { passed: true, score: Math.round(successRate * 100) }; - } - - case 'step_export': { - const outputPath = output.outputPath as string | undefined; - if (!outputPath) { - return { passed: false, reason: 'No output file path', score: 0 }; - } - return { passed: true, score: 100 }; - } - - default: - return { passed: true, score: 70 }; - } -} - -// ============================================================================ -// 默认质量评分 -// ============================================================================ - -/** - * 计算默认质量分数(无需 LLM,纯规则)。 - * 4 个步骤:analysis / script / storyboard / audio。 - * 默认步骤返回 70。 - */ -export function calculateDefaultScore(stepId: string, output: StepOutput): number { - switch (stepId) { - case 'step_analysis': { - const characters = output.characters as Array | undefined; - const scenes = output.scenes as Array | undefined; - let score = 50; - if (characters && characters.length > 0) score += 20; - if (scenes && scenes.length > 0) score += 20; - return score; - } - - case 'step_script': { - const scenes = output.scenes as Array | undefined; - let score = 50; - if (scenes && scenes.length >= 5) score += 25; - else if (scenes && scenes.length >= 3) score += 15; - const totalDuration = output.totalDuration as number | undefined; - if (totalDuration && totalDuration >= 300 && totalDuration <= 1800) score += 15; - return score; - } - - case 'step_storyboard': { - const shots = output.shots as Array | undefined; - let score = 50; - if (shots && shots.length >= 4) score += 30; - else if (shots && shots.length >= 2) score += 15; - return score; - } - - case 'step_audio': { - const duration = output.duration as number | undefined; - const targetDuration = output.targetDuration as number | undefined; - let score = 60; - if (duration && targetDuration) { - const deviation = Math.abs(duration - targetDuration) / targetDuration; - if (deviation < 0.05) score += 30; - else if (deviation < 0.1) score += 20; - else if (deviation < 0.2) score += 10; - else score -= 20; - } - return Math.max(0, Math.min(100, score)); - } - - default: - return 70; - } -} - -/** - * 使用审核标准进行评分(占位:实际由 LLM 完成)。 - * 返回标记需要 LLM-based review。 - */ -export function evaluateWithCriteriaPlaceholder(): QualityGateResult { - return { - passed: true, // 占位,实际由 SelfReviewLoop 决定 - details: 'Requires LLM-based review', - score: 100, - }; -} diff --git a/src/core/autonomous/evaluator/quality-gate.ts b/src/core/autonomous/evaluator/quality-gate.ts deleted file mode 100644 index 5a122f4e..00000000 --- a/src/core/autonomous/evaluator/quality-gate.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * QualityGate — 质量门禁(Facade) - * ================================ - * 每个 Pipeline Step 完成后,必须通过 QualityGate 判定质量。 - * 不合格的输出会触发 Self-Review Loop 进行自动修复。 - * - * 拆分思路(2 个 sibling 模块): - * - quality-gate-config.ts 两个配置字典 (REVIEW_CRITERIA + GATE_CONFIG) - * - quality-gate-evaluators.ts 基础检查 + 默认评分 + 占位 LLM 评分 - * - quality-gate.ts (facade) QualityGate 类 (薄壳) + createQualityGate 工厂 - * - * 调用方不需要修改。 - */ - -import type { - QualityGateResult, - QualityGateConfig, - StepOutput, - ReviewCriteria, -} from '../types/autonomous.types'; - -import { DEFAULT_QUALITY_GATE_CONFIG, FALLBACK_GATE_CONFIG } from './quality-gate-config'; -import { - performBasicChecks, - calculateDefaultScore, - evaluateWithCriteriaPlaceholder, -} from './quality-gate-evaluators'; - -// Re-export 配置字典(供外部使用) -export { DEFAULT_REVIEW_CRITERIA, DEFAULT_QUALITY_GATE_CONFIG } from './quality-gate-config'; - -// ============================================================================ -// QualityGate 类 -// ============================================================================ - -export class QualityGate { - private config: QualityGateConfig; - - constructor(config: QualityGateConfig) { - this.config = { - ...config, - enabled: config.enabled ?? true, - threshold: config.threshold ?? 70, - onFail: config.onFail ?? 'retry', - }; - } - - /** - * 评估 Step 输出质量。 - * - * 1. 未启用 → 直接通过 - * 2. 基础检查失败 → 直接不通过 - * 3. 有自定义 criteria → 占位 LLM 评分 - * 4. 默认规则 → 纯规则打分 + 阈值判定 - */ - evaluate(stepId: string, output: StepOutput, criteria?: ReviewCriteria): QualityGateResult { - if (!this.config.enabled) { - return { - passed: true, - details: 'Quality gate disabled', - score: 100, - }; - } - - // 基础检查 - const basicCheck = performBasicChecks(stepId, output); - if (!basicCheck.passed) { - return { - passed: false, - details: basicCheck.reason ?? 'Basic check failed', - score: basicCheck.score, - }; - } - - // 如果有自定义 criteria,进行评分 - if (criteria) { - return evaluateWithCriteriaPlaceholder(); - } - - // 使用默认规则 - const score = calculateDefaultScore(stepId, output); - const passed = score >= this.config.threshold; - - return { - passed, - details: passed - ? `Quality score ${score} meets threshold ${this.config.threshold}` - : `Quality score ${score} below threshold ${this.config.threshold}`, - score, - }; - } - - /** 获取 onFail 处理策略 */ - getOnFailStrategy(): 'retry' | 'skip' | 'stop' { - return this.config.onFail; - } - - /** 是否启用自审 */ - isSelfReviewEnabled(): boolean { - return this.config.reviewConfig?.enabled ?? false; - } - - /** 获取最大自审次数 */ - getMaxReviewRetries(): number { - return this.config.reviewConfig?.maxRetries ?? 2; - } -} - -// ============================================================================ -// 工厂函数 -// ============================================================================ - -export function createQualityGate(stepId: string): QualityGate { - const config = DEFAULT_QUALITY_GATE_CONFIG[stepId] ?? FALLBACK_GATE_CONFIG; - return new QualityGate(config); -} diff --git a/src/core/autonomous/evaluator/self-review-loop.ts b/src/core/autonomous/evaluator/self-review-loop.ts deleted file mode 100644 index ec1c7a76..00000000 --- a/src/core/autonomous/evaluator/self-review-loop.ts +++ /dev/null @@ -1,157 +0,0 @@ -/** - * SelfReviewLoop — AI 自审循环(Facade) - * ====================================== - * 核心创新:当 Step 输出未通过 QualityGate 时, - * 调用 LLM 自我分析失败原因,重新生成修复后的输出。 - * 最多循环 maxRetries 次(默认 3 次)。 - * - * 拆分思路(2 个 sibling 模块): - * - self-review-prompt-templates.ts 2 个 prompt 模板 + STEP_NAMES + 温度/token 常量 - * - self-review-parsers.ts extractJson / parseReviewResult / parseJsonOutput - * - self-review-loop.ts (facade) SelfReviewLoop 类 (薄壳) + createSelfReviewLoop 工厂 - */ - -import { aiService } from '../../../../src/core/services/ai/text/ai.service'; -import { logger } from '../../../../src/core/utils/logger'; -import type { ReviewResult, StepOutput } from '../types/autonomous.types'; - -import { parseReviewResult, parseJsonOutput } from './self-review-parsers'; -import { - REVIEW_PROMPT_TEMPLATE, - REPAIR_PROMPT_TEMPLATE, - STEP_NAMES, - REVIEW_TEMPERATURE, - REVIEW_MAX_TOKENS, - REPAIR_TEMPERATURE, - REPAIR_MAX_TOKENS, -} from './self-review-prompt-templates'; - -export class SelfReviewLoop { - private maxRetries: number; - private model: string; - private reviewCount: Map = new Map(); - - constructor(options: { maxRetries?: number; model?: string } = {}) { - this.maxRetries = options.maxRetries ?? 3; - this.model = options.model ?? 'glm-5'; - } - - /** 审核 Step 输出 */ - async review(stepId: string, output: StepOutput): Promise { - const stepName = STEP_NAMES[stepId] ?? stepId; - - const prompt = REVIEW_PROMPT_TEMPLATE.replace('{stepName}', stepName).replace( - '{originalOutput}', - JSON.stringify(output, null, 2) - ); - - try { - const response = await aiService.generate(prompt, { - model: this.model, - provider: 'openai', - max_tokens: REVIEW_MAX_TOKENS, - temperature: REVIEW_TEMPERATURE, - }); - - return parseReviewResult(response); - } catch (error) { - // 审核失败时,默认通过(不阻塞流程) - logger.error(`[SelfReviewLoop] Review failed for ${stepId}:`, error); - return { - passed: true, - score: 70, - dimensions: [], - reasons: [], - suggestions: [], - }; - } - } - - /** 判定是否应该重试 */ - shouldRetry(stepId: string, result: ReviewResult): boolean { - const currentCount = this.reviewCount.get(stepId) ?? 0; - if (!result.passed && currentCount < this.maxRetries) { - return true; - } - return false; - } - - /** 增加重试计数 */ - incrementRetry(stepId: string): number { - const current = this.reviewCount.get(stepId) ?? 0; - const next = current + 1; - this.reviewCount.set(stepId, next); - return next; - } - - /** 重置重试计数 */ - reset(stepId: string): void { - this.reviewCount.delete(stepId); - } - - /** 获取当前重试次数 */ - getRetryCount(stepId: string): number { - return this.reviewCount.get(stepId) ?? 0; - } - - /** 修复 Step 输出 */ - async repair( - stepId: string, - originalOutput: StepOutput, - reviewResult: ReviewResult - ): Promise { - const stepName = STEP_NAMES[stepId] ?? stepId; - - const reasons = - reviewResult.reasons.length > 0 ? reviewResult.reasons.join('\n') : '综合评分未达标'; - - const suggestions = - reviewResult.suggestions.length > 0 - ? reviewResult.suggestions.join('\n') - : '请根据审核反馈优化输出质量'; - - const reviewResultText = ` -评分:${reviewResult.score}/100 -${reviewResult.dimensions - .map((d) => `${d.dimension}: ${d.score}分 ${d.passed ? '✓' : '✗'} - ${d.detail}`) - .join('\n')} -修复建议:${suggestions} -`.trim(); - - const prompt = REPAIR_PROMPT_TEMPLATE.replace(/{stepName}/g, stepName) - .replace('{originalOutput}', JSON.stringify(originalOutput, null, 2)) - .replace('{reviewResult}', reviewResultText) - .replace('{fallbackReasons}', reasons); - - try { - const response = await aiService.generate(prompt, { - model: this.model, - provider: 'openai', - max_tokens: REPAIR_MAX_TOKENS, - temperature: REPAIR_TEMPERATURE, - }); - - const repaired = parseJsonOutput(response); - return repaired ?? originalOutput; // 解析失败时返回原输出 - } catch (error) { - logger.error(`[SelfReviewLoop] Repair failed for ${stepId}:`, error); - return originalOutput; - } - } -} - -// ============================================================================ -// 工厂函数 -// ============================================================================ - -let sharedInstance: SelfReviewLoop | null = null; - -export function createSelfReviewLoop(options?: { - maxRetries?: number; - model?: string; -}): SelfReviewLoop { - if (!sharedInstance) { - sharedInstance = new SelfReviewLoop(options); - } - return sharedInstance; -} diff --git a/src/core/autonomous/evaluator/self-review-parsers.ts b/src/core/autonomous/evaluator/self-review-parsers.ts deleted file mode 100644 index 8125cc3f..00000000 --- a/src/core/autonomous/evaluator/self-review-parsers.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Self-Review Loop JSON 解析器 - * ============================ - * 从 LLM 回复中提取结构化 JSON 的 3 个方法。 - * - * extractJson:3 种策略(直接解析 → code block → 首尾大括号) - * parseReviewResult:提取 5 维度评分 - * parseJsonOutput:提取修复后的 JSON(as StepOutput) - * - * 单一职责:纯解析,无 AI 调用。 - */ -import type { ReviewResult, ReviewDimension, StepOutput } from '../types/autonomous.types'; - -/** - * 从文本中提取 JSON 对象。 - * 3 种策略(按优先级): - * 1. 直接 JSON.parse - * 2. ```json ... ``` code block - * 3. 首个 { ... 最后一个 } 之间的内容 - * - * 全部失败返回 null。 - */ -export function extractJson(text: string): Record | null { - // 策略 1:直接解析 - try { - return JSON.parse(text) as Record; - } catch { - // 继续 - } - - // 策略 2:从 ```json 块中提取 - const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/); - if (codeBlockMatch) { - try { - return JSON.parse(codeBlockMatch[1].trim()) as Record; - } catch { - // 继续 - } - } - - // 策略 3:首个 { ... 最后一个 } 之间的内容 - const firstBrace = text.indexOf('{'); - const lastBrace = text.lastIndexOf('}'); - if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { - try { - return JSON.parse(text.slice(firstBrace, lastBrace + 1)) as Record; - } catch { - // 放弃 - } - } - - return null; -} - -/** 解析失败时的默认 ReviewResult(通过,不阻塞流程) */ -const DEFAULT_REVIEW_RESULT: ReviewResult = { - passed: true, - score: 70, - dimensions: [], - reasons: [], - suggestions: [], -}; - -/** 分数值夹具:0-100 范围限制 */ -function clampScore(value: unknown): number { - return Math.max(0, Math.min(100, Number(value) || 0)); -} - -/** - * 解析审核结果(5 维度评分)。 - * 解析失败时返回 DEFAULT_REVIEW_RESULT(通过,不阻塞)。 - */ -export function parseReviewResult(response: string): ReviewResult { - try { - const json = extractJson(response); - if (!json) throw new Error('No JSON found'); - - return { - passed: Boolean(json.passed), - score: clampScore(json.score), - dimensions: Array.isArray(json.dimensions) - ? json.dimensions.map((d: Record) => ({ - dimension: (d.dimension as ReviewDimension) ?? 'completeness', - score: clampScore(d.score), - passed: Boolean(d.passed), - detail: String(d.detail ?? ''), - })) - : [], - reasons: Array.isArray(json.reasons) ? json.reasons.map(String) : [], - suggestions: Array.isArray(json.suggestions) ? json.suggestions.map(String) : [], - }; - } catch { - return DEFAULT_REVIEW_RESULT; - } -} - -/** - * 解析修复后的 JSON 输出(as StepOutput)。 - * 返回 null 表示解析失败,调用方回退到原输出。 - */ -export function parseJsonOutput(text: string): StepOutput | null { - const json = extractJson(text); - return json as StepOutput | null; -} diff --git a/src/core/autonomous/evaluator/self-review-prompt-templates.ts b/src/core/autonomous/evaluator/self-review-prompt-templates.ts deleted file mode 100644 index e08c2286..00000000 --- a/src/core/autonomous/evaluator/self-review-prompt-templates.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Self-Review Loop Prompt 模板 - * =========================== - * 两个大 prompt 模板 + 11 步 stepName 映射。 - * 便于单独调优 prompt 文案,不用改 SelfReviewLoop 类逻辑。 - */ - -/** 审核 prompt 模板(5 维度评分) */ -export const REVIEW_PROMPT_TEMPLATE = `你是专业的 AI 内容质量审核员。请审核以下 AI 生成的 {stepName} 输出。 - -## 审核维度 - -1. **完整性 (completeness)**:输出是否包含所有必要字段/元素? -2. **一致性 (consistency)**:人物描写、场景描述前后是否矛盾? -3. **画面感 (visual_quality)**:描述是否具备足够的视觉细节供 AI 生图? -4. **时长匹配 (duration_match)**:对话/场景时长是否与内容体量匹配? -5. **情绪爆点 (punch_point)**:是否包含情绪爆点、转折、高潮? - -## 原输出 - -{originalOutput} - -## 审核要求 - -请严格按照上述 5 个维度评分(0-100分),并给出: -1. 每个维度的评分和是否通过(>=60分通过) -2. 不合格的具体原因(列出所有未通过项) -3. 修复建议 - -## 输出格式 - -请严格按以下 JSON 格式输出,不要包含任何其他内容: - -{ - "passed": true/false, - "score": 0-100, - "dimensions": [ - {"dimension": "completeness", "score": 0-100, "passed": true/false, "detail": "说明"}, - {"dimension": "consistency", "score": 0-100, "passed": true/false, "detail": "说明"}, - {"dimension": "visual_quality", "score": 0-100, "passed": true/false, "detail": "说明"}, - {"dimension": "duration_match", "score": 0-100, "passed": true/false, "detail": "说明"}, - {"dimension": "punch_point", "score": 0-100, "passed": true/false, "detail": "说明"} - ], - "reasons": ["不合格原因1", "不合格原因2"], - "suggestions": ["修复建议1", "修复建议2"] -}`; - -/** 修复 prompt 模板(基于审核反馈重新生成) */ -export const REPAIR_PROMPT_TEMPLATE = `你是专业的 {stepName} 内容生成专家。以下是你之前生成的 {stepName} 输出和审核反馈。 - -## 原输出 - -{originalOutput} - -## 审核反馈 - -{reviewResult} - -## 不合格原因 - -{fallbackReasons} - -## 修复要求 - -请根据以上反馈,重新生成符合以下要求的 {stepName} 输出: -1. 修复所有不合格项 -2. 保持与上下文的连贯性 -3. 输出格式保持不变 -4. 只输出修复后的内容,不要包含任何解释 - -## 直接输出修复后的 JSON 内容:`; - -/** 步骤 ID → 中文名称映射 */ -export const STEP_NAMES: Record = { - 'step-import': '导入解析', - 'step-analysis': 'AI 分析', - 'step-script': '剧本生成', - 'step-character': '角色设计', - 'step-scene': '场景规划', - 'step-storyboard': '分镜生成', - 'step-render': '批量渲染', - 'step-video-edit': '视频剪辑', - 'step-audio': '配音合成', - 'step-subtitle': '字幕嵌入', - 'step-export': '成片导出', -}; - -/** 审核温度(低温 = 稳定) */ -export const REVIEW_TEMPERATURE = 0.3; - -/** 修复温度(较高温度 = 产生变化) */ -export const REPAIR_TEMPERATURE = 0.7; - -/** 审核最大 token 数 */ -export const REVIEW_MAX_TOKENS = 4096; - -/** 修复最大 token 数 */ -export const REPAIR_MAX_TOKENS = 8192; diff --git a/src/core/autonomous/index.ts b/src/core/autonomous/index.ts deleted file mode 100644 index 06b2ec56..00000000 --- a/src/core/autonomous/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * frame-fab Autonomous Mode — Core Module - * - * 全自动 AI 漫剧制作系统核心模块 - * - * @example - * ```typescript - * import { createAutoPipelineEngine } from './autonomous'; - * - * const engine = createAutoPipelineEngine({ maxReviewRetries: 3 }); - * - * engine.onEvents({ - * onStepProgress: (stepId, progress) => { - * console.log(`[${stepId}] Progress: ${progress}%`); - * }, - * onPipelineComplete: (result) => { - * console.log('Done! Output:', result.outputPath); - * }, - * }); - * - * const result = await engine.run({ - * content: '从前有座山,山里有座庙...', - * mode: 'novel', - * style: 'anime', - * qualityLevel: 'balanced', - * }); - * ``` - */ - -export * from './types/autonomous.types'; -export * from './evaluator/quality-gate'; -export * from './evaluator/self-review-loop'; -export * from './auto-pipeline-engine'; diff --git a/src/core/autonomous/pipeline-checkpoint.ts b/src/core/autonomous/pipeline-checkpoint.ts deleted file mode 100644 index 2f38d553..00000000 --- a/src/core/autonomous/pipeline-checkpoint.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Pipeline 检查点管理 - * - * 把原 AutoPipelineEngine 中关于"检查点持久化"的几块逻辑集中: - * - 构造 pipelineId - * - 保存到 localStorage(容错 + 写 warn 日志) - * - 从 localStorage 读取(容错 + 返回 null) - * - 把检查点恢复回 stepStates + context - * - 30 秒定时保存 interval(启动/停止) - * - * 单一职责:检查点生命周期与持久化。 - */ - -import { logger } from '@/core/utils/logger'; - -import type { - AutoPipelineInput, - PipelineCheckpoint, - PipelineStatus, - StepState, -} from './types/autonomous.types'; - -/** 检查点 localStorage key 前缀 */ -const CHECKPOINT_KEY_PREFIX = 'autopipeline_checkpoint_'; - -/** 自动检查点保存间隔(毫秒),与原实现一致 */ -export const CHECKPOINT_INTERVAL_MS = 30000; - -/** - * 构造 pipeline 唯一 ID。 - * 行为与原 getPipelineId 逐字一致:优先使用输入的 title,否则 fallback 到 `pipeline_`。 - */ -export function buildPipelineId(input: AutoPipelineInput | undefined | null): string { - return input?.title ?? `pipeline_${Date.now()}`; -} - -/** - * 把检查点写入 localStorage。失败时 warn 但不抛出。 - */ -export function saveCheckpointToStorage(checkpoint: PipelineCheckpoint): boolean { - try { - const key = `${CHECKPOINT_KEY_PREFIX}${checkpoint.pipelineId}`; - localStorage.setItem(key, JSON.stringify(checkpoint)); - return true; - } catch (error) { - logger.warn('[AutoPipeline] Failed to save checkpoint:', error); - return false; - } -} - -/** - * 从 localStorage 读取检查点。失败/缺失一律返回 null。 - */ -export function loadCheckpointFromStorage(pipelineId: string): PipelineCheckpoint | null { - try { - const key = `${CHECKPOINT_KEY_PREFIX}${pipelineId}`; - const stored = localStorage.getItem(key); - if (stored) { - return JSON.parse(stored) as PipelineCheckpoint; - } - } catch { - // 忽略错误(原行为) - } - return null; -} - -/** - * 构造一个 PipelineCheckpoint 快照(不负责持久化)。 - * 把 stepStates 转换为 Record。 - */ -export function buildCheckpointSnapshot(params: { - pipelineId: string; - status: PipelineStatus; - currentStepId: string; - stepStates: Map; - context: Map; - now: number; -}): PipelineCheckpoint { - const entries = Array.from(params.stepStates.entries()); - return { - pipelineId: params.pipelineId, - status: params.status, - currentStepId: params.currentStepId, - steps: Object.fromEntries(entries) as unknown as PipelineCheckpoint['steps'], - input: params.context.get('__input__') as unknown as AutoPipelineInput, - startedAt: params.now, - updatedAt: params.now, - }; -} - -/** - * 把 checkpoint.steps 还原到 stepStates Map,并把每个 step 的 data 还原到 context。 - * - * 行为与原 restoreFromCheckpoint 逐字一致。 - */ -export function applyCheckpointToEngine( - checkpoint: PipelineCheckpoint, - stepStates: Map, - context: Map -): void { - const stepsRecord = checkpoint.steps as unknown as Record; - for (const [stepId, stepState] of Object.entries(stepsRecord)) { - const state = stepState as unknown as StepState; - stepStates.set(stepId, state); - if (stepState.data) { - context.set(stepId, stepState.data); - } - } -} diff --git a/src/core/autonomous/pipeline-event-dispatcher.ts b/src/core/autonomous/pipeline-event-dispatcher.ts deleted file mode 100644 index 53bc7ee5..00000000 --- a/src/core/autonomous/pipeline-event-dispatcher.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Pipeline 事件分发器 - * - * 把原 AutoPipelineEngine.emit 中的 13-case switch 拆为独立的派发函数。 - * - 类型安全:每个事件有独立签名 - * - 调用方通过 dispatchXxxEvent(...) 调用,避免字符串魔数 - * - 兼容旧 emit(eventType, ...args) 调用方式(保留兼容层) - */ - -import type { - AutoPipelineResult, - PipelineEventHandler, - QualityGateResult, - ReviewResult, - StepOutput, -} from './types/autonomous.types'; - -// ─────────── 事件类型常量(保留兼容旧 emit 字符串协议) ─────────── - -const PIPELINE_EVENT = { - STEP_START: 'step_start', - STEP_PROGRESS: 'step_progress', - STEP_COMPLETE: 'step_complete', - STEP_FAIL: 'step_fail', - STEP_REVIEW_START: 'step_review_start', - STEP_REVIEW_COMPLETE: 'step_review_complete', - QUALITY_GATE: 'quality_gate', - PIPELINE_START: 'pipeline_start', - PIPELINE_COMPLETE: 'pipeline_complete', - PIPELINE_FAIL: 'pipeline_fail', - PIPELINE_PAUSE: 'pipeline_pause', - PIPELINE_RESUME: 'pipeline_resume', - PIPELINE_CANCEL: 'pipeline_cancel', -} as const; - -/** - * 类型化的事件派发器:把单 case 的实现拆成独立函数, - * 避免巨型 switch 难以阅读与扩展。 - */ -export class PipelineEventDispatcher { - constructor(private handlers: PipelineEventHandler[]) {} - - /** step_start(stepId) */ - dispatchStepStart(stepId: string): void { - for (const handler of this.handlers) { - handler.onStepStart?.(stepId); - } - } - - /** step_progress(stepId, progress, message) */ - dispatchStepProgress(stepId: string, progress: number, message: string): void { - for (const handler of this.handlers) { - handler.onStepProgress?.(stepId, progress, message); - } - } - - /** step_complete(stepId, output) */ - dispatchStepComplete(stepId: string, output: StepOutput): void { - for (const handler of this.handlers) { - handler.onStepComplete?.(stepId, output); - } - } - - /** step_fail(stepId, errorMessage) */ - dispatchStepFail(stepId: string, errorMessage: string): void { - for (const handler of this.handlers) { - handler.onStepFail?.(stepId, errorMessage); - } - } - - /** step_review_start(stepId, reviewCount) */ - dispatchStepReviewStart(stepId: string, reviewCount: number): void { - for (const handler of this.handlers) { - handler.onStepReviewStart?.(stepId, reviewCount); - } - } - - /** step_review_complete(stepId, reviewResult) */ - dispatchStepReviewComplete(stepId: string, reviewResult: ReviewResult): void { - for (const handler of this.handlers) { - handler.onStepReviewComplete?.(stepId, reviewResult); - } - } - - /** quality_gate(stepId, result) */ - dispatchQualityGate(stepId: string, result: QualityGateResult): void { - for (const handler of this.handlers) { - handler.onQualityGate?.(stepId, result); - } - } - - /** pipeline_start() */ - dispatchPipelineStart(): void { - for (const handler of this.handlers) { - handler.onPipelineStart?.(); - } - } - - /** pipeline_complete(result) */ - dispatchPipelineComplete(result: AutoPipelineResult): void { - for (const handler of this.handlers) { - handler.onPipelineComplete?.(result); - } - } - - /** pipeline_fail(errorMessage) */ - dispatchPipelineFail(errorMessage: string): void { - for (const handler of this.handlers) { - handler.onPipelineFail?.(errorMessage); - } - } - - /** pipeline_pause() */ - dispatchPipelinePause(): void { - for (const handler of this.handlers) { - handler.onPipelinePause?.(); - } - } - - /** pipeline_resume() */ - dispatchPipelineResume(): void { - for (const handler of this.handlers) { - handler.onPipelineResume?.(); - } - } - - /** pipeline_cancel() */ - dispatchPipelineCancel(): void { - for (const handler of this.handlers) { - handler.onPipelineCancel?.(); - } - } -} diff --git a/src/core/autonomous/pipeline-executor.ts b/src/core/autonomous/pipeline-executor.ts deleted file mode 100644 index ce090829..00000000 --- a/src/core/autonomous/pipeline-executor.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Pipeline 步骤执行辅助 - * - * 从 AutoPipelineEngine 中抽离: - * - executeWithTimeout:Promise.race 风格的超时控制 - * - buildStepInput:合并前序依赖输出 + 全局输入 - * - * 单一职责:步骤输入构造与执行超时控制。 - */ - -import type { PipelineStep, StepInput } from './pipeline-types'; -import type { StepOutput } from './types/autonomous.types'; - -/** - * 用 Promise 包装 step.execute,附带超时拒绝。 - * 行为与原 executeWithTimeout 逐字一致: - * - 超时错误消息:`Step ${step.name} timed out after ${step.timeout}ms` - * - 成功/失败都清理 timer - */ -export function executeStepWithTimeout(step: PipelineStep, input: StepInput): Promise { - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - reject(new Error(`Step ${step.name} timed out after ${step.timeout}ms`)); - }, step.timeout); - - step - .execute(input) - .then((output) => { - clearTimeout(timer); - resolve(output); - }) - .catch((error) => { - clearTimeout(timer); - reject(error); - }); - }); -} - -/** - * 把"前序依赖步骤的输出" + "全局输入"合并为步骤输入。 - * - * 合并顺序: - * 1. 遍历 step.dependencies,从 context 取出每个 dep 的输出并浅合并 - * 2. 再把全局输入(context['__input__'])浅合并到 input(可覆盖 dep 输出) - * - * 行为与原 buildStepInput 逐字一致。 - */ -export function buildStepInput(step: PipelineStep, context: Map): StepInput { - const input: StepInput = {}; - - if (step.dependencies) { - for (const depId of step.dependencies) { - const depOutput = context.get(depId); - if (depOutput && typeof depOutput === 'object') { - Object.assign(input, depOutput); - } - } - } - - const globalInput = context.get('__input__'); - if (globalInput && typeof globalInput === 'object') { - Object.assign(input, globalInput); - } - - return input; -} diff --git a/src/core/autonomous/pipeline-step-state.ts b/src/core/autonomous/pipeline-step-state.ts deleted file mode 100644 index 7497eabb..00000000 --- a/src/core/autonomous/pipeline-step-state.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Pipeline 步骤状态管理 - * - * 把 AutoPipelineEngine 中的 - * - updateStepState - * - collectStepDurations - * - createInitialStepState - * 三个纯函数 / Map 更新操作抽离。 - * - * 单一职责:管理 StepState Map 的状态转换与统计。 - */ - -import type { StepState } from './types/autonomous.types'; - -/** 创建步骤的初始 pending 状态 */ -export function createInitialStepState(stepId: string): StepState { - return { - stepId, - name: stepId, - status: 'pending', - progress: 0, - reviewCount: 0, - }; -} - -/** - * 更新某个步骤的状态,自动维护 startedAt/completedAt 时间戳。 - * 行为与原 AutoPipelineEngine.updateStepState 逐字一致: - * - 进入 running 状态时设置 startedAt - * - 进入 completed/failed 时设置 completedAt - */ -export function applyStepStateTransition( - stepStates: Map, - stepId: string, - status: StepState['status'], - extra: Partial> = {} -): void { - const existing = stepStates.get(stepId) ?? createInitialStepState(stepId); - - const updated: StepState = { - ...existing, - status, - ...extra, - startedAt: existing.startedAt ?? (status === 'running' ? Date.now() : undefined), - completedAt: status === 'completed' || status === 'failed' ? Date.now() : undefined, - }; - - stepStates.set(stepId, updated); -} - -/** - * 收集所有有 start/end 时间的步骤的耗时。 - * 返回值:Record - */ -export function collectStepDurations(stepStates: Map): Record { - const durations: Record = {}; - for (const [stepId, state] of Array.from(stepStates.entries())) { - if (state.startedAt && state.completedAt) { - durations[stepId] = state.completedAt - state.startedAt; - } - } - return durations; -} - -/** - * 计算当前进度百分比(按已完成 + 跳过 步骤占总步骤的比例)。 - */ -export function computeProgressPercent( - stepStates: Map, - totalSteps: number -): number { - if (totalSteps <= 0) return 0; - const completed = Array.from(stepStates.values()).filter( - (s) => s.status === 'completed' || s.status === 'skipped' - ).length; - return Math.round((completed / totalSteps) * 100); -} diff --git a/src/core/autonomous/pipeline-types.ts b/src/core/autonomous/pipeline-types.ts deleted file mode 100644 index 4c59e4db..00000000 --- a/src/core/autonomous/pipeline-types.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * AutoPipelineEngine 内部 Step 类型 - * - * 把原文件里的 PipelineStep / StepInput / StepCheckpoint 接口抽离。 - * 这些类型仅供 auto-pipeline-engine 内部及其子模块使用,不属于外部 API。 - */ - -import type { StepOutput } from './types/autonomous.types'; - -/** Pipeline 中单个步骤的配置与执行器 */ -export interface PipelineStep { - id: string; - name: string; - stepId: string; - enabled: boolean; - maxRetries: number; - timeout: number; - dependencies?: string[]; - execute(input: StepInput): Promise; - getCheckpoint?(): StepCheckpoint | null; - restore?(state: StepCheckpoint): void; - onProgress?: (event: { stepId: string; progress: number; message: string }) => void; -} - -/** 步骤输入(合并前序步骤的输出 + 全局输入) */ -export interface StepInput { - [key: string]: unknown; -} - -/** 单步骤检查点 */ -export interface StepCheckpoint { - stepId: string; - completed: boolean; - data: StepOutput; - reviewCount: number; - retryIndex: number; - timestamp: number; -} diff --git a/src/core/autonomous/types/autonomous.types.ts b/src/core/autonomous/types/autonomous.types.ts deleted file mode 100644 index 0d86d3fa..00000000 --- a/src/core/autonomous/types/autonomous.types.ts +++ /dev/null @@ -1,294 +0,0 @@ -/** - * frame-fab Autonomous Mode — Core Types - * 全自动 AI 漫剧制作系统的核心类型定义 - */ - -// ============================================================================ -// 运行模式 -// ============================================================================ - -/** 当前 Autonomous Pipeline 状态 */ -export type AutonomousPipelineStatus = - | 'idle' - | 'running' - | 'paused' - | 'completed' - | 'failed' - | 'cancelled'; - -/** @deprecated 使用 AutonomousPipelineStatus */ -export type PipelineStatus = AutonomousPipelineStatus; - -/** 质量等级 */ -export type QualityLevel = 'fast' | 'balanced' | 'premium'; - -/** 漫剧风格 */ -export type MangaStyle = '2d' | '3d' | 'anime' | 'realistic'; - -// ============================================================================ -// 输入/输出 -// ============================================================================ - -/** 全自动 Pipeline 输入 */ -export interface AutoPipelineInput { - /** 原材料内容 */ - content: string; - /** 输入类型 */ - mode: 'novel' | 'script' | 'prompt'; - /** 项目标题(可选) */ - title?: string; - /** 风格选择 */ - style?: MangaStyle; - /** 质量等级 */ - qualityLevel?: QualityLevel; - /** 目标时长(分钟,可选) */ - targetDuration?: number; - /** 语言 */ - language?: 'zh' | 'en'; - /** 是否启用自审循环 */ - enableSelfReview?: boolean; - /** 最大自审循环次数 */ - maxReviewRetries?: number; -} - -/** 全自动 Pipeline 最终结果 */ -export interface AutoPipelineResult { - /** 是否成功 */ - success: boolean; - /** 最终输出文件路径 */ - outputPath?: string; - /** 生成的视频时长(秒) */ - duration?: number; - /** 分辨率 */ - resolution?: string; - /** 文件大小 */ - fileSize?: number; - /** 各步骤耗时 */ - stepDurations?: Record; - /** 错误信息 */ - error?: string; - /** 生成的场景数 */ - sceneCount?: number; - /** 生成的角色数 */ - characterCount?: number; - /** 渲染帧数 */ - renderedFrames?: number; -} - -// ============================================================================ -// Step 状态 -// ============================================================================ - -/** 单步状态 */ -export interface StepState { - stepId: string; - name: string; - status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'reviewing'; - progress: number; // 0-100 - message?: string; - startedAt?: number; - completedAt?: number; - reviewCount: number; - error?: string; - output?: StepOutput; -} - -/** Step 输出(通用) */ -export interface StepOutput { - [key: string]: unknown; -} - -// ============================================================================ -// Self-Review Loop -// ============================================================================ - -/** 自审结果 */ -export interface ReviewResult { - /** 是否通过 */ - passed: boolean; - /** 评分(0-100) */ - score: number; - /** 审核维度评分 */ - dimensions: ReviewDimensionScore[]; - /** 不合格原因列表 */ - reasons: string[]; - /** 修复建议 */ - suggestions: string[]; -} - -/** 审核维度评分 */ -export interface ReviewDimensionScore { - dimension: ReviewDimension; - score: number; - passed: boolean; - detail: string; -} - -/** 审核维度枚举 */ -export type ReviewDimension = - | 'completeness' // 完整性 - | 'consistency' // 一致性 - | 'visual_quality' // 画面感 - | 'duration_match' // 时长匹配 - | 'punch_point'; // 情绪爆点 - -/** 审核标准配置 */ -export interface ReviewCriteria { - dimensions: ReviewDimension[]; - /** 每维度最低分 */ - minScorePerDimension: number; - /** 综合最低分 */ - minTotalScore: number; - /** 最低通过维度数 */ - minPassedDimensions: number; -} - -// ============================================================================ -// Quality Gate -// ============================================================================ - -/** 质量门禁判定结果 */ -export interface QualityGateResult { - /** 是否通过 */ - passed: boolean; - /** 判定详情 */ - details: string; - /** 质量评分 */ - score: number; - /** 是否触发降级 */ - degraded?: boolean; - /** 降级原因 */ - degradationReason?: string; -} - -/** 质量门禁配置 */ -export interface QualityGateConfig { - /** 是否启用 */ - enabled: boolean; - /** 评分阈值 */ - threshold: number; - /** 失败处理策略 */ - onFail: 'retry' | 'skip' | 'stop'; - /** 自审配置 */ - reviewConfig?: { - enabled: boolean; - maxRetries: number; - }; -} - -// ============================================================================ -// Pipeline 事件 -// ============================================================================ - -/** Pipeline 事件类型 */ -export type PipelineEventType = - | 'step_start' - | 'step_progress' - | 'step_complete' - | 'step_fail' - | 'step_review_start' - | 'step_review_complete' - | 'quality_gate_pass' - | 'quality_gate_fail' - | 'pipeline_start' - | 'pipeline_complete' - | 'pipeline_fail' - | 'pipeline_pause' - | 'pipeline_resume' - | 'pipeline_cancel'; - -/** Pipeline 事件 */ -export interface PipelineEvent { - type: PipelineEventType; - timestamp: number; - stepId?: string; - progress?: number; - message?: string; - data?: unknown; - error?: string; -} - -/** Pipeline 事件处理器 */ -export interface PipelineEventHandler { - onStepStart?: (stepId: string) => void; - onStepProgress?: (stepId: string, progress: number, message?: string) => void; - onStepComplete?: (stepId: string, output: StepOutput) => void; - onStepFail?: (stepId: string, error: string) => void; - onStepReviewStart?: (stepId: string, attempt: number) => void; - onStepReviewComplete?: (stepId: string, result: ReviewResult) => void; - onQualityGate?: (stepId: string, result: QualityGateResult) => void; - onPipelineStart?: () => void; - onPipelineComplete?: (result: AutoPipelineResult) => void; - onPipelineFail?: (error: string) => void; - onPipelinePause?: () => void; - onPipelineResume?: () => void; - onPipelineCancel?: () => void; -} - -// ============================================================================ -// 检查点 -// ============================================================================ - -/** Step 检查点 */ -export interface StepCheckpoint { - stepId: string; - completed: boolean; - data: StepOutput; - output?: StepOutput; - reviewCount: number; - retryIndex: number; - timestamp: number; -} - -/** Pipeline 检查点 */ -export interface PipelineCheckpoint { - pipelineId: string; - status: PipelineStatus; - currentStepId?: string; - steps: Record; - input: AutoPipelineInput; - startedAt: number; - updatedAt: number; -} - -// ============================================================================ -// 步骤定义 -// ============================================================================ - -/** 步骤 ID 枚举(kebab-case 与 core/pipeline/pipeline.types.ts 对齐) */ -export enum AutonomousPipelineStepId { - IMPORT = 'step-import', - ANALYSIS = 'step-analysis', - SCRIPT = 'step-script', - CHARACTER = 'step-character', - SCENE = 'step-scene', - STORYBOARD = 'step-storyboard', - RENDER = 'step-render', - VIDEO_EDIT = 'step-video-edit', - AUDIO = 'step-audio', - SUBTITLE = 'step-subtitle', - EXPORT = 'step-export', -} - -/** @deprecated 使用 AutonomousPipelineStepId */ -export const PipelineStepId = AutonomousPipelineStepId; - -/** 步骤配置 */ -export interface StepConfig { - id: string; - name: string; - stepId: AutonomousPipelineStepId; - enabled: boolean; - maxRetries: number; - timeout: number; // ms - /** 自审配置 */ - reviewConfig?: { - enabled: boolean; - criteria: ReviewCriteria; - maxRetries: number; - }; - /** 质量门禁配置 */ - qualityGate?: QualityGateConfig; - /** 依赖步骤 */ - dependencies?: AutonomousPipelineStepId[]; -} diff --git a/src/core/data/character-templates.ts b/src/core/data/character-templates.ts index 70922bb4..63091ea5 100644 --- a/src/core/data/character-templates.ts +++ b/src/core/data/character-templates.ts @@ -4,7 +4,6 @@ */ import type { - Character, CharacterAppearance, ClothingItem, CharacterExpression, diff --git a/src/core/hooks/useProject.reducer.ts b/src/core/hooks/useProject.reducer.ts new file mode 100644 index 00000000..d6824f5f --- /dev/null +++ b/src/core/hooks/useProject.reducer.ts @@ -0,0 +1,96 @@ +/** + * useProject Reducer — 状态机化 + * + * 7 useState 集中为 1 个 reducer: + * - 项目数据: project / projects + * - 加载: isLoading / isSaving + * - 错误: error / hasUnsavedChanges / currentStep + * + * 对外 API 不变: 所有 setter 名 + signature 保持兼容, 调用方 0 改动. + */ + +import type { ProjectData, TaskStatus } from '@/shared/types'; +import type { FieldUpdater } from '@/shared/utils/reducer-helpers'; + +// ─── 状态类型 ────────────────────────────────────────────────────────────── + +export interface ProjectState { + project: ProjectData | null; + projects: ProjectData[]; + isLoading: boolean; + isSaving: boolean; + error: string | null; + hasUnsavedChanges: boolean; + currentStep: number; + /** 保留字段但始终为 null (接口契约) */ + taskStatus: TaskStatus | null; +} + +// ─── Action 类型 ─────────────────────────────────────────────────────────── + +export type ProjectAction = + | { type: 'set'; key: keyof ProjectState; value: unknown } + | { type: 'update'; key: keyof ProjectState; updater: (prev: unknown) => unknown }; + +// ─── 初始 State ──────────────────────────────────────────────────────────── + +export const initialProjectState: ProjectState = { + project: null, + projects: [], + isLoading: false, + isSaving: false, + error: null, + hasUnsavedChanges: false, + currentStep: 0, + taskStatus: null, +}; + +// ─── Reducer ─────────────────────────────────────────────────────────────── + +export function projectReducer(state: ProjectState, action: ProjectAction): ProjectState { + switch (action.type) { + case 'set': + return { ...state, [action.key]: action.value } as ProjectState; + case 'update': + return { + ...state, + [action.key]: action.updater(state[action.key]), + } as ProjectState; + default: + return state; + } +} + +// ─── Setter 工厂 ─────────────────────────────────────────────────────────── + +export interface ProjectSetters { + setProject: (v: FieldUpdater) => void; + setProjects: (v: FieldUpdater) => void; + setIsLoading: (v: FieldUpdater) => void; + setIsSaving: (v: FieldUpdater) => void; + setError: (v: FieldUpdater) => void; + setHasUnsavedChanges: (v: FieldUpdater) => void; + setCurrentStep: (v: FieldUpdater) => void; +} + +export function createProjectSetters(dispatch: (action: ProjectAction) => void): ProjectSetters { + const set = + (key: K) => + (payload: FieldUpdater | ProjectState[K]) => { + if (typeof payload === 'function') { + dispatch({ type: 'update', key, updater: payload as (prev: unknown) => unknown }); + } else { + dispatch({ type: 'set', key, value: payload as unknown }); + } + }; + + return { + setProject: set('project') as ProjectSetters['setProject'], + setProjects: set('projects') as ProjectSetters['setProjects'], + setIsLoading: set('isLoading') as ProjectSetters['setIsLoading'], + setIsSaving: set('isSaving') as ProjectSetters['setIsSaving'], + setError: set('error') as ProjectSetters['setError'], + setHasUnsavedChanges: set('hasUnsavedChanges') as ProjectSetters['setHasUnsavedChanges'], + setCurrentStep: set('currentStep') as ProjectSetters['setCurrentStep'], + }; +} diff --git a/src/core/hooks/useProject.ts b/src/core/hooks/useProject.ts index bf94992e..f1c0d3bc 100644 --- a/src/core/hooks/useProject.ts +++ b/src/core/hooks/useProject.ts @@ -1,16 +1,23 @@ /** - * 项目管理 Hook(自包含实现) + * 项目管理 Hook(reducer 实现) * - * 不依赖 project-storage / useProject.reducer / useProjectList, - * 内部用 useState + localStorage 完成项目 CRUD。 + * 内部用 useReducer + localStorage 完成项目 CRUD。 + * 状态机定义在 useProject.reducer.ts。 */ -import { useState, useCallback, useMemo } from 'react'; +import { useReducer, useCallback, useMemo } from 'react'; import { v4 as uuidv4 } from 'uuid'; import type { ProjectData, VideoInfo, Script, ProjectSettings, TaskStatus } from '@/shared/types'; -const PROJECTS_STORAGE_KEY = 'framefab_projects'; +import { + projectReducer, + initialProjectState, + createProjectSetters, + type ProjectState, +} from './useProject.reducer'; + +const PROJECTS_STORAGE_KEY = 'storyweaver_projects'; function loadProjects(): ProjectData[] { try { @@ -67,184 +74,189 @@ export interface UseProjectReturn { resetProject: () => void; } +// 初始状态工厂 (lazy load projects from storage) +function createInitialState(): ProjectState { + return { ...initialProjectState, projects: loadProjects() }; +} + export function useProject(_projectId?: string): UseProjectReturn { - const [project, setProject] = useState(null); - const [projects, setProjects] = useState(() => loadProjects()); - const [isLoading, setIsLoading] = useState(false); - const [isSaving, setIsSaving] = useState(false); - const [error, setError] = useState(null); - const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); - const [currentStep, setCurrentStep] = useState(0); + const [state, dispatch] = useReducer(projectReducer, undefined, createInitialState); + const setters = createProjectSetters(dispatch); - const taskStatus: TaskStatus | null = null; + const { projects } = state; - const recentProjects = useMemo(() => { - return [...projects] - .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) - .slice(0, 10); - }, [projects]); + const recentProjects = useMemo( + () => + [...projects] + .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) + .slice(0, 10), + [projects] + ); - const createProject = useCallback((name: string, description?: string): ProjectData => { - const now = new Date().toISOString(); - const newProject: ProjectData = { - id: uuidv4(), - name: name || '未命名项目', - description, - status: 'draft', - settings: { ...DEFAULT_SETTINGS }, - videos: [], - scripts: [], - createdAt: now, - updatedAt: now, - }; - setProjects((prev) => { - const next = [newProject, ...prev]; - persistProjects(next); - return next; - }); - setProject(newProject); - setHasUnsavedChanges(false); - return newProject; - }, []); + const createProject = useCallback( + (name: string, description?: string): ProjectData => { + const now = new Date().toISOString(); + const newProject: ProjectData = { + id: uuidv4(), + name: name || '未命名项目', + description, + status: 'draft', + settings: { ...DEFAULT_SETTINGS }, + videos: [], + scripts: [], + createdAt: now, + updatedAt: now, + }; + setters.setProjects((prev) => { + const next = [newProject, ...prev]; + persistProjects(next); + return next; + }); + setters.setProject(newProject); + setters.setHasUnsavedChanges(false); + return newProject; + }, + [setters] + ); - const loadProject = useCallback(async (id: string): Promise => { - setIsLoading(true); - setError(null); - try { - const all = loadProjects(); - const loaded = all.find((p) => p.id === id) || null; - if (loaded) { - setProject(loaded); - setHasUnsavedChanges(false); - return true; - } else { - setError('项目不存在'); + const loadProject = useCallback( + async (id: string): Promise => { + setters.setIsLoading(true); + setters.setError(null); + try { + const all = loadProjects(); + const loaded = all.find((p) => p.id === id) || null; + if (loaded) { + setters.setProject(loaded); + setters.setHasUnsavedChanges(false); + return true; + } else { + setters.setError('项目不存在'); + return false; + } + } catch { + setters.setError('加载项目失败'); return false; + } finally { + setters.setIsLoading(false); } - } catch { - setError('加载项目失败'); - return false; - } finally { - setIsLoading(false); - } - }, []); + }, + [setters] + ); const saveProject = useCallback(async (): Promise => { - if (!project) return false; - setIsSaving(true); + const currentProject = state.project; + if (!currentProject) return false; + setters.setIsSaving(true); try { - const updated = { ...project, updatedAt: new Date().toISOString() }; - setProjects((prev) => { + const updated = { ...currentProject, updatedAt: new Date().toISOString() }; + setters.setProjects((prev) => { const next = prev.map((p) => (p.id === updated.id ? updated : p)); persistProjects(next); return next; }); - setProject(updated); - setHasUnsavedChanges(false); + setters.setProject(updated); + setters.setHasUnsavedChanges(false); return true; } catch { - setError('保存项目失败'); + setters.setError('保存项目失败'); return false; } finally { - setIsSaving(false); + setters.setIsSaving(false); } - }, [project]); + }, [setters, state.project]); const updateProject = useCallback( (updates: Partial) => { - if (!project) return; - setProject((prev) => (prev ? { ...prev, ...updates } : null)); - setHasUnsavedChanges(true); + setters.setProject((prev) => (prev ? { ...prev, ...updates } : null)); + setters.setHasUnsavedChanges(true); }, - [project] + [setters] ); const deleteProject = useCallback( async (id: string): Promise => { try { - setProjects((prev) => { + setters.setProjects((prev) => { const next = prev.filter((p) => p.id !== id); persistProjects(next); return next; }); - if (project?.id === id) setProject(null); + if (state.project?.id === id) setters.setProject(null); return true; } catch { - setError('删除项目失败'); + setters.setError('删除项目失败'); return false; } }, - [project] + [setters, state.project] ); - const duplicateProject = useCallback(async (id: string): Promise => { - const source = loadProjects().find((p) => p.id === id); - if (!source) return null; - const now = new Date().toISOString(); - const duplicated: ProjectData = { - ...source, - id: uuidv4(), - name: `${source.name} (副本)`, - status: 'draft', - createdAt: now, - updatedAt: now, - }; - setProjects((prev) => { - const next = [duplicated, ...prev]; - persistProjects(next); - return next; - }); - return duplicated; - }, []); + const duplicateProject = useCallback( + async (id: string): Promise => { + const source = loadProjects().find((p) => p.id === id); + if (!source) return null; + const now = new Date().toISOString(); + const duplicated: ProjectData = { + ...source, + id: uuidv4(), + name: `${source.name} (副本)`, + status: 'draft', + createdAt: now, + updatedAt: now, + }; + setters.setProjects((prev) => { + const next = [duplicated, ...prev]; + persistProjects(next); + return next; + }); + return duplicated; + }, + [setters] + ); const setVideo = useCallback( - (videoInfo: VideoInfo) => { - updateProject({ videos: [videoInfo] }); - }, + (videoInfo: VideoInfo) => updateProject({ videos: [videoInfo] }), [updateProject] ); - const removeVideo = useCallback(() => { - updateProject({ videos: [] }); - }, [updateProject]); + const removeVideo = useCallback(() => updateProject({ videos: [] }), [updateProject]); const setScript = useCallback( - (script: Script) => { - updateProject({ scripts: [script] }); - }, + (script: Script) => updateProject({ scripts: [script] }), [updateProject] ); const updateScript = useCallback( (updates: Partial { @@ -21,11 +51,12 @@ export interface PipelineOptions { onError?: (stepId: string, error: Error) => void; } -// ========== PipelineStep 宽松接口(含 validate/rollback/getCheckpoint/restore 等可选方法) ========== -// 与 pipeline.types.ts 中的 PipelineStep 并存,各自服务不同层级的代码。 -// 新代码优先使用 pipeline.types.ts 中的定义。 - -export interface PipelineStep { +/** + * 宽松 PipelineStep 接口 — 含 validate/rollback/getCheckpoint/restore 等可选扩展方法。 + * 与 pipeline.types.ts 中的 PipelineStep 并存,各自服务不同层级的代码。 + * 新代码优先使用 pipeline.types.ts 中的严格定义。 + */ +export interface LoosePipelineStep { id: string; name: string; order?: number; diff --git a/src/core/services/ai/base-ai-service.ts b/src/core/services/ai/base-ai-service.ts index 1ddbcdfd..14e5c13e 100644 --- a/src/core/services/ai/base-ai-service.ts +++ b/src/core/services/ai/base-ai-service.ts @@ -26,7 +26,7 @@ */ import { logger } from '@/core/utils/logger'; -import { retryRequest } from '@/shared/utils'; +import { getErrorMessage, retryRequest } from '@/shared/utils'; // ========== Error Types ========== @@ -255,7 +255,7 @@ export abstract class BaseAIService { code = 'NETWORK_ERROR'; } - const message = error instanceof Error ? error.message : String(error); + const message = getErrorMessage(error); logger.error(`[${this.serviceName}] request failed`, { endpoint, diff --git a/src/core/services/ai/text/ai.service.ts b/src/core/services/ai/text/ai.service.ts index 46440ad1..e1736a00 100644 --- a/src/core/services/ai/text/ai.service.ts +++ b/src/core/services/ai/text/ai.service.ts @@ -53,7 +53,6 @@ import type { AIModel, AIModelSettings, VideoAnalysis, - ScriptSegment, MockConfig, } from './ai.service.types'; diff --git a/src/core/services/ai/text/script-analyzer.service.ts b/src/core/services/ai/text/script-analyzer.service.ts index 6fd8c1a1..7377af41 100644 --- a/src/core/services/ai/text/script-analyzer.service.ts +++ b/src/core/services/ai/text/script-analyzer.service.ts @@ -15,7 +15,7 @@ export class ScriptAnalyzer { */ exportToScript( result: AnalyzeResult, - format: 'screenplay' | 'comic' | 'manga' = 'manga' + _format: 'screenplay' | 'comic' | 'manga' = 'manga' ): string { const lines: string[] = [ `# ${result.metadata.title}`, diff --git a/src/core/services/desktop-app-info.ts b/src/core/services/desktop-app-info.ts index 44284bb0..8bd85b06 100644 --- a/src/core/services/desktop-app-info.ts +++ b/src/core/services/desktop-app-info.ts @@ -20,7 +20,7 @@ export function getAppVersion(): string { /** 应用名称 */ export function getAppName(): string { - return 'frame-fab AI'; + return 'Story Weaver AI'; } /** diff --git a/src/core/services/domain/character.service.ts b/src/core/services/domain/character.service.ts index bc95f319..e0dedf0d 100644 --- a/src/core/services/domain/character.service.ts +++ b/src/core/services/domain/character.service.ts @@ -19,13 +19,7 @@ import type { Character, CharacterAppearance, CharacterConsistency } from '@/sha import { createCharacter, duplicateCharacter } from './character-factory'; import { loadCharactersFromStorage, saveCharactersToStorage } from './character-persistence'; import { createCharacterSubscriber } from './character-subscriber'; -import { - buildCharacterFromTemplate, - countTemplates, - listTemplates, - validateCharacter, - type CharacterTemplateOverrides, -} from './character-template'; +import { buildCharacterFromTemplate, type CharacterTemplateOverrides } from './character-template'; import type { CharacterServiceOptions } from './character-types'; // 重导出公共类型,保持 `@/core/services/domain/character.service` 一站式导入 diff --git a/src/core/services/domain/composition-types.ts b/src/core/services/domain/composition-types.ts index d30f74ab..d7bce212 100644 --- a/src/core/services/domain/composition-types.ts +++ b/src/core/services/domain/composition-types.ts @@ -9,7 +9,6 @@ import type { AnimationProperty, CameraMotion, CompositionProject, - FrameAnimation, TransitionConfig, } from '@/shared/types'; @@ -61,7 +60,7 @@ export interface AnimationKeyframeInput { } /** localStorage 持久化键 */ -export const COMPOSITION_STORAGE_KEY = 'frame-fab-compositions'; +export const COMPOSITION_STORAGE_KEY = 'storyweaver-compositions'; /** 导出数据 schema 版本 */ export const EXPORT_SCHEMA_VERSION = '1.0'; diff --git a/src/core/services/domain/composition.service.ts b/src/core/services/domain/composition.service.ts index 1ef12fa0..010df710 100644 --- a/src/core/services/domain/composition.service.ts +++ b/src/core/services/domain/composition.service.ts @@ -54,12 +54,10 @@ export type { export class CompositionService { private compositions: Map = new Map(); - private projectId?: string; private autoSave: boolean; private subscriber = new CompositionSubscriber(); constructor(options: CompositionServiceOptions = {}) { - this.projectId = options.projectId; this.autoSave = options.autoSave ?? true; this.hydrateFromStorage(); } diff --git a/src/core/services/pipeline/pipeline-runner.ts b/src/core/services/pipeline/pipeline-runner.ts index 16d0ad19..fb605bf3 100644 --- a/src/core/services/pipeline/pipeline-runner.ts +++ b/src/core/services/pipeline/pipeline-runner.ts @@ -16,6 +16,7 @@ */ import { logger } from '@/core/utils/logger'; +import { getErrorMessage } from '@/shared/utils'; import type { PipelineCallbacks, @@ -209,7 +210,7 @@ export class PipelineRunner { return stepResult; } catch (error) { stepResult.status = 'error'; - stepResult.error = error instanceof Error ? error.message : String(error); + stepResult.error = getErrorMessage(error); stepResult.endTime = Date.now(); stepResult.duration = stepResult.endTime - stepResult.startTime; diff --git a/src/core/services/pipeline/pipeline.service.ts b/src/core/services/pipeline/pipeline.service.ts index 3de02e4f..07d6a009 100644 --- a/src/core/services/pipeline/pipeline.service.ts +++ b/src/core/services/pipeline/pipeline.service.ts @@ -34,7 +34,6 @@ import { } from './pipeline-step-factories'; import type { PipelineCallbacks as PipelineRunCallbacks, - PipelineContext, PipelineResult, PipelineStep, PipelineStatus, diff --git a/src/core/services/pipeline/review-export.service.ts b/src/core/services/pipeline/review-export.service.ts index 618d0568..ed82d8e4 100644 --- a/src/core/services/pipeline/review-export.service.ts +++ b/src/core/services/pipeline/review-export.service.ts @@ -6,8 +6,9 @@ import type { FrameComment, StoryboardVersion } from '@/core/services/domain/collaboration.service'; import type { CostRecord, CostStats } from '@/core/services/project/cost.service'; import type { EvaluationScores } from '@/core/services/project/evaluation.service'; +import { getErrorMessage } from '@/shared/utils'; -const REVIEW_EXPORT_ACTIVITY_KEY = 'frame-fab_review_export_activities'; +const REVIEW_EXPORT_ACTIVITY_KEY = 'storyweaver_review_export_activities'; export interface ReviewExportProjectMeta { id: string; @@ -58,7 +59,7 @@ class ReviewExportService { const generatedAt = input.generatedAt || new Date(); const lines: string[] = []; - lines.push('# frame-fab AI 评审记录导出'); + lines.push('# Story Weaver AI 评审记录导出'); lines.push(''); lines.push(`- 项目ID: ${input.project.id}`); lines.push(`- 项目名称: ${input.project.name}`); @@ -200,7 +201,7 @@ class ReviewExportService { source: options.source || 'unknown', status: 'failed', fileName: defaultFileName, - errorMessage: error instanceof Error ? error.message : String(error), + errorMessage: getErrorMessage(error), }); throw error; } diff --git a/src/core/services/project/cost-report.ts b/src/core/services/project/cost-report.ts index d64100d5..8fdbd2b3 100644 --- a/src/core/services/project/cost-report.ts +++ b/src/core/services/project/cost-report.ts @@ -131,7 +131,7 @@ export function renderCostReport( suggestions: string[] ): string { return ` -# frame-fab AI 成本报告 +# Story Weaver AI 成本报告 生成时间: ${new Date().toLocaleString('zh-CN')} @@ -166,6 +166,6 @@ ${Object.entries(stats.byModel) ${suggestions.join('\n\n')} --- -*报告由 frame-fab AI 成本追踪服务生成* +*报告由 Story Weaver AI 成本追踪服务生成* `.trim(); } diff --git a/src/core/services/project/evaluation.service.ts b/src/core/services/project/evaluation.service.ts index bc1690ea..1855a1f1 100644 --- a/src/core/services/project/evaluation.service.ts +++ b/src/core/services/project/evaluation.service.ts @@ -97,7 +97,7 @@ class EvaluationService { toMarkdown(report: EvaluationReport): string { const lines: string[] = []; - lines.push('# frame-fab AI 评测回归报告'); + lines.push('# Story Weaver AI 评测回归报告'); lines.push(''); lines.push(`生成时间: ${report.generatedAt}`); lines.push(''); diff --git a/src/core/services/project/project-import-export-exporter.ts b/src/core/services/project/project-import-export-exporter.ts index 2da56684..ab024403 100644 --- a/src/core/services/project/project-import-export-exporter.ts +++ b/src/core/services/project/project-import-export-exporter.ts @@ -12,7 +12,6 @@ import { CURRENT_VERSION, generateExportFilename, nowIso, - type ExportFormat, type ExportOptions, type ProjectExportData, } from './project-import-export-types'; @@ -50,9 +49,7 @@ export function buildProjectExportData( } /** 解析导出选项(与原 exportProject 内联 defaultOptions 字节级一致) */ -export function resolveExportOptions( - options: Partial = {} -): ExportOptions { +export function resolveExportOptions(options: Partial = {}): ExportOptions { return { format: 'json', includeMedia: false, @@ -65,10 +62,7 @@ export function resolveExportOptions( /** * 导出项目为 JSON 字符串 */ -export function exportToJSON( - project: ProjectData, - options: Partial = {} -): string { +export function exportToJSON(project: ProjectData, options: Partial = {}): string { return JSON.stringify(buildProjectExportData(project, options), null, 2); } diff --git a/src/core/services/project/project-import-export-types.ts b/src/core/services/project/project-import-export-types.ts index 5132a5f6..43cc23cb 100644 --- a/src/core/services/project/project-import-export-types.ts +++ b/src/core/services/project/project-import-export-types.ts @@ -62,10 +62,10 @@ export const CURRENT_VERSION = '1.0.0'; export const MIN_SUPPORTED_VERSION = '1.0.0'; /** 备份索引存储键 */ -export const BACKUP_INDEX_KEY = 'framefab_backups'; +export const BACKUP_INDEX_KEY = 'storyweaver_backups'; -/** 单条备份内容存储键前缀(与原 `framefab_backup_${id}` 一致) */ -export const BACKUP_ITEM_PREFIX = 'framefab_backup_'; +/** 单条备份内容存储键前缀(格式:`storyweaver_backup_${id}`) */ +export const BACKUP_ITEM_PREFIX = 'storyweaver_backup_'; /** 备份最大保留数量(与原 `if (backups.length > 10) backups.shift()` 一致) */ export const MAX_BACKUPS = 10; @@ -87,7 +87,7 @@ export function sanitizeProjectName(name: string): string { /** 生成导出文件名(与原 generateFilename 字节级一致) */ export function generateExportFilename(projectName: string, format: ExportFormat): string { const timestamp = new Date().toISOString().slice(0, 10); - return `framefab_${sanitizeProjectName(projectName)}_${timestamp}.${format}`; + return `storyweaver_${sanitizeProjectName(projectName)}_${timestamp}.${format}`; } /** 生成备份文件名(与原 backupProject 内联一致) */ diff --git a/src/core/services/project/project-import-export-validator.ts b/src/core/services/project/project-import-export-validator.ts index 7b816bff..d25d8368 100644 --- a/src/core/services/project/project-import-export-validator.ts +++ b/src/core/services/project/project-import-export-validator.ts @@ -8,7 +8,6 @@ import { ARRAY_PROJECT_FIELDS, MIN_SUPPORTED_VERSION, - REQUIRED_PROJECT_FIELDS, parseVersion, } from './project-import-export-types'; @@ -69,8 +68,6 @@ export function validateVersion(version: string): void { const { major: minMajor, minor: minMinor } = parseVersion(MIN_SUPPORTED_VERSION); if (major < minMajor || (major === minMajor && minor < minMinor)) { - throw new Error( - `项目文件版本 ${version} 不被支持。最低支持版本为 ${MIN_SUPPORTED_VERSION}` - ); + throw new Error(`项目文件版本 ${version} 不被支持。最低支持版本为 ${MIN_SUPPORTED_VERSION}`); } } diff --git a/src/core/services/project/project-import-export.service.ts b/src/core/services/project/project-import-export.service.ts index 8b95ae45..3001ca54 100644 --- a/src/core/services/project/project-import-export.service.ts +++ b/src/core/services/project/project-import-export.service.ts @@ -100,7 +100,7 @@ class ProjectImportExportService { * 行为与原 `ProjectImportExportService.backupProject` 字节级一致: * - 构造 ProjectExportData + JSON 序列化 * - 追加到索引(超出 10 条则 shift 最早一条) - * - 写入单条备份内容到 `framefab_backup_${id}` + * - 写入单条备份内容到 `storyweaver_backup_${id}` */ async backupProject(project: ProjectData): Promise { const backupData: ProjectExportData = { diff --git a/src/core/services/project/render-queue-runner.ts b/src/core/services/project/render-queue-runner.ts index 65963404..71a0cbdb 100644 --- a/src/core/services/project/render-queue-runner.ts +++ b/src/core/services/project/render-queue-runner.ts @@ -18,7 +18,6 @@ import { TICK_INTERVAL_MS, TICK_PROGRESS_POINTS, type FrameRenderJob, - type RenderLog, type RenderQueueState, } from './render-queue-types'; diff --git a/src/core/services/project/secure-storage.service.ts b/src/core/services/project/secure-storage.service.ts index 0934129d..d414eb47 100644 --- a/src/core/services/project/secure-storage.service.ts +++ b/src/core/services/project/secure-storage.service.ts @@ -43,7 +43,6 @@ import { buildProjectDataKey, buildSecureConfigKey, type CheckpointData, - type TauriStore, } from './secure-storage-types'; // 重导出公共类型 diff --git a/src/core/services/project/video-export.ts b/src/core/services/project/video-export.ts index 2531dc45..caf14f98 100644 --- a/src/core/services/project/video-export.ts +++ b/src/core/services/project/video-export.ts @@ -59,7 +59,7 @@ export async function exportAsMP4( export async function exportAsGIF( storyboard: StoryboardData, fileName: string, - options: ProjectExportOptions, + _options: ProjectExportOptions, onProgress?: ProgressCallback ): Promise { const scenes = storyboardToVideoScenes(storyboard, GIF_DEFAULT_SCENE_DURATION); diff --git a/src/core/services/storyboard-persistence.ts b/src/core/services/storyboard-persistence.ts index 4833e254..62cd56f3 100644 --- a/src/core/services/storyboard-persistence.ts +++ b/src/core/services/storyboard-persistence.ts @@ -8,11 +8,7 @@ import { logger } from '@/core/utils/logger'; -import { - buildStoryboardStorageKey, - resolveProjectKey, - type StoryboardFrame, -} from './storyboard-types'; +import { buildStoryboardStorageKey, type StoryboardFrame } from './storyboard-types'; /** * 从 localStorage 加载当前项目的分镜 diff --git a/src/core/services/storyboard-types.ts b/src/core/services/storyboard-types.ts index 75b691cb..e2682657 100644 --- a/src/core/services/storyboard-types.ts +++ b/src/core/services/storyboard-types.ts @@ -7,8 +7,6 @@ * 共用这套类型,保持领域模型一致。 */ -import type { StoryboardFrame } from '@/shared/types/storyboard'; - /** 分镜服务构造选项 */ export interface StoryboardServiceOptions { projectId?: string; @@ -49,7 +47,7 @@ export const STORYBOARD_FRAME_DEFAULTS = { } as const; /** localStorage 存储键前缀 */ -export const STORYBOARD_STORAGE_KEY = 'frame-fab-storyboards'; +export const STORYBOARD_STORAGE_KEY = 'storyweaver-storyboards'; /** 项目级 key 构造(与原 StoryboardService.loadFromStorage / saveToStorage 逻辑一致) */ export function buildStoryboardStorageKey(projectId?: string): string { diff --git a/src/core/services/video/ffmpeg/composer.ts b/src/core/services/video/ffmpeg/composer.ts index 6846dcc9..424261f7 100644 --- a/src/core/services/video/ffmpeg/composer.ts +++ b/src/core/services/video/ffmpeg/composer.ts @@ -10,12 +10,7 @@ import { fetchFile } from '@ffmpeg/util'; import { logger } from '@/core/utils/logger'; import { getFFmpegInstance, setActiveProgressCallback } from './ffmpeg-instance'; -import { - execFFmpegCommand, - readOutputAsBlob, - safeDeleteFiles, - writeInputFiles, -} from './ffmpeg-pipeline'; +import { execFFmpegCommand, readOutputAsBlob, safeDeleteFiles } from './ffmpeg-pipeline'; import { buildImageOnlyFilterComplex, buildMixedMediaFilterComplex } from './filter-builder'; import type { CompositionOptions, diff --git a/src/core/services/video/video-analysis.service.ts b/src/core/services/video/video-analysis.service.ts index 15f2de8b..ff16e9d0 100644 --- a/src/core/services/video/video-analysis.service.ts +++ b/src/core/services/video/video-analysis.service.ts @@ -33,9 +33,7 @@ import { getSuggestions } from './video-analysis-suggestions'; import { generateSummary } from './video-analysis-summary'; import { DEFAULT_ANALYSIS_CONFIG, - SCENE_TYPES, createEmptyAnalysis, - type SceneType, type VideoAnalysisConfig, } from './video-analysis-types'; diff --git a/src/core/services/video/video-compositor-helpers.ts b/src/core/services/video/video-compositor-helpers.ts index ad1c2acb..59bdb567 100644 --- a/src/core/services/video/video-compositor-helpers.ts +++ b/src/core/services/video/video-compositor-helpers.ts @@ -19,7 +19,6 @@ import { ffmpegWasmService, isFFmpegWasmAvailable, type ExportProgress, - type ProgressCallback, } from '@/core/services/video/ffmpeg-wasm.service'; import { logger } from '@/core/utils/logger'; diff --git a/src/core/services/video/video-compositor.service.ts b/src/core/services/video/video-compositor.service.ts index 7b22d97f..6b79af29 100644 --- a/src/core/services/video/video-compositor.service.ts +++ b/src/core/services/video/video-compositor.service.ts @@ -30,12 +30,7 @@ * - ffmpegInitialized 模块级状态保留(initializeVideoCompositor 内部) */ -import { - ffmpegWasmService, - loadFFmpeg, - isFFmpegWasmAvailable, - type ProgressCallback, -} from '@/core/services/video/ffmpeg-wasm.service'; +import { loadFFmpeg, type ProgressCallback } from '@/core/services/video/ffmpeg-wasm.service'; import { logger } from '@/core/utils/logger'; import { dispatchByMode } from './video-compositor-dispatch'; @@ -79,9 +74,6 @@ export type { ProgressCallback, } from '@/core/services/video/ffmpeg-wasm.service'; -/** FFmpeg.wasm 是否已初始化(模块级状态,跨调用保持) */ -let ffmpegInitialized = false; - /** * 初始化视频合成器: * - Tauri 环境:直接返回 true(本地 FFmpeg 总是可用) @@ -109,7 +101,6 @@ export async function initializeVideoCompositor( }); const loaded = await loadFFmpeg(progressCallback); - ffmpegInitialized = loaded; if (loaded) { logger.info('[VideoCompositor] FFmpeg.wasm 初始化成功'); diff --git a/src/features/ai/components/AIModelSelector.module.less b/src/features/ai/components/AIModelSelector.module.less deleted file mode 100644 index 88afef72..00000000 --- a/src/features/ai/components/AIModelSelector.module.less +++ /dev/null @@ -1 +0,0 @@ -/* placeholder stub for build (Phase 6 cleanup) */ diff --git a/src/features/ai/components/AIModelSelector.tsx b/src/features/ai/components/AIModelSelector.tsx deleted file mode 100644 index cfbb4d80..00000000 --- a/src/features/ai/components/AIModelSelector.tsx +++ /dev/null @@ -1,595 +0,0 @@ -import { motion } from 'framer-motion'; -import { - CheckCircle, - HelpCircle, - Video, - Image, - Edit3, - Star, - Zap, - Settings, - Search, -} from 'lucide-react'; -import { useState } from 'react'; - -import { Avatar } from '@/shared/components/ui/avatar'; -import { Badge } from '@/shared/components/ui/badge'; -import { Button } from '@/shared/components/ui/button'; -import { Card } from '@/shared/components/ui/card'; -import { Input } from '@/shared/components/ui/input'; -import { RadioGroup } from '@/shared/components/ui/radio-group'; -import { Tabs, TabsList, TabsTrigger } from '@/shared/components/ui/tabs'; -import { Tooltip } from '@/shared/components/ui/tooltip'; -import { theme } from '@/styles/theme'; // 【v3.3 代码审查】替换硬编码 #1677ff → theme.colors.info - -import styles from './AIModelSelector.module.less'; -import { TruncatedDescription } from './TruncatedDescription'; - -// 模型类型定义 -// 注: ModelCategory 与 @/shared/types.ai.core 同名同 body, 但本文件内部 3 处使用 -// (L48/60/268) 全部为局部类型, 保持内联定义避免不必要的 import. -export type ModelCategory = 'text' | 'code' | 'image' | 'video' | 'audio' | 'all'; -export type ModelProvider = - | 'openai' - | 'anthropic' - | 'google' - | 'baidu' - | 'iflytek' - | 'alibaba' - | 'tencent' - | 'zhipu' - | 'minimax' - | 'moonshot' - | 'bytedance' - | 'kling'; - -export interface AIModel { - id: string; - name: string; - nameCn?: string; - provider: ModelProvider; - category: ModelCategory[]; - description: string; - features: string[]; - tokenLimit: number; - isPro?: boolean; - pricing?: { input: number; output: number; unit: string }; -} - -interface AIModelSelectorProps { - selectedModel?: string; - onChange?: (modelId: string) => void; - onConfigureAPI?: (provider: ModelProvider) => void; - category?: ModelCategory; - compact?: boolean; - className?: string; - showImageVideo?: boolean; -} - -// 完整的模型列表 -const models: AIModel[] = [ - // ========== 文本模型 ========== - { - id: 'glm-5', - name: 'GLM-5', - nameCn: '智谱清言', - provider: 'zhipu', - category: ['text', 'code'], - description: '智谱最新大模型,2026年发布,支持超长上下文', - features: ['中文理解', '代码生成', '多模态'], - tokenLimit: 128000, - pricing: { input: 0.001, output: 0.003, unit: '1K tokens' }, - }, - { - id: 'minimax-m2.5', - name: 'MiniMax M2.5', - nameCn: 'MiniMax', - provider: 'minimax', - category: ['text', 'code'], - description: 'MiniMax M2.5 大模型,2026年2月发布,支持超长上下文', - features: ['超长上下文', '中文优化', '高性价比'], - tokenLimit: 100000, - pricing: { input: 0.01, output: 0.03, unit: '1K tokens' }, - }, - { - id: 'qwen-2.5', - name: 'Qwen 2.5', - nameCn: '通义千问', - provider: 'alibaba', - category: ['text', 'code', 'image'], - description: '阿里通义千问 2.5,2026年发布,全面升级', - features: ['多模态', '长上下文', '代码生成'], - tokenLimit: 32000, - pricing: { input: 0.006, output: 0.018, unit: '1K tokens' }, - }, - { - id: 'kimi-k2.5', - name: 'Kimi K2.5', - nameCn: '月之暗面', - provider: 'moonshot', - category: ['text', 'code', 'image'], - description: '月之暗面 Kimi K2.5,2026年发布,支持多模态理解', - features: ['多模态理解', '长上下文', '代码生成'], - tokenLimit: 200000, - isPro: true, - pricing: { input: 0.012, output: 0.036, unit: '1K tokens' }, - }, - { - id: 'doubao-2.0', - name: '豆包 2.0', - nameCn: '豆包', - provider: 'bytedance', - category: ['text', 'code', 'image'], - description: '字节跳动豆包 2.0,2026年发布,高性能多模态模型', - features: ['多模态', '快速响应', '中文优化'], - tokenLimit: 128000, - pricing: { input: 0.005, output: 0.015, unit: '1K tokens' }, - }, - { - id: 'gpt-4o', - name: 'GPT-4o', - nameCn: 'GPT-4o', - provider: 'openai', - category: ['text', 'code', 'image'], - description: '最强大的多模态大模型,支持文本、代码和图像分析', - features: ['视觉理解', '高级推理', '代码生成'], - tokenLimit: 128000, - isPro: true, - pricing: { input: 0.005, output: 0.015, unit: '1K tokens' }, - }, - { - id: 'claude-3-sonnet', - name: 'Claude 3.5 Sonnet', - nameCn: 'Claude', - provider: 'anthropic', - category: ['text', 'code', 'image'], - description: '平衡性能与速度的Claude模型,多任务处理能力强', - features: ['创意写作', '精确回答', '图像分析'], - tokenLimit: 200000, - pricing: { input: 0.003, output: 0.015, unit: '1K tokens' }, - }, - // ========== 图像生成模型 ========== - { - id: 'seedream-5.0', - name: 'Seedream 5.0', - nameCn: '即梦', - provider: 'bytedance', - category: ['image'], - description: '字节即梦 5.0,2026年发布,2K直出、4K AI增强、控制笔刷', - features: ['2K直出', '4K AI增强', '控制笔刷', '漫画风格'], - tokenLimit: 4096, - pricing: { input: 0.01, output: 0.05, unit: '1K tokens' }, - }, - { - id: 'kling-3.0-image', - name: 'Kling 3.0', - nameCn: '可灵 3.0', - provider: 'kling', - category: ['image'], - description: '快手可灵 3.0,2026年最新发布,高质量图像生成,叙事感增强', - features: ['图像生成', 'AI增强', '叙事感', '2K/4K输出'], - tokenLimit: 4096, - pricing: { input: 0.015, output: 0.08, unit: '1K tokens' }, - }, - { - id: 'kling-1.6-image', - name: 'Kling 1.6', - nameCn: '可灵 1.6', - provider: 'kling', - category: ['image'], - description: '快手可灵 1.6,2026年发布,高质量图像生成', - features: ['图像生成', 'AI增强', '动漫风格'], - tokenLimit: 4096, - pricing: { input: 0.02, output: 0.1, unit: '1K tokens' }, - }, - { - id: 'vidu-2.0-image', - name: 'Vidu 2.0', - nameCn: 'Vidu', - provider: 'bytedance', - category: ['image'], - description: '生数Vidu 2.0,2026年发布,图像+视频生成', - features: ['图像生成', '视频生成', 'AI增强'], - tokenLimit: 4096, - pricing: { input: 0.015, output: 0.08, unit: '1K tokens' }, - }, - // ========== 视频生成模型 ========== - { - id: 'seedance-2.0', - name: 'Seedance 2.0', - nameCn: '即梦', - provider: 'bytedance', - category: ['video'], - description: '字节即梦 2.0,2026年发布,文本/图片/视频输入,镜头保持一致', - features: ['文本生成视频', '图片生成视频', '视频生成视频', '镜头一致性'], - tokenLimit: 4096, - pricing: { input: 0.05, output: 0.2, unit: '1K tokens' }, - }, - { - id: 'kling-3.0-video', - name: 'Kling 3.0', - nameCn: '可灵 3.0', - provider: 'kling', - category: ['video'], - description: '快手可灵 3.0 Omni,2026年最新发布,图像理解和物理世界模拟增强', - features: ['文生视频', '图生视频', '多镜头', '15秒时长', '原生音视频同步'], - tokenLimit: 4096, - pricing: { input: 0.015, output: 0.08, unit: '1K tokens' }, - }, - { - id: 'kling-1.6-video', - name: 'Kling 1.6', - nameCn: '可灵 1.6', - provider: 'kling', - category: ['video'], - description: '快手可灵 1.6 视频生成,2026年发布,高一致性', - features: ['图像生成', '视频生成', 'AI增强'], - tokenLimit: 4096, - pricing: { input: 0.02, output: 0.1, unit: '1K tokens' }, - }, - { - id: 'vidu-2.0-video', - name: 'Vidu 2.0', - nameCn: 'Vidu', - provider: 'bytedance', - category: ['video'], - description: '生数Vidu 2.0,2026年发布,中国首个长时长视频大模型', - features: ['文生视频', '图生视频', '角色一致性', '动漫风格突出'], - tokenLimit: 4096, - pricing: { input: 0.015, output: 0.08, unit: '1K tokens' }, - }, -]; - -// 提供商信息 -const providerInfo: Record< - ModelProvider, - { name: string; nameCn: string; icon: string; color: string } -> = { - openai: { name: 'OpenAI', nameCn: 'OpenAI', icon: '🤖', color: '#10A37F' }, - anthropic: { name: 'Anthropic', nameCn: 'Anthropic', icon: '🧠', color: '#D97757' }, - google: { name: 'Google', nameCn: '谷歌', icon: '🔍', color: '#4285F4' }, - baidu: { name: 'Baidu', nameCn: '百度', icon: '🔴', color: '#2932E1' }, - alibaba: { name: 'Alibaba', nameCn: '阿里云', icon: '🟠', color: '#FF6A00' }, - zhipu: { name: 'Zhipu', nameCn: '智谱AI', icon: '🟢', color: '#00D08E' }, - iflytek: { name: 'iFlytek', nameCn: '科大讯飞', icon: '🔵', color: '#0099FF' }, - tencent: { name: 'Tencent', nameCn: '腾讯云', icon: '🟣', color: '#12B7F5' }, - minimax: { name: 'MiniMax', nameCn: 'MiniMax', icon: '⭐', color: '#6366F1' }, - moonshot: { name: 'Moonshot', nameCn: '月之暗面', icon: '🌙', color: '#8B5CF6' }, - bytedance: { name: 'ByteDance', nameCn: '字节跳动', icon: '🎵', color: '#FE2C55' }, - kling: { name: 'Kling', nameCn: '快手可灵', icon: '🎬', color: '#FF4906' }, -}; - -function AIModelSelector({ - selectedModel = 'glm-5', - onChange, - onConfigureAPI, - category = 'all', - compact = false, - className = '', - showImageVideo = true, -}: AIModelSelectorProps) { - const [selectedModelId, setSelectedModelId] = useState(selectedModel); - const [activeCategory] = useState(category); - const [searchQuery, setSearchQuery] = useState(''); - const [viewMode, setViewMode] = useState<'card' | 'list'>(compact ? 'list' : 'card'); - const [activeTab, setActiveTab] = useState('text'); - - // 过滤模型 - const filteredModels = models.filter((model) => { - if (activeTab === 'image' && !model.category.includes('image')) return false; - if (activeTab === 'video' && !model.category.includes('video')) return false; - if (activeTab === 'text' && !model.category.includes('text')) return false; - if (activeCategory !== 'all' && !model.category.includes(activeCategory)) return false; - if (searchQuery) { - const query = searchQuery.toLowerCase(); - return ( - model.name.toLowerCase().includes(query) || - model.nameCn?.toLowerCase().includes(query) || - model.provider.toLowerCase().includes(query) || - model.description.toLowerCase().includes(query) || - model.features.some((f) => f.toLowerCase().includes(query)) - ); - } - return true; - }); - - const handleModelSelect = (modelId: string) => { - setSelectedModelId(modelId); - if (onChange) onChange(modelId); - }; - - const getProviderInfo = (provider: ModelProvider) => - providerInfo[provider] || { name: provider, nameCn: provider, icon: '🔹', color: '#666' }; // 【v3.3】fallback 占位色(未知 provider) - - const renderPricing = (model: AIModel) => { - if (!model.pricing) return null; - return ( - - ¥{model.pricing.input}/{model.pricing.unit} - - ); - }; - - const renderCardView = () => ( -
- {filteredModels.map((model, index) => { - const provider = getProviderInfo(model.provider); - return ( - - handleModelSelect(model.id)} - style={{ padding: '16px', cursor: 'pointer' }} - > -
-
- - {provider.icon} - -
- - {model.nameCn || model.name} - - {model.isPro && ( - - Pro - - )} -
-
- {selectedModelId === model.id && ( - - - - )} -
- -
- - {provider.icon} {provider.nameCn} - - {renderPricing(model)} -
- - - {model.description} - - -
- {model.features.slice(0, 3).map((feature, idx) => ( - - {feature} - - ))} -
- -
- - - {' '} - {(model.tokenLimit / 1000).toFixed(0)}K tokens - - -
-
-
- ); - })} -
- ); - - const renderListView = () => ( -
- - {filteredModels.map((model, index) => { - const provider = getProviderInfo(model.provider); - return ( - -
handleModelSelect(model.id)} - > - - {provider.icon} - -
-
- {model.nameCn || model.name} - {model.isPro && ( - - Pro - - )} - {renderPricing(model)} -
-
- {provider.nameCn} - - {(model.tokenLimit / 1000).toFixed(0)}K - -
-
-
-
- ); - })} -
-
- ); - - const renderTabs = () => ( - - - - 文本模型 - - - 图像生成 - - - - - - ); - - return ( - - -

- 选择AI模型 - - - -

- -
- {!compact && ( - - setSearchQuery(e.target.value)} - className={styles.searchInput} - allowClear - icon={} - /> - - )} - {!compact && ( - -
- - -
-
- )} -
-
- - {showImageVideo && !compact && renderTabs()} - -
- {viewMode === 'card' ? renderCardView() : renderListView()} -
- - {onConfigureAPI && ( - - - - )} -
- ); -} - -export default AIModelSelector; diff --git a/src/features/ai/components/ModelSelector.tsx b/src/features/ai/components/ModelSelector.tsx deleted file mode 100644 index 7b151b71..00000000 --- a/src/features/ai/components/ModelSelector.tsx +++ /dev/null @@ -1,5 +0,0 @@ -/** - * 模型选择器 — 重新导出自 shared/components/business/ModelSelector - * 保留本文件以维持向后兼容 - */ -export { default } from '@/shared/components/business/ModelSelector'; diff --git a/src/features/ai/components/index.ts b/src/features/ai/components/index.ts deleted file mode 100644 index 5020a8bc..00000000 --- a/src/features/ai/components/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * AI feature - AI Model selection and configuration - */ - -export { default as AIModelSelector } from './AIModelSelector'; -export { default as ModelSelector } from './ModelSelector'; \ No newline at end of file diff --git a/src/features/ai/index.ts b/src/features/ai/index.ts deleted file mode 100644 index fd753428..00000000 --- a/src/features/ai/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * features/ai/index.ts - * AI feature exports - Model selection and AI configuration - */ - -export * from './components'; diff --git a/src/features/audio/components/AudioEditor.tsx b/src/features/audio/components/AudioEditor.tsx index 7f1f2e0e..a8eeb165 100644 --- a/src/features/audio/components/AudioEditor.tsx +++ b/src/features/audio/components/AudioEditor.tsx @@ -69,7 +69,6 @@ export default function AudioEditor({ handleVoiceRemove, handleVoicePlay, handleVoiceVolumeChange, - handleVoiceStartTimeChange, handleMusicSelect, handleMusicPlay, handleMusicRemove, @@ -79,7 +78,6 @@ export default function AudioEditor({ handleSfxRemove, handleSfxPlay, handleSfxVolumeChange, - handleSfxStartTimeChange, handleStartRecording, handleStopRecording, setMasterVolume, diff --git a/src/features/audio/components/AudioEditorPanel.tsx b/src/features/audio/components/AudioEditorPanel.tsx index 41e6a239..ecfe1bd3 100644 --- a/src/features/audio/components/AudioEditorPanel.tsx +++ b/src/features/audio/components/AudioEditorPanel.tsx @@ -6,7 +6,9 @@ import React, { Suspense } from 'react'; import { tauriService } from '@/core/services'; +import { logger } from '@/core/utils/logger'; import { AudioEditor as AudioEditorComponent } from '@/features/audio'; +import { toast } from '@/shared/components/ui/sonner'; import { Spin } from '@/shared/components/ui/spin'; import type { ProjectData } from '@/shared/types'; @@ -24,7 +26,10 @@ export const AudioEditorPanel: React.FC = ({ project, onP updatedAt: new Date().toISOString(), }; onPersistPatch({ audioConfig: config }); - tauriService.writeText(project.id, JSON.stringify(updatedProject)).catch(() => undefined); + tauriService.writeText(project.id, JSON.stringify(updatedProject)).catch((err) => { + logger.error('保存音频配置失败:', err); + toast.error('保存音频配置失败'); + }); }; return ( diff --git a/src/features/audio/components/VoiceTab.tsx b/src/features/audio/components/VoiceTab.tsx index e54d1e95..c788f636 100644 --- a/src/features/audio/components/VoiceTab.tsx +++ b/src/features/audio/components/VoiceTab.tsx @@ -31,12 +31,12 @@ interface VoiceTabProps { export default function VoiceTab({ voiceTracks, playingVoiceId, - videoDuration, + videoDuration: _videoDuration, disabled, isRecording, recordingTime, onVoicePlay, - onVoiceRemove, + onVoiceRemove: _onVoiceRemove, onVoiceVolumeChange, onVoiceImport, onStartRecording, diff --git a/src/features/audio/components/hooks/useAudioEditor.ts b/src/features/audio/components/hooks/useAudioEditor.ts index 616cf1f3..29677ced 100644 --- a/src/features/audio/components/hooks/useAudioEditor.ts +++ b/src/features/audio/components/hooks/useAudioEditor.ts @@ -102,8 +102,8 @@ export interface UseAudioEditorReturn { export function useAudioEditor({ initialConfig, onConfigChange, - videoDuration = 60, - disabled = false, + videoDuration: _videoDuration = 60, + disabled: _disabled = false, }: UseAudioEditorOptions): UseAudioEditorReturn { // ========== 状态 ========== // 13 个 useState 已迁移到 useReducer 状态机 (2026-06-11) diff --git a/src/features/auto-pipeline/components/AIBriefingPanel.tsx b/src/features/auto-pipeline/components/AIBriefingPanel.tsx deleted file mode 100644 index edb915f4..00000000 --- a/src/features/auto-pipeline/components/AIBriefingPanel.tsx +++ /dev/null @@ -1,163 +0,0 @@ -/** - * AIBriefingPanel — AI 任务简报面板 - * - * 展示当前 AI 正在执行的任务: - * - 任务目标 - * - 为什么要这样做 - * - 正在调用哪个模型 - * - 预计耗时 - */ - -import { Bot, Brain, Zap, Clock } from 'lucide-react'; - -import { cn } from '@/shared/utils/class-names'; - -interface AIBriefingPanelProps { - stepId: string; - stepName: string; - model?: string; - estimatedTime?: number; // seconds - reason?: string; - className?: string; -} - -const STEP_BRIEFINGS: Record< - string, - { - goal: string; - reason: string; - model: string; - defaultTime: number; - } -> = { - 'step-import': { - goal: '解析原材料', - reason: '自动识别小说/剧本格式,智能切分章节', - model: '内置解析器', - defaultTime: 10, - }, - 'step-analysis': { - goal: '分析故事结构', - reason: '识别人物、场景、情节曲线和情绪爆点', - model: 'GLM-5 / M2.5', - defaultTime: 60, - }, - 'step-script': { - goal: '生成视频剧本', - reason: '将小说文本转化为结构化的视频分镜脚本', - model: 'GLM-5', - defaultTime: 120, - }, - 'step-character': { - goal: '设计角色', - reason: '生成角色设定卡,保证跨镜头一致性', - model: 'Seedream 5.0 + GLM-5', - defaultTime: 180, - }, - 'step-scene': { - goal: '规划场景', - reason: '规划全局场景布局、色彩基调和氛围', - model: 'GLM-5', - defaultTime: 60, - }, - 'step-storyboard': { - goal: '生成分镜', - reason: '生成每个镜头的参考图和动作描述', - model: 'Seedream 5.0', - defaultTime: 300, - }, - 'step-render': { - goal: '批量渲染', - reason: 'AI 批量生成所有关键帧图像', - model: 'Seedream 5.0 / Kling 1.6', - defaultTime: 600, - }, - 'step-video-edit': { - goal: '剪辑合成', - reason: '将帧序列合成为连续视频,添加转场', - model: 'FFmpeg WASM', - defaultTime: 120, - }, - 'step-audio': { - goal: '配音合成', - reason: '文字转语音,生成角色对话和旁白', - model: 'Edge TTS / CosyVoice 2.0', - defaultTime: 180, - }, - 'step-subtitle': { - goal: '字幕嵌入', - reason: '生成时间轴字幕并嵌入视频', - model: '内置字幕引擎', - defaultTime: 60, - }, - 'step-export': { - goal: '导出成片', - reason: '最终编码输出 MP4/WebM 文件', - model: 'FFmpeg WASM', - defaultTime: 120, - }, -}; - -export function AIBriefingPanel({ - stepId, - stepName, - model, - estimatedTime, - reason, - className, -}: AIBriefingPanelProps) { - const briefing = STEP_BRIEFINGS[stepId] ?? { - goal: '处理中...', - reason: reason ?? 'AI 正在工作中', - model: model ?? '—', - defaultTime: 60, - }; - - const time = estimatedTime ?? briefing.defaultTime; - const timeLabel = time < 60 ? `${time}秒` : `${Math.round(time / 60)}分钟`; - - return ( -
- {/* 头部 */} -
- - {stepName} -
- - {/* 目标 */} -
-
- - 任务目标 -
-

{briefing.goal}

-
- - {/* 原因 */} -
-
- - 为什么要这样做 -
-

{briefing.reason}

-
- - {/* 底部信息 */} -
-
- 调用模型: - {model ?? briefing.model} -
-
- - 预计 {timeLabel} -
-
-
- ); -} diff --git a/src/features/auto-pipeline/components/AutoPipelineWizard.tsx b/src/features/auto-pipeline/components/AutoPipelineWizard.tsx deleted file mode 100644 index 358ce6e3..00000000 --- a/src/features/auto-pipeline/components/AutoPipelineWizard.tsx +++ /dev/null @@ -1,181 +0,0 @@ -/** - * AutoPipelineWizard — 一步式启动向导 - * - * 用户只需要: - * 1. 粘贴或上传小说/剧本 - * 2. 选择风格和质量等级 - * 3. 点击「开始制作」 - * - * 之后全部交给 AI 自主完成! - */ - -import { useState } from 'react'; - -import type { MangaStyle, QualityLevel } from '@/core/autonomous/types/autonomous.types'; -import { Button } from '@/shared/components/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@/shared/components/ui/card'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/shared/components/ui/select'; -import { Textarea } from '@/shared/components/ui/textarea'; - -import { useAutoPipeline } from '../hooks/useAutoPipeline'; - -export function AutoPipelineWizard() { - const [content, setContent] = useState(''); - const [style, setStyle] = useState('anime'); - const [quality, setQuality] = useState('balanced'); - const [title, setTitle] = useState(''); - - const { start, isRunning, progress, currentStep, error } = useAutoPipeline(); - - const handleStart = () => { - if (!content.trim()) return; - - start({ - content: content.trim(), - mode: 'novel', - title: title.trim() || undefined, - style, - qualityLevel: quality, - enableSelfReview: true, - maxReviewRetries: 3, - }); - }; - - return ( -
- {/* 标题区 */} -
-

AI 全自动漫剧制作

-

- 粘贴你的小说或剧本,AI 自动完成从剧本解析到成片导出的全部工作 -

-
- - {/* 输入区 */} - - - 第一步:提交你的故事 - - - {/* 项目名称 */} -
- - setTitle(e.target.value)} - className="w-full px-3 py-2 border rounded-md" - disabled={isRunning} - /> -
- - {/* 内容输入 */} -
- -