From 78bd294d57366550b4e36edb730167e4ba4488dc Mon Sep 17 00:00:00 2001 From: kang Date: Mon, 18 May 2026 16:51:34 +0800 Subject: [PATCH] style: add board light mode --- docs/source-analysis.html | 17 +++- web/app/globals.css | 129 +++++++++++++++++++++++++ web/app/page.tsx | 6 -- web/components/ad-recreation-board.tsx | 52 ++++++++-- 4 files changed, 188 insertions(+), 16 deletions(-) diff --git a/docs/source-analysis.html b/docs/source-analysis.html index 097ae7e..2bc379c 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -591,9 +591,10 @@ - - + + + @@ -1023,6 +1024,18 @@ ProductRefStateItem {

变更记录

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

+
+
+

2026-05-18 · 主工作台增加明亮模式

+ UI + Style +
+
+

问题:工作台已对齐登录页暗色质感,但长时间编辑分镜、产品图和首尾帧时,需要一个更亮的检查环境,方便看表格、缩略图和文案密度。

+

改动:AdRecreationBoard 顶部增加“明亮/暗色”切换按钮,使用 localStorage["skg-board-theme"] 记住用户选择;globals.css 增加 skg-board-theme--light,把暗场玻璃面板映射为暖白底、浅绿金色光感、深色文本和浅色边框,保留登录页同源的材质感。

+

影响:只改变工作台视觉模式,不改变素材下载、音频解析、抽帧、主体模板、产品素材池、首尾帧或模型链路;web/app/page.tsx 同步移除旧全局浮动主题按钮,避免右下角出现第二套不相关的主题入口。后续新增图片/视频板块仍应复用同一套媒体悬停放大和删除逻辑。

+
+

2026-05-18 · 主工作台视觉质感对齐登录页

diff --git a/web/app/globals.css b/web/app/globals.css index 09e1643..2e0cf4f 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -540,6 +540,135 @@ nextjs-portal { color: #fff; } +.skg-board-theme--light { + color: #22261f; + background: + radial-gradient(circle at 12% 0%, rgba(214, 179, 106, 0.18), transparent 30%), + radial-gradient(circle at 82% 8%, rgba(143, 176, 113, 0.14), transparent 28%), + linear-gradient(126deg, #f7f4ea 0%, #eef1e7 48%, #f9f6ee 100%); +} + +.skg-board-theme--light::before { + background: + linear-gradient(90deg, rgba(42, 50, 36, 0.05) 1px, transparent 1px), + linear-gradient(180deg, rgba(42, 50, 36, 0.045) 1px, transparent 1px); + opacity: 0.72; +} + +.skg-board-theme--light::after { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.36), transparent 46%, rgba(214, 179, 106, 0.08)), + linear-gradient(90deg, rgba(255, 255, 255, 0.3), transparent 42%, rgba(255, 255, 255, 0.24)); +} + +.skg-board-theme--light .skg-board-ambient { + background: + radial-gradient(circle at 20% 18%, rgba(214, 179, 106, 0.2), transparent 28%), + radial-gradient(circle at 70% 6%, rgba(143, 176, 113, 0.16), transparent 30%), + radial-gradient(circle at 52% 100%, rgba(214, 179, 106, 0.12), transparent 38%); +} + +.skg-board-theme--light .skg-board-topbar, +.skg-board-theme--light .skg-board-panel { + border-color: rgba(82, 93, 62, 0.16) !important; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(255, 255, 255, 0.48)), + rgba(249, 246, 236, 0.7) !important; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.78), + 0 18px 48px rgba(65, 55, 30, 0.1); +} + +.skg-board-theme--light .skg-board-topbar { + background: + linear-gradient(100deg, rgba(214, 179, 106, 0.14), rgba(143, 176, 113, 0.08) 42%, rgba(255, 255, 255, 0.58)), + rgba(252, 249, 241, 0.82) !important; +} + +.skg-board-theme--light .skg-board-theme-toggle { + border-color: rgba(82, 93, 62, 0.16) !important; + background: rgba(255, 255, 255, 0.54) !important; + color: rgba(36, 40, 30, 0.72) !important; +} + +.skg-board-theme--light .text-white, +.skg-board-theme--light [class*="text-white/"] { + color: rgba(32, 36, 28, 0.78) !important; +} + +.skg-board-theme--light [class*="bg-black/"], +.skg-board-theme--light [class*="bg-white/"] { + background-color: rgba(255, 255, 250, 0.52) !important; +} + +.skg-board-theme--light [class*="border-white/"] { + border-color: rgba(70, 78, 54, 0.14) !important; +} + +.skg-board-theme--light [class*="text-[#d7efbc]"] { + color: #43662d !important; +} + +.skg-board-theme--light [class*="text-[#e8c77a]"], +.skg-board-theme--light [class*="text-[#f2d58a]"], +.skg-board-theme--light [class*="text-[#f5d98e]"] { + color: #856015 !important; +} + +.skg-board-theme--light [class*="text-emerald-"] { + color: #2f6d3d !important; +} + +.skg-board-theme--light [class*="text-cyan-"], +.skg-board-theme--light [class*="text-sky-"], +.skg-board-theme--light [class*="text-teal-"] { + color: #17606f !important; +} + +.skg-board-theme--light [class*="text-amber-"], +.skg-board-theme--light [class*="text-yellow-"] { + color: #8a5c00 !important; +} + +.skg-board-theme--light [class*="text-rose-"], +.skg-board-theme--light [class*="text-red-"] { + color: #9f1239 !important; +} + +.skg-board-theme--light [class*="text-violet-"], +.skg-board-theme--light [class*="text-purple-"] { + color: #62438a !important; +} + +.skg-board-theme--light [class*="border-[#8fb071]"] { + border-color: rgba(67, 102, 45, 0.28) !important; +} + +.skg-board-theme--light [class*="border-[#d6b36a]"] { + border-color: rgba(133, 96, 21, 0.26) !important; +} + +.skg-board-theme--light [class*="bg-[#8fb071]"], +.skg-board-theme--light [class*="bg-[#d6b36a]"] { + background-color: rgba(214, 179, 106, 0.14) !important; +} + +.skg-board-theme--light input, +.skg-board-theme--light textarea, +.skg-board-theme--light select { + color: #22261f !important; +} + +.skg-board-theme--light input::placeholder, +.skg-board-theme--light textarea::placeholder { + color: rgba(34, 38, 31, 0.36) !important; +} + +.skg-board-theme--light ::selection { + background: rgba(214, 179, 106, 0.32); + color: #171a14; +} + .login-hero { isolation: isolate; color: #282828; diff --git a/web/app/page.tsx b/web/app/page.tsx index 33360e7..375a34e 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -1,6 +1,5 @@ "use client" import { useCallback, useEffect, useMemo, useRef, useState } from "react" -import { useTheme } from "next-themes" import { ReactFlow, Background, BackgroundVariant, Controls, useNodesState, useEdgesState, @@ -14,7 +13,6 @@ import { type CanvasPanelDock, type NodeData, } from "@/components/nodes" -import { ThemeToggle } from "@/components/theme-toggle" import { AdRecreationBoard } from "@/components/ad-recreation-board" import { addManualFrame, analyzeJob, createJob, getJob, listJobs, uploadJob, deleteJob, deleteFrame, deleteGeneratedImage, @@ -144,7 +142,6 @@ const EDGES_RAW: Array<[string, string]> = [ ] export default function Home() { - const { resolvedTheme } = useTheme() const [jobs, setJobs] = useState([]) const [activeJobId, setActiveJobId] = useState(null) const job = useMemo(() => jobs.find((j) => j.id === activeJobId) ?? null, [jobs, activeJobId]) @@ -1199,9 +1196,6 @@ export default function Home() {
-
- -
diff --git a/web/components/ad-recreation-board.tsx b/web/components/ad-recreation-board.tsx index ecb2e3c..aa70202 100644 --- a/web/components/ad-recreation-board.tsx +++ b/web/components/ad-recreation-board.tsx @@ -4,7 +4,7 @@ import { type ReactNode, type RefObject, useEffect, useMemo, useRef, useState } import { createPortal } from "react-dom" import { AlertTriangle, Check, ChevronDown, Circle, Film, FileText, Image as ImageIcon, Info, Link2, Loader2, - Mic, Package, PanelRight, Play, Plus, RefreshCw, Scissors, Sparkles, Trash2, Upload, Wand2, + Mic, Moon, Package, PanelRight, Play, Plus, RefreshCw, Scissors, Sparkles, Sun, Trash2, Upload, Wand2, } from "lucide-react" import { toast } from "sonner" import { @@ -78,6 +78,9 @@ const VIDEO_MODELS = [ ] as const type VideoModel = (typeof VIDEO_MODELS)[number]["value"] +type BoardThemeMode = "dark" | "light" + +const BOARD_THEME_STORAGE_KEY = "skg-board-theme" type DraftSegment = { id: string @@ -1268,6 +1271,7 @@ export function AdRecreationBoard({ const [sixViewBusyKey, setSixViewBusyKey] = useState(null) const [generatingAll, setGeneratingAll] = useState(false) const [runtimeModels, setRuntimeModels] = useState() + const [boardTheme, setBoardTheme] = useState("dark") const fileRef = useRef(null) const selectedFrames = job ? job.frames.filter((frame) => data.selectedFrames.has(frame.index)).sort((a, b) => a.timestamp - b.timestamp) @@ -1307,6 +1311,15 @@ export function AdRecreationBoard({ setSelectedVideoIds(new Set()) }, [activeJobId]) + useEffect(() => { + try { + const saved = window.localStorage.getItem(BOARD_THEME_STORAGE_KEY) + if (saved === "light" || saved === "dark") setBoardTheme(saved) + } catch { + // Ignore storage failures; dark mode remains the product default. + } + }, []) + useEffect(() => { let cancelled = false getRuntimeHealth() @@ -1334,6 +1347,18 @@ export function AdRecreationBoard({ if (trimmed) setUrl("") } + const toggleBoardTheme = () => { + setBoardTheme((current) => { + const next: BoardThemeMode = current === "dark" ? "light" : "dark" + try { + window.localStorage.setItem(BOARD_THEME_STORAGE_KEY, next) + } catch { + // Ignore storage failures; the in-memory theme still switches. + } + return next + }) + } + const selectAllFrames = () => { if (!job) return for (const frame of job.frames) { @@ -1477,7 +1502,7 @@ export function AdRecreationBoard({ } return ( -
+
@@ -1485,12 +1510,23 @@ export function AdRecreationBoard({
feed ad recreation worksheet

信息流广告复刻工作表

-
- - - - - +
+ +
+ + + + + +
web/next.config.mjsNext.js 构建配置:静态导出、图片不走优化、禁用开发环境左下角 Next Dev Indicator,并移除 Next 16 已不支持的 eslint 顶层配置,避免本地 dev 出现配置 Issue 提示。
web/app/globals.css全局主题变量、登录页视觉样式、信息流工作台同源质感样式、ReactFlow 样式引用,以及本地开发态 nextjs-portal 遮挡隐藏规则。工作台新增 skg-board-theme / skg-board-panel / skg-board-topbar 等样式,把主界面的颜色、玻璃质感和金绿细节统一到登录页的暗场视觉语言。
web/app/page.tsx产品工作台主状态:jobs、activeJobId、生成任务状态;主渲染为全屏素材输入列 + 信息流广告复刻工作表;“开始分析”会把 job 放入并行素材分析队列,下载完成后触发 triggerTranscribe 解析音频,并触发 analyzeJob 自动抽 12 张参考帧,形成“音频文案路 + 视频视觉路”同步推进;底部吸附音频条不再从主界面渲染。
web/app/globals.css全局主题变量、登录页视觉样式、信息流工作台同源质感样式、ReactFlow 样式引用,以及本地开发态 nextjs-portal 遮挡隐藏规则。工作台新增 skg-board-theme / skg-board-panel / skg-board-topbar 等样式,把主界面的颜色、玻璃质感和金绿细节统一到登录页的暗场视觉语言;明亮模式通过 skg-board-theme--light 复用同一套结构,改成暖白底、浅绿金色层次和深色文本,不另起一套界面。
web/app/page.tsx产品工作台主状态:jobs、activeJobId、生成任务状态;主渲染为全屏素材输入列 + 信息流广告复刻工作表;“开始分析”会把 job 放入并行素材分析队列,下载完成后触发 triggerTranscribe 解析音频,并触发 analyzeJob 自动抽 12 张参考帧,形成“音频文案路 + 视频视觉路”同步推进;底部吸附音频条和旧全局浮动主题按钮不再从主界面渲染,避免和工作台内的明暗模式切换重复。
web/components/ad-recreation-board.tsx信息流广告复刻工作表:顶部由 buildWorkflowSteps 统一生成 01-09 流程顺序、状态和判定依据,WorkflowOrderBar 展示完整顺序,WorkflowStepBadge / PipelineLane / 分镜列标题共用同一套编号。左侧素材输入只负责链接/上传和任务切换,不再重复放横版原视频预览;右侧顶部用“音频文案、抽帧参考、相似主体、产品素材池”四个状态条显示后台并行进度。源视频工作区展示视频下载状态和默认折叠的文案依据。音频解析结果改成默认折叠的辅助信息,展开后同一行看讲话人/节奏/背景音;主工作区左侧放大为按 9:16 显示的竖版原视频播放器,播放器内覆盖“当前点抽帧”,按当前播放秒数手动补参考帧;右侧上方是音频波形 / 切点参考,下方左侧是参考帧池,右侧是逐句时间轴;下一行只保留“相似主体 / 主体模板”。音频波形用参考图式的连续灰色包络显示响度、停顿和密集爆点,顶部同时显示当前播放秒数、总时长和鼠标指针停点秒数。视频播放时通过 requestAnimationFrame 平滑驱动波形播放线,同时同步高亮并滚动当前句;点击音频波形或字幕行会跳转原视频时间。逐句时间轴左侧参考帧池的主入口是“自动抽帧 12 张”,一键按动作峰值目标重新抽取 12 张源视频参考帧,优先抓手势、表情变化、节奏点和镜头变化;缩略图按竖版完整比例显示不裁切并用更多列紧凑铺开,点选状态直接叠在参考帧池缩略图上,鼠标停留会通过固定浮层放大展示完整帧。“生成 10 张高清图”放在下方相似主体白底视图区,不和抽参考按钮平齐;如果用户没有勾选帧,默认把全部关键帧作为主体参考,勾选后只传已选帧;生成区可在“透明骨架 / 普通真人”之间切换,可选择桌面导入的 5 套内置形象作为创意方向,并可填写统一主体方向,例如年轻女性、更运动、更高级。关键帧和相似主体白底视图都用更小的竖版缩略图密排;白底视图只展示每个 view 的最新一张,缩略图上提供“重新生成这一张”和“删除这一张”,单张重生会用 replace_views=true 替换同一视角。前端调用 generateSubjectAssets 时按主体类型传 subject_style=transparent_humansource_actor,按需传 character_id,并使用 reconstruction_mode=similar;后端会把关键帧和内置形象视为同一个主体的创意证据,并锁定同一性别表现、年龄段、体型、材质、风格和视觉身份,同时生成全身多视角 + 肩颈正/左右近景 + 后颈肩背特写,避免整套图出现男女性别、老少年龄或样式混杂。音频结果下方是信息流复刻分镜工作台:顶部产品参考区是“同一产品素材池”,不限量上传产品图,不做不同产品身份判断;上传原图推荐长边 1200-2000px、短边至少 600px,但后端会统一生成最长边 1600px、JPEG 92 的 AI 工作副本,并回显尺寸、自动转换和风险标注;上传后按“套在脖子上的 U 形肩颈按摩仪”进行同一产品批量识别,左/右按佩戴者身体左右、上/下按佩戴方向,额外标注内外侧、开口方向、局部结构点、背景类型、用途标签、生成风险和备注,用户只检查备注,鼠标悬停通过固定浮层显示大图预览,能盖过滚动容器和分镜框架;缺视角补图失败时保留重试入口。脚本区在分镜行上方提供“作者想法”和“整片改写”,每行新口播文案可直接编辑并可单段 AI 改写,分镜时间和原内容列压缩为窄摘要列,新口播列进一步收窄,把横向空间留给画面规划和首尾帧。每条音频分镜纵向排列,行内从左到右串起原内容、新口播文案、画面规划/产品融入和历史候选视频槽;画面规划区先选择镜头类型(人物/情绪、人物+产品、产品特写、场景过渡),再用人物/产品开关、首帧规划、尾帧规划和产品出现方式决定这一条到底需不需要产品图或相似主体参考。当前主流程暂停直接调用视频模型,不再提供“生成本条 · Seedance”或“一键提交全部”视频入口;行内新增“首尾帧闸门”,分别显示/生成首帧和尾帧,旧 keyframe 类型首尾帧会被忽略,只认真正的 asset 首尾帧。生成首尾帧时调用 generateSceneAsset,先按人物描述、镜头类型、首尾状态和产品佩戴需求,从相似主体 6/10 视图里自动挑选最多 5 张最相关主体视角,再传入 subject_images 和该行自动挑选的产品图 product_images;关键帧只作为前置主体重构证据和行数据承载位置,不再作为后续视频首尾帧参考。视频候选槽只展示历史候选和待生成占位,按钮改为“保存本条规划 / 保存全部规划”。只有该行勾选“产品”时,首尾帧生成才会从产品素材池按分镜角色、视角优先级、用途标签、置信度和风险自动挑选最多 6 张相关产品图;未勾选产品时不会把产品图提交给首尾帧/后续生视频模型。只有该行勾选“人物”时,才会传按需筛选后的相似主体参考图;否则 prompt 会明确禁止强行添加主角式透明骨架人,后端也不会再给产品特写强加透明骨架人约束。ModelTrace 会在音频解析、产品识别/补图、相似主体高清视图包、脚本改写等入口旁直接展示模型名;所有生图入口都显示并使用 gpt-image-2,没有其他图片模型 fallback;点击后用固定浮层展示模型链路、输入输出和回退逻辑。旧分镜卡、抽帧控制和视频生成组件仍保留在文件里,但当前主路径不渲染。
AdRecreationBoard 主题切换顶部指标区左侧有“明亮/暗色”按钮,使用 Sun / Moon 图标切换 skg-board-theme--light 类名,并把选择写入 localStorage["skg-board-theme"]。暗色仍是默认模式;明亮模式只改变工作台外观,不改变任务、素材、分镜、模型调用或接口数据。
SourceReferenceBuildPanel“相似主体 / 主体模板”当前承担主体资产生成和主体模板复用的前端入口:面板先分成“主体模板库”和“本次生成 / 入库草稿”。模板库优先读取 GET /subject-templates 数据库模板,并保留 GET /character-library/skg 的内置形象作为策划初始模板;入库草稿显示本次来源、生成数量、模板命名和备注,点击保存会调用 saveSubjectTemplate 把当前主体视图复制到主体模板库。选择数据库模板后,后续 generateSubjectAssets 会传 subject_template_id,让后端以已保存模板视图作为新主体参考。
web/components/media-asset-tile.tsx项目内媒体素材缩略图基底组件:图片、视频、抽帧、产品图、相似主体图、首尾帧和视频候选默认从这里获得统一交互。组件负责缩略图显示、顶层固定浮层 hover 放大、删除按钮、重新生成等操作按钮、忙碌遮罩和图片/视频共用预览,避免每个新板块重复手写不同的媒体交互。
web/app/login/page.tsx生产登录页:访问账号/访问密钥表单、保持登录、错误/成功状态;当前只在原版 Digital Oasis 动态背景上叠加一个组合登录框,桌面端左侧是动态角色,右侧是图标化登录表单;面板左上角展示官网 SKG 字标和中文“营销内容工作台”系统标识。