AI写一堆屎山代码怎么破?

给你的Vibecoding的agent前面加上一个禁止屎山代码的约束就好了
# 屎山代码鉴定准则 v1.0

> 本文档提供一套系统化的代码质量鉴定标准,用于客观评估代码库的"屎山化"程度。
> 评级体系采用**扣分制**:满分 100 分,每个问题扣对应分值,最终得分越低越"山"。

---

## 评级总览

| 得分区间 | 等级 | 称号 | 说明 |
|---------|------|------|------|
| 90-100 | ⭐ 优秀 | 清风明月 | 可读、可测、可维护,新人半天能上手 |
| 75-89 | 🟢 良好 | 小有瑕疵 | 整体规范,局部有改进空间 |
| 60-74 | 🟡 及格 | 勉强能看 | 能跑,但改一个地方要查三个地方 |
| 40-59 | 🟠 山脚 | 初具规模 | 已经出现明显的维护困难 |
| 20-39 | 🔴 山腰 | 山高路远 | 每次修改都像在拆炸弹 |
| 0-19 | 💀 山顶 | 不可名状 | 别动,动就崩;重写比重构快 |

---

## 第一章:命名与可读性(20 分)

### 1.1 变量/函数命名(-3 分/项)

- [ ] **谜语命名**`a`, `b`, `temp`, `data2`, `handleStuff()`, `doIt()`
- [ ] **拼音缩写混搭**`getYongHuList()`, `chaXunShuJu()`, `tmp_canshu`
- [ ] **命名与含义不符**:函数叫 `isValid()` 但实际会修改数据库;变量叫 `count` 存的是数组
- [ ] **同类不同风格**:同一文件里 `get_user_name()` / `fetchUserName()` / `queryusername()` 共存

### 1.2 注释(-2 分/项)

- [ ] **零注释**:整个文件没有任何注释,全靠"读代码"
- [ ] **废话注释**`i++; // i 加 1`
- [ ] **注释与代码不符**:注释说"返回用户列表",实际返回的是订单
- [ ] **大量注释掉的代码**:超过 20 行被注释的死代码没人清理

### 1.3 魔法数字与硬编码(-2 分/项)

- [ ] **魔法数字满天飞**`if (status == 3)` —— 3 是什么意思?
- [ ] **硬编码路径/地址**`"/Users/zhangsan/Desktop/test.txt"`, `"http://192.168.1.100:8080"`
- [ ] **硬编码密钥/密码**`password = "123456"` 直接写在源码里

---

## 第二章:结构与设计(25 分)

### 2.1 函数/方法(-3 分/项)

- [ ] **超长函数**:单个函数超过 200 行(超过 500 行扣 5 分)
- [ ] **参数爆炸**:函数参数超过 5 个
- [ ] **嵌套地狱**:if/for/try 嵌套超过 4 层(金字塔/箭头形代码)
- [ ] **职责混乱**:一个函数同时做数据查询、业务计算、格式化输出、发邮件

### 2.2 类/模块(-3 分/项)

- [ ] **上帝类**:一个类超过 1000 行,什么功能都在里面
- [ ] **万能工具类**`Utils.java` / `helpers.ts` 包含 50+ 个互不相关的方法
- [ ] **循环依赖**:A 依赖 B,B 依赖 C,C 依赖 A
- [ ] **没有分层**:UI 逻辑、业务逻辑、数据访问全混在一个文件里

### 2.3 代码重复(-4 分/项)

- [ ] **复制粘贴编程**:相同/相似代码块出现 3 次以上未抽取
- [ ] **复制后只改一处**:两段 100 行代码只有第 50 行不同,但没人合并

---

## 第三章:错误处理(15 分)

### 3.1 异常处理(-3 分/项)

- [ ] **吞异常**`catch (e) {}` 空 catch 块,错误被默默吃掉
- [ ] **万能 catch**`catch (Exception e)` 捕获所有异常然后打一行日志了事
- [ ] **用异常控制流程**:用 try/catch 代替 if/else 做业务判断
- [ ] **错误信息丢失**:catch 之后不记录原始错误,只输出"操作失败"

### 3.2 防御性编程(-2 分/项)

- [ ] **不做空值检查**:直接 `user.profile.avatar.url` 不考虑任何中间环节为 null
- [ ] **不做边界检查**:数组/列表直接取下标不检查长度
- [ ] **不做输入校验**:外部输入直接拼 SQL / 直接渲染到页面

---

## 第四章:状态管理与副作用(15 分)

### 4.1 全局状态(-3 分/项)

