189 Commits

Author SHA1 Message Date
seaislee1209
75b950849d fix(team-members): 角色 select 恢复 100% 宽 + 自定义箭头不贴边
上次误把宽度改窄了,实际诉求是箭头不贴右边框。
原生 select 箭头位置浏览器定,padding 推不动。
appearance:none 关原生 + background-image 内嵌 SVG chevron + right 12px center 控位置。
padding-right 36px 给箭头留空间。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:18:16 +08:00
seaislee1209
f54bf94422 fix(users): 编辑 modal 观察者改 toggle switch + 说明文字下移
之前 checkbox + 一整段说明文字塞一个 label 里,文字换行 + checkbox 浮到中间,挤眼。
- 顶部一行:左标签「设为观察者」+ 右 toggle switch(复用 SettingsPage 同款样式,加到 UsersPage.module.css)
- 下方一行:小字灰色说明「可查看全部团队的内容资产...」

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:16:20 +08:00
seaislee1209
690b0c00e7 fix(team-members): 编辑 modal 角色 select 改固定宽 200px
100% 宽时下拉箭头被推到最右、内容左对齐,中间一大片空白显眼。
角色就 2 项,200px 已经够。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:13:53 +08:00
seaislee1209
c1dbc7ac86 refactor(users): 改名/观察者/角色切换归并到「编辑」modal — 操作列只剩 3 个按钮
行内/独立按钮模式之前太散,actions 列挤满。本质都是「编辑用户属性」,合并到 modal:

UsersPage(超管):
- 删 cell 内联「改名」按钮 + 内联编辑 state(editingUsernameId/Value + startEditUsername/cancelEditUsername/handleSaveUsername)
- 删 actions「设为观察者/取消观察者」按钮 + handleToggleObserver
- 「编辑」modal 标题改「编辑用户」,加 [用户名] (admin 行 disabled) + [观察者复选框] (仅 team_admin 显示)
- handleSaveQuota → handleSaveUser:串调 username → observer → quota,任一失败 toast + 停留 modal
- cell 保留 observer badge 只读显示
- actions 列剩 [编辑] [重置密码] [禁用/启用]

TeamMembersPage(团管):
- 删 cell 内联「改名」按钮 + 内联编辑 state
- 删 actions「设为副管理员/取消副管理员」按钮
- 「编辑配额」改「编辑」,modal 标题「编辑成员」,加 [用户名] (按 canEditUsernameFor) + [角色 select] (canEditRoleFor 决定 select 还是 readonly 文本)
- 新 helper canEditRoleFor:仅主管可改非主管成员的角色
- handleSaveQuota → handleSaveMember:串调 username → role → quota
- actions 列剩 [编辑] [重置密码(权限矩阵)] [禁用/启用]

后端零改动,纯前端串调现有 PATCH endpoints。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:11:14 +08:00
seaislee1209
cec1e5d770 feat(observer): 团管观察者标记 — 可看全局内容资产(不见 ¥)
后端:
- User.is_observer BooleanField (0016 migration, default=False)
- AdminAuditLog 加 user_observer_toggle 操作类型
- UserSerializer fields 含 is_observer (/auth/me 透出)
- IsSuperAdminOrObserver permission 类:超管 + (is_team_admin && is_observer)
- 3 个 assets endpoint (overview/team_members/user_videos) 权限从 IsSuperAdmin 改为 IsSuperAdminOrObserver
- admin_user_observer_toggle_view (PATCH /admin/users/<id>/observer):
  仅超管,只允许打在团管上,拒超管自己 + 拒成员
- admin_users_list_view 返回 is_team_owner/is_observer 字段(前端 row-level 判断用)

