174 Commits

Author SHA1 Message Date
seaislee1209
2f6d3a60cc fix(admin): 超管补「升为主管理员」入口 — 不然主管撤了加不回去
之前只能撤主管不能升,撤完只能新建账号或重建团队,不合理。
后端 admin_team_member_role_view 早就支持 is_team_owner=true(L1254-1260,
设 owner=True 时自动同时 admin=True);前端 setMemberRole API 只传
is_team_admin,从没用过 is_team_owner 参数。

修法:
- lib/api.ts 加 adminApi.setMemberAsOwner(teamId, memberId)
- TeamsPage 副管/成员行 role badge 旁加小灰字 "→主管" 按钮
- 点击 confirm 提示"不会自动降级现有主管,需自己先撤旧主管再升新主管"
  (后端没强制约束一团队一主管,降级逻辑交给操作员判断)

UX 流程:
- 主管 → 单击 badge = 撤销 (现有)
- 副管 → 单击 badge = 取消副管(变成员)(现有) + [→主管] 升主管
- 成员 → 单击文字 = 升副管 (现有)              + [→主管] 升主管

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:08:30 +08:00
seaislee1209
2289ce7d30 docs(v0.20.1): 批次开发计划 + 完成报告 + changelog 归档
- 新增 docs/todo/v0.20.1-批次开发计划.md(本次实施的源 plan,7 批次拆解)
- 新增 docs/todo/v0.20.1-完成报告.md(改动总览/批次详情/测试结果/风险记录)
- docs/changelog.md 顶部追加 v0.20.1 条目(commit hash 对应,验收结果总结)

7 批次全部本地完成,等用户授权后 push 到 dev:
  A e86e3d4 主管理员撤销 bug
  B 72f351d 视频封面帧前端
  C 6ee5c8f api_prompt + 调试折叠区
  D c53144b 站内通知系统
  G ed67a27 团管重置密码
  H 11c1cdf reEdit prompt 丢失
  I 6b13cff Safari 自适应根因

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:40:28 +08:00
seaislee1209
ed67a27399 feat(team): 团管重置成员密码 — 新 API + 严格权限矩阵 + 成员管理页按钮
权限矩阵(plan §G,服务端硬校验,前端按钮只是 UX 不算数):
| 操作者          | 可重置                  | 不可重置             |
|----------------|------------------------|---------------------|
| 主管(owner=T)  | 同团队副管 + 成员       | 其他主管 / 自己      |
| 副管(admin=T)  | 同团队成员              | 副管 / 主管 / 自己   |

后端:
- 新 view team_reset_member_password_view (POST /api/v1/team/members/<id>/reset-password)
- permission IsTeamAdmin(覆盖主管+副管两种)+ 服务端逐层判断:
  1. 同团队?              (target.team_id != operator.team_id → 403)
  2. 不能改自己?           (id 相同 → 400)
  3. 主管密码须超管?       (target.is_team_owner → 403)
  4. 副管只有主管能改?     (target.is_team_admin && !operator.is_team_owner → 403)
  5. 走到这里都是合法 case → 生成 8 位随机密码(secrets+string)+ must_change_password=True
- log_admin_action audit 留痕(action=user_password_reset, after.reset_by=team_admin, operator=...)
- urls.py 加路由

前端:
- lib/api.ts teamApi.resetMemberPassword(memberId) → 返回 { new_password, ... }
- TeamMembersPage.tsx:
  - canResetPasswordFor(m) helper 同权限矩阵(主管→副管+成员、副管→成员、不能改自己)
  - 成员行 actions 加 "重置密码" 按钮(只在 canResetPasswordFor 为 true 时显示)
  - 点击 → window.confirm 二次确认 → API → 弹结果 modal 显示新密码 + 复制按钮
  - 结果 modal 用 monospace font 大字 + 浅灰底显示密码,带 ⚠ 提醒"关闭后无法再次查看"
  - showToast 反馈复制/失败

后端 6 项 curl 测试全通过:
- T1 主管→副管        200 ✓
- T2 主管→成员        200 ✓
- T3 主管→自己        400 "不能重置自己的密码" ✓
- T4 副管→主管        403 "主管须由超级管理员重置" ✓
- T5 副管→成员        200 ✓
- T6 副管→另一副管    403 "只有主管理员能重置副管理员密码" ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:36:16 +08:00
seaislee1209
c53144b2ac feat(notification): 站内通知系统 — Notification 模型 + 4 个 API + Sidebar 铃铛 + 通知中心页
后端 — 新建 app apps.notifications:
- Notification model:type/title/content/link_url/is_read,索引 (recipient, is_read, -created_at)
- 4 个 endpoint:
  - GET    /api/v1/notifications/         (列表 + 总未读数,unread_only/page/page_size)
  - GET    /api/v1/notifications/unread-count  (轻量,前端 60s 轮询用)
  - PATCH  /api/v1/notifications/<id>/read     (标单条已读)
  - POST   /api/v1/notifications/read-all      (一键全部已读)
- 严格守 user 隔离:所有查询都 filter(recipient=request.user)
- INSTALLED_APPS 注册 + urls.py include
- migration 0001_initial 应用成功
- MySQL 严格模式:所有 CharField 加 default=''(memory feedback_mysql_default)

后端 — anomaly_detector 集成:
- _RULE_LABELS / _team_admin_recipients() / _notify_user_disabled() / _notify_team_disabled() helper
- process_anomalies 里 _disable_user/_disable_team 之后调对应 notify
- 接收人 = 同团队的主管+副管(is_team_admin OR is_team_owner)
- 用 bulk_create 一次写多条
- try/except 保护:通知失败不阻断封禁主流程

前端:
- types/index.ts:AppNotification / NotificationListResponse(避开浏览器 Web API Notification 冲突)
- lib/api.ts:notificationApi (list/getUnreadCount/markRead/markAllRead)
- store/notification.ts:Zustand store 乐观更新(markRead 先动 UI 再发请求)
- pages/NotificationsPage.tsx:标题 + 全部标记已读按钮 + 未读蓝点 + 相对时间 + 点击跳 link_url + 分页
- App.tsx:/notifications 路由(ProtectedRoute 不限 role)
- Sidebar.tsx(用户 76px):铃铛 SVG + 红点 + 60s 轮询 + visibilitychange 立即刷新
- AdminLayout.tsx(超管 220px):同步加铃铛(本来 sub-agent 只加了用户侧 sidebar,我补全 admin 侧)

测试:
- 新建 web/test/v0.20.1-smoke.mjs:11 项 — 铃铛/红点/跳页/标题/100dvh/min-height:0/调试折叠/poster
- 11/11 通过 + v2-smoke 25/25 + modal-interaction 8/8 全部基线 OK
- 后端 4 endpoint 用 curl 验过:list / unread-count / PATCH read / POST read-all 都正常

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:32:29 +08:00
seaislee1209
6b13cfff70 fix(admin): 笔记本 14寸 Safari 翻页按钮被截 — 根因三件套修法
用户报:Mac Safari + 14寸笔记本,/admin/records 翻页按钮永远在屏幕外,
拖动能看到内容超出但滚到底也看不见翻页按钮。

根因三个叠加:

