diff --git a/.project.json b/.project.json index ebcc291..e4930e3 100644 --- a/.project.json +++ b/.project.json @@ -45,7 +45,7 @@ "type" : "oauth_app" } ], - "description" : "SKG 信息流广告快速复刻工作台:粘贴 TK 链接或上传视频后点击开始,系统自动下载源视频;下载完成后并行启动音频文案路和视频视觉路。音频路提取原文案\/字幕、中文翻译、讲话人、语速节奏、背景音乐\/环境声\/音效;视觉路自动抽 12 张动作\/节奏参考帧,转换层按真人重构、卡通重构、元素重构、自主描述四个方向生成全新主体 6 视图,再汇合产品素材池、分镜口播和视频候选生成。", + "description" : "SKG 营销内容多人创作平台:默认首页面向公司团队成员的个人隔离创作空间,主路径为文生图、图生图、文生视频、图生视频和营销图文方案生成;每个登录用户只看到自己的任务和结果。任务详情页沉淀参考图、生成图、视频候选、提示词和图文方案,可继续生成、删除和复用。旧 TK 复刻\/一键出片能力保留为高级入口,不再作为默认工作台。", "kind" : "app", "name" : "SKG 营销内容工作台", "ownership" : "company", diff --git a/RULES.md b/RULES.md index bf11ff2..3114da8 100644 --- a/RULES.md +++ b/RULES.md @@ -11,7 +11,7 @@ - 详见 `CLAUDE.md` 立项决策段 + `.memory/plan.md` 七步管线拆解 - 风格:`04-Dark-Gallery-Ambient`(路径:`~/Projects/research/20260305-网页风格库/04-Dark-Gallery-Ambient.md`) - 第一冲刺:步骤 1-4(下载 / 拆轨 / 关键帧 / ASR+翻译) -- 当前产品方向(2026-05-20 再确认):信息流广告快速复刻默认进入“三字段候选生成”工作流。主界面为“左侧素材输入列 + 右侧信息流复刻工作表”;工作台已取消 1800x1000 固定画布和整页缩放,改为正常流式桌面容器,宽度跟随浏览器展开,只保留 1280px 最低操作宽度防止核心表格被压烂,不再通过应用层 `zoom` 把整页缩小导致文字发虚。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动两条路:音频文案路自动识别中文、英文和其他多语言原音频文案/字幕,统一补齐中文镜像,并分析讲话人、语速节奏、背景音乐/环境声/音效;视频视觉路自动抽取参考帧。源视频工作区主体链路改为“上方参考帧池 + 转换层、下方主体元素结果栏”:参考帧池竖向排列;转换层是轻量对话式生图确认区,参考图可通过左侧缩略图 `+`、参考帧拖拽、胶片拖拽或本地图片拖入进入转换层,用户选择 GPT/Gemini 套件后先分析参考图;识别结果里的特征 chip 只作为“保留元素”本地选择,点亮=保留、再点取消,点击不立即请求模型,随下一条发送消息提交;用户再在下方发送区发送复刻/创新/卡通和画面要求,界面只保留生成要求输入框、张数控件和提示词就绪状态,不展示当前要求摘要、保留元素副本、收起记录计数或重复模型确认话术,生成数量通过发送区旁边的张数控件控制;后端返回英文出图 prompt 后不再自动弹窗,发送区主按钮直接切换为“确认生成 N 张”,用户点击才生成对应数量的统一多角度套图。主体元素结果栏在转换层下方横向展示套图输出、文件夹分组、单张重生、删除和 hover 预览,空态只保留紧凑提示,不再挤占右侧整列。旧下方“相似主体 / 主体模板库”不再作为主路径。波形下方的画面胶片只是临时预览,点击只跳转原视频时间点,双击或拖进参考帧池才正式加入关键帧,已加入的胶片直接显示“已添加”。产品图上传后独立形成产品资产包,自动识别视角/结构/比例并补缺角度。分镜工作台按逐句时间轴默认只露“文案 / 场景一句话 / 人物+产品+动作”,产品素材池、批量控制、三字段、视频候选和高级区都必须可折叠;视频候选无内容时默认不占大面积,有候选时默认只显示迷你缩略条,展开后才显示 4-grid。单条默认生成 4 个视频候选,顶部支持整片批量生成候选;首尾帧、视觉规划、产品出现方式和旧 6 字段保留在“高级”抽屉与后端 quick-plan 自动展开中,不能再作为客户默认闸门。 +- 当前产品方向(2026-05-24 重设计):默认首页彻底从“信息流广告复刻管线”切换为多人通用的 SKG 营销内容创作平台,服务约 6 名公司成员同时使用。主路径是文生图、图生图、文生视频、图生视频和营销图文方案生成;用户登录后只看到自己的任务、结果和详情页,继续沿用后端 owner 隔离。首页结构为左侧创作入口 + 参考图 + 我的任务,中间创作台,右侧当前任务结果;任务详情页固定为 `/detail/?job=`,沉淀参考图、生成图、视频候选、提示词和图文方案,并支持继续生成、删除和复用。旧 TK 复刻工作台和 Agent Cut 一键出片保留为高级入口,不再作为默认工作台或默认理解框架。 ## 部署事实 - 平台:VPS `76.13.31.179`(Ubuntu 24.04 / Docker Compose / Coolify Traefik) diff --git a/docs/source-analysis.html b/docs/source-analysis.html index 5c97d27..31add80 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -570,19 +570,19 @@