前端:
- User/AdminUser/TeamMember type 加 is_observer
- adminApi.toggleUserObserver
- ProtectedRoute 新 requireAdminOrObserver prop + requireAdmin 智能 fallback(团管被拒回 /team/dashboard)
- App.tsx /admin 父路由 requireAdminOrObserver,子路由除 assets 外仍 requireAdmin (race 防御)
- RoleAwareAdminIndexRedirect:观察者团管入 /admin 跳 /admin/assets,超管跳 /admin/dashboard
- AdminLayout sidebar 角色过滤:观察者只见「内容资产」+ 「返回首页」改「返回团队管理」+ logo「观察者」字样
- TeamAdminLayout 观察者团管加「全局资产」入口跳 /admin/assets
- AdminAssetsPage 4 处 ¥ 条件渲染 (hideMoney = role !== 'super_admin')
- UsersPage 行加「设为观察者/取消观察者」按钮(仅 is_team_admin && team_id) + 观察者 badge
- toast 提示「需该用户重新登录后生效」(JWT 不缓存 is_observer claim)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 15:58:18 +08:00
seaislee1209
a842f87812 feat(users): 超管+团管可改用户名 — 内联编辑 + 5 步权限矩阵
后端:
- AdminAuditLog 新增 user_username_update 操作类型 (0015 migration)
- admin_user_username_update_view (PATCH /admin/users/<id>/username, 仅超管, admin 账号不可改)
- team_member_username_update_view (PATCH /team/members/<id>/username, 团管, 同 reset-password 5 步矩阵: 同团/拒自己/拒admin/拒主管/副管不改副管)
- 长度按 UTF-8 字节计 3-20 字节 (≈ 3-20 英文字符 或 1-6 中文字符)

前端:
- adminApi.updateUserUsername + teamApi.updateMemberUsername
- UsersPage 用户名 cell 内联「改名」按钮 (admin 行隐藏)
- TeamMembersPage 用户名 cell 内联「改名」按钮 (canEditUsernameFor 守卫)
- 按现有 TeamsPage inline edit 模板 (inline-flex + whiteSpace:nowrap)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 15:43:35 +08:00
seaislee1209
e500c2d6a0 feat(assets): 3 个资产页视频也加 poster 首帧 — batch B 漏的补全
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 6m40s
v0.20.1 batch B 只给生成页 GenerationCard + 消费记录 RecordDetailModal +
视频详情 VideoDetailModal 加了 poster,3 个资产页漏了:
- 用户资产页 (AssetsPage.tsx)
- 超管内容资产 (AdminAssetsPage.tsx)
- 团管内容资产 (TeamAssetsPage.tsx)

用户实测:消费记录详情能看到首帧海报 + 加载圈;打开内容资产页 / 生成页面
其他位置的视频卡片黑底硬等加载,没首帧 — 体验不一致。

修法:
- 后端 admin_assets_user_videos / team_assets_member_videos view 各加一行
  'thumbnail_url': r.thumbnail_url or '' (batch B 的 3 个 records view 已有)
- AssetVideo 类型加 thumbnail_url?: string
- 3 个资产页 <video> 加 poster={... ? rewriteTosUrl(...) : undefined}
  (跟 GenerationCard/RecordDetailModal/VideoDetailModal 写法一致)

GenerationCard.tsx 已在 batch B 加过 poster — 用户感觉"生成页面也没"是因为本地
ARK_API_KEY 未配生不了新视频,老 record thumbnail_url 字段是空。新生成的视频
会有(后端 tasks.py:_handle_completed ffmpeg 已经写入)。

测试:tsc 0 error, v0.20.1-smoke 11/11 + modal-interaction 8/8 + announcement 17/17

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:23:56 +08:00
seaislee1209
08b5e66fbc fix(theme): 浅色下视频上的下载/收藏按钮看不见 — 改回深半透底+白图标
v0.20.0 浅色主题切换时把 --color-bg-on-media 改成 rgba(255,255,255,0.90),
但 --color-on-overlay 没动还是白色 ⇒ 白底白图标完全看不见。

用户实测:生成页面视频卡片 hover 时下载+收藏按钮浅色下都消失。

根因:这俩 var 是给 "video 上的悬浮控件" 设计的,视频帧本身可能任何颜色
(用户上传白雪景 / 深夜景都有),控件必须用 深底+白字 才能在任意视频背景上可读。
这是行业惯例(YouTube/抖音/Bilibili 浅色主题下视频控件也是黑底白字)。