1. 100vh 在 Safari 桌面端不可靠 — 算的是含工具栏/书签栏的 layout viewport,
   不是用户实际能看到的 visual viewport。.layout { height: 100vh } 实际比可见高,
   底部被 UI bar 盖住。
2. Flex overflow 经典 bug — .content { flex: 1; overflow-y: auto } 没加 min-height: 0,
   flex 子默认 min-height: auto 跟随内容撑开,Safari/Chrome 都不让 flex 父约束高度,
   导致 overflow-y: auto 形同虚设,.content 不滚动,底部按钮被外层 .layout
   overflow:hidden 切掉。
3. 翻页按钮无 padding-bottom — 即使前两个修了,贴着容器边缘也不舒服。

根因修法(不打 padding 补丁):

1. AdminLayout.module.css .layout — 用 100dvh + fallback 100vh
   (Dynamic Viewport Height,浏览器动态算可见区域,Safari 17+/Chrome 108+/FF 101+ 支持)
2. AdminLayout.module.css .content — 加 min-height: 0,让 flex 子元素正确 shrink
3. 4 个 admin 管理页 .pagination 加 padding-bottom: 8px(RecordsPage/UsersPage/LoginRecordsPage/AuditLogsPage)
   作为视觉缓冲(三件套里最末层的 polish)
4. ProfilePage 同样 100vh 模式 → 加 100dvh fallback,防同款 bug 在用户端复现

为什么不只加 padding-bottom:
- padding 是治标 — 假设 viewport 不会再变,加缓冲就行
- 但 Safari 用户切换 zoom / 显示书签栏 / 切换 fullscreen 时实际可见区会变,
  固定 padding 仍会被切
- 100dvh 是浏览器动态计算可见区域,永远准
- min-height: 0 修的是 flex 容器自身的滚动机制,根因层面解决

验收:
- 14寸笔记本 Safari → /admin/records 翻页按钮可见可点 ✓
- 桌面大屏不受影响(dvh == vh == 视口)
- Safari < 17 fallback 到 100vh,行为同改前(不变好也不变差)
- Chrome / Firefox dvh 都支持,正常

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:26:54 +08:00
seaislee1209
11c1cdf8cc fix(records): TeamAssetsPage 重新编辑 prompt 丢失 — VideoDetailModal fallback 补 editorHtml
根因(4 层叠加):
1. TeamAssetsPage 没传 onReEdit prop 给 VideoDetailModal → 走 modal 内部 fallback
2. fallback 只 setPrompt(task.prompt),没设 editorHtml
3. PromptInput 是 contenteditable,渲染依据是 editorHtml,不是 prompt
4. assetVideoToTask 把 editorHtml 显式置 '' → fallback 拿到的就是空串

修法:fallback 跟 store/generation.ts:reEdit 对齐,
用 useInputBarStore.setState 一次性批量灌入所有字段,
关键补 editorHtml: task.editorHtml || task.prompt || '',
让 PromptInput 渲染 + rebuildMentionSpans 走完整路径
(原文 + assetMentions 重建带缩略图的 @ 标签)。

附带顺手补:
- mode 用 switchMode 而不是 setMode(switchMode 会清 keyframe 状态)
- assetMentions: task.assetMentions || []
- references 用 setState 批量传(原代码用 setState 又用 setX 混用,不一致)

影响范围:
- 团管 /team/assets → 任意视频 → "重新编辑" → /app prompt 文本框有内容 ✓
- 超管 /admin/assets → 同上 ✓
- 用户 /user-assets → reEdit 走的是另一条路(generation.ts:reEdit),不受影响

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:24:28 +08:00
seaislee1209
6ee5c8ffdb feat(records): api_prompt 永久留痕 + 详情弹窗调试信息折叠区
后端:
- GenerationRecord 加 api_prompt TextField(blank, default='')
- 0021_add_api_prompt migration
- video_generate_view 计算完 _format_prompt_for_ark 后立即 save api_prompt
  (即使 create_task 抛错也保留,方便事后查实际传了什么)
- admin_records / team_records view 各回传 api_prompt 字段

前端:
- AdminRecord 类型加 api_prompt?: string
- RecordDetailModal 详情弹窗右侧底部加"调试信息(开发/客服参考)"折叠区
  - 默认收起,小灰字 ▸/▾ toggle
  - 仅当 api_prompt && api_prompt !== prompt 才显示"实际发给火山"等宽字 box
    (历史记录 api_prompt 为空则不显示这栏)
  - 火山 Task ID + 复制按钮(showToast 反馈)
  - 失败任务才显示原始错误(raw_error)
  - 平时用户察觉不到,客服/财务复盘时点开就能看完整调试信息

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:19:36 +08:00
seaislee1209
72f351d54f feat(records): 视频卡片/详情弹窗用 thumbnail_url 显示首帧 poster
后端 GenerationRecord.thumbnail_url 字段早就被 tasks.py:_handle_completed (L109-111)
通过 ffmpeg 提取首帧 + 上传 TOS 填充,但只在 _serialize_task (生成页) 返回。
admin_records / team_records / profile_records 三个 view 都没回传,前端无从用。

后端:三个 records view 各加一行 'thumbnail_url': r.thumbnail_url or ''
前端:
- types/index.ts AdminRecord 加 thumbnail_url?: string
- 三处 <video> 加 poster={thumbnailUrl ? rewriteTosUrl(...) : undefined}
  - RecordDetailModal.tsx (消费记录详情)
  - VideoDetailModal.tsx (资产页/生成页详情)
  - GenerationCard.tsx (生成页卡片)

效果:卡片首屏直接显示首帧海报(几十 KB),不再等视频 metadata 加载完
才有视觉;hover 触发 v.play() 时再真正下载视频,UX 不变。
老记录 thumbnail_url='' 时 poster=undefined,行为同改前。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:17:16 +08:00
seaislee1209
e86e3d45b1 fix(admin): 主管理员撤销 bug — TeamsPage 主管 badge 加 onClick
之前 L825 主管理员 badge 无 onClick,管理员之前把某成员设为主管后撤不掉,只能后台改 DB。
后端 admin_team_member_role_view 收到 is_team_admin=false 已支持同时清 is_team_owner。
前端补 onClick + confirm + 调 setMemberRole(false) 即可,后端不动。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:14:38 +08:00
seaislee1209
6d683d4e76 fix(records): lightbox/player overlay 关闭误触外层 modal — Portal + stopPropagation
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m2s
回归测试发现的真 bug (V1 V2 历史遗留):
点击 ReferenceList 的图片 lightbox 或音视频 player 的 overlay 关闭时,
事件冒泡 + V2 modal backdrop-filter 创建的独立 stacking context 双重作用
导致 RecordDetailModal 也被一起关掉。

根因双重:
  1. lightbox/player overlay 的 onClick 没 stopPropagation,
     React 合成事件冒泡到外层 modal overlay 触发 onClose
  2. V2 modal inner 加了 backdrop-filter: blur 创建独立 stacking context,
     lightbox 视觉上 z 10002 全屏覆盖,但实际命中区域被限制在 modal inner 内;
     点击 lightbox 视觉外的暗色区域 (modal 外) 直接命中 modal overlay → onClose

