2 Commits

Author SHA1 Message Date
10d955c6f2 feat: simplify subject conversion composer 2026-05-20 17:07:12 +08:00
eeb7186d7a auto-save 2026-05-20 17:00 (~2) 2026-05-20 17:00:53 +08:00
4 changed files with 2311 additions and 2279 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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 再确认):信息流广告快速复刻默认进入“三字段候选生成”工作流。主界面为“左侧素材输入列 + 右侧信息流复刻工作表”。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动两条路:音频文案路提取原音频文案/字幕,并分析讲话人、语速节奏、背景音乐/环境声/音效;视频视觉路自动抽取参考帧。源视频工作区右侧主体链路是“参考帧池 → 转换层 → 主体元素”:参考帧池竖向排列;转换层是轻量对话式生图确认区,参考图可通过左侧缩略图 `+`、参考帧拖拽、胶片拖拽或本地图片拖入进入转换层,用户选择 GPT/Gemini 套件后先分析参考图,再在下方消息输入区发送复刻/创新/卡通/数量和画面要求;后端返回英文出图 prompt 后必须弹窗确认,用户点确认才生成对应数量的统一多角度套图。右侧主体元素区的套图输出、文件夹分组、单张重生、删除和 hover 预览逻辑保持不变。旧下方“相似主体 / 主体模板库”不再作为主路径。波形下方的画面胶片只是临时预览,点击只跳转原视频时间点,双击或拖进参考帧池才正式加入关键帧,已加入的胶片直接显示“已添加”。产品图上传后独立形成产品资产包,自动识别视角/结构/比例并补缺角度。分镜工作台按逐句时间轴默认只露“文案 / 场景一句话 / 人物+产品+动作”,产品素材池、批量控制、三字段、视频候选和高级区都必须可折叠;视频候选无内容时默认不占大面积,有候选时默认只显示迷你缩略条,展开后才显示 4-grid。单条默认生成 4 个视频候选,顶部支持整片批量生成候选;首尾帧、视觉规划、产品出现方式和旧 6 字段保留在“高级”抽屉与后端 quick-plan 自动展开中,不能再作为客户默认闸门。
- 当前产品方向2026-05-20 再确认):信息流广告快速复刻默认进入“三字段候选生成”工作流。主界面为“左侧素材输入列 + 右侧信息流复刻工作表”。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动两条路:音频文案路提取原音频文案/字幕,并分析讲话人、语速节奏、背景音乐/环境声/音效;视频视觉路自动抽取参考帧。源视频工作区右侧主体链路是“参考帧池 → 转换层 → 主体元素”:参考帧池竖向排列;转换层是轻量对话式生图确认区,参考图可通过左侧缩略图 `+`、参考帧拖拽、胶片拖拽或本地图片拖入进入转换层,用户选择 GPT/Gemini 套件后先分析参考图,再在下方消息输入区发送复刻/创新/卡通和画面要求,生成数量通过发送区旁边的张数控件控制;后端返回英文出图 prompt 后必须弹窗确认,用户点确认才生成对应数量的统一多角度套图。右侧主体元素区的套图输出、文件夹分组、单张重生、删除和 hover 预览逻辑保持不变。旧下方“相似主体 / 主体模板库”不再作为主路径。波形下方的画面胶片只是临时预览,点击只跳转原视频时间点,双击或拖进参考帧池才正式加入关键帧,已加入的胶片直接显示“已添加”。产品图上传后独立形成产品资产包,自动识别视角/结构/比例并补缺角度。分镜工作台按逐句时间轴默认只露“文案 / 场景一句话 / 人物+产品+动作”,产品素材池、批量控制、三字段、视频候选和高级区都必须可折叠;视频候选无内容时默认不占大面积,有候选时默认只显示迷你缩略条,展开后才显示 4-grid。单条默认生成 4 个视频候选,顶部支持整片批量生成候选;首尾帧、视觉规划、产品出现方式和旧 6 字段保留在“高级”抽屉与后端 quick-plan 自动展开中,不能再作为客户默认闸门。
## 部署事实
- 平台VPS `76.13.31.179`Ubuntu 24.04 / Docker Compose / Coolify Traefik

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,7 @@
import { type DragEvent as ReactDragEvent, type MouseEvent as ReactMouseEvent, type ReactNode, type RefObject, useEffect, useMemo, useRef, useState } from "react"
import { createPortal } from "react-dom"
import {
AlertTriangle, BookOpen, Check, ChevronDown, Circle, Film, FileText, Image as ImageIcon, Info, Link2, Loader2,
AlertTriangle, BookOpen, Check, ChevronDown, Circle, Film, FileText, Image as ImageIcon, Info, Link2, Loader2, Minus,
MessageSquare, Mic, Moon, Package, PanelRight, Play, Plus, RefreshCw, Scissors, Send, Sparkles, Sun, Trash2, Upload, Wand2,
} from "lucide-react"
import { toast } from "sonner"
@@ -3854,7 +3854,7 @@ function SourceSubjectPipeline({
const agentTraits = agentAnalysis?.trait_chips ?? []
const selectedAgentTraits = subjectAgent?.selected_traits ?? []
const effectiveAgentMode = subjectAgent?.selected_mode ?? agentMode
const effectiveAgentQuantity = subjectAgent?.quantity ?? agentQuantity
const effectiveAgentQuantity = agentQuantity
const effectiveAgentViews = subjectViewsForQuantity(effectiveAgentQuantity)
const effectivePrompt = (agentPrompt || subjectAgent?.generation_prompt_en || "").trim()
const effectiveRequirement = (subjectAgent?.requirements_zh || agentRequirement).trim()
@@ -4021,7 +4021,7 @@ function SourceSubjectPipeline({
<span className="text-[9px] text-white/34">{agentReferenceFrames.length}/{RECONSTRUCTION_FRAME_LIMIT}</span>
</div>
{agentReferenceFrames.length ? (
<div className="grid grid-cols-3 gap-1.5">
<div className="flex min-h-[78px] items-center justify-center gap-1.5 overflow-x-auto rounded border border-white/8 bg-black/18 px-1.5 py-1">
{agentReferenceFrames.map((frame, index) => (
<MediaAssetTile
key={frame.index}
@@ -4029,7 +4029,7 @@ function SourceSubjectPipeline({
alt={`转换参考 ${index + 1}`}
label={`参考 ${index + 1}`}
meta={`${frame.timestamp.toFixed(1)}s`}
className="aspect-[9/16] bg-black"
className="aspect-[9/16] w-[44px] shrink-0 bg-black 2xl:w-[50px]"
objectFit="contain"
previewObjectFit="contain"
previewPlacement="left"
@@ -4041,7 +4041,7 @@ function SourceSubjectPipeline({
))}
</div>
) : (
<div className="flex h-[116px] flex-col items-center justify-center rounded border border-dashed border-white/15 px-3 text-center text-[10px] leading-snug text-white/34">
<div className="flex h-[72px] flex-col items-center justify-center rounded border border-dashed border-white/15 px-3 text-center text-[10px] leading-snug text-white/34">
{agentReferenceUploadBusy ? <Loader2 className="mb-1.5 h-4 w-4 animate-spin text-cyan-100/80" /> : <Upload className="mb-1.5 h-4 w-4 text-cyan-100/55" />}
<span className="font-semibold text-white/50"></span>
<span className="mt-0.5 text-white/28"> +</span>
@@ -4119,34 +4119,62 @@ function SourceSubjectPipeline({
</div>
)}
</div>
<div className="mt-2 flex flex-wrap gap-1">
{["复刻这个人的形象生成6张", "参考创意但人物不同生成6张", "卡通风格生成6张", "人物更大占画面90%"].map((text) => (
<button
key={text}
type="button"
onClick={() => setAgentInput(text)}
className="rounded-full border border-white/10 bg-black/24 px-2 py-0.5 text-[9px] text-white/48 transition hover:border-white/22 hover:text-white/72"
>
{text}
</button>
))}
</div>
<div className="mt-2 rounded-md border border-white/10 bg-black/35 p-1.5">
<textarea
value={agentInput}
onChange={(event) => setAgentInput(event.target.value)}
placeholder="例如保留透明骨架和蓝色头带但人物更大服装统一生成6张。"
placeholder="直接描述复刻这个人、参考创意换人物、卡通风格、人物占画面90%..."
className="h-[72px] w-full resize-none rounded border border-transparent bg-transparent px-1 py-1 text-[10.5px] leading-snug text-white outline-none transition placeholder:text-white/24 focus:border-cyan-200/45"
/>
<button
type="button"
onClick={() => void sendSubjectAgentRequirement()}
disabled={!!subjectAgentBusy || (!agentInput.trim() && !agentRequirement.trim())}
className="skg-primary-action mt-1 inline-flex h-8 w-full items-center justify-center gap-1.5 px-2 text-[10.5px] font-semibold transition disabled:cursor-not-allowed disabled:opacity-40"
>
{subjectAgentBusy === "message" ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Send className="h-3.5 w-3.5" />}
</button>
<div className="mt-1 flex items-center gap-1.5">
<div className="flex h-8 shrink-0 items-center overflow-hidden rounded-md border border-white/10 bg-black/35">
<button
type="button"
onClick={() => setAgentQuantity((current) => clampNumber(current - 1, 1, 10))}
disabled={agentQuantity <= 1 || !!subjectAgentBusy}
className="inline-flex h-full w-7 items-center justify-center text-white/48 transition hover:bg-white/[0.06] hover:text-white/78 disabled:cursor-not-allowed disabled:opacity-30"
title="减少生成数量"
aria-label="减少生成数量"
>
<Minus className="h-3.5 w-3.5" />
</button>
<label className="flex h-full items-center gap-1 border-x border-white/10 px-1.5 text-[9px] text-white/38">
<span></span>
<input
type="number"
min={1}
max={10}
value={agentQuantity}
aria-label="生成张数"
onChange={(event) => {
const next = Number.parseInt(event.target.value, 10)
setAgentQuantity(Number.isFinite(next) ? clampNumber(next, 1, 10) : 6)
}}
disabled={!!subjectAgentBusy}
className="h-6 w-8 bg-transparent text-center text-[10px] font-semibold text-white outline-none disabled:opacity-45"
/>
</label>
<button
type="button"
onClick={() => setAgentQuantity((current) => clampNumber(current + 1, 1, 10))}
disabled={agentQuantity >= 10 || !!subjectAgentBusy}
className="inline-flex h-full w-7 items-center justify-center text-white/48 transition hover:bg-white/[0.06] hover:text-white/78 disabled:cursor-not-allowed disabled:opacity-30"
title="增加生成数量"
aria-label="增加生成数量"
>
<Plus className="h-3.5 w-3.5" />
</button>
</div>
<button
type="button"
onClick={() => void sendSubjectAgentRequirement()}
disabled={!!subjectAgentBusy || (!agentInput.trim() && !agentRequirement.trim())}
className="skg-primary-action inline-flex h-8 flex-1 items-center justify-center gap-1.5 px-2 text-[10.5px] font-semibold transition disabled:cursor-not-allowed disabled:opacity-40"
>
{subjectAgentBusy === "message" ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Send className="h-3.5 w-3.5" />}
</button>
</div>
</div>
</div>