- [ ] **全局变量满天飞**:超过 10 个全局可变状态
- [ ] **隐式状态传递**:通过全局变量在函数间传递数据,看不出数据流
- [ ] **状态不同步**:多个地方维护同一份数据的一致性,但没有统一管理

### 4.2 副作用(-3 分/项)

- [ ] **函数有隐藏副作用**:调用 `getData()` 实际会修改数据库
- [ ] **时序依赖**:必须按特定顺序调用一系列函数,否则就崩,但没有文档说明
- [ ] **资源泄漏**:打开的文件/连接/句柄不关闭,靠"程序退出自动回收"

---

## 第五章:依赖与配置(10 分)

### 5.1 依赖管理(-3 分/项)

- [ ] **依赖版本不锁定**:没有 lock 文件,不同环境构建出不同结果
- [ ] **大量无用依赖**`package.json` / `Cargo.toml` 里有一堆不再使用的依赖
- [ ] **循环依赖 / 版本冲突**:依赖之间互相打架,靠降版本凑合

### 5.2 配置管理(-2 分/项)

- [ ] **配置散落各处**:同一类配置分散在 5 个不同的文件里
- [ ] **环境判断硬编码**`if (location.hostname === 'localhost')` 判断是否开发环境

---

## 第六章:测试与文档(10 分)

### 6.1 测试(-5 分/项)

- [ ] **零测试**:没有任何单元测试、集成测试
- [ ] **测试形同虚设**:测试只测 `2 + 2 = 4`,不覆盖核心业务逻辑
- [ ] **测试不稳定**:同一个测试有时过有时不过(flaky test)

### 6.2 文档(-3 分/项)

- [ ] **零文档**:没有 README、没有架构说明、没有 API 文档
- [ ] **文档过期**:文档描述的和实际代码完全不同

---

## 第七章:版本控制(5 分)

### 7.1 Git 提交(-2 分/项)

- [ ] **提交信息是谜语**`fix`, `update`, `aaa`, `临时提交`
- [ ] **巨型提交**:一个 commit 改动超过 20 个文件、1000 行
- [ ] **提交无关改动**:一个 commit 里混杂功能开发、Bug 修复、格式化、依赖更新

### 7.2 分支管理(-2 分/项)

- [ ] **长期不合并的分支**:分支超过 2 周不合并,冲突已无法解决
- [ ] **直接在 main 上开发**:没有分支隔离,main 随时可能崩

---

## 第八章:专项扣分(附加项,不计入基础 100 分)

以下情况属于"一票否决"级别,发现即判定为屎山:

| 编号 | 项 | 扣分 | 说明 |
|------|-----|------|------|
| S1 | SQL 注入漏洞 | -20 | 用户输入直接拼 SQL |
| S2 | 明文存储密码 | -20 | 数据库里存明文密码 |
| S3 | 生产环境 print 调试 | -10 | `console.log` / `print` / `println!` 散落在线上代码里 |
| S4 | 无意义的提交历史 | -10 | 50 个 commit 全是 `fix` / `update` |
| S5 | 大文件入库 | -15 | 二进制文件、视频、模型文件被 git 跟踪 |
| S6 | 死代码占比 > 30% | -10 | 大量未被调用的函数/类/文件 |
| S7 | 单文件超 2000 行 | -10 | 一个源文件超过 2000 行 |

---

## 附录 A:快速自查清单

回答以下 10 个问题,每个"否"计 1 分(满分 10 分,越低越山):

1. 新人能否在 **半天内** 理解项目核心架构?
2. 修改一个功能是否 **只需要改动 1-2 个文件**
3. 出现 Bug 时能否 **快速定位到相关代码**
4. 代码是否有 **一致的命名风格和组织方式**
5. 核心业务逻辑是否有 **单元测试覆盖**
6. 是否能 **一键构建并运行** 项目?
7. 错误发生时是否有 **清晰的错误信息和日志**
8. 依赖版本是否 **锁定且定期更新**
9. 是否有 **清晰的模块边界和分层**
10. 团队成员是否 **敢在不问原作者的情况下修改代码**

> **评分**:10 分 = 清风明月,7-9 分 = 小有瑕疵,4-6 分 = 初具规模,0-3 分 = 不可名状。

---

## 附录 B:屎山成因分析

屎山不是一天堆成的,常见成因:

| 阶段 | 表现 | 根因 |
|------|------|------|
| 赶工期 | "先上线再说" | 业务压力,技术债无暇偿还 |
| 人走茶凉 | 核心开发者离职,无人敢动 | 知识未沉淀,代码即文档 |
| 打补丁 | 每次只修表面,不治根因 | 缺乏重构时间和勇气 |
| 需求膨胀 | 功能无限叠加,架构从未升级 | 初期设计未考虑扩展性 |
| 缺乏规范 | 每个人按自己风格写 | 没有 Code Review,没有编码规范 |
| 信息孤岛 | 同一个逻辑写了 N 遍 | 模块间缺乏沟通,重复造轮子 |