修复 (ReferenceList.tsx):
  - 用 React.createPortal 把 lightbox + audio-video player overlay 渲染到 document.body
    脱离 modal inner 的 backdrop-filter stacking context,真正全屏
  - overlay onClick 加 e.stopPropagation() 作为 React 事件链兜底
  - playerWrap 内部 stopPropagation 保留(防止点 player 内部冒泡关闭 player)

新增测试 (web/test/modal-interaction.mjs):
  专项验证 RecordDetailModal 内部交互, 7 个用例:
  1. 任务详情弹窗打开 OK
  2. 参考素材区存在 OK
  3. thumb 有 title 属性 (V2 加的 tooltip 真生效)
  4. 点击 thumb 弹出 lightbox (DOM 验证)
  4.1 关闭 lightbox 不连带关 modal (本次 fix 的关键回归)
  5. 下载按钮 stopPropagation + 触发下载
  6. modal 关闭 ✕ 按钮 OK
  7. 全程无 console.error

全套回归通过:
  - TS 编译过
  - modal-interaction 8/8 (含 lightbox 冒泡修复验证)
  - v2-smoke 25/25 (无新挂)
  - vitest 71/162 与基线一致 (无新挂)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:31:02 +08:00
seaislee1209
385e1bb49e feat(records): 任务详情弹窗排版优化 — 参考素材搬左侧 + tooltip + max-height
用户反馈:右侧太挤,左侧视频下大片空白浪费,参考素材埋在右侧底部要滚才能看。

排版重组 (RecordDetailModal.tsx):
  - 左侧 mediaPanel:视频 + 参考素材(纵向排列)
    - 视频固定 480×270 (16:9 + object-fit contain)
    - 参考素材区紧贴视频下方,有标题"参考素材(N)"
  - 右侧 infoPanel:状态 / 失败原因 / 基本信息 / 提示词
    - prompt 区域宽度从挤压改为可读宽度
    - 不再有参考素材占下方位置
  - 整体不再有"空白左下角"+ "右侧挤死"+"参考要滚"问题

参考素材区限高 (RecordDetailModal.tsx refScrollBox):
  - max-height: 250px + overflow-y: auto
  - Seedance 单任务最多 9 张图(memory: project_seedance_max_refs),
    80px thumb × 5/行 = 1-2 行,250px 兜底有余
  - 极端情况(未来扩 audio/video 参考)走内部滚动,不推视频上去

Tooltip (ReferenceList.tsx):
  - refItem 加 title={label},hover thumb 弹原生 tooltip 显示完整文件名
  - 解决长文件名 "公司年会现场抓拍-高分辨率-竖屏-第3版.jpg" 被省略号截断后看不全的问题
  - 零成本(浏览器原生),无 JS 开销

新增 memory:
  ~/.claude/.../memory/project_seedance_max_refs.md
  - 火山 Seedance API 单任务最多 9 张参考图片
  - 防止以后又按"上百张"做 UI 假设

验证:
  - TS 编译过
  - modal screenshot 4 张更新 (docs/screenshots/v2/modal/)
  - 视频 + 参考素材左侧贴齐,右侧 prompt 区宽松

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:43:27 +08:00
seaislee1209
a1c16be1ea feat(records): 任务详情弹窗加视频展示 + RGB 故障字失败态
需求: 消费记录 detail modal 加多一个视频展示框
  - completed + result_url → 视频播放器
  - failed → 失败图标 (RGB 故障字风格)
  - processing/queued → spinner
  现有信息排版整体搬到右侧,布局不动。

后端 (apps/generation/views.py):
  admin_records_view + team_records_view 返回字段加 'result_url'
  (model 早就有 result_url, 只是这俩 list API 没暴露)

前端类型 (web/src/types/index.ts):
  AdminRecord 加 result_url?: string

RecordDetailModal 重构 (web/src/components/RecordDetailModal.tsx):
  - 弹窗宽 560 → 1080 (maxWidth 95vw, maxHeight 85vh)
  - body display: flex 双栏
  - 左侧 480 固定宽 + 16:9 视频框 + object-fit: contain
    (沿用 GenerationCard.resultArea 同一套 sizing 思路,
     不同长宽比视频用 contain 居中 + 黑边补足)
  - 右侧 flex 1, overflow-y auto, 现有内容整体搬过去排版不动
  - 视频用 controls + preload="metadata", 不自动播放, 跟全局音量走

MediaArea 组件分支:
  - completed + result_url: <video controls />
  - completed - result_url: 视频 icon + "视频已生成" 占位
  - failed: FailureGlitch RGB 故障字
  - processing/queued: 旋转 spinner + 文字

FailureGlitch 视觉细节:
  - 标题 "生成失败" 44px Space Grotesk weight 700
    text-shadow: -2px 0 var(--info) cyan, 2px 0 #ff00aa magenta
    + 30px 红色 glow → 模拟 CRT RGB 信号偏移
  - 副标题: 错误原因摘要 (truncate 80 char) 等宽字体
  - 背景: 红色斜纹 + 顶/底彩色条纹 (red/cyan 间隔) 仿信号丢失

验证 (docs/screenshots/v2/modal/):
  completed__{dark,light}.png - 视频框 + 右侧信息
  failed__{dark,light}.png    - RGB 故障字
  TS 编译过, backend 已重启

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:48:23 +08:00
seaislee1209
5f30258112 test+fix(theme): V2 smoke test (25/25 pass) + 修复 AdminLayout 缺主题切换按钮
新增功能回归 smoke test 覆盖 V2 改动点:
  test/v2-smoke.mjs — 25 个端到端检查,涵盖:
    1) LoginPage + LoginModal 浅色渲染 (4 项)
    2) admin 真实登录流程 + access_token 持久化 (2 项)
    3) 主题切换端到端 — 默认 dark / 点切换 → light / localStorage 持久化 /
       刷新保持 / 来回切换 (6 项)
    4) Admin 7 个路由侧栏导航 (7 项)
    5) 团管 Sidebar + 生成页 InputBar 核心元素 (4 项 + 公告 soft-skip)
    收尾: 全程无 console.error / 无 page crash (2 项)

发现并修复 1 个真 bug:
  AdminLayout (super admin 后台) 缺主题切换按钮。
  原因:V1 只给 components/Sidebar.tsx (76px 团管侧栏) 加了 toggle 按钮,
  pages/AdminLayout.tsx (220px super admin 侧栏) 漏了。super admin 无法切主题。
  修复:在 AdminLayout sidebarFooter 加月亮/太阳 SVG 切换按钮,
  跟 components/Sidebar 一致;收起态只显示 icon,展开态 icon + 文字。
  新增 .themeToggle class 样式 (hover bg + primary color)。

验证: tsc 过 / smoke 25/25 / 无 console.error / 无 page crash
本地 commit dev 不 push

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 21:03:53 +08:00
seaislee1209
0c9aaa11cd fix(theme): V2-fix — 清除残留 30+ 处死黑/死白硬编码 (用户反馈表格线死黑)
用户反馈: UsersPage 表格线浅色下死黑死黑。
全扫定位:V1 sub-agent 当年标注 "unmapped/near-match" 保留的硬编码,
在 V2 浅色重做下集中暴露为"死黑/死白"问题。