业务管线

-

2026-05-23 产品重置:默认首页已从“TK 信息流复刻管线”改为 SKG 营销内容工作台 直接创作台。主路径现在是三件事:一图/文字生图、一图/文字生视频、自动写短视频文案并把文案提示词送去生图或生视频。下面旧 TK 复刻管线作为最后版本保留在代码、数据快照和 git tag 中,不再是默认入口。

+

2026-05-24 完整重设计:默认首页已从“TK 信息流复刻 / 三字段分镜管线”推倒,改为面向公司约 6 名成员同时使用的 SKG 营销内容多人创作平台。主路径是文生图、图生图、文生视频、图生视频和营销图文方案生成;每个登录用户只看到自己的任务和详情页结果。旧 TK 复刻工作台与 Agent Cut 一键出片保留为高级入口,不再作为默认工作台。

-

当前产品方向已收窄为“信息流广告快速复刻”:主界面左侧是拉满工作台可用高度的 65px 胶囊工具条,鼠标移入或键盘聚焦会从侧边滑出素材输入面板,点击素材任务按钮可固定展开,右侧主画布是信息流复刻工作表;工作台已取消 1800x1000 固定画布和整页 zoom 缩放,改为正常流式桌面容器,宽度跟随浏览器展开,只保留 1280px 最低操作宽度防止核心表格被压烂,避免小数缩放造成文字发虚和比例失衡。后台仍按 01-09 流程顺序计算素材任务、源视频、音频文案、抽帧、主体资产、产品资产、分镜文案、三字段规划和视频候选这些状态,但这些判断不再默认显现在工作区顶部,避免状态提示挤占首屏操作空间。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动音频文案路和视频视觉路。音频文案路自动识别中文、英文和其他多语言原音频文案/字幕,统一补齐中文镜像,分析讲话人、语速节奏、背景音乐/环境声/音效,并为后续新口播和分镜文案提供时间轴;视频视觉路同步抽取参考帧。源视频工作区主体链路是“上方参考帧池 + 转换层、下方主体元素结果栏”:参考帧池只作为竖向原始参考;转换层改为轻量对话式生图确认区,参考图可通过左侧缩略图 +、参考帧拖拽、胶片拖拽或本地图片拖入进入转换层,用户选择 GPT/Gemini 套件后先分析参考图,再在下方消息输入区发送复刻、创新、卡通、数量和画面要求;系统返回英文出图 prompt 后不再自动弹窗,发送区主按钮直接切换为“确认生成 N 张”,用户点击后才调用主体生成并把结果送到下方主体元素结果栏。主体元素结果栏保留已有套图输出、文件夹分组、单张重生、删除和 hover 预览逻辑,空态只保留紧凑提示,不再占据右侧整列。旧下方主体模板库不再作为主路径。波形下方的画面胶片由前端临时从源视频截取,密度可调,点击只跳转原视频时间点,双击或拖入参考帧池才调用手动抽帧接口正式写入关键帧;已写入的胶片显示“已添加”,相同素材、相同密度和时长下会复用内存缓存,避免返回页面时重复扫视频。产品图上传后独立形成产品资产包:自动识别视角、左右/上下/内外侧、结构点、比例和风险,并补缺角度。最终分镜规划按逐句时间轴把文案、主体元素和产品资产汇合;每条分镜默认是左侧“文案 / 场景一句话 / 人物+产品+动作”三字段、右侧横向视频候选轨。客户可直接改中文镜像,前端会调用改写/翻译链路自动优化对应英文主值;单条和整片都可选择生成数量,整片按行排队提交。视频候选提交后立即写入当前任务,完成后自动回填 mp4,不需要用户另点“保存”;候选卡的普通点击只用于打开预览,右上角提供显式下载按钮;候选选择不再作为默认点击语义。首尾帧、视觉规划、产品出现方式等细节保留在高级抽屉和后端自动展开逻辑里,不再作为客户默认闸门。

+