---

## 附录 C:使用方法

### 自评流程

1. **复制本文件**到目标项目
2. 逐项检查,勾选存在的问题
3. 计算总扣分,得出最终得分
4. 对照评级表确定等级

### 定期复查

建议每个迭代结束时进行一次快速自查(附录 A),每个季度进行一次全面鉴定。

---

> **免责说明**:本准则用于自我审视和团队交流,不用于人身攻击。
> 代码写成屎山不丢人,知道自己是屎山还不改才丢人。

---

*版本:v1.0 | 创建日期:2026-06-11*

项目代码量统计报告

根据统计结果,比格熊数字导演工作站 (bgxiong-ai-story) 项目的整体代码量如下:

📊 总体概况

指标数值
总目录数
7,372 个
总文件数(排除依赖)
2,264 个
核心业务代码总行数(Rust + TypeScript/TSX,排除依赖)
~23.4 万行

🔧 后端代码(Rust)

模块文件数代码行数
src-tauri/src/(Tauri 桌面应用核心)
364 个
~78,922 行
src/(Rust CLI/核心库)
24 个
~1,842 行
Rust 后端合计
388 个
~80,764 行

💻 前端代码(TypeScript/React)

模块文件数代码行数
ui/src/(React + TypeScript 前端)
719 个
~133,846 行
前端合计
719 个
~133,846 行

📁 其他文件类型

类型文件数行数
Markdown 文档
495 个
~154,566 行
JSON 配置
3,971 个
~74,331 行
TOML 配置
1,017 个
~13,997 行
CSS/SCSS
3 个
~979 行
YAML
15 个
~115 行

🏗️ 项目架构特点

  1. 前后端分离:前端使用 React + TypeScript(~13.4万行),后端使用 Rust + Tauri(~8.1万行)
  2. 代码比例:前端约占 57%,后端约占 43%
  3. 文档丰富:Markdown 文档约 15.5 万行,说明项目文档完善
  4. 配置繁多:大量 JSON/TOML 配置文件,支持多 AI 服务商配置

这是一个中大型桌面应用项目,采用 Tauri 2 框架构建,具备完整的 AI 故事创作功能(包括角色管理、剧集/场次/分镜管线、文生图/图生图/视频生成等)。

V3 设置全量迁移计划(不兼容、不兜底)

> 创建时间:2026-05-06
> 背景:设置模块已从 V2 重构为 V3,但大量页面仍访问旧 V2 `.items` 结构导致运行时 TypeError。

## V3 架构核心概念

| 旧 V2 概念 | 新 V3 概念 |
|---|---|
| `textProviders.items[]` (AiProviderProfile) | `capabilityBindings[]` (CapabilityBinding) + `credentials[]` (ProviderCredential) |
| `AiProviderProfile.id` (即 `profileId`) | `CapabilityBinding.id` (即新的 `profileId`) |
| `profile.providerKind` | `binding.providerKind` |
| `profile.baseUrl/apiKey/accessKeyId/secretAccessKey` | `credential.baseUrl/apiKey/accessKeyId/secretAccessKey` |
| `profile.name` | `credential.name` (或 `binding.id` 兜底) |
| `profileHasImageEndpointCreds(profile)` | V3 版:按 `binding.providerKind` + `credential` 做校验 |
| `effectiveImageReferenceCaps(profile)` | V3 版:接收 `providerKind` + `binding.extra` 做校验 |
| React dep `[appSettings.textProviders.items]` | `[appSettings]` |

## V3 核心数据结构

```typescript
interface AppSettingsV3 {
  settingsVersion: 3;
  credentials: ProviderCredential[];       // 服务商凭证主档
  capabilityBindings: CapabilityBinding[];  // 能力绑定(含 providerKind)
  defaults: CategoryDefaultsV3;             // 各类默认绑定id
}

interface CapabilityBinding {
  id: string;
  capability: "text" | "speech" | "image" | "imageToImage" | "video" | "keyframe";
  providerKind: string;
  credentialId: string;
  models: string;
  extra?: Record<string, unknown>;
}

interface ProviderCredential {
  id: string;
  name: string;
  vendor: string;
  authMode: "apiKey" | "aksk" | "none";
  baseUrl: string;
  apiKey: string;
  accessKeyId: string;
  secretAccessKey: string;
  extra?: Record<string, unknown>;
}
```

## 第一步:appSettings.ts 新增 V3 工具函数

### 1.1 已添加

