feat: add confirmed subject conversion flow
This commit is contained in:
2
RULES.md
2
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 再确认):信息流广告快速复刻默认进入“三字段候选生成”工作流。主界面为“左侧素材输入列 + 右侧信息流复刻工作表”。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动两条路:音频文案路提取原音频文案/字幕,并分析讲话人、语速节奏、背景音乐/环境声/音效;视频视觉路自动抽取参考帧。源视频工作区右侧主体链路是“参考帧池 → 转换层 → 主体元素”:参考帧池竖向排列;转换层当前先清空为待重构占位,不再承接拖拽、模型选择、对话、分析或生成按钮;右侧主体元素区的套图输出、文件夹分组、单张重生、删除和 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
@@ -3315,8 +3315,10 @@ function SourceSubjectPipeline({
|
||||
const [agentMode, setAgentMode] = useState<SubjectReconstructionMode>(() => job.subject_agent?.selected_mode ?? "custom")
|
||||
const [agentQuantity, setAgentQuantity] = useState(() => job.subject_agent?.quantity ?? 6)
|
||||
const [agentRequirement, setAgentRequirement] = useState(() => job.subject_agent?.requirements_zh ?? "")
|
||||
const [agentPrompt, setAgentPrompt] = useState(() => job.subject_agent?.generation_prompt_en ?? "")
|
||||
const [agentInput, setAgentInput] = useState("")
|
||||
const [subjectAgentBusy, setSubjectAgentBusy] = useState<"analyze" | "message" | null>(null)
|
||||
const [promptConfirmOpen, setPromptConfirmOpen] = useState(false)
|
||||
const [promptMemoryByMode, setPromptMemoryByMode] = useState<Record<SubjectReconstructionMode, string[]>>(() => loadSubjectPromptMemory(job.id))
|
||||
const [cartoonStyle] = useState<CartoonReconstructionStyle>("3d_animation")
|
||||
const [subjectBusyFor, setSubjectBusyFor] = useState<{ jobId: string; jobLabel: string; mode: SubjectReconstructionMode; viewCount: number; sourceCount: number; profileLabel: string; modelLabel: string } | null>(null)
|
||||
@@ -3412,8 +3414,10 @@ function SourceSubjectPipeline({
|
||||
setAgentMode(job.subject_agent?.selected_mode ?? "custom")
|
||||
setAgentQuantity(job.subject_agent?.quantity ?? 6)
|
||||
setAgentRequirement(job.subject_agent?.requirements_zh ?? "")
|
||||
setAgentPrompt(job.subject_agent?.generation_prompt_en ?? "")
|
||||
setAgentInput("")
|
||||
setSubjectAgentBusy(null)
|
||||
setPromptConfirmOpen(false)
|
||||
setPromptMemoryByMode(loadSubjectPromptMemory(job.id))
|
||||
setLastSubjectProfile(null)
|
||||
setSubjectBusyFor(null)
|
||||
@@ -3428,6 +3432,7 @@ function SourceSubjectPipeline({
|
||||
setAgentMode(agent?.selected_mode ?? "custom")
|
||||
setAgentQuantity(agent?.quantity ?? 6)
|
||||
setAgentRequirement(agent?.requirements_zh ?? "")
|
||||
setAgentPrompt(agent?.generation_prompt_en ?? "")
|
||||
}, [job.id, job.subject_agent?.updated_at])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -3459,7 +3464,11 @@ function SourceSubjectPipeline({
|
||||
|
||||
const subjectModelLabel = (value: SubjectModelBundle) => subjectModelBundleConfig(value).label
|
||||
|
||||
const generateSubjectPack = async (mode: SubjectReconstructionMode, sourceIndices = agentReferenceFrameIndices) => {
|
||||
const generateSubjectPack = async (
|
||||
mode: SubjectReconstructionMode,
|
||||
sourceIndices = agentReferenceFrameIndices,
|
||||
views = selectedSubjectViews,
|
||||
) => {
|
||||
if (subjectBusyFor) {
|
||||
toast.warning("主体套图正在生成中,完成后再重生。")
|
||||
return
|
||||
@@ -3492,14 +3501,14 @@ function SourceSubjectPipeline({
|
||||
? null
|
||||
: buildSubjectProfileForRequest()
|
||||
const subjectStyle = reconstructionSubjectStyle(mode)
|
||||
const userDirection = buildReconstructionDirection(mode, rawDirection, cartoonStyle, selectedSubjectViews.length)
|
||||
const userDirection = buildReconstructionDirection(mode, rawDirection, cartoonStyle, views.length)
|
||||
rememberPromptForMode(mode, rawDirection)
|
||||
const modeName = reconstructionElementName(mode)
|
||||
setSubjectBusyFor({
|
||||
jobId: requestJobId,
|
||||
jobLabel: shortId(requestJobId),
|
||||
mode,
|
||||
viewCount: selectedSubjectViews.length,
|
||||
viewCount: views.length,
|
||||
sourceCount: sourceFrames.length,
|
||||
profileLabel: requestProfile?.summary ?? "按自主描述",
|
||||
modelLabel: subjectModelLabel(subjectModelBundle),
|
||||
@@ -3529,7 +3538,7 @@ function SourceSubjectPipeline({
|
||||
background: "white",
|
||||
size: SUBJECT_ASSET_SIZE,
|
||||
source_frame_indices: sourceFrames.slice(0, RECONSTRUCTION_FRAME_LIMIT).map((frame) => frame.index),
|
||||
views: selectedSubjectViews,
|
||||
views,
|
||||
subject_profile: requestProfile?.payload ?? null,
|
||||
prompt: sourceLockedReplication
|
||||
? `${buildSourceLockedSubjectPrompt(subjectStyle)} ${userDirection}`
|
||||
@@ -3546,7 +3555,7 @@ function SourceSubjectPipeline({
|
||||
if (updatedFrame && updatedElement && newestAsset) {
|
||||
setExpandedSubjectPackKey(subjectAssetPackKey(updatedFrame, updatedElement, newestAsset))
|
||||
}
|
||||
toast.success(`${reconstructionModeConfig(mode).label}已提交:${selectedSubjectViews.length} 张会逐张出来`)
|
||||
toast.success(`${reconstructionModeConfig(mode).label}已提交:${views.length} 张会逐张出来`)
|
||||
} catch (e) {
|
||||
try {
|
||||
onJobUpdate(await getJob(requestJobId))
|
||||
@@ -3685,7 +3694,16 @@ function SourceSubjectPipeline({
|
||||
quantity: agentQuantity,
|
||||
})
|
||||
onJobUpdate(updated)
|
||||
const nextAgent = updated.subject_agent
|
||||
if (nextAgent) {
|
||||
setAgentMode(nextAgent.selected_mode)
|
||||
setAgentQuantity(nextAgent.quantity)
|
||||
setAgentRequirement(nextAgent.requirements_zh)
|
||||
setAgentPrompt(nextAgent.generation_prompt_en)
|
||||
setAgentReferenceFrameIndices(nextAgent.source_frame_indices)
|
||||
}
|
||||
setAgentInput("")
|
||||
setPromptConfirmOpen(true)
|
||||
} catch (e) {
|
||||
toast.error("生图要求更新失败:" + (e instanceof Error ? e.message : String(e)))
|
||||
} finally {
|
||||
@@ -3714,12 +3732,22 @@ function SourceSubjectPipeline({
|
||||
const agentMessages = subjectAgent?.messages ?? []
|
||||
const agentTraits = agentAnalysis?.trait_chips ?? []
|
||||
const selectedAgentTraits = subjectAgent?.selected_traits ?? []
|
||||
const canGenerateAgentPack = agentMode === "custom"
|
||||
? Boolean(agentRequirement.trim() || agentReferenceFrames.length)
|
||||
const effectiveAgentMode = subjectAgent?.selected_mode ?? agentMode
|
||||
const effectiveAgentQuantity = subjectAgent?.quantity ?? agentQuantity
|
||||
const effectiveAgentViews = subjectViewsForQuantity(effectiveAgentQuantity)
|
||||
const effectivePrompt = (agentPrompt || subjectAgent?.generation_prompt_en || "").trim()
|
||||
const effectiveRequirement = (subjectAgent?.requirements_zh || agentRequirement).trim()
|
||||
const canGenerateAgentPack = effectiveAgentMode === "custom"
|
||||
? Boolean(effectiveRequirement || agentReferenceFrames.length)
|
||||
: agentReferenceFrames.length > 0
|
||||
const agentModeRunning = runningActorModes.has(agentMode)
|
||||
const agentModeRunning = runningActorModes.has(effectiveAgentMode)
|
||||
const confirmSubjectGeneration = () => {
|
||||
setPromptConfirmOpen(false)
|
||||
void generateSubjectPack(effectiveAgentMode, agentReferenceFrameIndices, effectiveAgentViews)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid gap-2 xl:grid-cols-[150px_minmax(210px,0.75fr)_minmax(0,1.25fr)] 2xl:grid-cols-[170px_minmax(240px,0.8fr)_minmax(0,1.3fr)]">
|
||||
<div className="min-w-0">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
@@ -3793,6 +3821,16 @@ function SourceSubjectPipeline({
|
||||
selected={selected}
|
||||
title={selected ? "已选 · 点击取消" : "点击选择"}
|
||||
onClick={() => onToggleFrame(frame.index)}
|
||||
actions={[
|
||||
{
|
||||
key: "send-to-conversion",
|
||||
label: allConversionFrameIndices.has(frame.index) ? "已在转换层" : "送入转换层",
|
||||
icon: allConversionFrameIndices.has(frame.index) ? <Check className="h-3 w-3" /> : <Plus className="h-3 w-3" />,
|
||||
onClick: () => addAgentReferenceFrame(frame),
|
||||
disabled: allConversionFrameIndices.has(frame.index),
|
||||
tone: "cyan",
|
||||
},
|
||||
]}
|
||||
topLeft={<span className="rounded bg-black/72 px-1 font-mono text-[9px] text-white/70">{String(index + 1).padStart(2, "0")}</span>}
|
||||
topRight={<span className="rounded-full bg-black/72 p-0.5">{selected ? <Check className="h-3 w-3 text-emerald-200" /> : <Circle className="h-3 w-3 text-white/50" />}</span>}
|
||||
onDelete={onDeleteFrame ? () => onDeleteFrame(frame.index) : undefined}
|
||||
@@ -3814,9 +3852,171 @@ function SourceSubjectPipeline({
|
||||
<div className="min-w-0">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<SectionTitle icon={<Wand2 className="h-4 w-4" />} title="转换层" />
|
||||
<span className="rounded-md border border-white/10 bg-black/35 px-2 py-1 text-[10px] text-white/42">
|
||||
{agentReferenceFrames.length ? `${agentReferenceFrames.length}/${RECONSTRUCTION_FRAME_LIMIT} 图` : "待选图"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex min-h-[410px] items-center justify-center rounded-md border border-dashed border-white/12 bg-black/18 p-4 2xl:min-h-[500px]">
|
||||
<span className="text-[10.5px] text-white/28">转换层待重构</span>
|
||||
<div className="min-h-[410px] rounded-md border border-white/10 bg-black/24 p-2 2xl:min-h-[500px]">
|
||||
<div className="mb-2 grid grid-cols-2 gap-1.5">
|
||||
{SUBJECT_MODEL_BUNDLE_OPTIONS.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
onClick={() => setSubjectModelBundle(option.value)}
|
||||
className={`rounded-md border px-2 py-1.5 text-left transition ${
|
||||
subjectModelBundle === option.value
|
||||
? "border-cyan-200/65 bg-cyan-300/12 text-cyan-50"
|
||||
: "border-white/10 bg-black/26 text-white/52 hover:border-white/24 hover:text-white/76"
|
||||
}`}
|
||||
title={option.detail}
|
||||
>
|
||||
<span className="block text-[10px] font-semibold">{option.label}</span>
|
||||
<span className="mt-0.5 block truncate text-[8.5px] opacity-65">{option.detail}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border border-white/10 bg-black/22 p-2">
|
||||
<div className="mb-1.5 flex items-center justify-between gap-2">
|
||||
<span className="text-[10px] font-semibold text-white/72">参考图</span>
|
||||
<span className="text-[9px] text-white/34">最多 {RECONSTRUCTION_FRAME_LIMIT} 张</span>
|
||||
</div>
|
||||
{agentReferenceFrames.length ? (
|
||||
<div className="grid grid-cols-3 gap-1.5">
|
||||
{agentReferenceFrames.map((frame, index) => (
|
||||
<MediaAssetTile
|
||||
key={frame.index}
|
||||
src={effectiveFrameUrl(job.id, frame)}
|
||||
alt={`转换参考 ${index + 1}`}
|
||||
label={`参考 ${index + 1}`}
|
||||
meta={`${frame.timestamp.toFixed(1)}s`}
|
||||
className="aspect-[9/16] bg-black"
|
||||
objectFit="contain"
|
||||
previewObjectFit="contain"
|
||||
previewPlacement="left"
|
||||
previewMaxWidth={300}
|
||||
topLeft={<span className="rounded bg-black/72 px-1 font-mono text-[9px] text-white/70">{String(index + 1).padStart(2, "0")}</span>}
|
||||
onDelete={() => removeAgentReferenceFrame(frame.index)}
|
||||
deleteLabel="移出转换层"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-24 items-center justify-center rounded border border-dashed border-white/12 px-2 text-center text-[10px] leading-snug text-white/32">
|
||||
从左侧参考帧点 + 加入。
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void runSubjectAgentAnalyze()}
|
||||
disabled={!agentReferenceFrames.length || !!subjectAgentBusy}
|
||||
className="skg-secondary-action mt-2 inline-flex h-7 w-full items-center justify-center gap-1.5 px-2 text-[10px] font-semibold transition disabled:cursor-not-allowed disabled:opacity-40"
|
||||
>
|
||||
{subjectAgentBusy === "analyze" ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Sparkles className="h-3.5 w-3.5" />}
|
||||
分析参考图
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{agentAnalysis ? (
|
||||
<div className="mt-2 rounded-md border border-emerald-200/18 bg-emerald-300/[0.055] p-2">
|
||||
<div className="text-[10px] font-semibold text-emerald-50/76">识别结果</div>
|
||||
<p className="mt-1 max-h-16 overflow-auto text-[9.5px] leading-snug text-white/58">{agentAnalysis.summary_zh}</p>
|
||||
{agentTraits.length ? (
|
||||
<div className="mt-2 flex max-h-16 flex-wrap gap-1 overflow-auto">
|
||||
{agentTraits.slice(0, 12).map((trait) => {
|
||||
const active = selectedAgentTraits.includes(trait)
|
||||
return (
|
||||
<button
|
||||
key={trait}
|
||||
type="button"
|
||||
onClick={() => toggleSubjectAgentTrait(trait)}
|
||||
className={`rounded-full border px-2 py-0.5 text-[9px] transition ${
|
||||
active
|
||||
? "border-emerald-100/60 bg-emerald-300/15 text-emerald-50"
|
||||
: "border-white/10 bg-black/26 text-white/46 hover:border-white/22 hover:text-white/70"
|
||||
}`}
|
||||
>
|
||||
{trait}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="mt-2 rounded-md border border-white/10 bg-black/22 p-2">
|
||||
<div className="mb-1.5 flex items-center justify-between gap-2">
|
||||
<span className="text-[10px] font-semibold text-white/72">生图对话</span>
|
||||
<span className="text-[9px] text-white/34">
|
||||
{reconstructionModeConfig(effectiveAgentMode).label} · {effectiveAgentQuantity} 张
|
||||
</span>
|
||||
</div>
|
||||
<div className="max-h-28 space-y-1.5 overflow-auto rounded border border-white/8 bg-black/20 p-1.5">
|
||||
{agentMessages.length ? agentMessages.slice(-5).map((message, index) => (
|
||||
<div
|
||||
key={`${message.created_at}-${index}`}
|
||||
className={`rounded-md px-2 py-1 text-[9.5px] leading-snug ${
|
||||
message.role === "user"
|
||||
? "ml-5 bg-cyan-300/12 text-cyan-50/74"
|
||||
: "mr-5 bg-white/[0.055] text-white/58"
|
||||
}`}
|
||||
>
|
||||
{message.content}
|
||||
</div>
|
||||
)) : (
|
||||
<div className="flex h-14 items-center justify-center text-center text-[10px] leading-snug text-white/30">
|
||||
分析后,直接写你要复刻、创新、卡通、数量和画面要求。
|
||||
</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>
|
||||
<textarea
|
||||
value={agentInput}
|
||||
onChange={(event) => setAgentInput(event.target.value)}
|
||||
placeholder="例如:保留透明骨架和蓝色头带,但人物更大,服装统一,生成6张。"
|
||||
className="mt-2 h-20 w-full resize-none rounded-md border border-white/10 bg-black/35 px-2 py-1.5 text-[10.5px] leading-snug text-white outline-none transition placeholder:text-white/24 focus:border-cyan-200/55"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void sendSubjectAgentRequirement()}
|
||||
disabled={!!subjectAgentBusy || (!agentInput.trim() && !agentRequirement.trim())}
|
||||
className="skg-primary-action mt-2 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>
|
||||
|
||||
{effectivePrompt ? (
|
||||
<div className="mt-2 rounded-md border border-[#d6b36a]/24 bg-[#d6b36a]/[0.075] p-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-[10px] font-semibold text-[#f4dc88]">待确认提示词</span>
|
||||
<span className="text-[9px] text-white/42">{effectiveAgentViews.length} 视图</span>
|
||||
</div>
|
||||
<p className="mt-1 line-clamp-4 text-[9.5px] leading-snug text-white/58">{effectivePrompt}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPromptConfirmOpen(true)}
|
||||
disabled={!canGenerateAgentPack || subjectBusy || agentModeRunning}
|
||||
className="skg-primary-action mt-2 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"
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
确认并生成 {effectiveAgentViews.length} 张
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3928,6 +4128,61 @@ function SourceSubjectPipeline({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{promptConfirmOpen && effectivePrompt && typeof document !== "undefined" ? createPortal(
|
||||
<div className="fixed inset-0 z-[10020] flex items-center justify-center bg-black/72 p-4 backdrop-blur-sm">
|
||||
<div className="w-full max-w-2xl rounded-xl border border-white/14 bg-[#11140f] p-4 shadow-[0_28px_90px_rgba(0,0,0,0.72)]">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-white">确认出图提示词</div>
|
||||
<div className="mt-1 text-[11px] text-white/46">
|
||||
{subjectModelLabel(subjectModelBundle)} · {reconstructionModeConfig(effectiveAgentMode).label} · {effectiveAgentViews.length} 张
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPromptConfirmOpen(false)}
|
||||
className="rounded-md border border-white/10 bg-black/30 px-2 py-1 text-[11px] text-white/56 transition hover:border-white/24 hover:text-white"
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
{effectiveRequirement ? (
|
||||
<div className="mt-3 rounded-md border border-white/10 bg-black/24 px-3 py-2">
|
||||
<div className="text-[10px] font-semibold text-white/54">用户要求</div>
|
||||
<p className="mt-1 text-[11px] leading-relaxed text-white/72">{effectiveRequirement}</p>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mt-3 rounded-md border border-[#d6b36a]/24 bg-[#d6b36a]/[0.07] px-3 py-2">
|
||||
<div className="mb-1 text-[10px] font-semibold text-[#f4dc88]">最终英文提示词</div>
|
||||
<textarea
|
||||
readOnly
|
||||
value={effectivePrompt}
|
||||
className="h-44 w-full resize-none rounded-md border border-white/10 bg-black/30 px-2 py-2 font-mono text-[11px] leading-relaxed text-white/76 outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPromptConfirmOpen(false)}
|
||||
className="skg-secondary-action inline-flex h-9 items-center justify-center px-4 text-[12px] font-semibold"
|
||||
>
|
||||
先不生成
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={confirmSubjectGeneration}
|
||||
disabled={!canGenerateAgentPack || subjectBusy || agentModeRunning}
|
||||
className="skg-primary-action inline-flex h-9 items-center justify-center gap-1.5 px-4 text-[12px] font-semibold transition disabled:cursor-not-allowed disabled:opacity-40"
|
||||
>
|
||||
<Sparkles className="h-4 w-4" />
|
||||
确定生成 {effectiveAgentViews.length} 张
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user