新增 5 个主题感知 token:
  --color-bg-glass-hover           DARK rgba(255,255,255,0.12) / LIGHT rgba(0,0,0,0.06)
  --color-border-glass-hover       DARK rgba(255,255,255,0.15) / LIGHT rgba(0,0,0,0.12)
  --color-bg-thumbnail             DARK rgba(0,0,0,0.30) / LIGHT rgba(0,0,0,0.05)
  --color-bg-thumbnail-hover       DARK rgba(0,0,0,0.15) / LIGHT rgba(0,0,0,0.04)
  --color-shadow-thumb             DARK rgba(0,0,0,0.30) / LIGHT rgba(0,0,0,0.10)

修复(10 个文件, 30+ 处):

表格死黑线 (用户主诉):
  UsersPage / TeamsPage .table td border-bottom: rgba(42,42,56,0.5) 死紫黑
    → var(--color-border-row) (浅色 rgba(0,0,0,0.06) 浅灰)
  .table tr:hover td bg: rgba(255,255,255,0.02) 浅色下消失
    → var(--color-bg-row-hover) (浅色 rgba(0,0,0,0.03))

玻璃面 hover 白透 alpha (浅色下死白看不见):
  VideoDetailModal closeBtn/floatingBtn/iconBtn hover:
    rgba(255,255,255,0.12-0.15) → var(--color-bg-glass-hover)
  TeamsPage detailClose hover: 同上
  GenerationCard moreBtn:hover border:
    rgba(255,255,255,0.15) → var(--color-border-glass-hover)

视频缩略图 bg (浅色下不该死黑):
  AssetsPage thumbnail bg / hover overlay: rgba(0,0,0,0.3/0.15)
    → var(--color-bg-thumbnail / -hover)
  UniversalUpload thumbInner box-shadow: rgba(0,0,0,0.3)
    → var(--color-shadow-thumb)

Modal/Upload overlay & hover:
  UsersPage drawerOverlay: rgba(0,0,0,0.5) → var(--color-overlay-soft)
  TeamsPage detailOverlay: rgba(0,0,0,0.65) → var(--color-modal-overlay)
  UniversalUpload uploadOverlay: rgba(0,0,0,0.5) → var(--color-overlay-soft)

Upload trigger 虚线边 hover:
  KeyframeUpload / UniversalUpload trigger hover: #5a5a6a / #1e1e2a
    → var(--color-border-modal-hover / -bg-modal-hover) (已有 var)
  trigger bg: rgba(255,255,255,0.03) → var(--color-bg-upload)

TeamsPage memberTable:
  memberTableWrapper bg: rgba(255,255,255,0.03) → var(--color-bg-upload)
  memberTable th bg: rgba(255,255,255,0.02) → var(--color-bg-row-hover)

AssetLibraryModal 状态 / 主色 alpha:
  addAssetCard hover bg rgba(108,99,255,0.04) → var(--color-primary-bg)
  dropZone hover/active: 同上 + var(--color-primary-bg-hover)
  statusActive 0.12: rgba(0,184,148,0.12) → var(--color-success-bg)
  statusProcessing 0.12: rgba(243,156,18,0.12) → var(--color-warning-bg)
  statusFailed 0.12: rgba(231,76,60,0.12) → var(--color-danger-bg)
  dropZoneWarning #ff4d4f + alpha → var(--color-danger-hover/-bg/-border)

UniversalUpload uploadError:
  rgba(239,68,68,0.25) → var(--color-danger-bg)

ProfilePage warningBanner:
  rgba(243,156,18,0.12/0.3) → var(--color-warning-bg / -border)

验证: tsc 通过, vitest 71 fail / 162 pass 与基线一致, 24 张 V2 截图重出
关键页对比 (docs/screenshots/v2/):
  - UsersPage / TeamsPage 浅色表格线变浅灰 GitBook 风
  - VideoDetailModal close 浅色下能看到 hover
  - 视频缩略图浅色不死黑

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 20:24:24 +08:00
seaislee1209
f8a39d55c7 feat(theme): 浅色主题 V2 — 玻璃质感重做 + LandingPage 浅色化 + 双语言系统
Phase A: index.css 完全重写 [data-theme="light"] block
  - 玻璃方向修正: bg-card 从 rgba(0,0,0,0.05) 黑透明 → 双 token 拆分
    --color-bg-card: #ffffff 实体白 (admin 卡)
    --color-bg-glass: rgba(255,255,255,0.65) 透明白玻璃 (sidebar/modal/banner)
  - Aurora 浅色不再 display:none, 改 pastel 紫蓝桃 0.20-0.32 alpha
  - Inset highlight 方向反转: 浅色用 rgba(255,255,255,0.50) 白高光 (玻璃顶边标志)
  - Backdrop-filter 五档标准: --bf-glass-sm/md/lg/xl (12-40px + saturate 140-180%)
  - Multi-layer shadow: --shadow-card-light (2 stops) + --shadow-glass-light (3 stops + inset)
  - 暖调 chip: --color-chip-warm-* GitBook 公告风格
  - 文字主色: #171823 微紫 → #171717 Vercel Black

Phase B: LandingPage + AuroraCanvas 浅色化
  - 移除 LandingPage 的 data-theme="dark" 强制 (V1 的回避)
  - LandingPage.module.css 21 处颜色全 var 化
  - AuroraCanvas: 订阅 useThemeStore, 新 LIGHT_ORBS 数组 pastel 紫蓝桃,
    vignette 浅色用白色, grain opacity 减半

Phase C: 13 个玻璃面升级 (3 sub-agent 并行)
  - Modal 类 (Login/ForceChange/VideoDetail.infoPanel/RecordDetail/AssetLibrary/
    Announcement/Confirm/TeamsPage.detailModal): 接入 bg-modal-glass +
    bf-glass-lg/xl + shadow-glass-light (含 inset highlight)
  - Bar/Dropdown/Toast (AnnouncementBanner/Toast/Dropdown/Select/DatePicker):
    bg-glass-strong + bf-glass-md + inset-highlight
  - Sidebar + 生成页 (Sidebar/PromptInput/GenerationCard): glass + 顶边白高光
  - AnnouncementBanner 写双套独立 [data-theme] 规则 (CSS gradient 内不能 var alpha)

Phase D: admin 实体卡 multi-layer shadow (13 处, 1 sub-agent)
  - DashboardPage / TeamsPage / UsersPage / RecordsPage / AdminAssetsPage /
    LoginRecordsPage / AuditLogsPage / ProfilePage / SettingsPage
    的 .statCard / .tableWrapper / .chartWrapper / .accordionItem 等
    加 var(--shadow-card-light) 双层柔阴影

AdminLayout 修复 (V1 漏的):
  - .layout 改 transparent, 让 AmbientBackground pastel aurora 在主区透出
  - .sidebar 加 bf-glass-md + inset highlight + 立体阴影

LoginModal / ForceChangePassword 残留 mint 清理:
  - submitBtn bg/border/color 用 mint-accent var, 字重 500→600 + 字距 0.04em
  - input:focus border 用 var(--color-mint-accent)
  - 加 bf-glass-sm + inset highlight

验证:
  - TS 编译过
  - vitest 71 fail / 162 pass 与 V1 基线完全一致, 无新增回归
  - 24 张 V2 截图位于 docs/screenshots/v2/ (本地, .gitignore 排除 png)