- `resolveProviderKindFromBinding(s, bindingId)` — 从绑定 ID 解析 providerKind
- `resolveCredentialFromBinding(s, bindingId)` — 从绑定 ID 查找凭证

### 1.2 需新增

```typescript
/** V3 版:检查某绑定是否具备图像端点凭证(替代 profileHasImageEndpointCreds) */
function bindingHasImageEndpointCreds(s: AppSettingsV3, bindingId: string): boolean;

/** V3 版:检查某绑定是否具备图生图凭证(替代 profileHasImageToImageCreds) */
function bindingHasImageToImageCreds(s: AppSettingsV3, bindingId: string): boolean;

/** V3 版:查找某能力桶下所有 ComfyUI 绑定 ID 集合 */
function findComfyBindingIds(s: AppSettingsV3, capability: "image" | "imageToImage"): Set<string>;

/** V3 版:从 Comfy 绑定 + 工作流列表构造 ImageModelPick[] */
function buildComfyWorkflowPicksFromBindings(
  s: AppSettingsV3,
  workflows: ComfyWorkflow[],
  profileIds: Set<string>,
): ImageModelPick[];
```

## 第二步:imageReferenceCaps.ts 改造

### 新增 V3 版函数

```typescript
/**
 * V3 版:查询有效垫图能力(含 extra 覆盖)
 * 替代 effectiveImageReferenceCaps(p: AiProviderProfile)
 */
export function effectiveImageReferenceCapsV3(
  providerKind: string,
  extra?: Record<string, unknown>,
): ImageReferenceCaps;
```

同时保留旧版签名(`@deprecated`)以维持 `sceneImageDisplay.ts` 等文件的向后兼容过渡。

## 第三步:逐文件替换(分类处理)

### 分类 A:简单 providerKind 解析(14 个文件)

**旧代码模式:**
```typescript
const profile = appSettings.textProviders.items.find(x => x.id === pick.profileId);
const pk = profile?.providerKind;
```

**替换为:**
```typescript
const pk = resolveProviderKindFromBinding(appSettings, pick.profileId);
```

**依赖数组:** `[x, appSettings.textProviders.items]``[x, appSettings]`

**涉及文件:**

| # | 文件 | 行号 | 能力类型 |
|---|------|------|---------|
| 1 | `ui/src/components/story-tab/StoryTab.tsx` | 134 | text |
| 2 | `ui/src/components/scene-tab/SceneImageGenerateModal.tsx` | 80 | text |
| 3 | `ui/src/components/storyboard/modals/StoryboardGenerateShotsModal.tsx` | 50 | text |
| 4 | `ui/src/features/character-tab/modals/ActorPortraitGenerateModal.tsx` | 69 | text |
| 5 | `ui/src/features/character-tab/modals/ActorSaveExtractModal.tsx` | 35 | text |
| 6 | `ui/src/features/role-tab/modals/GenerateRolePortraitModal.tsx` | 114 | text |
| 7 | `ui/src/features/role-tab/modals/RolePortraitModelPickerModal.tsx` | 117 | text |
| 8 | `ui/src/features/role-tab/modals/GeneratePromptModal.tsx` | 130 | text |
| 9 | `ui/src/components/scene-tab/modals/AutoGenerateScenesModal.tsx` | 138 | text |
| 10 | `ui/src/components/chapter-tab/modals/StoryRolesUpsertModal.tsx` | 163 | text |
| 11 | `ui/src/components/chapter-tab/modals/OptimizeChapterPromptModal.tsx` | 103 | text |
| 12 | `ui/src/components/chapter-tab/modals/ChapterBatchAllChaptersScenesModal.tsx` | 208 | text |
| 13 | `ui/src/features/role-tab/hooks/useRoleBatchOps.ts` | 99, 201, 328 | text ×2, image |
| 14 | `ui/src/features/role-tab/hooks/useRoleBatchAiModals.ts` | 80, 269, 299 | text ×2, image |
| 15 | `ui/src/features/role-tab/ai/useRolePortraitAiPicks.ts` | 142, 154, 172 | text, image, i2i |

### 分类 B:ComfyUI 工作流相关(4 个文件)

**旧代码模式:**
```typescript
const comfyProfileIds = new Set(
  appSettings.imageProviders.items
    .filter(p => p.providerKind === IMAGE_KIND_COMFYUI && profileHasImageEndpointCreds(p))
    .map(p => p.id)
);
for (const p of appSettings.imageProviders.items) {
  if (!comfyProfileIds.has(p.id)) continue;
  const profileName = p.name;
  ...
}
```

