// 拆解结果报告 — SPA 内 React 组件,替代旧 result.html / app.js iframe 嵌入。
// 数据来自 GET /api/jobs/{id}/result,shape 对齐 schemas_v4.AnalysisResultV4。
// 12 个 section 只读展示,不带可编辑功能(M3.5 再说)。
const RR = (() => {
// ============== 工具 ==============
const safeArr = (x) => Array.isArray(x) ? x : [];
const safeStr = (x) => typeof x === "string" ? x : x == null ? "" : String(x);
const fmtSec = (s) => {
const n = Number(s);
if (!isFinite(n) || n < 0) return "—";
if (n < 60) return `${n.toFixed(1)}s`;
const m = Math.floor(n / 60); const r = n - m * 60;
return `${m}m${r.toFixed(0)}s`;
};
const pct = (v) => `${Math.round((Number(v) || 0) * 100)}%`;
// ============== 容器 ==============
const Section = ({ idx, title, badge, children, style }) => (
§ {String(idx).padStart(2, "0")}
{title}
{badge && {badge}}
{children}
);
const Card = ({ title, children, style }) => (
{title &&
{title}
}
{children}
);
const KV = ({ k, v, mono = false }) => (
{k}
{v || "—"}
);
const Chip = ({ children, tone }) => (
{children}
);
const Empty = ({ msg = "暂无数据" }) => (
{msg}
);
const Loading = () => (
加载拆解结果…
);
const ErrorBox = ({ message }) => (
);
// ============== 1. Header ==============
const ResultHeader = ({ meta = {}, jobId }) => {
const dl = (kind) => {
window.open(`/api/jobs/${encodeURIComponent(jobId)}/download/${kind}`, "_blank");
};
return (
JOB · {jobId?.slice(0, 12) || "—"}
{meta.title || meta.video_filename || "未命名拆解任务"}
{meta.industry && {meta.industry}}
{meta.duration_sec && · 时长 {fmtSec(meta.duration_sec)}}
{meta.resolution && · {meta.resolution}}
{meta.platform_hint && · {meta.platform_hint}}
);
};
// ============== 2. SummaryCards ==============
const SummaryCards = ({ stream, dual }) => {
const sv = stream || {};
const dd = dual || {};
const ct = dd.content_tone || {};
const rer = dd.real_estate_relevance || {};
return (
{sv.stream_label || "—"}
置信度 {pct(sv.confidence)}
{sv.reasoning && {sv.reasoning}
}
{safeArr(sv.signals).length > 0 && (
{sv.signals.map((s, i) => {s})}
)}
{ct.tone_label || "—"}
置信度 {pct(ct.confidence)}
{ct.reasoning && {ct.reasoning}
}
{safeArr(ct.key_evidence).length > 0 && (
{ct.key_evidence.map((s, i) => {s})}
)}
{rer.relevance_label || "—"}
置信度 {pct(rer.confidence)}
{rer.reasoning && {rer.reasoning}
}
{safeArr(rer.hit_keywords).length > 0 && (
{rer.hit_keywords.map((s, i) => {s})}
)}
{rer.migration_value && (
迁移价值:{rer.migration_value}
)}
{dd.overall_assessment || "—"}
);
};
// ============== 3. HookSection ==============
const HookSection = ({ hook }) => {
const h = hook || {};
return (
{!h.hook_type && !h.reusable_template ? : (
<>
{h.reusable_template || "—"}
{safeArr(h.core_trigger_words).length === 0 ? — :
h.core_trigger_words.map((w, i) => {w})}
{h.emotional_trigger || "—"}
{h.why_it_works || "—"}
>
)}
);
};
// ============== 4. CopyStructureTable ==============
const CopyStructureTable = ({ cs }) => {
const segs = safeArr(cs?.segments);
return (
{segs.length === 0 ? : (
<>
{cs.narrative_arc && {cs.narrative_arc}
}
| # | 时间 | 段类型 | 转化钩子 |
文案要点 | 功能描述 |
{segs.map((s, i) => (
| {i + 1} |
{fmtSec(s.start_sec)}–{fmtSec(s.end_sec)} |
{s.segment_type || "—"} |
{s.conversion_hook_type || "—"} |
{s.key_text || "—"} |
{s.function_description || "—"} |
))}
>
)}
);
};
// ============== 5. VisualRhythmCards ==============
const VisualRhythmCards = ({ visual }) => {
const v = visual || {};
const dist = v.shot_distribution || {};
const distEntries = Object.entries(dist);
const cuts = Number(v.avg_cut_frequency) || 0;
return (
{distEntries.length > 0 && (
{distEntries.map(([k, val]) => (
))}
)}
{safeArr(v.color_tone_keywords).length > 0 && (
{v.color_tone_keywords.map((c, i) => {c})}
)}
{v.visual_model_prompt && (
{v.visual_model_prompt}
)}
{v.camera_language_notes && (
{v.camera_language_notes}
)}
);
};
// ============== 6. EmotionCurveSVG ==============
const EmotionCurveSVG = ({ emotion }) => {
const e = emotion || {};
const points = safeArr(e.emotion_points);
if (points.length === 0) return (
);
// 轨迹归一化:横轴 = timestamp,纵轴 = intensity
const W = 720, H = 140, P = 24;
const ts = points.map(p => Number(p.timestamp_sec) || 0);
const tsMax = Math.max(...ts, 1);
const polyPoints = points.map((p, i) => {
const x = P + (Number(p.timestamp_sec) / tsMax) * (W - 2 * P);
const y = H - P - (Number(p.intensity) || 0) * (H - 2 * P);
return `${x.toFixed(1)},${y.toFixed(1)}`;
}).join(" ");
return (
{e.emotion_arc_summary && {e.emotion_arc_summary}
}
{points.map((p, i) => (
{fmtSec(p.timestamp_sec)} · {p.emotion} · {pct(p.intensity)}
{p.is_climax && " ★"}
))}
);
};
// ============== 7. CommentInsightsCards ==============
const CommentInsightsCards = ({ comments }) => {
const c = comments || {};
return (
{safeArr(c.high_freq_pain_points).map((p, i) => - {p}
)}
{safeArr(c.high_freq_pain_points).length === 0 && - —
}
{safeArr(c.question_blind_spots).map((p, i) => - {p}
)}
{safeArr(c.question_blind_spots).length === 0 && - —
}
{safeArr(c.negative_feedback_notes).map((p, i) => - {p}
)}
{safeArr(c.negative_feedback_notes).length === 0 && - —
}
{safeStr(c.viral_potential_assessment) || "—"}
{safeArr(c.insights).length > 0 && (
| 类型 | 描述 | 可执行建议 | 置信度 |
{c.insights.map((ins, i) => (
| {ins.insight_type || "—"} |
{ins.description || "—"} |
{ins.actionable || "—"} |
{pct(ins.confidence)} |
))}
)}
);
};
// ============== 8. AidsTable ==============
const AidsTable = ({ aids }) => {
const a = aids || {};
if (!a.applicable) return (
{a.overall_comment || "本视频不适合用 AIDIS 框架(A·Attention / I·Interest / D·Desire / I·Information / S·Solution)拆解。"}
);
const stages = safeArr(a.stages);
return (
{a.overall_comment && {a.overall_comment}
}
| 阶段 | 占比 | 达标 | 文案片段 | 说明 |
{stages.map((s, i) => (
| {s.stage_label || s.stage} |
{pct(s.char_percent)}
|
{s.meets_criterion ? ✓ : ✗} |
{s.script_excerpt || "—"} |
{s.notes || "—"} |
))}
);
};
// ============== 9. RawScriptViewer ==============
const RawScriptViewer = ({ raw, timed }) => {
const [tab, setTab] = useState("plain");
const hasTimed = safeArr(timed).length > 0;
return (
{hasTimed && (
)}
{tab === "plain" ? (
{safeStr(raw) || "—"}
) : (
| 时间 | 内容 |
{timed.map((seg, i) => (
| {fmtSec(seg.start_sec)}–{fmtSec(seg.end_sec)} |
{seg.text} |
))}
)}
);
};
// ============== 10. RewriteSection ==============
const RewriteSection = ({ rewrite }) => {
const r = rewrite || {};
if (!r.strategy || r.strategy === "not_applicable") return (
{r.skipped_reason || "本视频未生成二创建议(房产相关度判定为无迁移价值)。"}
);
return (
{safeStr(r.rewritten_script) || "—"}
{safeArr(r.preserved_elements).map((p, i) => - {p}
)}
{safeArr(r.preserved_elements).length === 0 && - —
}
{safeArr(r.changed_elements).map((p, i) => - {p}
)}
{safeArr(r.changed_elements).length === 0 && - —
}
画面级 AIGC 提示词与时长打包属于复刻流程(chunk_regroup → r_chunks),见 /remix 复刻入口;
aspect_ratio: {r.aspect_ratio || "9:16"}{r.negative_prompt ? ` · negative: ${r.negative_prompt}` : ""}
);
};
// ============== 11. VideoGenSection ==============
const VideoGenSection = ({ video }) => {
const v = video || {};
if (!v.status || v.status === "pending") return null;
const shots = safeArr(v.shots);
return (
{v.status}
} />
{v.error && {v.error}
}
{v.final_video_path && (
)}
{shots.length > 0 && (
| # | 状态 | 平台 | 外部任务 | 错误 |
{shots.map((s, i) => (
| {(s.shot_index ?? i) + 1} |
{s.status || "—"}
|
{s.platform || "—"} |
{s.external_task_id || "—"} |
{s.error || "—"} |
))}
)}
);
};
// ============== 12.0 RewriteTextCell · 段级二创可编辑单元格 (M5 加) ==============
// 显示 rewrite_text;点"编辑"切到 textarea;保存调 PATCH /jobs/{id}/chunks/{cid}/rewrite-text
const RewriteTextCell = ({ jobId, chunkId, value, editedAt, onSaved }) => {
const [editing, setEditing] = useState(false);
const [draft, setDraft] = useState(value || "");
const [busy, setBusy] = useState(false);
const [err, setErr] = useState("");
React.useEffect(() => { setDraft(value || ""); }, [value]);
if (!editing) {
return (
{value ?
{value}
:
—}
{editedAt && (
· 已人工编辑
)}
);
}
const save = async () => {
const trimmed = (draft || "").trim();
if (!trimmed) { setErr("不能为空"); return; }
setBusy(true); setErr("");
try {
await API.jobs.patchRewriteText(jobId, chunkId, trimmed);
setEditing(false);
onSaved && onSaved();
} catch (e) {
setErr(e.message || String(e));
} finally { setBusy(false); }
};
return (
);
};
// ============== 12. ChunkAlignmentSection (M4) ==============
// 原 ASR chunks 与 R2 复刻 rewrite_chunks 的 1-1 对齐展示。
// 老 _v4.json(无 chunks 字段)下整段不渲染。
const ChunkAlignmentSection = ({ jobId, chunks, rewriteChunks, onUpdated }) => {
const cs = safeArr(chunks);
if (cs.length === 0) return null; // 老链路或 ASR 跳过:不渲染
const rcs = safeArr(rewriteChunks);
const rcMap = {};
rcs.forEach(rc => { if (rc && rc.chunk_id) rcMap[rc.chunk_id] = rc; });
const aligned = rcs.length === cs.length;
return (
按豆包 Seed-ASR 2.0 句切产出的第一轮 chunks(c_001…),文本已经过 LLM 同音字修正。
R2 二创产出的 rewrite_chunks 与之严格 1:1(每条 = 该 chunk 的口播稿)。
画面级 AIGC 提示词与时长打包属于复刻流程的第二轮 r_chunks,见下方。
| chunk_id |
起止 / 时长 |
原文 |
复刻文 |
AIGC 提示词 |
切片 |
{cs.map((c, i) => {
const rc = rcMap[c.chunk_id];
const dur = (c.duration_sec ?? (c.t_end - c.t_start)) || 0;
const promptShort = rc && rc.aigc_prompt
? (rc.aigc_prompt.length > 100 ? rc.aigc_prompt.slice(0, 100) + "…" : rc.aigc_prompt)
: "";
return (
| {c.chunk_id} |
{fmtSec(c.t_start)}–{fmtSec(c.t_end)}
{dur.toFixed(2)}s
|
{safeStr(c.text) || —}
{c.text_original && c.text_original !== c.text && (
原文:{c.text_original}
)}
|
{rc ? (
) : —}
|
{promptShort || "—"} |
{c.clip_path
? ✓
: —}
|
);
})}
{!aligned && (
⚠️ rewrite_chunks 长度({rcs.length})≠ chunks 长度({cs.length}),R2 对齐失败;请重新拆解
)}
);
};
// ============== TranscriptLinkSection(原始文件包 + 修正记录)==============
const TranscriptLinkSection = ({ meta }) => {
const tp = meta?.transcript_path;
const cp = meta?.corrected_transcript_path;
if (!tp && !cp) return null;
return (
豆包 ASR 完整原始返回 + LLM 修正后的逐字稿;下游 chunks / 拆解 / 复刻都从这里读。
{tp && - {tp} · 豆包原始(含 audio_info / utterances / words[])
}
{cp && - {cp} · LLM 修正后稿子(含 corrections 修正记录)
}
);
};
// ============== RegroupedChunkSection(第二轮 r_chunks,复刻产物)==============
const RegroupedChunkSection = ({ chunkRegroup }) => {
const cr = chunkRegroup || null;
const r_chunks = safeArr(cr && cr.r_chunks);
return (
{!r_chunks.length ? (
第二轮 chunks 由复刻流程的 chunk_regroup stage 产出(每个 r_chunk 对应一次云端
视频生成调用,时长对齐 platform 档位)。当前任务尚未触发复刻,去 /remix 选择
视频生成 platform + 凭据后即可生成。
) : (
| r_chunk_id |
包含 c_chunks |
时长(s) |
AIGC 提示词(画面) |
口播文案(拼接) |
原画面引用 |
{r_chunks.map((rc, i) => {
const useOrig = rc.use_original_clip_user != null
? rc.use_original_clip_user
: !!rc.use_original_clip_default;
return (
| {rc.chunk_id} |
{(rc.source_chunk_ids || []).join(", ")} |
{(rc.duration_sec || 0).toFixed(1)} |
{safeStr(rc.aigc_prompt)} |
{safeStr(rc.rewrite_text)} |
{useOrig ? "用原画面" : "纯 AIGC"}
{rc.use_original_clip_user != null && (
(用户改)
)}
|
);
})}
)}
);
};
// ============== 顶层 ResultReport ==============
const useState = React.useState;
const useEffect = React.useEffect;
const ResultReport = ({ jobId }) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
const fetchData = React.useCallback(() => {
if (!jobId) { setData(null); setLoading(false); return; }
setLoading(true);
setError(null);
API.jobs.result(jobId)
.then(d => { setData(d); })
.catch(e => { setError(e.message || String(e)); })
.finally(() => { setLoading(false); });
}, [jobId]);
useEffect(() => { fetchData(); }, [fetchData]);
if (loading) return ;
if (error) return ;
if (!data) return ;
// 图文笔记分支:完全不走 12 section 视频拆解视图
if (data.meta?.is_image_post) {
return ;
}
// 图文复刻 child job:渲染 plan + 出图按钮
if (data.meta?.is_image_replicate) {
return ;
}
return (
);
};
// ============== 图文复刻 child job 视图 ==============
const ImageReplicateReport = ({ meta, replication, childJobId, onChange }) => {
const plan = replication.plan || {};
const gen = replication.image_generation || null;
const planErr = replication.plan_error;
const [credentials, setCredentials] = useState([]);
const [chosenCredId, setChosenCredId] = useState("");
const [generating, setGenerating] = useState(false);
const [genMsg, setGenMsg] = useState(null);
useEffect(() => {
API.credentials.list().then(d => {
// 只列支持出图的 provider
const supported = new Set(["siliconflow", "openai_multimodal",
"openai_compat", "gemini", "seedance"]);
setCredentials((d.items || []).filter(c => supported.has(c.provider)));
}).catch(() => setCredentials([]));
}, []);
const startGen = async () => {
if (generating || !chosenCredId) return;
setGenerating(true); setGenMsg(null);
try {
const r = await API.replicate.generateImages(childJobId, Number(chosenCredId));
setGenMsg({
kind: r.failed > 0 ? "warn" : "ok",
msg: `出图完成:成功 ${r.generated} / 失败 ${r.failed} · ${r.provider}:${r.model_used} · ${r.elapsed_sec}s`,
});
onChange && onChange();
} catch (e) {
setGenMsg({ kind: "err", msg: String(e.message || e) });
} finally {
setGenerating(false);
}
};
const hooks = safeArr(plan.hooks);
const newImgs = safeArr(plan.new_images);
const techs = safeArr(plan.anti_dup_techniques);
const genItems = safeArr(gen?.items);
// 把 gen.items.filename 反查回每张 plan image
const genByIndex = {};
for (const it of genItems) genByIndex[it.index] = it;
return (
公式:{plan.formula_id || "—"}
父任务:{(meta.parent_job_id || "").slice(0, 8)}
{meta.template_name && 模板:{meta.template_name}}
目标 {replication.target_image_count || newImgs.length} 张
LLM 耗时 {meta.analyze_elapsed_sec || "—"} s
{planErr && (
方案生成异常:{planErr}
)}
{plan.skeleton && (
骨架:{plan.skeleton}
)}
{plan.emotion_curve && (
情绪曲线:{plan.emotion_curve}
)}
{/* 出图入口 —— 用户 review 完 plan 决定是否真烧 t2i */}
出图逻辑:把父帖第 i 张图作为参考图 + plan.new_images[i].prompt_zh
作为目标描述,喂给 i2i 接口(SiliconFlow FLUX / OpenAI Edits / Gemini Nano Banana / 火山 Seedream)。
保留爆款的构图、镜头、视觉氛围,只换内容;落到{" "}
data/image_clips/{childJobId}/,图片素材库自动收。
{credentials.length === 0 && (
没有任何 t2i 凭据,去 模型管理 加一条
)}
{genMsg && (
{genMsg.msg}
)}
{gen && (
上次出图:{gen.provider}:{gen.model_name}
{gen.mode &&
{gen.mode}}
{gen.ref_image_count ? ` · 用了 ${gen.ref_image_count} 张父图作 ref` : " · 无参考图(纯 t2i)"}
{" · "}{gen.ran_at?.slice(0, 19).replace("T", " ")}
{" · "}成功 {gen.ok_count} / 失败 {gen.fail_count}
{" · "}
去图片素材库看
)}
{newImgs.length === 0 ? : (
{newImgs.map((img, i) => {
const result = genByIndex[img.index ?? (i + 1)];
const url = result?.ok
? `/api/image-library/notes/${childJobId}/img/${result.filename}`
: null;
// ref 图:先看本次 gen items 里有没有给 ref_image,没有就按 (i mod parent_count) 推测
const refName = result?.ref_image;
const refUrl = refName && meta.parent_job_id
? `/api/image-library/notes/${meta.parent_job_id}/img/${refName}`
: null;
return (
{/* ref 父图(小) */}
{refUrl ? (
<>
ref
>
) : (
(无 ref)
)}
{/* 箭头 */}
→
{/* 新图 */}
{url ? (
) : (
{result?.error ? "出图失败" : "未出图"}
)}
#{img.index ?? (i + 1)}
{img.role || "—"}
{refName && (
ref: {refName}
)}
{img.purpose && {img.purpose}}
{result?.ok && 已出图}
{result?.error && 失败}
{img.prompt_zh || "—"}
{result?.error && (
{result.error}
)}
);
})}
)}
{/* 标题 / 正文 复刻链(原 vs 新对照) */}
{(plan.title_replicate || plan.caption_replicate) && (
{plan.title_replicate && (
原标题
{plan.title_replicate.original || "—"}
→
新标题
{plan.title_replicate.draft || plan.title_draft || "—"}
套用公式:{plan.title_replicate.formula_used || "—"}
{safeArr(plan.title_replicate.swap_log).length > 0 && (
字段替换:
{plan.title_replicate.swap_log.map((s, i) => (
[{s.from} → {s.to}]
))}
)}
{safeArr(plan.title_replicate.anti_dup_techniques_applied).length > 0 && (
用到的降重:{plan.title_replicate.anti_dup_techniques_applied.join(" · ")}
)}
)}
{plan.caption_replicate && (
段落功能序列对齐:
{safeArr(plan.caption_replicate.structure_followed).join(" → ")}
{safeArr(plan.caption_replicate.section_map).length > 0 && (
| 段落 |
原文摘要 |
新文段落 |
{plan.caption_replicate.section_map.map((s, i) => (
| {s.section} |
{s.parent_excerpt || "—"} |
{s.new_excerpt || "—"} |
))}
)}
新正文完整版
{plan.caption_replicate.draft || plan.caption_draft || "—"}
{safeArr(plan.caption_replicate.anti_dup_techniques_applied).length > 0 && (
用到的降重:{plan.caption_replicate.anti_dup_techniques_applied.join(" · ")}
)}
)}
{safeArr(plan.tags_draft).length > 0 && (
{plan.tags_draft.map((t, i) => {t})}
)}
)}
{/* 兼容旧版(没有 title_replicate / caption_replicate 字段时退回纯 draft 卡) */}
{!plan.title_replicate && !plan.caption_replicate && (plan.title_draft || plan.caption_draft) && (
{plan.title_draft || "—"}
{safeArr(plan.tags_draft).length > 0 && (
{plan.tags_draft.map((t, i) => {t})}
)}
{plan.caption_draft || "—"}
)}
{hooks.length > 0 && (
| 位置 |
钩子类型 |
用户心理触发 |
{hooks.map((h, i) => (
| {h.position || "—"} |
{h.hook_type || "—"} |
{h.psych_trigger || "—"} |
))}
)}
{techs.length > 0 && (
{techs.map((t, i) => {t})}
)}
);
};
// ============== 图文笔记视图 ==============
const ImagePostReport = ({ meta, post }) => {
const urls = safeArr(post.image_urls);
const names = safeArr(post.image_names);
const analysis = post.analysis || null;
const analysisErr = post.analysis_error || (analysis && analysis._error);
// 图文复刻 entry 状态
const [imageReplicateTemplates, setImageReplicateTemplates] = useState([]);
const [chosenReplicateTpl, setChosenReplicateTpl] = useState(() => {
try { return localStorage.getItem("rr.imageReplicate.tplId") || ""; } catch { return ""; }
});
const [rulePacks, setRulePacks] = useState([]);
const [chosenRulePackId, setChosenRulePackId] = useState(() => {
try { return localStorage.getItem("rr.imageReplicate.ruleId") || ""; } catch { return ""; }
});
const [imageCountInput, setImageCountInput] = useState("");
const [replicating, setReplicating] = useState(false);
const [replicateMsg, setReplicateMsg] = useState(null);
useEffect(() => {
Promise.all([
API.templates.list({ category: "image_replicate" }).catch(() => ({ items: [] })),
API.templates.list({ category: "replicate" }).catch(() => ({ items: [] })),
]).then(([a, b]) => {
setImageReplicateTemplates([...(a.items || []), ...(b.items || [])]);
});
API.rulePacks.list().catch(() => ({ items: [] }))
.then(d => setRulePacks((d.items || []).filter(r => r.enabled !== false)));
}, []);
const onTplChange = (v) => {
setChosenReplicateTpl(v);
try { localStorage.setItem("rr.imageReplicate.tplId", v); } catch {}
};
const onRuleChange = (v) => {
setChosenRulePackId(v);
try { localStorage.setItem("rr.imageReplicate.ruleId", v); } catch {}
};
const startReplicate = async () => {
if (replicating || !meta.job_id) return;
setReplicating(true);
setReplicateMsg(null);
try {
const body = {};
if (chosenReplicateTpl) body.template_id = Number(chosenReplicateTpl);
if (imageCountInput) body.image_count = Number(imageCountInput);
if (chosenRulePackId) body.rule_pack_id = chosenRulePackId;
const r = await API.replicate.start(meta.job_id, body);
const tpl = imageReplicateTemplates.find(t => String(t.id) === String(chosenReplicateTpl));
const rule = rulePacks.find(rp => rp.pack_id === chosenRulePackId);
const tplLabel = `模板:${tpl?.name || "默认(图文爆款复刻 v1.0)"}`;
const ruleLabel = rule ? ` · 规则:${rule.display_name}` : " · 无规则限制";
setReplicateMsg({
kind: "ok",
msg: `已发起图文复刻 · 任务 ${r.child_job_id} · ${tplLabel}${ruleLabel}`,
childId: r.child_job_id,
});
} catch (e) {
setReplicateMsg({ kind: "err", msg: String(e.message || e) });
} finally {
setReplicating(false);
}
};
return (
来源:{post.source || meta.source || "xhs_image_post"}
共 {post.image_count || urls.length} 张
{meta.industry_key && 行业:{meta.industry_key}}
{meta.analyze_template_name && (
模板:{meta.analyze_template_name}
)}
{meta.saved_at && (
入库 {meta.saved_at.slice(0, 19).replace("T", " ")}
)}
{(post.link_url || meta.source_url) && (
)}
这条小红书链接是
图文笔记,已自动落 {urls.length} 张图到
资产 → 图片素材库,并按「图文拆解模板」做了 LLM 拆解。
→ 去图片素材库
{/* 图文复刻入口:选模板 + 张数 → POST /replicate */}
图文复刻
按「图文爆款复刻」模板复刻一条新笔记(标题 + 正文 + 每张图的中文 prompt)
setImageCountInput(e.target.value.replace(/\D/g, ""))}
disabled={replicating}
placeholder={`图片张数(默认 ${urls.length})`}
style={{ fontSize: 11.5, padding: "4px 8px", width: 130,
border: "1px solid var(--line-1)", borderRadius: 4,
background: "white", color: "var(--fg-0)",
fontFamily: "inherit" }}/>
{replicateMsg && (
{replicateMsg.msg}
{replicateMsg.childId && (
查看 JSON
)}
)}
{urls.length === 0 ? (
) : (
{urls.map((u, i) => (
{names[i] || `${i + 1}`}
))}
)}
{analysisErr && (
{analysisErr}
{analysis?._raw_text && (
{analysis._raw_text}
)}
)}
{/* 原始爆款文案:标题 + 正文 —— downloader 抠下来 */}
{(post.original_title || post.original_caption) && (
{post.original_title && (
{post.original_title}
)}
{post.original_caption && (
{post.original_caption}
)}
)}
{analysis && !analysisErr && (
{analysis.stream_verdict && (
)}
{analysis.title_breakdown && (
{analysis.title_breakdown.key_elements && (
关键元素:
数字 [{safeArr(analysis.title_breakdown.key_elements.numbers).join("、") || "—"}] ·
痛点 [{safeArr(analysis.title_breakdown.key_elements.pain_points).join("、") || "—"}] ·
效果 [{safeArr(analysis.title_breakdown.key_elements.outcomes).join("、") || "—"}]
)}
)}
{analysis.caption_breakdown && (
{analysis.caption_breakdown.key_elements && (
关键元素:
钩子句 {safeArr(analysis.caption_breakdown.key_elements.hooks).length} 条 ·
产品 [{safeArr(analysis.caption_breakdown.key_elements.products).join("、") || "—"}] ·
CTA [{safeArr(analysis.caption_breakdown.key_elements.cta).join("、") || "—"}]
)}
)}
{analysis.dual_dimension && (
)}
{analysis.hook && (
)}
{analysis.image_breakdown && (
| # |
角色 |
画面摘要 |
作用 |
{safeArr(analysis.image_breakdown).map((b, i) => (
| {b.index ?? (i + 1)} |
{b.role || "—"} |
{b.visual_summary || "—"} |
{b.purpose || "—"} |
))}
)}
{analysis.copy_structure && (
)}
{analysis.visual_rhythm && (
)}
{analysis.emotion_curve && (
{analysis.emotion_curve}
)}
{analysis.aids && (
{Object.entries(analysis.aids).map(([k, v]) => (
))}
)}
{analysis.rewrite_suggestion && (
{analysis.rewrite_suggestion}
)}
)}
);
};
return { ResultReport };
})();
const ResultReport = RR.ResultReport;
Object.assign(window, { ResultReport });