修法:浅色下也用 rgba(0,0,0,0.55) 深半透底,保持 --color-on-overlay 白色不变。
跟 dark 主题语义对称(都是深底白字,只是 alpha 不同)。

涉及:
- GenerationCard.module.css .downloadBtn (生成页卡片下载+收藏)
- VideoDetailModal.module.css 其他 video overlay 用 var 的元素也受益

未改:VideoDetailModal 内部 .floatingBtn / .timeDisplay / .controls 等
硬编码 rgba(255,...) — 那些也是视频 overlay 控件,黑底白字是对的,不动。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:15:42 +08:00
seaislee1209
aa1a70121a feat(notification): 公告颜色自适应 — 算法 strip 暗色/浅色专用灰度色,彩色保留
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m47s
用户反馈:公告里手写的 <div style="color:#e0e0e0"> 在浅色背景下糊;
工具栏只改了"红字""蓝字"按钮(自动适配),自由手写硬编码颜色还是会踩坑。

方案:写 adaptAnnouncementColors helper,sanitize 前预处理 HTML,用算法识别
"灰度系 + 极端亮度(>200 或 <80)"的颜色 → strip 整条声明,让继承主题色;
彩色(三通道差 ≥ 30)一律保留,因为它们通常双主题都可读。

判断细节:
- 用 canvas.fillStyle 解析任意 CSS 颜色值(支持 hex/rgb/rgba/hsl/命名色)
- 灰度判断:max(r,g,b) - min(r,g,b) < 30(允许微偏色)
- 亮度判断:(r+g+b)/3 > 200(浅) 或 < 80(深) 双向 strip
- CSS var / currentColor / inherit / transparent 一律保留(用户已经主题适配过)
- 不止 color,background-color / border-* / outline-color 都覆盖

实际验证(用户给的 HTML 例子):
- div color: #e0e0e0 (224,224,224)  → 灰度+亮 → strip ✓
- h2 color: #a78bfa (167,139,250)   → 紫色 → 保留 ✓
- span color: #34d399 (52,211,153)  → 绿色 → 保留 ✓
- hr border #374151 (55,65,81)      → 灰度+暗 → strip ✓

实现:
- 新建 web/src/lib/adaptAnnouncementColors.ts(~110 行,纯 DOMParser+canvas,无依赖)
- AnnouncementModal:sanitize 前调用 adaptAnnouncementColors
- NotificationsPage 展开公告:同上
- SSR 安全:document 不存在时原样返回

smoke 验证:
- 测试公告同时含 #e0e0e0/#374151(灰度,应 strip)+ #a78bfa/#34d399(彩,应留)
- 展开后 page.evaluate 扫 inline style 验证 4 项颜色去留 — 4/4 全过
- announcement-integration-smoke 17/17 (从 13 加 4 项颜色检查)
- v0.20.1-smoke 11/11 + modal-interaction 8/8 + v2-smoke 25/25 + vitest 71/162

UX 影响:
- 超管手写 HTML 用 #e0e0e0 类暗色专用默认色 → 自动 strip,浅深都清晰
- 超管手写 #ff5e5e/#34d399 类彩色 → 保留,两个主题都看得见
- "我自定义了颜色就保留,没自定义按系统主题"语义达成

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:48:46 +08:00
seaislee1209
bd3e80fd58 feat(notification): 消息中心改 accordion 模式 + 跳转按钮 + 公告颜色 CSS var 自适应
用户反馈三点:
1. 公告里"红字"/"蓝字"工具按钮硬编码颜色 #ff4d4f / #00b8e6 在浅色下糊
2. 消息中心列表里公告直接渲染完整 HTML,行被撑得很大
3. 点行就自动 navigate(link_url),用户希望"只看不跳",看完主动决定要不要跳转

改动:

a) SettingsPage 公告编辑器颜色按钮(2 个):
   - 红字: <span style="color:#ff4d4f"> → var(--color-danger)
   - 蓝字: <span style="color:#00b8e6"> → var(--color-primary)
   - 分割线 border-top 颜色 #333 → var(--color-border-card)
   - 三个都改成 CSS var,自动适配浅/深主题
   - title 文案加"(自适应主题)"提示超管