当前默认业务管线是“个人隔离任务 → 选择创作入口 → 上传参考图或使用空白任务 → 生成图片 / 视频 / 图文方案 → 进入详情页继续沉淀”。首页左侧固定为创作入口、参考图和“我的任务”;中间是创作台,按产品、人群、平台、时长、语气和创作要求发起生成;右侧展示当前任务的图片、视频和图文结果,并提供 /detail/?job=<id> 详情页入口。详情页读取同一个 Job,展示参考图、所有生成图、视频候选、提示词和本页生成的营销图文方案,并支持继续生成、删除和复制。底层仍复用既有 /creative/jobs/image/creative/copy/jobs/{id}/frames/{idx}/generate/jobs/{id}/frames/{idx}/storyboard/video;多人互不影响依赖后端 owner_id 和飞书 / 备用登录会话隔离。旧信息流复刻链路仍保留在 web/components/ad-recreation-board.tsx/agent/,但只作为高级能力。

-
01

素材输入

有当前素材任务即通过;输入框只负责创建或切换任务。

-
02

源视频下载

job.video_url 存在即通过;created/downloading 视为运行中。公开视频默认不带 cookies 下载;只有 TikTok 明确要求登录态时才配置 YTDLP_COOKIES_FILE,生产容器禁止使用 YTDLP_COOKIES_FROM_BROWSER=chrome

-
03

音频文案

audio_script.source_texttranscript 逐句时间轴有内容即通过。

-
04

抽帧参考

job.frames.length > 0 即通过;参考帧只做主体重构证据。

-
05

主体重构

关键帧里存在 subject_assets 即通过;真人、卡通、元素和有文字的自主描述走参考创新,自主描述空文本走源形象锁定。

-
06

产品素材池

product_refs 有记录即通过;不限量上传,后续按分镜最多挑 6 张。

-
07

分镜文案

逐句时间轴生成后进入分镜;新口播可单段或整片改写。

-
08

三字段规划

客户默认只编辑文案、场景一句话、人物+产品+动作;高级抽屉保留首尾帧和 6 字段。

-
09

视频候选

每行右侧直接显示横向视频轨,数量可选;新候选一直向右追加,不再用 4-grid 撑高行。

+
01

个人任务

GET /jobs 按当前登录用户过滤;旧无 owner 任务只对备用账号可见。

+
02

创作入口

首页四入口:文生图、图生图、文生视频、图生视频;参考图入口要求已有本地图或任务首帧。

+
03

参考图 / 空白任务

POST /creative/jobs/image 创建 0 号关键帧;有图则保存参考图,无图则创建空白图源。

+
04

生成图片

generateImage 复用 /frames/0/generate;图生图传 mode=edit,文生图传 mode=text

+
05

生成视频

generateStoryboardVideo 复用 0 号关键帧作为首帧/参考图,视频任务排队后写入 generated_videos

+
06

营销图文

POST /creative/copy 返回 hook、脚本、caption、image prompt 和 video prompt,可回填到图/视频入口。

+
07

结果沉淀

首页右侧展示当前任务最新图片、视频和图文;所有图片/视频缩略图继续复用 MediaAssetTile

+
08

详情页

/detail/?job=<id> 展示参考图、全量生成图、视频候选、提示词和营销图文,并支持继续生成。

+
09

高级复刻

AdRecreationBoard/agent/ 作为高级入口保留,不再是默认路径。