**替换为:**
```typescript
const comfyProfileIds = findComfyBindingIds(appSettings, "image");
const comfyBindings = appSettings.capabilityBindings.filter(
  b => b.providerKind === IMAGE_KIND_COMFYUI && bindingHasImageEndpointCreds(appSettings, b.id)
);
for (const b of comfyBindings) {
  const cred = findCredentialById(appSettings, b.credentialId);
  const profileName = cred?.name ?? b.id;
  ...
}
```

**涉及文件:**

| # | 文件 | 替换内容 |
|---|------|---------|
| 1 | `ui/src/hooks/useComfyWorkflowImagePicks.ts` | 全量重写:comfyProfileIdSet → V3 版本 |
| 2 | `ui/src/hooks/useImageT2iModelSurface.ts` | comfyProfileIds 计算 → V3 |
| 3 | `ui/src/hooks/useImageI2iModelSurface.ts` | comfyProfileIds 计算 → V3 |
| 4 | `ui/src/components/storyboard/hooks/useStoryboardModelPicks.ts` | comfyProfileIds + 工作流循环 → V3 |

### 分类 C:垫图能力查询(3 个文件)

**旧代码模式:**
```typescript
const profile = appSettings.imageProviders.items.find(i => i.id === pick.profileId);
if (profile && effectiveImageReferenceCaps(profile).supportsReferenceImages) return pick;
```

**替换为:**
```typescript
const binding = findBindingById(appSettings, pick.profileId);
if (binding && effectiveImageReferenceCapsV3(binding.providerKind, binding.extra).supportsReferenceImages) return pick;
```

**涉及文件:**

| # | 文件 | 行号 |
|---|------|------|
| 1 | `ui/src/domain/sceneImageDisplay.ts` | 9-18 (firstImageToImagePickSupportingPads) |
| 2 | `ui/src/features/character-tab/CharacterTabScreen.tsx` | 237 |
| 3 | `ui/src/components/scene-tab/SceneTabMainLegacyImpl.tsx` | 282, 292 |

### 分类 D:useShotWorkbenchCapabilities 专用

**涉及文件:** `ui/src/components/storyboard/modals/shot-workbench/hooks/useShotWorkbenchCapabilities.ts`

删除本地 `resolveProviderKind` 辅助函数,改用 `resolveProviderKindFromBinding`

## 第四步:废弃函数清理

标记以下函数为 `@deprecated`(保留空壳仅供编译通过):
- `profileHasImageEndpointCreds` → 替代为 `bindingHasImageEndpointCreds`
- `profileHasImageToImageCreds` → 替代为 `bindingHasImageToImageCreds`

## 执行顺序

1.`appSettings.ts` 添加 `resolveProviderKindFromBinding` / `resolveCredentialFromBinding`
2. `appSettings.ts` 添加 V3 凭证检查函数 + Comfy 辅助函数
3. `imageReferenceCaps.ts` 添加 `effectiveImageReferenceCapsV3`
4. **分类 A** 文件批量替换(14 文件)
5. **分类 B** 文件重写 ComfyUI 逻辑(4 文件)
6. **分类 C** 文件替换垫图查询(3 文件)
7. **分类 D** 文件替换(1 文件)
8. 全量 ReadLints 检查

## 影响范围统计

| 类别 | 文件数 | 替换点数 |
|------|--------|---------|
| 分类 A(providerKind 解析) | 15 | ~24 |
| 分类 B(ComfyUI) | 4 | ~12 |
| 分类 C(垫图能力) | 3 | ~5 |
| 分类 D(workbench) | 1 | ~6 |
| 基础设施(appSettings + caps) | 2 | ~8 |
| **总计** | **~25** | **~55** |

故事板 graph:全部场次与选集共用同一套持久化(无 __all__ 兜底)

目标(已确认)
  • 「全部场次 + 点场次」「选集 + 点同一场次」读写同一 storyboard_graph_state 键:(project_id, episode_id=剧集节点 id, chapter_id=场次节点 id)

  • 不需要兜底、不考虑历史兼容:可删除/收紧依赖 __all__ / STORYBOARD_SCOPE_ALL 的故事板前端路径;遗留库中仅 __all__ 键下的 graph 行可视为可丢弃(不迁移)。

  • 以项目最优解落地:单一解析函数 + 全链路同一 episodeId,避免双轨。

核心结论

推荐实现(最优、改动面收敛)

1. 单一「故事板剧集作用域」解析(前端)

新增小工具(例如 ui/src/domain/storyboardEpisodeScope.ts)或内联 useMemo