b) NotificationsPage accordion 模式:
   - 加 expandedId: number | null state,始终最多 1 条展开
   - 折叠态:chip + title + 时间 + 一行剥 HTML 后的纯文本预览(stripAndTruncate, 60 字 '…')
   - 展开态:头部 + 下方完整内容(announcement 用 DOMPurify+HTML / 其他 plain) +
            link_url 非空时显示【前往查看】按钮(蓝底白字,带箭头 icon)
   - chevron icon 旋转 0deg/180deg 视觉指示折叠/展开
   - 同 id 再点 → 收起;不同 id → 切换(前一个自动收起)
   - 切页时自动重置 expandedId 为 null

c) 点击行为:
   - handleRowClick(自动跳) → handleToggle(只 markRead + 切 expandedId)
   - 新加 handleJump(url):用户主动点【前往查看】才触发 navigate / window.open(http url)
   - 展开区域 onClick 加 stopPropagation 防误触收起

d) smoke test 更新:
   - 测试公告内容做长用 EXPANDED-ONLY-MARKER 末尾标记,preview 截断后看不到
   - 7.2.0 折叠态 preview 截断验证(marker 不可见)
   - 7.2 展开后 marker 可见
   - 7.3 再点收起 marker 不再可见
   - 用 chip [公告] 文字作为稳定点击锚点(只在头部出现不在展开内容里)

验证:
- typecheck 0 error
- announcement-integration-smoke 13/13(从 10 项扩到 13,加 accordion 路径)
- v0.20.1-smoke 11/11 + v2-smoke 25/25 + modal-interaction 8/8 全过
- vitest 71 fail / 162 pass 与基线一致

GlobalAnnouncementGate 强弹 modal 行为不变(plan §一 7 — 公告强制阅读语义保留)。
重看路径走 sidebar 大铃铛 → 消息中心 → accordion 展开看全文 → 可点【前往查看】跳。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:24:49 +08:00
seaislee1209
e55a6665f2 test+docs(notification): announcement-integration-smoke.mjs 10/10 + plan 归档 + 完成报告
新建 web/test/announcement-integration-smoke.mjs 10 项 E2E:
- 空内容 400 / HTML 发送 200 / fan-out 数 = User 总数
- tudou 拿到未读 / 浏览器自动强弹 modal / 关闭标已读 / 再开不弹
- 消息中心 [公告] chip + HTML 渲染 / 无 console.error

跑通基线对比:
- vitest 71 fail / 162 pass (0 新增回归)
- v2-smoke 25/25 + modal-interaction 8/8 + v0.20.1-smoke 11/11
- 新 announcement-integration-smoke 10/10

归档 plan 文件 docs/todo/通知公告整合.md(v2,本次实施的源 plan)。
写完成报告 docs/todo/通知公告整合-完成报告.md(改动文件清单 + 3 commit hash + 测试结果 + 关键设计决策 + 边缘 case 处理)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:04:43 +08:00
seaislee1209
850acf646e refactor(notification): 删 AnnouncementBanner 废弃文件
AnnouncementBanner 在 v0.12.6 公告改 modal 之后就已经废弃了,
VideoGenerationPage L148 注释明确写"公告已改为弹窗,旧的横幅不再显示",
现在公告整合到 Notification 表后,无任何 import 引用,清理。

同步删 AnnouncementBanner.module.css 配套样式。
typecheck 0 error,smoke 全过。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:59:38 +08:00
seaislee1209
7a503db814 feat(notification): 公告整合进 Notification — fan-out + 强弹 Modal + chip + 删 announcement_enabled 概念
之前公告(QuotaConfig.announcement,v0.12.6)与 v0.20.1 消息中心 UX 重叠 —
两个铃铛 + 两套未读 + 两套入口让用户分不清。整合到统一 Notification 表:

后端:
- apps.notifications Notification.TYPE_CHOICES 加 'announcement'
- 新 endpoint POST /api/v1/admin/announcement/publish (IsSuperAdmin)
  - body { content: HTML 字符串 }
  - 空内容 400 "公告内容不能为空"
  - User.objects.all() (含 is_active=False 封禁用户,解封后能看到历史)
  - bulk_create(batch_size=500) 防大团队 OOM
  - 同步把 content 存档到 QuotaConfig.announcement 作为下次编辑器初始值
  - audit log: settings_update, target=announcement
- 重写 GET /announcement 内部查 Notification 表最新未读
- 重写 POST /announcement/read 标记所有未读公告已读
- endpoint 签名不变保持老前端兼容(返回结构相同)

前端:
- App.tsx 顶层挂 <GlobalAnnouncementGate /> — 任意路由有未读公告强弹 modal
  必须看(关闭遮罩点击也算关闭→标已读),关闭后 60s 内不再弹
- AnnouncementModal 改成纯展示组件: props { content, onClose },不自己 fetch
  HTML 内容用 DOMPurify.sanitize 防 XSS
- 删 VideoGenerationPage 右上角小喇叭 + 旧 AnnouncementModal 自动弹 + 重看路径
  (用户重看走 sidebar 大铃铛 → 消息中心)
- SettingsPage 公告区:
  - 删 announcement_enabled checkbox(不再有"启用/停用"概念,发了就强弹)
  - "保存公告"按钮 → 改 "发送公告" 按钮
  - 二次 confirm "确认发送给所有用户?发送后所有人打开页面会强制看到这条公告,无法撤回"
  - 调 announcementApi.publish (POST /admin/announcement/publish)
- NotificationsPage 每条加 type chip([公告]/[安全]/[额度]/[系统]) 4 色
  announcement type 用 DOMPurify + dangerouslySetInnerHTML 渲染(其他 type 纯文本)
- types/index.ts NotificationType 加 'announcement'

验证:
- 后端 curl 全过:发空 400 / 发 HTML 200 sent_to=21 / tudou GET 拿到未读 / read 后 GET 拿空
- typecheck 0 error
- v0.20.1-smoke 11/11 + modal-interaction 8/8 + v2-smoke 25/25
- vitest 71 fail / 162 pass 与基线 0 新增回归

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:58:38 +08:00
seaislee1209
c54fdda0e8 revert(admin): 撤掉 sticky 翻页 — 用户反馈 sticky 让内容在按钮后透视,不对
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 7m10s
sticky bottom: 0 的语义是"natural 位置在视口底之下时贴底,否则正常坐落"。
当 .content 滚动容器下方还有内容时,翻页按钮浮在视口底部,
表格行从它身后滚过 — 视觉上"翻页按钮下面又有表格内容"非常违和。

恢复成普通 .pagination + padding-bottom 兜底,
依赖批次 I 的根因三件套(100dvh + min-height: 0)解决 Safari 翻页被工具栏遮挡:
- 100dvh 保证 .layout 高度 = 用户实际可见区(不被 Safari 工具栏吃掉)
- min-height: 0 保证 .content 内部能正常 overflow-y: auto 滚动
- padding-bottom: 8px 给按钮一点缓冲

用户期望就是"滚动条能滚到最底,翻页按钮可见可点",而不是"翻页按钮固定不动"。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:42:36 +08:00
seaislee1209
f77d30a4e6 fix(admin): 翻页按钮改 sticky 贴底 — 笔记本小屏不用滚到底就能点
v0.20.1 §I 只修了"能滚到能点到",用户期望是"始终可见不用滚"。
4 个 admin 管理页 .pagination 改 position: sticky + bottom: 0 + 背景遮挡,
翻页按钮固定在 .content 滚动容器视口底部,内容长度无关。

z-index: 10 确保 sticky 时压在表格行之上;background var(--color-bg-page)
覆盖透视下方内容(浅/深主题各自适应)。

涉及 4 个 page CSS:RecordsPage / UsersPage / LoginRecordsPage / AuditLogsPage

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:08:30 +08:00
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