@@ -595,7 +595,8 @@ web/next.config.mjsNext.js 构建配置:静态导出、图片不走优化、禁用开发环境左下角 Next Dev Indicator,并移除 Next 16 已不支持的 eslint 顶层配置,避免本地 dev 出现配置 Issue 提示。 web/app/globals.css全局主题变量、登录页视觉样式、信息流工作台玻璃拟态 token、ReactFlow 样式引用,以及本地开发态 nextjs-portal 遮挡隐藏规则。工作台在 skg-board-theme 内按 Figma 本地 MCP 参考改成黑灰玻璃系统:深灰背景、#383838 胶囊侧栏、rgba(255,255,255,.1) 玻璃面、backdrop-filter: blur(5px)20px 圆角、10px 10px 10px rgba(0,0,0,.3) 阴影和绿黄状态色;新增 skg-board-shellskg-board-railskg-glass-cardskg-glass-card--flatskg-status-orb 等样式。侧栏改为跟随视口拉满工作台可用高度的悬停胶囊,桌面最小 600px,展开时在同一侧栏内承载素材输入抽屉。明暗主题已分开维护 shell、panel、glass、stat、action 和音频波形 token;暗色压低灰雾和面板底色,明亮模式改为暖白工作台,避免指标卡、按钮和波形继续残留黑底/白线;顶部指标卡增加紫、黄绿、琥珀、青绿、绿色光斑变量,接近原版多色玻璃卡效果。主/次按钮、指标卡和空状态继续走统一类,避免各板块散写不同玻璃效果。 - web/app/page.tsx当前默认首页:SKG 营销内容工作台 直接创作台。页面围绕“生视频 / 生图 / 写文案”三种模式组织,左侧是模式切换、参考图上传和最近任务,中间是产品、人群、时长和创作要求输入,右侧展示图片、视频和文案结果。图片/视频缩略图统一复用 MediaAssetTile,支持顶层 hover 预览和删除;文案结果可一键把生成的 image/video prompt 填回生图或生视频模式。旧 TK 复刻工作台组件仍保留在 web/components/ad-recreation-board.tsx,但不再作为默认首页渲染。 + web/app/page.tsx当前默认首页:多人通用的 SKG 营销内容工作台。页面围绕文生图、图生图、文生视频、图生视频四个入口组织,左侧是创作入口、参考图上传和“我的任务”,中间是产品、人群、平台、时长、语气和创作要求,右侧展示当前任务图片、视频和营销图文结果,并可打开详情页。图片/视频缩略图统一复用 MediaAssetTile,支持顶层 hover 预览和删除;文案结果可一键把 image/video prompt 回填到图或视频入口。旧 TK 复刻工作台组件仍保留在 web/components/ad-recreation-board.tsx,但不再作为默认首页渲染。 + web/app/detail/page.tsx任务详情页:静态导出路由 /detail/?job=<id>,通过 query 读取 job id,调用 getJob 恢复同一任务。页面展示参考图、全部生成图、视频候选、营销图文方案和历史提示词,可继续调用 generateImagegenerateStoryboardVideogenerateCreativeCopy,并支持删除图片/视频。该页继续依赖后端 owner 过滤,用户不能通过切换 URL 读取别人的任务。 web/app/agent/page.tsx新增一键出片终端页:只保留 TikTok 链接、产品图上传、实时 Agent Terminal 和最终成片播放器;通过 POST /agent-runs 创建受限后台状态机任务,通过 GET /agent-runs/{id} 轮询日志、进度、审片图和最终 mp4。该页不替代旧工作台深度编辑能力,只承接“用户只看成品”的快速出片主路径。 web/components/ad-recreation-board.tsx信息流广告复刻工作表:外壳按 Figma “Dashboard Glassmorphism”参考整体改为黑灰玻璃工作台,WorkbenchRail 默认收起为拉满工作台可用高度的 65px 胶囊工具条,只保留真实动作入口:素材任务、资源库和主题切换;鼠标移入或键盘聚焦侧栏时,skg-board-rail 切换 is-open 并从左侧展开 320px 素材输入抽屉,点击素材任务按钮可固定展开。顶部从登录页式 brand strip 改为轻量生产控制条,左侧显示 未来健康 · 营销内容工作台、主标题 营销内容工作台 和副标题 信息流广告复刻生产,右侧保留素材/当前/视频/文案段/背景音指标,并用紫、黄绿、琥珀、青绿、绿色光斑卡片增强原版玻璃拟态的颜色层次。主内容只保留源视频拆解工作区,素材输入的数据流、接口、模型调用和状态推导不变。工作台外层已取消 1800x1000 固定基准画布、ResizeObserver 档位计算和 CSS zoom 整页缩放,改为正常流式桌面容器:min-height: 100vhwidth: 100%max-width: 1920px,并保留 min-width: 1280px 作为最低操作宽度;核心列宽不再被整体缩放,文字、图标和边线由浏览器原生字号渲染,避免小数缩放导致发虚。buildWorkflowSteps 仍统一生成 01-09 流程顺序、状态和判定依据,WorkflowStepBadge / PipelineLane / 分镜列标题也继续共用同一套编号;但完整 WorkflowOrderBar、右侧素材/视频/音频/文案/参考帧需求 chips、文案依据下拉和“音频文案、抽帧参考、主体重构、产品素材池”四个状态条不再默认渲染在工作区顶部。侧边素材输入面板只负责链接/上传和任务切换,不再重复放横版原视频预览;主画布源视频工作区直接进入核心操作。讲话人、节奏和背景音分析仍写入 AudioScript,但不再作为“音频解析结果”卡片默认渲染;源视频工作区撤销右上“布局调节”临时面板,不再读取或写入 localStorage["skg-source-workspace-layout:v1"];当前固定为左侧原视频列 380px、9:16 视频高 500px、逐句时间轴最大高 360px、参考帧池 140px、主体空态 78px;转换层不再固定拉长,按内容自然高度显示,内容过多时最多到 560px 后在自身区域内滚动;上方是按 9:16 显示的竖版原视频播放器,播放器内覆盖“当前点抽帧”,按当前播放秒数手动补参考帧,播放器下方是逐句时间轴,英文和中文都最多显示两行;右侧上方是无标题的波形与切点参考框,下方主体链路改为上方参考帧池 + 转换层、下方主体元素结果栏。音频波形用参考图式的连续灰色包络显示响度、停顿和密集爆点,并通过 skg-audio-waveform 读取明暗主题变量,避免明亮模式继续使用黑底/白色波形;顶部把低/中/高密度按钮和当前播放秒数、总时长、鼠标指针停点秒数直接放在波形上方。视频播放时通过 requestAnimationFrame 平滑驱动波形播放线,同时同步高亮并滚动当前句;点击音频波形或字幕行会跳转原视频时间。音频波形下方同框渲染无标题的 TimelineFilmstrip 临时画面胶片,前端按低/中/高密度从源视频 canvas 截取预览缩略图,并按 frame.time / duration 的百分比定位到和波形同一条时间轴上;波形与胶片之间不显示分隔横线,胶片轨道贴近波形,缩略图轻微上下错落并倾斜重叠排列,hover 时用同一张胶片卡在原位置生成固定顶层克隆,约 4.8 倍放大并自动限制在视口内,避免被工作区、滚动容器或相邻面板遮挡;单击胶片只跳转视频时间点,不写入任务数据,双击胶片或拖进参考帧池时才调用手动抽帧并正式加入 job.frames,已加入的胶片显示“已添加”;胶片预览按 job、视频、密度和时长缓存,未切换低/中/高时返回页面不重新扫视频。参考帧池的主入口是“自动抽帧 12 张”,一键按动作峰值目标重新抽取 12 张源视频参考帧,优先抓手势、表情变化、节奏点和镜头变化;缩略图按竖版完整比例显示不裁切,点选状态直接叠在参考帧池缩略图上,鼠标停留会通过固定浮层放大展示完整帧。转换层改为轻量对话式生图确认区并拿到主操作宽度:左侧参考帧可点 + 或直接拖入转换层,本地图片拖入会通过 uploadReferenceFrame 保存为参考帧;转换层上方是参考输入区,下方不再显示当前要求摘要、保留元素副本或对话记录计数,只保留带张数控件的“发送消息”输入 composer;模型确认类回复不再逐条展示,生成英文 prompt 后发送区主按钮直接切换为“确认生成 N 张”,点击后才调用主体套图生成。主体元素结果栏在转换层下方,空态只占紧凑提示;有结果时按每次生成的套图文件夹显示,左侧横向展示当前套图,右侧切换套图包,保留单张重生和删除;缩略图上提供“重新生成这一张”和“删除这一张”,单张重生会用 replace_views=true 替换同一视角。前端对卡通重构传 subject_style=cartoon_subject,其他方向传 subject_style=source_actor;形象锁定或自主描述空文本可走 reconstruction_mode=same,其他参考创新走 similar 并把参考帧作为 /images/edits 的 image refs 一起提交。主体生成完成后会形成 subject_consensus_brief。音频结果下方是信息流复刻分镜工作台:顶部产品参考区是“同一产品素材池”,不限量上传产品图,不做不同产品身份判断;上传原图推荐长边 1200-2000px、短边至少 600px,但后端会统一生成最长边 1600px、JPEG 92 的 AI 工作副本,并回显尺寸、自动转换和风险标注;上传后按“套在脖子上的 U 形肩颈按摩仪”进行同一产品批量识别,左/右按佩戴者身体左右、上/下按佩戴方向,额外标注内外侧、开口方向、局部结构点、背景类型、用途标签、生成风险和备注,用户只检查备注,鼠标悬停通过固定浮层显示大图预览,能盖过滚动容器和分镜框架;缺视角补图失败时保留重试入口。脚本区在分镜行上方提供“作者想法”和“整片改写”,每行新口播文案可直接编辑并可单段 AI 改写。每条音频分镜默认是左侧三字段、右侧横向视频候选轨;高级区仍保留首尾帧 prompt、产品出现方式和旧 6 字段。ModelTrace 会在音频解析、产品识别/补图、主体重构视图包、脚本改写等入口旁直接展示模型名;生图入口会显示 gpt-image-2 / gemini-3-pro-image-preview 链路和短时熔断规则,点击后用固定浮层展示模型链路、输入输出和回退逻辑。旧分镜卡、抽帧控制和视频生成组件仍保留在文件里,但当前主路径不渲染。 SourceSubjectPipeline源视频工作区主体管线主路径:上方是竖向 参考帧池 和宽幅 转换层,下方是 主体元素 结果栏。参考帧池固定 140px,转换层不再固定高度,按内容自然显示并以 560px 为最大高度,超出后在自身区域内滚动;主体空态固定为 78px 紧凑提示。参考帧池保留自动 12 张、胶片拖入正式成帧、点击勾选和删除;参考帧缩略图保持小尺寸固定宽度、aspect-[9/16]object-contain 显示,hover 预览通过 MediaAssetTile 的左侧紧凑浮层显示,并新增 + 操作把参考帧送入转换层。转换层是轻量对话式生图确认区:顶部选择 GPT 套件或 Gemini 套件,参考输入区支持左侧 +、拖拽参考帧、胶片拖入和本地图片拖拽上传(上传图会写入 job.frames),下方固定为带张数控件的“发送消息”输入 composer,不再重复显示当前要求摘要、保留元素副本或收起记录计数;模型确认类回复不再逐条显示,复刻、参考创意换人物、卡通风格和人物占比等常用意图也不再显示为独立快捷 chip;识别结果里的特征 chip 是“保留元素”选择,点亮表示随下一条消息提交给 subject-agent/message,再次点击取消,清空按钮一次性取消全部,单次点击不再直接请求模型;subject-agent/message 返回英文 generation_prompt_en 后不再自动弹窗,标题右侧显示“提示词就绪”,底部主按钮从“发送消息”切换为“确认生成 N 张”;用户继续输入会更新需求,点击确认生成才调用 generateSubjectAssets。后端会为每次主体套图注入同一份 pack bible:参考创新模式锁定同一个全新主体和同一套服装,源形象锁定模式锁定参考帧里的可见主体、体态、发型、服装和配色;后处理会裁出白底主体并允许放大到画布高度上限约 96%,实测典型主体有效高度约 90%,避免模型生成“小人 + 大白边”。主体元素结果栏按每次生成的 pack_id 组织成“套图文件夹”:左侧横向展开当前选中套图,右侧显示可滚动的套图包列表;同一方向可保留多套,生成中按 pack 显示 2/6 这类进度,单张完成就替换对应占位卡;空态只显示紧凑提示,不再占右侧整列。缩略图复用 MediaAssetTile,支持 hover 放大、单张重生和删除。旧下方 SourceReferenceBuildPanel 不再主路径渲染。 @@ -640,13 +641,14 @@
当前前端主链路:
-web/app/page.tsx
-  -> SKG 营销内容工作台三模式创作台:生视频 / 生图 / 写文案
+	web/app/page.tsx
+	  -> SKG 营销内容工作台四入口创作台:文生图 / 图生图 / 文生视频 / 图生视频
   -> 参考图上传或空白任务:POST /creative/jobs/image → 生成只有 0 号关键帧的 Job
   -> 生图:generateImage(job.id, 0, { prompt, mode: edit/text }) → jobs/<jobId>/gen
   -> 生视频:generateStoryboardVideo(job.id, 0, { prompt, first_image: keyframe 0, duration }) → jobs/<jobId>/storyboard_videos