// 语义:显式选集 > App 传入的 chapter > 当前场次所属剧集
resolvedStoryboardEpisodeId =
  (activeChapterId?.trim() ||
    chapter?.id?.trim() ||
    activeScene?.chapter_id?.trim() ||
    "");
  • StoryboardTabsceneGraphEpisodeId 改为上述 resolvedStoryboardEpisodeId不再 || STORYBOARD_SCOPE_ALL)。

  • 无场次:不加载 graph(现有逻辑已处理)。

  • 有场次但 resolved 为空:视为数据损坏/孤儿场次 — 不兜底:toast 固定文案(如「场次未关联剧集,无法加载故事板画布」),loadStoryboardGraph / save 不调用;与「不要兼容」一致。

2. 与 graph 键一致的所有调用点(前端)

以下必须使用同一 resolvedStoryboardEpisodeId(从 StoryboardTab 下传或从 App 用相同公式计算,避免分叉):

位置

调整

[useStoryboardSceneGraph](d:/00Dev/bgxiong-ai-story/ui/src/components/storyboard/hooks/useStoryboardSceneGraph.ts)

load/save、cache scope、pendingepisodeId 均用解析结果;sceneGraphCacheScopeKey 中去掉对 STORYBOARD_SCOPE_ALL 的回退拼接(空 episode 时不生成 key 或与「无 scene」一致)

[useChapterSceneBoardGenerate](d:/00Dev/bgxiong-ai-story/ui/src/components/storyboard/hooks/useChapterSceneBoardGenerate.ts)

episodeScopeId / 刷新 graph 的 episodeId 与上一致

[useStoryboardCanvasActions](d:/00Dev/bgxiong-ai-story/ui/src/components/storyboard/hooks/useStoryboardCanvasActions.ts)

cleanupStoryboardGraphAfterSegmentDeleteepisodeScopeId 一致

StoryboardTabcompileClipFrameworkFromStoryboard

episodeId 参数一致

SceneI2iPadSelector chapterId

与垫图/场次上下文一致:传 resolvedStoryboardEpisodeId(若该组件语义为「剧集 id」)

App.tsx refreshStoryboardShots

loadOrderedStoryboardNodesUnderScene 第三参改为 activeChapter?.id ?? activeScene?.chapter_id ?? STORYBOARD_SCOPE_ALL 中的前两项优先;最优:抽共享 resolveEpisodeScopeForStoryboard(activeChapter, activeScene) 与 Tab 同式,删除STORYBOARD_SCOPE_ALL 的默认 — 无解析结果则传空并由 loadOrdered 短路或让 loadOrdered 要求必填

3. [sceneStoryboardChildNodes.ts](d:/00Dev/bgxiong-ai-story/ui/src/domain/sceneStoryboardChildNodes.ts)

  • 删除 episodeId 默认值 STORYBOARD_SCOPE_ALL;改为调用方必传 已解析 的剧集 id。

  • episodeId 为空:直接返回 []throw(二选一;推荐返回 [] + 上层 toast,避免未处理异常)。

4. 常量与类型清理

  • [storyboardGraph.ts](d:/00Dev/bgxiong-ai-story/ui/src/domain/storyboardGraph.ts)STORYBOARD_SCOPE_ALL 若仅故事板 graph 使用,可删除并全量替换;若 片段画布等仍用 __all__(见 [types.ts](d:/00Dev/bgxiong-ai-story/ui/src/components/segment-canvas/types.ts)App Clips),则保留常量故事板模块禁止再引用

  • [useStoryboardSceneGraph](d:/00Dev/bgxiong-ai-story/ui/src/components/storyboard/hooks/useStoryboardSceneGraph.ts):移除仅用于 __all__ 回退的 import/拼接。

5. 后端(可选收紧,与「无兜底」对齐)

  • [storyboard_graph::normalize_scope_id](d:/00Dev/bgxiong-ai-story/src-tauri/src/storyboard_graph.rs):对 load_storyboard_graph_state / upsert_storyboard_graph_state 的入口,若 trim 为空则 Err 而非写入/读取 __all__(需同步改 [commands/storyboard.rs](d:/00Dev/bgxiong-ai-story/src-tauri/src/commands/storyboard.rs) 三处 normalize_scope_id)。

  • 风险:仅当保证前端永远传非空剧集 id 时安全;否则 Tauri 命令会报错(符合「严格」)。

  • 文档注释:chapter_scene_board_pipeline.rs 1388 行附近 更新为「episode_scope_id 必须为剧集节点 id,与前端一致」。

6. 数据(不兼容策略)

  • 不编写迁移合并 __all__ → 真实剧集:旧行自然废弃。

  • 可选(本地开发):手动删 storyboard_graph_stateepisode_id='__all__' 的行;作为代码交付必选项。