完成报告: docs/todo/亮色主题切换V2-完成报告.md
V2 plan: docs/todo/亮色主题切换V2.md
视觉对齐稿: docs/todo/showcase.html

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 19:46:55 +08:00
seaislee1209
f0f47e8368 feat(theme): 亮色主题切换完整实现 — dark/light 双套 var + Sidebar 切换 + 浅色色板
Stage 1 (var 化, 350 处): 425 处硬编码颜色 → CSS var, 涉及 49 个 tsx/css module 文件,
   按 hot files (DashboardPage/TeamDashboardPage/RecordDetailModal/ReferenceList) →
   Modal/Asset/Profile/Login → 生成页家族/管理后台/公共 UI 三波 8 个 sub-agent 并行处理。
   index.css :root 加 ~70 个新 var (modal/text 层级/状态色 bg 变体/chart/mention pill 等)。
Stage 2 (双套 var): :root 保留 DARK 默认值, [data-theme="light"] 覆盖 ~95 个 token。
   浅色色板按 Vercel Geist (#fafafa / #171717 / shadow-border) + Linear Light surface 分层规范,
   主色 #6c63ff → #5048cc 加深 18% 满足 WCAG AA。aurora 极光在 light 下 display:none。
Stage 3 (切换机制): 新建 store/theme.ts (Zustand + localStorage 持久化),
   Sidebar 加月亮/太阳 SVG 切换按钮 (位于头像上方),
   DashboardPage/TeamDashboardPage/ProfilePage 的 ECharts 配 key={theme} 强制重渲染。
Stage 4 (微调): LandingPage 强制 data-theme="dark" 保持品牌识别 (登录流程一直深色),
   sidebar bg / card bg / border 在浅色下加深 0.02 提升轮廓辨识度。
Stage 5 (验证): Playwright 头无浏览器自动登录 admin + screenshot_user, 截深/浅各 12 个页面 = 24 张
   到 docs/screenshots/ (本地档, .gitignore 排除 png 不入库)。
   vitest 71fail/162pass 与改造前基线完全一致, 无新增回归。

完成报告: docs/todo/亮色主题切换-完成报告.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:10:35 +08:00
seaislee1209
3da801d6e6 docs: 亮色主题切换开发计划 (P3, AI 自主完成预备)
5 个 Stage 分阶段推进:
- Stage 1: 109 处 inline hex/rgba 颜色替换为 var(--color-*)
- Stage 2: :root 拆 [data-theme="dark/light"] 两套, 浅色色板抄 Linear/Vercel
- Stage 3: themeStore + 切换按钮 + localStorage 持久化
- Stage 4: 浅色色板调试 + 半透明色反相 / ECharts 重渲染 / AuroraCanvas 处理
- Stage 5: vitest + Playwright 本地无头浏览器深/浅截图回归

预期 AI 连续工时 7-9 小时, 人工 3.5-5 天。
关键发现: 项目未实际使用 Arco (CLAUDE.md 写错), 颜色高度集中
(Top 4 文件占 75%, Top 10 颜色占 60%+ 已有对应 var)。

待用户启动 /loop 后 AI 自主完成, 完成时输出截图 + 完成报告。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:43:26 +08:00
zyc
c2c811a8fe chore: 触发构建
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m7s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:53:34 +08:00
zyc
f101878954 feat: 前端预览资源切换到 CDN 域名 airflow-play.airlabs.art
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
新增 rewriteTosUrl 在渲染层把 airdrama-media.tos-cn-beijing.volces.com
替换成 airflow-play.airlabs.art,覆盖 <video>/<audio> src 及 tosThumb
图片缩略;下载仍走原 TOS 直连域名以避开 CDN CORS 配置依赖。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 16:11:30 +08:00
seaislee1209
3f858257ea fix: v0.19.6 CI deploy.yaml retry 循环失败时正确 exit 1
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m4s
根因 deploy.yaml 6 处 retry 循环用 \`for ... do command && break; done\`
模式, bash for 循环本身的 exit code 永远是 0(只要循环正常结束),
即使所有 attempt 都失败。CI 看 step exit 0 -> 误判绿色。

实际事故 v0.19.5 (85aa024) push dev 后 Gitea Actions 显示绿色钩,
但测试服 K8s 上没有创建对应的 ReplicaSet, web pod 仍跑 v0.19.4。
查 K8s ReplicaSet 历史发现自 4-24 12:12 之后没有任何新 RS,
说明 deploy step 的 kubectl apply 没把新 image tag 提交到 etcd
(或某处中间静默失败被吞)。SWR 上镜像已经推上去, 是 deploy 这步
后续操作出了问题但 CI 没察觉。

修复 6 处 retry 全部加 \`ok=0/ok=1/break\` flag, 循环结束后 \`[ $ok
-eq 1 ] || exit 1\` 守卫, 真失败时 step exit 非 0 -> CI 红色:
  - backend build (3 次)
  - backend push (3 次)
  - web build (3 次)
  - web push (3 次)
  - kubectl download (3 次)
  - deploy to K3s (5 次, 含 kubectl apply / rollout restart)

以后再遇到部署失败, Gitea Actions 会真正显示红色, 不再"假绿色"
骗人。同时已有的 Report-failure-to-Log-Center step (if: failure())
会被触发, 飞书 / log-center 收到告警。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:40:42 +08:00
seaislee1209
85aa0249b9 fix: v0.19.5 生成页慢速往上滚仍跳到底部 — 双层 anchor 叠加根因
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 14m10s
v0.19.4 只修了 useEffect 的 scrollToBottom 误触, 没修 handleScroll
里的 anchor 累加。用户实测: 鼠标滚轮慢速 / 慢拖滑动条往上翻仍会
跳到最底部, 但快速拽到顶不复现。

根因(双层 anchor 叠加)
1. 浏览器自动 scroll anchoring(默认 overflow-anchor: auto): 滚动
   容器头部插入内容时浏览器自动 scrollTop += diff, 保持视觉位置
2. handleScroll 里 .then 又手动 el.scrollTop += diff
=> 双倍 anchor, 总位移 = 2 * diff, 把 scrollTop 推到 max(底部)

为什么"快速拽不复现, 慢速复现":
浏览器 scroll anchoring 在用户主动 scroll 期间会暂时关闭。快速
拽到顶后立即 loadMore 完成, 浏览器把"用户刚释放"视作仍在 scroll
跳过自动 anchor, 只手动一次, 不叠加。慢速操作时 scroll event 间
有 100~300ms 静止间隔, 浏览器认为 scroll 已停, 自动 anchor 启动
+ 我们手动 anchor = 双倍。

修复(两道防线)
1. CSS: .contentArea 加 overflow-anchor: none, 彻底关掉浏览器自动
   anchor, 由代码统一管 — 这是根因修复
2. handleScroll: 加 loadMoreInFlightRef 防重入 flag, 慢速操作下
   多次进入 if 分支只 schedule 一次 anchor; rAF 完成后清 flag —
   兜底防御, 避免极端时序下 rAF 累加

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:17:01 +08:00
seaislee1209
10994df952 fix: v0.19.4 生成页往上翻加载历史时不再跳到底部
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m15s
现象: 用户往上翻看历史, loadMore 往 tasks 数组头部 prepend 老任务后,
页面自动跳到最底, 打断浏览。

根因 VideoGenerationPage.tsx useEffect 依赖 tasks.length, 只要数量
增加就 scrollTo(scrollHeight)。这个逻辑本意是"新任务生成 push 到
末尾时滚到底", 但区分不出"头部 prepend" vs "尾部 push", 于是
loadMore 也触发了滚底。handleScroll 里的 anchor(scrollTop += diff)
本来会做视觉保位, 但 useEffect 的 scrollTo smooth 抢先/盖过它。

修复 改成比末尾 task 的 id 是否变化 (prevLastIdRef) 而非 length
- 新任务 push 到末尾 → 末尾 id 变 → 滚到底 
- 头部加载历史 → 末尾 id 不变 → 保位置 
- 轮询更新任务属性(如生成完成) → 数组内容变但末尾 id 不变 → 不打扰 
- 删除某条任务(非末尾) → 末尾 id 不变 → 不滚 
- 只有"末尾多了新东西"这一种情况才滚底, 符合用户直觉

验证 vitest 71 failed 全部为 v0.18.x 以来的 preexisting 失败
(phase2/phase3 路径解析问题), stash 前后对比完全一致, 本次改动
零新增回归。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:08:09 +08:00
seaislee1209
ecdb9cb471 fix: v0.19.3 admin settings GET 补返回 1080P 两个单价字段
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m45s
v0.19.0 做 1080P 时 QuotaConfig 加了 base_token_price_1080p 和
base_token_price_1080p_video 字段, serializer (PUT) 和计费逻辑
(_get_token_price) 都处理了, 但 _settings_dict (GET) 漏了两行,
导致管理后台设置页两个 1080P 单价输入框显示空白。

实际影响
- DB 值对 (51 / 31), 计费走 _get_token_price 直接读 DB, 计费一直正确
- 前端 SettingsPage fetchSettings 用 setSettings(data) 覆盖,
  GET 返回缺字段 -> state 变 undefined -> 输入框显示空
- 管理员点保存: undefined 被 JSON.stringify 省略 -> PUT body 不含
  这两字段 -> serializer validated_data 里没有 -> DB 未改
- 所以目前"巧合安全", 但风险: 管理员在空输入框填数字后清空,
  Number("") = 0 会覆盖 DB, 把单价刷成 0

修复
- backend/apps/generation/views.py _settings_dict() 加两行返回
  base_token_price_1080p / base_token_price_1080p_video
- 前端 GET 后 state 直接拿到 51 / 31, 输入框自动显示, 不依赖"巧合"

回归测试 (backend/tests/test_1080p_api.py)
- 新增 TestAdminSettingsResponse.test_get_returns_all_token_price_fields
  断言 GET /admin/settings 返回 6 个 token_price 字段全齐
- 失败消息明示: "缺字段会导致前端输入框显示空" 以防以后再漏

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:09:55 +08:00
seaislee1209
13440f2709 feat: v0.19.2 prompt 里 @素材名 按火山规范转为「图片N/视频N/音频N」
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m58s
火山 Seedance 模型只能理解"素材类型+序号"的指代(官方文档 FAQ Q3);
对文件名 / asset id / URL 类字符串一律读不懂,只能按 content 数组里
图片出现顺序瞎猜谁是谁,导致用户看到的"人物颠倒"概率性现象(典型
任务 cgt-20260422163517-4k8x6)。

改动
- backend/apps/generation/views.py:
  - 新增 _format_prompt_for_ark(prompt, label_placeholders) helper
    用 str.replace 避 regex 元字符崩溃, 按 label 长度降序防子串吞噬
  - video_generate_view references 循环同步维护 image_n/video_n/audio_n
    三个独立计数器 + label_to_placeholder 映射
  - 关键不变量: 任意时刻 counter == content_items 里该类型 *_url 已 push 数
    group 老路径 counter 照推但不登记 label + WARNING, 避免编号错位
  - 调 create_task 前构造 api_prompt 传给火山, DB.prompt 保留用户原文
    (带 @xxx.jpg) 以便 reEdit 重建带缩略图标签

测试覆盖 14 项 (airlabs-test MySQL 全绿)
- 单元 9 项: 基础替换 / 多类型独立计数 / 重复 @ / 子串冲突 / 正则元字符 /
  空 mapping / label 未 @ / 中文标点 / 空 label 跳过
- 集成 5 项: local 正常替换 / DB 原文保留 / group 老路径不换 + WARN /
  混用 local+group counter 对齐 (关键回归) / 图片音频独立计数

兼容性
- reEdit: DB 保留原文, PromptInput.rebuildMentionSpans 按 @label 正则
  仍能重建 span, 缩略图正常
- regenerate: 走同一 POST /api/v1/video/generate, 二次过转换
- Celery: 只 query 不重发, 不受影响

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 20:52:32 +08:00
seaislee1209
f2dc8d4713 feat: v0.19.1 素材组删除改用一次性 DeleteAssetGroup + 幂等清本地
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m59s
之前 DELETE /api/v1/assets/groups/<id> 的做法是循环调 DeleteAsset 再
删本地记录,现在火山开放了 DeleteAssetGroup(文档明确级联删除组内所有
Asset),改为一次调用,原子、快、无半成功。

变更
- backend/utils/assets_client.py:
  - ApiInfo 注册 DeleteAssetGroup
  - 新增 delete_asset_group(group_id)
- backend/apps/generation/views.py:
  - asset_group_detail_view DELETE 分支改为一次 delete_asset_group
  - 加幂等保护: 火山返回 NotFound.group_id 时继续清本地, 修复场景为
    用户在火山控制台手删素材组后本地 DB 出现孤儿, 再在前端点一次
    "删除素材组"即可清掉本地残留

测试 (airlabs-test)
- assets_client 4 项 PASS: 创建 → 删除 → 验证 gone → 重删返回
  NotFound.group_id → 纯假 id 同样 NotFound.group_id
- view 层 2 场景 PASS:
  - A 火山+本地都在 → 都清空
  - B 火山侧已手删, 本地还在 → 本地也能清

文档整理
- docs/API文档/about-Asset-素材组相关/ 新增 8 个火山最新 Asset API 文档
  (CreateAsset/Group, List*, Get*, Update*, Delete*), 原"使用指南"移入
  该目录归档

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 21:09:19 +08:00
seaislee1209
17fc3e5652 feat: 所有任务展示/列表/导出补「分辨率」字段 — 1080P 可见性完善
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m18s
v0.19.0 上线 1080P 后,多处展示只有"比例"没有"分辨率",用户和财务看到
费用差异却不知道是因为分辨率不同。本次补齐:

- VideoDetailModal 视频详情信息栏:模式·模型·时长·比例·【分辨率】·tokens·费用
- RecordDetailModal 消费记录详情弹窗:基本信息加「分辨率」字段
- RecordsPage 超管消费记录 CSV 导出:比例列后加「分辨率」列
- TeamRecordsPage 团管消费记录 CSV 导出:同上
- ProfilePage 个人中心记录列表:右侧费用旁加分辨率小标签(仅当有值)
- types/index.ts: AdminRecord 加 resolution?: Resolution 字段

后端 API 之前已返回 resolution(v0.19.0 的 5 处手工序列化已覆盖 L1751
admin_records / L1815 team_records / L2704 profile_records / L2837/2919
内容资产),前端只需接住展示即可。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 22:08:06 +08:00
seaislee1209
27bfa689ce test: 加测试服 E2E — 1080P 分辨率支持线上验证 (8/8 通过)
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m29s
针对 airflow-studio.test.airlabs.art 使用团管账号 tudou 真实验证:
- Sidebar「今日剩余次数」文案 + 无钻石图标
- Toolbar 默认 720P
- AirDrama 可切 1080P
- 1080P 下 Fast Dropdown 置灰(UI 不可达 Fast+1080P)
- Fast 下 1080P Dropdown 置灰(反向)
- ProfilePage 预警文案无「今日额度」老称谓
- API 拒绝 Fast+1080P 组合(400 invalid_resolution)
- API 拒绝 adaptive ratio(400)

已跑通,附带 resolution-1080p.spec.ts (本地版,admin 账号,5/5 通过)
和 backend tests (23 unit + 5 integration 全过)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 19:14:28 +08:00
seaislee1209
6b22e1fa3f fix: 用户端文案从「额度」改为具体单位「次数」— 消除点数概念混淆
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m5s
- Sidebar 左下角:去钻石图标(避免用户带入即梦/豆包的"点数"概念)+
  数据从 daily_seconds (秒数池残留) 改为 daily_generation_limit (次数);
  文案 "剩余额度"→"今日剩余次数"(必须写全,用户不猜);
  数字字号放大 14→18,tabular-nums 稳定排版
- ProfilePage 预警 banner: "今日额度已使用 X%"→"今日生成次数已用 X%";
  "今日额度已用完"→"今日生成次数已用完"
- generation.ts 错误映射: "额度不足,请联系管理员"→
  "今日生成次数或团队余额不足,请联系管理员"(两种可能都列出)

秒数池(daily_seconds_limit)是 v0.10.0 计费改次数+金额前的遗留概念,
这次把用户端可见的"额度"全部替换为明确的"生成次数/余额"单位,避免用户
把"额度"理解成即梦/豆包的"点数"来找客服问问题。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 19:07:02 +08:00
seaislee1209
39667ff19c feat: v0.19.0 1080P 分辨率支持 — 完整前后端 + 严格计费准确性 + 47 测试
火山 Seedance 2.0 于 2026-04-16 上线 1080P 支持。本次实现前端 UI、
后端校验/计费、数据库迁移,并严格遵守三原则:
1. 禁止兜底/静默降级 — Fast+1080P 组合在 UI/store/serializer/view/计价
   五层防御,任一层穿透都 fail loud,不悄悄按 720P 扣费
2. 钱的计算绝对准确 — 前端预估公式与后端 estimate_tokens 完全一致
   `(输入时长+输出时长) × 宽 × 高 × fps / 1024`;实际扣费按火山返回
   total_tokens × 官方单价;预估端不维护最低 token 修正表
3. 不隐藏 bug — 无 `or '720p'` / `|| '720p'` 兜底;类型严格;异常暴露

## 后端(7 处 + 1 次迁移)

- models.py: QuotaConfig 加 base_token_price_1080p(51)/base_token_price_1080p_video(31);
  GenerationRecord.resolution 加 RESOLUTION_CHOICES 约束 + default='720p'
- migrations/0020: 含 RunPython data migration 回填历史 resolution='' → '720p'
- utils/billing.py:
  * RESOLUTION_MAP 加 1080P 六种宽高比(21:9 是 2206×946,不是 seedance 1.0 值)
  * get_resolution 去掉 tier 默认值,非法组合 raise KeyError 不静默降级
  * estimate_tokens 纯官方公式,加 input_video_duration 参数(公式完整)
- utils/airdrama_client.py: create_task 加 resolution 必填参数(无默认值)
- apps/generation/serializers.py:
  * VideoGenerateSerializer 加 resolution ChoiceField
  * aspect_ratio 改 ChoiceField 显式拒绝 adaptive
  * SystemSettingsSerializer 加 2 个 1080P 单价
- apps/generation/views.py:
  * _get_token_price 加 resolution 必填参数,Fast+1080P raise ValueError
  * _sum_video_duration 累加视频参考时长
  * video_generate_view 读 resolution、400 拒绝 Fast+1080P 组合、
    传给 get_resolution/estimate_tokens/_get_token_price/create_task/
    GenerationRecord.resolution(移除 L450 硬编码 '720p')
  * _settle_payment 按 record.resolution 取单价(1080P 结算按 1080P 价)
  * _serialize_task + 5 处手工序列化加 resolution 字段(无 `or '720p'`)
- apps/accounts/views.py: team 接口返回 token_price_1080p/_video

## 前端(10 处)

- types/index.ts: Resolution 类型;GenerationTask/BackendTask/Team/
  QuotaConfig/AssetVideo 加字段(全部必填,无 optional)
- store/inputBar.ts: resolution state;setModel/setResolution 双向拦截
  Fast+1080P 组合,toast 提示引导,不静默降级
- store/generation.ts: addTask/backendToFrontend/reEdit/regenerate 全链路
  携带 resolution;mapErrorMessage 改 '今日生成次数或团队余额不足'
- components/Toolbar.tsx:
  * 加分辨率选择器 Dropdown(位置:比例和时长之间)
  * modelItems/resolutionItems 双向 disabled(Fast 下 1080P 灰 / 1080P 下 Fast 灰)
  * estimatedTokens 对齐后端公式(含输入视频时长 + assetMentions 视频时长)
  * estimatedCost 按 resolution 选单价(Fast→fast_*、1080p→1080p_*、其他→基础)
  * tooltip 明示"实际费用以火山 API 返回的 token 数为准"
- components/Dropdown.tsx: 加 disabled 属性支持
- components/VideoDetailModal.tsx: 重新编辑恢复 resolution
- components/GenerationCard.tsx: 动态显示 task.resolution.toUpperCase()
- pages/SettingsPage.tsx: 加 2 个 1080P 单价输入框(独立分组)
- pages/AdminAssetsPage.tsx / TeamAssetsPage.tsx: 去 || '720p' 兜底
- lib/api.ts: videoApi.generate 参数 resolution 必填

## 测试(47 个用例)

### 后端(28 个)
- tests/test_1080p_billing.py(23): RESOLUTION_MAP 像素、estimate_tokens
  公式(含/不含输入视频、不做最低 token 修正)、_get_token_price 六种
  组合、Fast+1080P 抛异常、calculate_cost 对齐官方示例 4.97 / 12.39 元
- tests/test_1080p_api.py(5): video_generate_view 拒绝 Fast+1080P (400)
  + 拒绝 adaptive + 拒绝非法 resolution + 默认值兼容 + 合法组合通过

### 前端(19 个)
- test/unit/resolution1080p.test.ts(14): store 状态、双向拦截
  (1080P 下切 Fast 被阻止 model 不变、反向同样)、官方像素契约测试、
  价格示例对齐(720P 4.97 / 1080P 12.39)
- test/e2e/resolution-1080p.spec.ts(5): 真实浏览器验证默认 720P、
  Dropdown 双向置灰、tooltip 明示以火山为准

## 与官方文档对齐

- 参数:resolution (480p/720p/1080p 小写)、ratio、duration、generate_audio
- 像素:来自 docs/API文档/创建视频生成任务API.md Seedance 2.0 & 2.0 fast 列
- 单价:来自 docs/API文档/seedance模型价格.md (46/28/51/31/37/22)
- Fast 不支持 1080P:来自 docs/API文档/Seedance 2.0 1080P.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 19:06:45 +08:00
seaislee1209
624e12ae46 docs: v0.18.3 文档整理 + 新火山 API 文档 + changelog
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m38s
- 新增 4 个火山官方 API 文档(Seedance 2.0 1080P / seedance 模型价格 /
  seedance 2.0 系列教程 / 创建视频生成任务API)
- 归档 6 个过期文档到 docs/archive/(旧 Seedance API 邀测版 /
  旧 Assets API 邀测版 / celery 轮询修复 / design-review / prd / test-report)
- 新增 docs/todo/ 目录(提示词 AI 优化功能待办)
- changelog.md 补 v0.18.3 条目

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 18:03:49 +08:00
seaislee1209
dafdc8983f fix: v0.18.3 版权报错友好提示 + 图片删除即梦式连续重命名
Bug 1: 版权限制错误友好提示
- ERROR_MESSAGES 加 OutputVideoSensitiveContentDetected.PolicyViolation 映射
- 漫威等知名 IP 触发的版权拦截不再显示英文 raw error

Bug 2: 图片删除后同类型引用连续重命名(即梦逻辑)
- inputBar.ts::removeReference 重写:删除后同类型剩余引用按顺序 1/2/3 连续编号
- 用 DOMParser 同步更新 editorHtml 里对应 data-ref-id 的 @mention span textContent
- 缩略图区和提示词栏同步刷新,避免"两个图片2"命名冲突

验证
- 11 个 Vitest 单元测试覆盖图片/视频/音频删除、空 editorHtml、无 @mention、
  连续快速删除等边界场景
- 3 个 Playwright E2E 真实浏览器验证:上传 3 张图 → 删中间 → 再上传 → 编号不冲突

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 18:03:36 +08:00
seaislee1209
2281c64ee8 fix: 音频不能作为唯一参考素材 — 前端校验 + toast 提示
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 6m0s
Seedance API 不支持"纯音频"和"文本+音频"输入,必须搭配图片或视频。
- canSubmit() 校验同时检查 references 和 assetMentions
- Toolbar 点击禁用按钮时弹出 toast 提示原因

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:10:39 +08:00
zyc
41115faa16 add md
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m19s
2026-04-13 20:47:43 +08:00
seaislee1209
0b770340c8 fix: 修复资产页素材库引用不可查看 + 重新编辑素材泄漏
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m52s
1. AdminAssetsPage/TeamAssetsPage: asset:// 协议 URL 改用 thumb_url 显示缩略图
2. generation.ts reEdit/regenerate: 过滤 isAssetRef,素材库引用不混入 references 数组
3. PromptInput extractText: 实时同步 assetMentions store,删除 @标签后不再残留旧数据

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 18:33:08 +08:00
zyc
177a9c7dec feat: HTTP→HTTPS 自动跳转 — Traefik Middleware + CI/CD 部署补全
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m36s
- 新增 redirect-https-middleware.yaml (Traefik 301 永久重定向)
- ingress.yaml 添加 middleware annotation
- deploy.yaml 补充 cert-manager-issuer 和 redirect-middleware 的 kubectl apply

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:38:58 +08:00
zyc
a6a3928091 perf: kubectl 4s 超时 + 5 次重试,避免 K3s 内网抖动卡死部署
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m9s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:29:24 +08:00
zyc
ab1b00f94a feat: HTTP 自动跳转 HTTPS — Traefik Middleware + Ingress annotation
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:17:02 +08:00
seaislee1209
5972f45784 fix: 素材库引用缩略图烂图 + pollStatus 跨项目素材保护
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
- MentionTag: onError fallback,缩略图加载失败显示视频/图片占位图标
- createMentionSpan/VideoDetailModal: img onError 隐藏烂图
- buildReferenceSnapshots: 素材库引用用 thumb_url 做 previewUrl
- isAssetRef 标记防止视频缩略图被 <video> 渲染、重编辑防重复
- pollStatus: 已 active 的素材跳过远程查询,防止跨项目素材被误删

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:11:12 +08:00
seaislee1209
db1bbfa1d4 Merge branch 'dev' of https://gitea.airlabs.art/zyc/video-shuoshan into dev
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
2026-04-04 22:18:54 +08:00
seaislee1209
4b2dd9ef5e fix: 音频 ♫ 符号溢出到 prompt 文本 — 改用 CSS ::before 渲染
createMentionSpan 里音频的 ♫ 之前用 textContent 设置,
被 extractText() 的 el.textContent 读进了 prompt 纯文本,
导致 renderPromptWithMentions 匹配后留下额外的 ♫ 字符。

改用 CSS ::before content 渲染,不参与 textContent,
prompt 里不再有多余的 ♫。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:17:07 +08:00
zyc
3bc8b78507 perf: docker cleanup 保留基础镜像缓存,只清 dangling 镜像
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m4s
2026-04-04 21:54:11 +08:00
seaislee1209
5da67435b2 fix: v0.18.1 用户测试 8 项 Bug 修复
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
- MutationObserver 立刻同步 editorHtml(删 @ 标签后时长/数量立即重置)
- parseAssetMentionsFromDOM 从 DOM 实时读取(不用 stale state)
- renderPromptWithMentions 支持音频 ♫ + 视频首帧 + assetType
- rebuildMentionSpans 按 label 长度降序匹配(防子串冲突)
- 删除素材后 group 缩略图优先找图片/视频(不用音频 URL)
- 素材组整组删除功能(后端 DELETE + 前端按钮)
- Celery poll 架构重构(一次性任务 + recover_stuck_tasks 统一驱动)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:06:02 +08:00
zyc
d73175b101 fix: kubectl 装到 /usr/bin 避开 /usr/local/bin 的 bind mount
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m16s
2026-04-04 20:28:05 +08:00
zyc
f37c38d38b fix: kubectl 安装前先删旧目录避免 mv 覆盖目录报错
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
2026-04-04 20:25:52 +08:00
zyc
4cf9a0a4bb perf: 轮询调度间隔从30秒改为10秒
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
2026-04-04 20:21:25 +08:00
zyc
127ed9659d Merge remote-tracking branch 'origin/dev' into dev
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
2026-04-04 20:16:15 +08:00
zyc
ded5c4c44f fix bug 2026-04-04 20:13:23 +08:00
seaislee1209
ba33c35dd8 add test
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m13s
2026-04-04 19:38:36 +08:00
zyc
6353d2ec4f feat: rename /assets route to /user-assets
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
2026-04-04 19:29:31 +08:00
seaislee1209
f1a7ad8a2f fix: nginx /assets 路由 403 修复 — 静态缓存改为正则匹配文件扩展名
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m32s
/assets/ location 会拦截 SPA 路由 /assets(资产页),导致刷新 403。
改为正则匹配 /assets/*.{js,css,png,...},只缓存实际静态文件,
不影响 SPA fallback 到 index.html。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 19:16:38 +08:00