-  -> 写文案:POST /creative/copy → 返回 hook、script、image_prompt_en、video_prompt_en → 可一键填回生图或生视频
-  -> 最近任务:GET /jobs?limit=8 → 只读取当前登录用户可见的新创作任务;历史无 owner 旧任务只对备用账号可见
+	  -> 营销图文:POST /creative/copy → 返回 hook、script、image_prompt_en、video_prompt_en → 可一键填回生图或生视频
+	  -> 最近任务:GET /jobs?limit=12 → 只读取当前登录用户可见的新创作任务;历史无 owner 旧任务只对备用账号可见
+	  -> 任务详情页:web/app/detail/page.tsx?job=<id> → getJob → 展示参考图、生成图、视频、提示词、图文方案 → 可继续生成 / 删除 / 复制
 
 旧版 TK 复刻链路(最后版本保留):
 web/app/page.tsx
@@ -673,7 +675,12 @@ api/main.py
           
你看到的区域SKG 营销内容工作台首页
主要源码web/app/page.tsx;前端 API client 在 web/lib/api.ts;轻量创作后端在 api/main.py/creative/jobs/image/creative/copy,实际图片和视频生成继续复用 /jobs/{id}/frames/{idx}/generate/jobs/{id}/frames/{idx}/storyboard/video
-
适合怎么描述“首页模式、参考图上传、创作要求、文案结果如何回填到生图/生视频、结果区缩略图/视频预览/删除、最近任务恢复”。
+
适合怎么描述“首页四入口、参考图上传、我的任务、创作要求、图文方案如何回填到生图/生视频、右侧结果缩略图/视频预览/删除、进入详情页”。
+
+
+
你看到的区域任务详情页
+
主要源码web/app/detail/page.tsx;通过 /detail/?job=<id> 读取任务;复用 getJobgenerateImagegenerateStoryboardVideogenerateCreativeCopydeleteGeneratedImagedeleteGeneratedVideo
+
适合怎么描述“某个任务里参考图、生成图、视频候选、提示词、营销图文方案怎么沉淀,继续生成或删除入口在哪里”。
你看到的区域一分钟二创出片终端
@@ -682,8 +689,8 @@ api/main.py
你看到的区域信息流广告复刻工作表
-
主要源码AdRecreationBoard in web/components/ad-recreation-board.tsx;品牌 token、暖光背景、主/次按钮、stat 卡片和空状态角色样式在 web/app/globals.css;状态、轮询和接口回写仍在 web/app/page.tsx
-
适合怎么描述“登录页进工作台后的品牌条、米白主按钮、金色选中态、明亮/暗色模式、素材输入列、开始分析后的自动下载、音频文案路和视频视觉路怎样并行、后台状态哪些需要隐藏或露出”。
+
主要源码旧高级能力保留在 AdRecreationBoard in web/components/ad-recreation-board.tsx;品牌 token、暖光背景、主/次按钮、stat 卡片和空状态角色样式在 web/app/globals.css。当前默认首页不再挂载该组件。
+
适合怎么描述“需要恢复或改造旧复刻台时,再明确说信息流复刻工作表、素材输入列、源视频工作区、转换层、分镜工作台”。
你看到的区域源视频工作区
@@ -1174,6 +1181,19 @@ ProductRefStateItem {

变更记录

这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。

+
+
+

2026-05-24 · 默认首页完整重设计为多人创作平台

+ UI + Product + Docs +
+
+

问题:前一版已经从复刻管线降到直接创作台,但仍是三模式表单,缺少团队成员各自工作、四类主入口和任务详情沉淀。用户确认这次要完全推倒旧复刻优先设计,平台先服务多人快速生图、生视频和图文创作。

+

改动:重写 web/app/page.tsx 为多人创作首页:左侧四入口(文生图、图生图、文生视频、图生视频)、参考图和我的任务,中间创作台,右侧当前任务图片/视频/图文结果。新增 web/app/detail/page.tsx,静态路由 /detail/?job=<id> 按 job 展示参考图、全量生成图、视频候选、营销图文方案和历史提示词,并支持继续生成、删除和复制。进入重设计前创建本地数据备份 .backups/pre-redesign-20260524-012047、源码快照包、dirty patch,并把 Git 标签 backup/pre-redesign-20260524-012047 推到 Gitea。

+

影响:默认产品使用方式改为“选择入口 → 填写要求 → 生成 → 详情页沉淀”;旧 AdRecreationBoard/agent/ 只作为高级复刻能力保留。.project.jsonRULES.md 已同步多人创作平台定位。

+
+

2026-05-24 · 项目更名并接入飞书免登录与多用户隔离

diff --git a/web/app/detail/page.tsx b/web/app/detail/page.tsx new file mode 100644 index 0000000..0540569 --- /dev/null +++ b/web/app/detail/page.tsx @@ -0,0 +1,543 @@ +"use client" + +import { useCallback, useEffect, useMemo, useState } from "react" +import { + ArrowLeft, + Clapperboard, + Copy, + FileText, + Image as ImageIcon, + Loader2, + Play, + RefreshCw, + Sparkles, + Wand2, +} from "lucide-react" +import { Toaster, toast } from "sonner" +import { MediaAssetTile } from "@/components/media-asset-tile" +import { + apiAssetUrl, + deleteGeneratedImage, + deleteGeneratedVideo, + generateCreativeCopy, + generateImage, + generateStoryboardVideo, + getJob, + listJobs, + type CreativeCopyVariant, + type GeneratedImage, + type GeneratedVideo, + type Job, + type JobSummary, +} from "@/lib/api" + +type ImageItem = GeneratedImage & { frameIdx: number } +type BusyTask = "image" | "video" | "copy" | "load" | null + +function cx(...items: Array) { + return items.filter(Boolean).join(" ") +} + +function jobTitle(item: Job | JobSummary | null) { + if (!item) return "未选择任务" + const raw = item.url.replace(/^creative:\/\//, "").replace(/^upload:\/\//, "") + return raw || item.id +} + +function sourceFrameSrc(job: Job | null) { + return job?.frames?.[0]?.url ? apiAssetUrl(job.frames[0].url) : "" +} + +function videoSrc(job: Job, video: GeneratedVideo) { + return apiAssetUrl(video.url || `/jobs/${job.id}/storyboard-videos/${video.id}.mp4`) +} + +function imageItems(job: Job | null): ImageItem[] { + if (!job) return [] + return job.frames + .flatMap((frame) => (frame.generated_images ?? []).map((image) => ({ ...image, frameIdx: frame.index }))) + .sort((a, b) => b.created_at - a.created_at) +} + +function createdLabel(ts?: number) { + if (!ts) return "" + return new Date(ts * 1000).toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }) +} + +export default function DetailPage() { + const [jobId, setJobId] = useState("") + const [job, setJob] = useState(null) + const [recentJobs, setRecentJobs] = useState([]) + const [prompt, setPrompt] = useState("") + const [product, setProduct] = useState("SKG 颈部按摩仪") + const [audience, setAudience] = useState("久坐办公、低头刷手机的人群") + const [tone, setTone] = useState("真实、直接、有购买理由") + const [seconds, setSeconds] = useState(12) + const [copyVariants, setCopyVariants] = useState([]) + const [busy, setBusy] = useState(null) + const [error, setError] = useState("") + + const images = useMemo(() => imageItems(job), [job]) + const videos = useMemo(() => job?.generated_videos ?? [], [job]) + const runningVideo = videos.some((item) => item.status === "queued" || item.status === "in_progress") + + const loadJob = useCallback(async (id: string) => { + if (!id) return + setBusy("load") + setError("") + try { + const loaded = await getJob(id) + setJob(loaded) + setJobId(id) + window.history.replaceState(null, "", `/detail/?job=${id}`) + } catch (e) { + const message = e instanceof Error ? e.message : "读取任务失败" + setError(message) + toast.error(message) + } finally { + setBusy(null) + } + }, []) + + const refreshJobs = useCallback(async () => { + try { + setRecentJobs(await listJobs(20)) + } catch { + setRecentJobs([]) + } + }, []) + + useEffect(() => { + const id = new URLSearchParams(window.location.search).get("job") || "" + setJobId(id) + refreshJobs() + if (id) loadJob(id) + }, [loadJob, refreshJobs]) + + useEffect(() => { + if (!job || !runningVideo) return + const timer = window.setInterval(async () => { + try { + setJob(await getJob(job.id)) + } catch { + window.clearInterval(timer) + } + }, 2600) + return () => window.clearInterval(timer) + }, [job, runningVideo]) + + const requireJobAndPrompt = () => { + if (!job) { + toast.error("先选择任务") + return false + } + if (!prompt.trim()) { + toast.error("先写生成要求") + return false + } + return true + } + + const runImage = async () => { + if (!requireJobAndPrompt() || !job) return + setBusy("image") + setError("") + try { + setJob(await generateImage(job.id, 0, { + prompt: `${prompt.trim()}\n\nProduct: ${product}. Audience: ${audience}. Tone: ${tone}.`, + mode: sourceFrameSrc(job) ? "edit" : "text", + })) + toast.success("图片已生成") + } catch (e) { + const message = e instanceof Error ? e.message : "生图失败" + setError(message) + toast.error(message) + } finally { + setBusy(null) + } + } + + const runVideo = async () => { + if (!requireJobAndPrompt() || !job) return + setBusy("video") + setError("") + try { + setJob(await generateStoryboardVideo(job.id, 0, { + prompt: `${prompt.trim()}\n\nProduct: ${product}. Audience: ${audience}. Tone: ${tone}. Keep the SKG product shape stable and visible.`, + duration: seconds, + count: 1, + first_image: { kind: "keyframe", frame_idx: 0 }, + size: "720x1280", + })) + toast.success("视频已提交") + } catch (e) { + const message = e instanceof Error ? e.message : "生视频失败" + setError(message) + toast.error(message) + } finally { + setBusy(null) + } + } + + const runCopy = async () => { + const goal = prompt.trim() || `${product} ${audience}` + setBusy("copy") + setError("") + try { + const result = await generateCreativeCopy({ goal, product, audience, tone, seconds }) + setCopyVariants(result.variants) + toast.success("图文方案已生成") + } catch (e) { + const message = e instanceof Error ? e.message : "写文案失败" + setError(message) + toast.error(message) + } finally { + setBusy(null) + } + } + + const deleteImage = async (image: ImageItem) => { + if (!job) return + try { + setJob(await deleteGeneratedImage(job.id, image.frameIdx, image.id)) + toast.success("图片已删除") + } catch (e) { + toast.error(e instanceof Error ? e.message : "删除失败") + } + } + + const deleteVideo = async (video: GeneratedVideo) => { + if (!job) return + try { + setJob(await deleteGeneratedVideo(job.id, video.id)) + toast.success("视频已删除") + } catch (e) { + toast.error(e instanceof Error ? e.message : "删除失败") + } + } + + const copyText = async (text: string) => { + try { + await navigator.clipboard.writeText(text) + toast.success("已复制") + } catch { + toast.error("复制失败") + } + } + + return ( +
+ +
+
+
+ + + +
+

任务详情

+

{jobTitle(job)}

+
+
+ +
+ +
+ + +
+ {job ? ( +
+
+
+ +

生成图片

+
+ {images.length ? ( +
+ {images.map((image) => ( +
+ deleteImage(image)} + /> +
{createdLabel(image.created_at)}
+
+ ))} +
+ ) : ( +
暂无图片结果
+ )} +
+ +
+
+ +

生成视频

+
+ {videos.length ? ( +
+ {videos.map((video) => ( +
+ deleteVideo(video)} + /> +
+
+
+ {video.error ?
{video.error}
: null} +
+ ))} +
+ ) : ( +
暂无视频结果
+ )} +
+ +
+
+ +

营销图文

+
+ {copyVariants.length ? ( +
+ {copyVariants.map((variant, index) => ( +
+
+

{variant.title || `方案 ${index + 1}`}

+ +
+

{variant.hook_zh}

+
{variant.script_zh}
+
+ ))} +
+ ) : ( +
暂无图文方案
+ )} +
+
+ ) : ( +
+ 请选择任务 +
+ )} +
+ +