验证

  • 选集 A → 选场次 S → 改画布 → 切「全部场次」→ 再点 S:布局与节点与选集时一致

  • 从未选集路径进入:仅点 S(activeScene.chapter_id 有值):同上。

  • 清除场次或孤儿场次:resolved 为空时行为符合预期(不静默读 __all__)。

实施任务清单

  1. 引入 resolvedStoryboardEpisodeId 解析并接入 StoryboardTab + 所有子 hook / API 参数。

  2. 更新 App.refreshStoryboardShotssceneStoryboardChildNodes(去默认 __all__)。

  3. 删除故事板路径上对 STORYBOARD_SCOPE_ALL 的依赖;保留或收缩常量定义。

  4. (可选)后端 normalize_scope_id 对空串改为 Err + cargo check

  5. 手动回归两条路径 + npm run build / 相关 ReadLints

有改名字需求的朋友可以...

到官网去改 https://www.bgxiong.com/user/profile
后期我会把这修改资料加入到客户端
再等等...

在尝试找到把SRT转TEXT+的途径

达芬奇API的某些限制,
让现阶段做动态字幕有点困难!

比格熊v1.2.9马上发布 新功能已调色完毕

### 🆕 新功能
**音效卡片拖拽添加**
- SFX 模块和 Mine 模块均支持拖拽音效卡片到时间轴
- 鼠标按下:抓取手型 + 绿色边框 + 音效名称变绿
- 拖拽出插件窗口外释放:自动添加到达芬奇时间轴
- 使用 Pointer Events (`setPointerCapture`) 技术,支持鼠标移出窗口仍能拖拽
- 拖拽预览标签跟随鼠标,拖出窗口时变亮绿色提示
**搜索框自动聚焦**
- 进入音效模块时,搜索框自动获取焦点,可直接输入搜索
### 🔧 技术改进
**Pointer Events 实现**
- 替换传统的 mouse 事件为 pointer 事件
- `pointerdown` + `setPointerCapture` 捕获指针
- `pointermove``pointerup` 处理拖拽和释放
- 解决鼠标移出 Electron 窗口后事件丢失的问题
**拖拽状态管理**
- 统一的 `dragState` 全局状态管理
- `cleanupDragState()` 统一清理样式和状态
- 支持 `pointerId` 追踪和释放
### 🎨 样式优化
- 波形图自适应卡片宽度
- 卡片内边距优化(padding: 6px 5px)
- 波形图边距统一(margin: 2px 2px 0 0)
### 📝 文件变更
**修改文件**
- `renderer.js` - SFX 模块拖拽功能、搜索框聚焦
- `modules/mine/renderer.js` - Mine 模块拖拽功能
- `css/styles.css` - 波形图和卡片样式

置顶 【激活码】20260325新鲜热乎的尝鲜

我这分批放出激活码是为了缓解服务器压力
这是尝鲜测试用激活码不具备长期有效期
J28D-VBZW-44YE-REGJ                             Y8L3-XKU2-U5F4-K6BN
TQ3R-WQVM-6ZTQ-NGG9                         R59Y-JNDL-22S5-QTMT
8LK9-RTTW-2SDM-RSY3                            VLQD-CXVC-69FB-AFJS
83HZ-25DF-562V-Y3DD                              7Q8J-9KRR-5HMD-7BL3
ENMG-GLJM-BU77-7B3J                           SVAQ-PR6P-QH58-HMHT
FAEP-CWME-GEPQ-9RX7

基于 Rust + WebGPU 的技术栈是目前高性能跨平台开发(Native + Web)的最前沿方案。

基于 Rust + WebGPU 的技术栈是目前高性能跨平台开发(Native + Web)的最前沿方案。对于追求性能和系统稳定性的开发者来说,这套组合几乎是“终极形态”。

以下是这套技术栈的核心构成及入门路径:


1. 核心架构:wgpu 生态

在 Rust 社区中,wgpu 是 WebGPU 规范的旗舰级实现。它不仅能让代码运行在浏览器中,还能直接调用 Windows (Direct3D 12)、macOS (Metal) 和 Linux (Vulkan) 的原生驱动。

  • wgpu-rs (核心库): 提供安全的 Rust 绑定。

  • WGSL (WebGPU Shading Language): 官方指定的着色器语言,语法类似 Rust,易于上手,且避开了 GLSL 的历史包袱。

  • naga (着色器翻译器): 负责将 WGSL 翻译为底层 API 能理解的指令。

2. 为什么选择 Rust + WebGPU?

这套方案之所以强大,是因为它解决了两个痛点:内存安全跨平台统一性

3. 入门技术栈建议

如果你准备从零开始构建一个高性能工具(例如 AI 图像处理或视频滤镜引擎),可以参考以下配置:

  • UI 层: eguiiced。这两个库在 Rust 生态中非常流行,且都完美支持 wgpu 后端,适合快速构建跨平台工具界面。

  • 窗口管理: winit。这是 Rust 处理窗口、输入事件的事实标准。

  • 数学运算: nalgebraglam。后者专门为图形学优化,性能极佳。

  • Web 部署: wasm-bindgen。将你的 Rust 逻辑打包成 WebAssembly。

4. 关键应用场景:AI 推理与视频处理

对于本地 AI 推理,你可以利用 WebGPU 的计算着色器 (Compute Shader) 直接对张量进行并行运算。

  • 模型转换: 将模型权重的二进制数据映射到 GPU 的 Storage Buffer

  • 并行计算: 在 WGSL 中编写计算内核(Kernel),实现类似矩阵乘法 (GEMM) 的操作。

  • 零拷贝: 借助 Rust 的高效内存布局,减少 CPU 与 GPU 之间的数据交换延迟。


快速开始建议

  1. 环境配置: 确保 Rust 环境已更新,安装 cargo

  2. 克隆 Demo: 到 GitHub 搜索 gfx-rs/wgpu 仓库,查看其 examples 文件夹。里面有从简单的三角形渲染到复杂的计算着色器应用。

  3. 学习 WGSL: 如果你熟悉 Rust,你会发现 WGSL 的语法非常亲切。

小贴士: 由于你可能处理大量资源或追求极高性能,建议在开发时开启 wgpu 的验证层(Validation Layers),它能帮你捕捉到 90% 以上的非法显存访问错误。

WebGPU 技术了解一下

到 2026 年,WebGPU 已经正式告别了“试验田”阶段,成为了 Web 高性能计算和图形渲染的绝对标准

如果说 WebGL 是在浏览器里“画图”,那么 WebGPU 则是在浏览器里“直接操控 GPU”。

以下是 WebGPU 当前发展的关键维度:

1. 核心生态:从“尝鲜”转向“默认”

WebGPU 1.0 在 2023 年发布后,经历了两年的高速迭代。

  • 引擎全面转向: Three.jsBabylon.js 等主流 Web 图形引擎已经将 WebGPU 作为首选后端。相比 WebGL,WebGPU 在处理复杂场景(如数万个独立对象的渲染)时,CPU 开销降低了约 80%。

  • 原生级性能: 借助 WebAssembly (Wasm) 和 WebGPU 的深度集成,现在的 Web 应用可以实现接近原生桌面软件的图形表现。这使得云端视频剪辑、高级调色工具和复杂的 3D 设计软件(如 Web 版 CAD)在性能上几乎没有短板。

2. AI 浪潮下的“杀手锏”:计算着色器 (Compute Shaders)

WebGPU 与 WebGL 最大的区别在于其强大的通用计算能力

  • 端侧 AI (On-device AI): 这是 WebGPU 发展最迅猛的领域。由于其提供了对现代 GPU 特性(如共享内存、原子操作)的访问,诸如 WebLLMTransformers.js 等项目可以让大语言模型(LLM)和 Stable Diffusion 直接在用户的本地 GPU 上运行,无需上传数据到服务器。

  • 视频处理加速: 对于视频后期从业者而言,WebGPU 允许在浏览器内进行实时的 4K 视频滤镜处理、AI 降噪和自动抠图,其效率是传统 JavaScript 或 WebGL 无法比拟的。

3. 浏览器与硬件兼容性

  • 多端统一: 截至 2026 年,Chrome、Edge、Safari 和 Firefox 的稳定版均已全面支持 WebGPU。它在底层完美映射了系统的原生 API(如 Windows 的 Direct3D 12、macOS 的 Metal、Linux 的 Vulkan)。

  • 移动端爆发: 随着高性能移动芯片的普及,手机端的 WebGPU 支持也已趋于成熟,网页端手游的品质已经开始向主机级画质靠拢。

4. 技术特性演进

  • FP16 与精度优化: 现在的 WebGPU 已经广泛支持 半精度浮点数 (FP16),这对 AI 推理至关重要,能在减少显存占用的同时提升一倍的运算速度。

  • 子组操作 (Subgroups): 这一高级特性的加入,使得开发者可以更精细地控制 GPU 线程间的通信,进一步压榨硬件性能。


总结:对开发者的影响

对于开发者(尤其是熟悉 RustC++ 这种底层语言的人)来说,WebGPU 极大地拓宽了 Web 的边界。你不再受限于简单的网页交互,而是可以直接开发高性能的跨平台桌面应用(通过 Electron 加速)或高性能的 Web AI 工具。