auto-save 2026-05-13 18:57 (+1, ~3)
This commit is contained in:
@@ -2142,6 +2142,19 @@
|
|||||||
"type": "session-heartbeat",
|
"type": "session-heartbeat",
|
||||||
"message": "Codex 会话活跃 · 最近命令:codex · 1 项未提交变更 · 最近提交:auto-save 2026-05-13 18:46 (~1)",
|
"message": "Codex 会话活跃 · 最近命令:codex · 1 项未提交变更 · 最近提交:auto-save 2026-05-13 18:46 (~1)",
|
||||||
"files_changed": 1
|
"files_changed": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-05-13T18:52:04+08:00",
|
||||||
|
"type": "commit",
|
||||||
|
"message": "auto-save 2026-05-13 18:51 (~1)",
|
||||||
|
"hash": "417cbe1",
|
||||||
|
"files_changed": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-05-13T10:59:29Z",
|
||||||
|
"type": "session-heartbeat",
|
||||||
|
"message": "Codex 会话活跃 · 最近命令:codex · 3 项未提交变更 · 最近提交:auto-save 2026-05-13 18:51 (~1)",
|
||||||
|
"files_changed": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,3 +19,11 @@
|
|||||||
- 部署完成后,不允许在 `.project.json` 缺少最新公网链接的状态下结束任务
|
- 部署完成后,不允许在 `.project.json` 缺少最新公网链接的状态下结束任务
|
||||||
- 部署完成后,必须同步更新 `RULES.md` 的部署事实
|
- 部署完成后,必须同步更新 `RULES.md` 的部署事实
|
||||||
- 如果只更新了代码但没回写部署元数据,这个任务不算完成
|
- 如果只更新了代码但没回写部署元数据,这个任务不算完成
|
||||||
|
|
||||||
|
## Source Analysis Contract
|
||||||
|
|
||||||
|
- 项目内源码解析页固定为 `docs/source-analysis.html`
|
||||||
|
- 该页面用于帮助用户把产品需求准确描述到源码位置:功能区、节点职责、数据模型、接口、变更影响都要能查到
|
||||||
|
- 任何改动只要影响产品理解、节点职责、界面行为、数据模型、API、运行方式或用户操作路径,必须在同一次任务里更新 `docs/source-analysis.html`
|
||||||
|
- 更新时至少补充“变更记录”,必要时同步更新源码结构地图、界面区域到源码、数据模型、接口地图、节点职责边界
|
||||||
|
- 不要把源码解析页接入主应用路由,除非用户明确要求;它默认是项目内独立 HTML 文档
|
||||||
|
|||||||
4
RULES.md
4
RULES.md
@@ -40,4 +40,6 @@
|
|||||||
- 任何部署或域名变化,都要先改元数据,再视为任务完成
|
- 任何部署或域名变化,都要先改元数据,再视为任务完成
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
- 待补充
|
- 项目内源码解析页:`docs/source-analysis.html`
|
||||||
|
- 源码解析页是给产品协作和需求描述用的独立 HTML,不接入 Next 应用路由
|
||||||
|
- 后续任何功能、节点职责、接口、数据模型或用户操作路径变更,都要同步更新 `docs/source-analysis.html` 的对应章节和变更记录
|
||||||
|
|||||||
924
docs/source-analysis.html
Normal file
924
docs/source-analysis.html
Normal file
@@ -0,0 +1,924 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>SKG TK 二创验证 · 源码解析与协作地图</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #f8fafc;
|
||||||
|
--panel: #ffffff;
|
||||||
|
--panel-soft: #f1f5f9;
|
||||||
|
--ink: #0f172a;
|
||||||
|
--muted: #64748b;
|
||||||
|
--line: #dbe3ed;
|
||||||
|
--blue: #2563eb;
|
||||||
|
--violet: #7c3aed;
|
||||||
|
--orange: #ea580c;
|
||||||
|
--green: #059669;
|
||||||
|
--rose: #e11d48;
|
||||||
|
--code: #111827;
|
||||||
|
--shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
html { scroll-behavior: smooth; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--ink);
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(37, 99, 235, 0.06), transparent 340px),
|
||||||
|
var(--bg);
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
a { color: inherit; }
|
||||||
|
code, pre {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 280px minmax(0, 1fr);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 24px 18px;
|
||||||
|
border-right: 1px solid var(--line);
|
||||||
|
background: rgba(255, 255, 255, 0.82);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(1180px, calc(100vw - 320px));
|
||||||
|
padding: 34px 40px 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
padding-bottom: 18px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
.brand h1 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.25;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
.brand p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 10px;
|
||||||
|
margin: 2px 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #334155;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
nav a:hover,
|
||||||
|
nav a:focus {
|
||||||
|
color: var(--blue);
|
||||||
|
background: #eff6ff;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
.search {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.search:focus {
|
||||||
|
border-color: rgba(37, 99, 235, 0.55);
|
||||||
|
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12);
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.button:hover { border-color: #94a3b8; color: var(--ink); }
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
padding: 28px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 18px;
|
||||||
|
background: var(--panel);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.hero .eyebrow {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--blue);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.hero h2 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
max-width: 980px;
|
||||||
|
font-size: clamp(30px, 5vw, 54px);
|
||||||
|
line-height: 1.04;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
.hero p {
|
||||||
|
max-width: 880px;
|
||||||
|
margin: 0;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
padding: 13px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
.meta b {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
.meta span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-top: 28px;
|
||||||
|
padding: 24px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 18px;
|
||||||
|
background: rgba(255, 255, 255, 0.86);
|
||||||
|
box-shadow: 0 10px 35px rgba(15, 23, 42, 0.045);
|
||||||
|
}
|
||||||
|
section h2 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 24px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
section > p {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
|
||||||
|
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
|
||||||
|
.grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.card h3 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.card p,
|
||||||
|
.card li {
|
||||||
|
color: #475569;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.card p:last-child { margin-bottom: 0; }
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 24px;
|
||||||
|
padding: 0 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
.tag.blue { background: #dbeafe; color: #1d4ed8; }
|
||||||
|
.tag.violet { background: #ede9fe; color: #6d28d9; }
|
||||||
|
.tag.orange { background: #ffedd5; color: #c2410c; }
|
||||||
|
.tag.green { background: #d1fae5; color: #047857; }
|
||||||
|
.tag.rose { background: #ffe4e6; color: #be123c; }
|
||||||
|
.tag.gray { background: #e2e8f0; color: #475569; }
|
||||||
|
|
||||||
|
.pipeline {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(8, minmax(120px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
.step {
|
||||||
|
min-width: 138px;
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.step .num {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: var(--blue);
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.step h3 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.step p {
|
||||||
|
margin: 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 11px 12px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
tr:last-child td { border-bottom: 0; }
|
||||||
|
td code {
|
||||||
|
color: #0f172a;
|
||||||
|
background: #f1f5f9;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 12px 0 0;
|
||||||
|
padding: 14px;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--code);
|
||||||
|
color: #e5e7eb;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1e3a8a;
|
||||||
|
}
|
||||||
|
.callout.warn {
|
||||||
|
border-color: #fed7aa;
|
||||||
|
background: #fff7ed;
|
||||||
|
color: #9a3412;
|
||||||
|
}
|
||||||
|
.callout.good {
|
||||||
|
border-color: #bbf7d0;
|
||||||
|
background: #f0fdf4;
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
.callout p { margin: 0; }
|
||||||
|
|
||||||
|
.flow {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.flow-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 170px minmax(0, 1fr) minmax(0, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.flow-row > div {
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.flow-row strong { display: block; margin-bottom: 4px; }
|
||||||
|
.flow-row span { color: var(--muted); }
|
||||||
|
|
||||||
|
.todo {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
counter-reset: todo;
|
||||||
|
}
|
||||||
|
.todo-item {
|
||||||
|
position: relative;
|
||||||
|
padding: 14px 14px 14px 46px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.todo-item::before {
|
||||||
|
counter-increment: todo;
|
||||||
|
content: counter(todo);
|
||||||
|
position: absolute;
|
||||||
|
left: 14px;
|
||||||
|
top: 14px;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 7px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
.todo-item h3 { margin: 0 0 4px; font-size: 14px; }
|
||||||
|
.todo-item p { margin: 0; color: var(--muted); font-size: 13px; }
|
||||||
|
|
||||||
|
.changelog {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.change {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.change header {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
.change h3 { margin: 0; font-size: 14px; }
|
||||||
|
.change .body { padding: 14px; }
|
||||||
|
.change .body p { margin: 0 0 8px; color: #475569; font-size: 13px; }
|
||||||
|
.change .body p:last-child { margin-bottom: 0; }
|
||||||
|
|
||||||
|
.hidden-by-search { display: none !important; }
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.shell { display: block; }
|
||||||
|
aside {
|
||||||
|
position: static;
|
||||||
|
height: auto;
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
width: 100%;
|
||||||
|
padding: 24px 18px 56px;
|
||||||
|
}
|
||||||
|
.meta-grid,
|
||||||
|
.grid-2,
|
||||||
|
.grid-3,
|
||||||
|
.grid-4,
|
||||||
|
.flow-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
aside, .toolbar { display: none; }
|
||||||
|
.shell { display: block; }
|
||||||
|
main { width: 100%; padding: 0; }
|
||||||
|
section, .hero { box-shadow: none; break-inside: avoid; }
|
||||||
|
body { background: #fff; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="shell">
|
||||||
|
<aside>
|
||||||
|
<div class="brand">
|
||||||
|
<h1>SKG TK 二创验证</h1>
|
||||||
|
<p>源码解析与协作地图。用于把产品需求准确映射到代码位置。</p>
|
||||||
|
</div>
|
||||||
|
<nav aria-label="页面目录">
|
||||||
|
<a href="#purpose">为什么有这页</a>
|
||||||
|
<a href="#how-to-use">怎么用它描述需求</a>
|
||||||
|
<a href="#runtime">运行与入口</a>
|
||||||
|
<a href="#pipeline">业务管线</a>
|
||||||
|
<a href="#source-map">源码结构地图</a>
|
||||||
|
<a href="#ui-map">界面区域到源码</a>
|
||||||
|
<a href="#data-model">数据模型</a>
|
||||||
|
<a href="#api-map">接口地图</a>
|
||||||
|
<a href="#node-contract">节点职责边界</a>
|
||||||
|
<a href="#current-state">当前已通与阻塞</a>
|
||||||
|
<a href="#request-language">需求描述模板</a>
|
||||||
|
<a href="#change-log">变更记录</a>
|
||||||
|
<a href="#update-rule">更新规则</a>
|
||||||
|
</nav>
|
||||||
|
<div class="toolbar">
|
||||||
|
<input id="search" class="search" type="search" placeholder="搜索:节点 / 文件 / 接口 / 功能" />
|
||||||
|
<button class="button" type="button" onclick="window.print()">打印</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="hero" id="purpose" data-search>
|
||||||
|
<div class="eyebrow">Source Analysis · 2026-05-13</div>
|
||||||
|
<h2>这个页面是产品协作地图,不是应用功能页。</h2>
|
||||||
|
<p>
|
||||||
|
它把“你看到的界面、你想改的功能、实际要动的源码、可能影响的数据和接口”放在同一个地方。
|
||||||
|
后续描述需求时,可以直接说“改源码地图里的某个区域 / 某个节点职责 / 某个接口行为”,这样改动范围会更准,也更容易追踪每次变更带来的影响。
|
||||||
|
</p>
|
||||||
|
<div class="meta-grid">
|
||||||
|
<div class="meta"><b>项目路径</b><span>/Users/kangwan/Projects/business/20260512-20260512-skg-tk-二创验证</span></div>
|
||||||
|
<div class="meta"><b>前端</b><span>Next.js 16 · 端口 4290 · web/app/page.tsx</span></div>
|
||||||
|
<div class="meta"><b>后端</b><span>FastAPI · 端口 4291 · api/main.py</span></div>
|
||||||
|
<div class="meta"><b>本文档位置</b><span>docs/source-analysis.html · 独立文件,不接入应用</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section id="how-to-use" data-search>
|
||||||
|
<h2>怎么用它描述需求</h2>
|
||||||
|
<div class="grid-3">
|
||||||
|
<div class="card">
|
||||||
|
<h3>1. 先说你在改哪个产品区</h3>
|
||||||
|
<p>例如“镜头拆解 / 元素提取面板”、“元素改造 Storyboard 节点”、“分镜头编排工作台 4 图槽”。不要只说“这里乱”,要指向页面里的功能区。</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>2. 再说这个区应该承担什么职责</h3>
|
||||||
|
<p>例如“Vision 只给候选元素,用户必须能编辑、删除、重新提取”,这会直接落到 <code>FrameLightbox</code> 和元素接口。</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>3. 最后说不希望发生什么</h3>
|
||||||
|
<p>例如“点击元素不要跳页面”、“不要直接进入编排打断思路”、“不要把参考视频复刻成一样的东西”。这能约束交互和文案。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="callout good" style="margin-top:14px">
|
||||||
|
<p>建议表达格式:我要改「功能区」;当前问题是「行为」;正确职责是「业务目的」;不要影响「已有流程」。</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="runtime" data-search>
|
||||||
|
<h2>运行与入口</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr><th>项目</th><th>命令 / 入口</th><th>说明</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>前端开发服务</td>
|
||||||
|
<td><code>cd web && pnpm dev</code></td>
|
||||||
|
<td>Next.js App Router,主页面是 <code>web/app/page.tsx</code>,默认端口 4290。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>后端开发服务</td>
|
||||||
|
<td><code>cd api && source .venv/bin/activate && uvicorn main:app --port 4291 --reload</code></td>
|
||||||
|
<td>FastAPI,所有任务状态、视频、关键帧、清洗、元素、分镜保存都在 <code>api/main.py</code>。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>测试页面</td>
|
||||||
|
<td><code>http://localhost:4290/?job=c6767f3a166b</code></td>
|
||||||
|
<td>URL 里可放多个 job id:<code>?job=id1,id2,id3</code>,前端会恢复多个任务并激活最后一个。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>源码解析页</td>
|
||||||
|
<td><code>open docs/source-analysis.html</code></td>
|
||||||
|
<td>独立静态 HTML,不被 Next 构建、不影响产品工作台。</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="pipeline" data-search>
|
||||||
|
<h2>业务管线</h2>
|
||||||
|
<p>当前产品不是“复制别人的视频”,而是拆解参考视频,提取可借鉴的镜头元素,再改造成 SKG 产品语境的视频素材。</p>
|
||||||
|
<div class="pipeline">
|
||||||
|
<div class="step"><div class="num">1</div><h3>输入</h3><p>TK 链接或本地上传,后端下载/保存源视频。</p></div>
|
||||||
|
<div class="step"><div class="num">2</div><h3>镜头拆解</h3><p>拆轨、抽关键帧、手动加帧,形成参考分镜池。</p></div>
|
||||||
|
<div class="step"><div class="num">3</div><h3>清洗水印</h3><p>对关键帧做全图或区域清洗,必要时应用为当前参考图。</p></div>
|
||||||
|
<div class="step"><div class="num">4</div><h3>Vision 识别</h3><p>识别场景和候选元素,只是候选,不应锁死。</p></div>
|
||||||
|
<div class="step"><div class="num">5</div><h3>元素提取</h3><p>编辑/新增/删除元素,对元素反复生成提取图。</p></div>
|
||||||
|
<div class="step"><div class="num">6</div><h3>元素改造</h3><p>把参考主体、场景、动作和 SKG 产品放入分镜结构。</p></div>
|
||||||
|
<div class="step"><div class="num">7</div><h3>生成视频</h3><p>用分镜结构生成首帧和视频片段。当前未实现。</p></div>
|
||||||
|
<div class="step"><div class="num">8</div><h3>合成成品</h3><p>片段、字幕、配音、转场合成最终 mp4。当前未实现。</p></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="source-map" data-search>
|
||||||
|
<h2>源码结构地图</h2>
|
||||||
|
<div class="grid-2">
|
||||||
|
<div class="card">
|
||||||
|
<h3>前端核心</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>web/app/page.tsx</code></td><td>产品工作台主状态:jobs、activeJobId、selectedFrames、clipboard、ReactFlow 节点和边。</td></tr>
|
||||||
|
<tr><td><code>web/components/nodes/index.tsx</code></td><td>DAG 节点定义:Input、Keyframe、ASR、Translate、Rewrite、Storyboard、VideoGen、Compose。</td></tr>
|
||||||
|
<tr><td><code>web/components/lightbox.tsx</code></td><td>镜头拆解和元素提取的主工作面板:清洗、识别、元素编辑、区域提取、抠图。</td></tr>
|
||||||
|
<tr><td><code>web/components/storyboard-bar.tsx</code></td><td>顶部已选分镜条:展示选入编排的关键帧,点击进入工作台。</td></tr>
|
||||||
|
<tr><td><code>web/components/storyboard-workbench.tsx</code></td><td>全屏分镜编排工作台:4 图槽、改造目标、时长、自动保存。</td></tr>
|
||||||
|
<tr><td><code>web/lib/api.ts</code></td><td>前端类型和 API client,是前后端数据契约镜像。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>后端核心</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>api/main.py</code></td><td>FastAPI 单文件后端:状态模型、任务恢复、下载、抽帧、Vision、清洗、元素、分镜、文件返回。</td></tr>
|
||||||
|
<tr><td><code>jobs/<jobId>/state.json</code></td><td>运行时状态文件,不在源码列表里,但刷新恢复依赖它。</td></tr>
|
||||||
|
<tr><td><code>jobs/<jobId>/frames</code></td><td>关键帧 jpg。注意 frame.index 是稳定 ID,不等于数组下标。</td></tr>
|
||||||
|
<tr><td><code>jobs/<jobId>/cleaned</code></td><td>清洗后待应用图片。</td></tr>
|
||||||
|
<tr><td><code>jobs/<jobId>/elements</code></td><td>元素提取图,多版本命名:<code>idx_elementId_cutoutId.jpg</code>。</td></tr>
|
||||||
|
<tr><td><code>jobs/<jobId>/gen</code></td><td>关键帧生图结果。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<pre>前端主链路:
|
||||||
|
web/app/page.tsx
|
||||||
|
-> ReactFlow 节点:web/components/nodes/index.tsx
|
||||||
|
-> 镜头拆解面板:web/components/lightbox.tsx
|
||||||
|
-> 顶部分镜条:web/components/storyboard-bar.tsx
|
||||||
|
-> 分镜工作台:web/components/storyboard-workbench.tsx
|
||||||
|
-> API 契约:web/lib/api.ts
|
||||||
|
|
||||||
|
后端主链路:
|
||||||
|
api/main.py
|
||||||
|
-> Job / KeyFrame / KeyElement / StoryboardScene
|
||||||
|
-> 下载 / 上传 / 抽帧 / Vision / 清洗 / 元素提取 / 分镜保存
|
||||||
|
-> jobs/<jobId>/state.json + 图片文件落盘</pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="ui-map" data-search>
|
||||||
|
<h2>界面区域到源码</h2>
|
||||||
|
<div class="flow">
|
||||||
|
<div class="flow-row">
|
||||||
|
<div><strong>你看到的区域</strong><span>Input 节点和视频缩略图</span></div>
|
||||||
|
<div><strong>主要源码</strong><span><code>InputNode</code> in <code>web/components/nodes/index.tsx</code>;状态处理在 <code>page.tsx</code>。</span></div>
|
||||||
|
<div><strong>适合怎么描述</strong><span>“输入/下载/视频就绪这个节点应该如何提示用户下一步”。</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flow-row">
|
||||||
|
<div><strong>你看到的区域</strong><span>镜头拆解节点上方关键帧</span></div>
|
||||||
|
<div><strong>主要源码</strong><span><code>KeyframeNode</code> 和 <code>FrameLightbox</code>;后端 <code>/frames</code>、<code>/describe</code>、<code>/cleanup</code>。</span></div>
|
||||||
|
<div><strong>适合怎么描述</strong><span>“关键帧选用、清洗、识别、元素提取的操作顺序要怎么组织”。</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flow-row">
|
||||||
|
<div><strong>你看到的区域</strong><span>元素列表和提取图</span></div>
|
||||||
|
<div><strong>主要源码</strong><span><code>FrameLightbox</code>;类型 <code>KeyElement</code>;接口 <code>addElement/updateElement/deleteElement/cutoutElement/deleteCutout</code>。</span></div>
|
||||||
|
<div><strong>适合怎么描述</strong><span>“Vision 识别出来的是候选,用户要能修正、重复提取、删除错误元素”。</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flow-row">
|
||||||
|
<div><strong>你看到的区域</strong><span>元素改造 · Storyboard 节点</span></div>
|
||||||
|
<div><strong>主要源码</strong><span><code>StoryboardNode</code>;上方元素缩略图来自所有已提取 cutouts。</span></div>
|
||||||
|
<div><strong>适合怎么描述</strong><span>“这里是素材入口,不是最终视频编辑器;点击是否进入工作台要不要打断当前任务”。</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flow-row">
|
||||||
|
<div><strong>你看到的区域</strong><span>顶部分镜条</span></div>
|
||||||
|
<div><strong>主要源码</strong><span><code>StoryboardBar</code>;只展示 selectedFrames,不负责提取元素。</span></div>
|
||||||
|
<div><strong>适合怎么描述</strong><span>“选好的分镜如何按时间序组织,以及如何进入具体分镜编排”。</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flow-row">
|
||||||
|
<div><strong>你看到的区域</strong><span>分镜头编排工作台</span></div>
|
||||||
|
<div><strong>主要源码</strong><span><code>StoryboardWorkbench</code>;保存到 <code>frame.storyboard</code>;接口 <code>PUT /storyboard</code>。</span></div>
|
||||||
|
<div><strong>适合怎么描述</strong><span>“每个分镜需要哪些图片槽、哪些改造说明,如何为视频生成做准备”。</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="data-model" data-search>
|
||||||
|
<h2>数据模型</h2>
|
||||||
|
<div class="grid-2">
|
||||||
|
<div class="card">
|
||||||
|
<h3>Job</h3>
|
||||||
|
<p>一个视频任务。前端维护多个 <code>jobs[]</code>,当前激活的是 <code>activeJobId</code>。URL 查询参数会持久化多个 job。</p>
|
||||||
|
<pre>Job {
|
||||||
|
id, url, status, progress, message,
|
||||||
|
video_url, duration, width, height,
|
||||||
|
frames: KeyFrame[],
|
||||||
|
transcript: TranscriptSegment[],
|
||||||
|
storyboard_images?: StoryboardImage[]
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>KeyFrame</h3>
|
||||||
|
<p>关键帧是整个产品的核心单位。<code>index</code> 是稳定 ID,手动加帧后不连续,不能用数组下标代替。</p>
|
||||||
|
<pre>KeyFrame {
|
||||||
|
index, timestamp, url,
|
||||||
|
description,
|
||||||
|
cleaned_url, cleaned_applied,
|
||||||
|
elements: KeyElement[],
|
||||||
|
storyboard: StoryboardScene,
|
||||||
|
generated_images: GeneratedImage[]
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>KeyElement</h3>
|
||||||
|
<p>从关键帧里识别或手动添加的可借鉴元素。Vision 给的是候选,用户可编辑,并可多次生成提取图。</p>
|
||||||
|
<pre>KeyElement {
|
||||||
|
id,
|
||||||
|
name_zh, name_en, position,
|
||||||
|
source: auto | manual | region,
|
||||||
|
region,
|
||||||
|
cutouts: string[],
|
||||||
|
cutout_id
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>StoryboardScene</h3>
|
||||||
|
<p>分镜编排结果,不是复刻说明。它把参考图和 SKG 改造方向绑定到一个分镜上。</p>
|
||||||
|
<pre>StoryboardScene {
|
||||||
|
duration,
|
||||||
|
subject_image,
|
||||||
|
scene_image,
|
||||||
|
product_image,
|
||||||
|
action_image,
|
||||||
|
subject,
|
||||||
|
product,
|
||||||
|
scene,
|
||||||
|
action
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="api-map" data-search>
|
||||||
|
<h2>接口地图</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr><th>功能</th><th>接口</th><th>前端调用</th><th>说明</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>创建任务</td><td><code>POST /jobs</code></td><td><code>createJob</code></td><td>提交 TK 链接,后台开始下载,停在 downloaded 等用户点解析。</td></tr>
|
||||||
|
<tr><td>上传视频</td><td><code>POST /jobs/upload</code></td><td><code>uploadJob</code></td><td>保存 source.mp4,然后同样进入下载完成状态。</td></tr>
|
||||||
|
<tr><td>解析视频</td><td><code>POST /jobs/{id}/analyze</code></td><td><code>analyzeJob</code></td><td>拆轨 + 抽关键帧。当前不自动跑 ASR,避免 audio 阻塞视觉管线。</td></tr>
|
||||||
|
<tr><td>手动加帧</td><td><code>POST /jobs/{id}/frames?t=</code></td><td><code>addManualFrame</code></td><td>按视频时间戳抽一帧,index 递增但 frames 按 timestamp 排序。</td></tr>
|
||||||
|
<tr><td>Vision 识别</td><td><code>POST /frames/{idx}/describe</code></td><td><code>describeFrame</code></td><td>写入 frame.description,后续可从 objects 加候选元素。</td></tr>
|
||||||
|
<tr><td>清洗水印</td><td><code>POST /frames/{idx}/cleanup</code></td><td><code>cleanupFrame</code></td><td>支持全图和区域清洗,生成 cleaned 待应用版本。</td></tr>
|
||||||
|
<tr><td>应用清洗</td><td><code>POST /cleanup/apply</code></td><td><code>applyCleanedFrame</code></td><td>物理覆盖 frames/{idx}.jpg,并备份原图。</td></tr>
|
||||||
|
<tr><td>元素增改删</td><td><code>POST/PATCH/DELETE /elements</code></td><td><code>addElement/updateElement/deleteElement</code></td><td>让用户修正 Vision 错误,避免候选结果锁死。</td></tr>
|
||||||
|
<tr><td>元素提取</td><td><code>POST /elements/{element_id}/cutout</code></td><td><code>cutoutElement</code></td><td>调用图像模型生成独立白底素材图,每次累积一张 cutout。</td></tr>
|
||||||
|
<tr><td>分镜保存</td><td><code>PUT /frames/{idx}/storyboard</code></td><td><code>updateStoryboard</code></td><td>保存 4 图槽、时长和改造说明。</td></tr>
|
||||||
|
<tr><td>生图</td><td><code>POST /frames/{idx}/generate</code></td><td><code>generateImage</code></td><td>基于关键帧或已选生成图做 image-to-image,目前可用。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="node-contract" data-search>
|
||||||
|
<h2>节点职责边界</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr><th>节点</th><th>当前职责</th><th>不该承担</th><th>改动主要文件</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><span class="tag blue">输入 Input</span></td>
|
||||||
|
<td>创建/上传任务,显示视频就绪,触发解析。</td>
|
||||||
|
<td>不要自动一路跑到 ASR 或生图;用户需要控制解析节奏。</td>
|
||||||
|
<td><code>page.tsx</code>、<code>InputNode</code>、<code>api/main.py</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="tag orange">镜头拆解 / 元素提取</span></td>
|
||||||
|
<td>关键帧选择、清洗、Vision 候选、元素编辑、区域提取、元素 cutout。</td>
|
||||||
|
<td>不要把 Vision 结果当最终结论;不要点击元素就跳走。</td>
|
||||||
|
<td><code>KeyframeNode</code>、<code>FrameLightbox</code>、元素接口</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="tag violet">元素改造 Storyboard</span></td>
|
||||||
|
<td>展示可用元素素材,承接“参考元素 → SKG 画面”的入口。</td>
|
||||||
|
<td>不要等同于最终视频生成;不要暗示复刻原视频。</td>
|
||||||
|
<td><code>StoryboardNode</code>、<code>StoryboardBar</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="tag violet">分镜工作台</span></td>
|
||||||
|
<td>每个分镜填 4 图槽和改造 brief,为后续生成首帧/视频片段做准备。</td>
|
||||||
|
<td>当前不负责真实调用视频模型。</td>
|
||||||
|
<td><code>StoryboardWorkbench</code>、<code>updateStoryboard</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="tag gray">ASR / Translate / Rewrite</span></td>
|
||||||
|
<td>未来的文案轨,目前部分占位或受 audio 阻塞。</td>
|
||||||
|
<td>不要阻断视觉素材管线。</td>
|
||||||
|
<td><code>ASRNode</code>、<code>TranslateNode</code>、<code>RewriteNode</code>、ASR 接口</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="tag green">Video Gen / Compose</span></td>
|
||||||
|
<td>未来生成视频和合成成品。</td>
|
||||||
|
<td>当前只是占位,不要描述成已打通。</td>
|
||||||
|
<td><code>VideoGenNode</code>、<code>ComposeNode</code>、未来模型接口</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="current-state" data-search>
|
||||||
|
<h2>当前已通与阻塞</h2>
|
||||||
|
<div class="grid-2">
|
||||||
|
<div class="card">
|
||||||
|
<h3>已通</h3>
|
||||||
|
<ul>
|
||||||
|
<li>TK 链接 / 上传创建 job。</li>
|
||||||
|
<li>视频下载或本地保存,ffmpeg 抽关键帧。</li>
|
||||||
|
<li>手动按时间戳加关键帧。</li>
|
||||||
|
<li>关键帧清洗水印,全图或区域清洗。</li>
|
||||||
|
<li>Vision 识别关键帧,输出 scene、objects、style、suggested_prompt。</li>
|
||||||
|
<li>元素增改删、区域元素添加、元素多次提取图。</li>
|
||||||
|
<li>分镜工作台 4 图槽和改造说明自动保存。</li>
|
||||||
|
<li>nano-banana-pro image-to-image 生图。</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>阻塞 / 占位</h3>
|
||||||
|
<ul>
|
||||||
|
<li>ASR:SKG 网关 audio endpoint 404 或渠道不可用。</li>
|
||||||
|
<li>Translate:本身 text 通,但产品流里依赖 ASR 段落。</li>
|
||||||
|
<li>Rewrite:需要 SKG 产品信息模板和目标脚本结构。</li>
|
||||||
|
<li>Video Gen:sora/video endpoint 未通,Seedance/Kling/Veo3 外部 key 未接。</li>
|
||||||
|
<li>Compose:还没做本地 ffmpeg 字幕/TTS 合成。</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="callout warn" style="margin-top:14px">
|
||||||
|
<p>最重要的产品判断:当前视觉素材管线已经能继续推进,文案/音频/视频生成不要再反过来卡住镜头拆解和元素改造。</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="request-language" data-search>
|
||||||
|
<h2>需求描述模板</h2>
|
||||||
|
<div class="todo">
|
||||||
|
<div class="todo-item">
|
||||||
|
<h3>改镜头拆解 / 元素提取</h3>
|
||||||
|
<p>“我在关键帧 lightbox 里,Vision 识别后的元素列表应该怎么编辑/重提取/删除;点击元素不要跳转;提取图怎么预览和复制。”</p>
|
||||||
|
</div>
|
||||||
|
<div class="todo-item">
|
||||||
|
<h3>改 Storyboard 节点</h3>
|
||||||
|
<p>“我在 DAG 的元素改造节点,它上方缩略图展示什么、hover 预览什么、点击后是否进入工作台、是否自动选中对应分镜。”</p>
|
||||||
|
</div>
|
||||||
|
<div class="todo-item">
|
||||||
|
<h3>改分镜工作台</h3>
|
||||||
|
<p>“我在全屏分镜编排工作台,每个分镜需要哪些槽位、字段如何命名、保存后如何传给后续生成视频。”</p>
|
||||||
|
</div>
|
||||||
|
<div class="todo-item">
|
||||||
|
<h3>改数据/接口</h3>
|
||||||
|
<p>“这个动作需要持久化到 state.json,字段加在 Job/KeyFrame/KeyElement/StoryboardScene 哪一层,刷新后要恢复。”</p>
|
||||||
|
</div>
|
||||||
|
<div class="todo-item">
|
||||||
|
<h3>改节点语义</h3>
|
||||||
|
<p>“这个节点的业务职责要改,不只是 UI 文案;请同步更新节点标题、subtitle、说明、可点击行为、状态推导和本源码解析页。”</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="change-log" data-search>
|
||||||
|
<h2>变更记录</h2>
|
||||||
|
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
|
||||||
|
<div class="changelog">
|
||||||
|
<article class="change">
|
||||||
|
<header>
|
||||||
|
<h3>2026-05-13 · 新增独立源码解析与协作地图</h3>
|
||||||
|
<span class="tag blue">docs</span>
|
||||||
|
</header>
|
||||||
|
<div class="body">
|
||||||
|
<p><strong>目的:</strong>把产品功能区、源码位置、接口、数据模型、需求描述方式固定下来,减少“描述不准导致改偏”。</p>
|
||||||
|
<p><strong>影响:</strong>新增 <code>docs/source-analysis.html</code>,不接入 Next 应用,不影响工作台运行。</p>
|
||||||
|
<p><strong>以后描述:</strong>可以直接引用本页的功能区名称、节点职责和源码文件名。</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="change">
|
||||||
|
<header>
|
||||||
|
<h3>2026-05-13 · Storyboard 元素缩略图 hover 预览修复</h3>
|
||||||
|
<span class="tag violet">StoryboardNode</span>
|
||||||
|
</header>
|
||||||
|
<div class="body">
|
||||||
|
<p><strong>问题:</strong>元素改造节点上方小图 hover 没有像镜头拆解节点一样显示原图预览,并且首次修复时出现运行时错误。</p>
|
||||||
|
<p><strong>原因:</strong>节点内部 overflow 裁剪了预览;随后 portal 预览里把变量写成了不存在的 <code>aspectRatio</code>。</p>
|
||||||
|
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code>。现在 hover 展示来源原帧和提取元素,运行时异常已验证为 0。</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="change">
|
||||||
|
<header>
|
||||||
|
<h3>2026-05-13 · 元素识别结果不再锁死</h3>
|
||||||
|
<span class="tag orange">元素提取</span>
|
||||||
|
</header>
|
||||||
|
<div class="body">
|
||||||
|
<p><strong>问题:</strong>Vision 识别可能错,但元素列表像最终结果;点击提取图会跳页面,打断用户思路。</p>
|
||||||
|
<p><strong>改动:</strong>支持元素改名、改英文提示、改位置、删除元素、重复提取、删除单张提取图;提取图不再用链接跳新页。</p>
|
||||||
|
<p><strong>影响:</strong><code>FrameLightbox</code>、<code>web/lib/api.ts</code>、<code>PATCH /jobs/{job_id}/frames/{idx}/elements/{element_id}</code>。</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="change">
|
||||||
|
<header>
|
||||||
|
<h3>2026-05-13 · 分镜编排入口聚焦到具体分镜</h3>
|
||||||
|
<span class="tag violet">StoryboardWorkbench</span>
|
||||||
|
</header>
|
||||||
|
<div class="body">
|
||||||
|
<p><strong>问题:</strong>从 Storyboard 或顶部分镜条进入编排时,没有明确定位到用户正在看的那一帧。</p>
|
||||||
|
<p><strong>改动:</strong>工作台接受 <code>focusedFrame</code>,点击缩略图会打开工作台并聚焦对应分镜。</p>
|
||||||
|
<p><strong>影响:</strong><code>page.tsx</code>、<code>StoryboardBar</code>、<code>StoryboardWorkbench</code>、<code>StoryboardNode</code>。</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="change">
|
||||||
|
<header>
|
||||||
|
<h3>2026-05-13 · 视觉管线不再被 ASR 阻断</h3>
|
||||||
|
<span class="tag green">Pipeline</span>
|
||||||
|
</header>
|
||||||
|
<div class="body">
|
||||||
|
<p><strong>问题:</strong>SKG 网关 audio 不通时,视觉解析也容易被标记失败。</p>
|
||||||
|
<p><strong>改动:</strong><code>analyze</code> 主流程强调拆轨和关键帧,声音文案轨独立处理。</p>
|
||||||
|
<p><strong>影响:</strong><code>api/main.py</code>、<code>page.tsx</code>、节点语义说明。</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="update-rule" data-search>
|
||||||
|
<h2>更新规则</h2>
|
||||||
|
<div class="callout">
|
||||||
|
<p>以后任何改动只要影响产品理解、节点职责、界面行为、数据模型、API、运行方式或用户操作路径,都要同步更新本页的对应章节和“变更记录”。</p>
|
||||||
|
</div>
|
||||||
|
<table style="margin-top:14px">
|
||||||
|
<thead>
|
||||||
|
<tr><th>改动类型</th><th>必须更新本页哪里</th><th>原因</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>节点文案 / 节点功能</td><td>业务管线、节点职责边界、界面区域到源码、变更记录</td><td>避免用户按旧节点理解描述需求。</td></tr>
|
||||||
|
<tr><td>新增 / 修改接口</td><td>接口地图、数据模型、变更记录</td><td>避免前后端契约不清。</td></tr>
|
||||||
|
<tr><td>新增数据字段</td><td>数据模型、源码结构地图、变更记录</td><td>刷新恢复和 state.json 依赖字段一致。</td></tr>
|
||||||
|
<tr><td>改交互路径</td><td>界面区域到源码、需求描述模板、变更记录</td><td>用户描述“点击哪里”时必须和真实路径一致。</td></tr>
|
||||||
|
<tr><td>修 bug</td><td>变更记录;如果暴露新坑,也更新当前已通与阻塞</td><td>让后续同类问题能快速定位。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const input = document.getElementById("search");
|
||||||
|
const searchable = Array.from(document.querySelectorAll("[data-search]"));
|
||||||
|
input.addEventListener("input", () => {
|
||||||
|
const q = input.value.trim().toLowerCase();
|
||||||
|
searchable.forEach((el) => {
|
||||||
|
const text = el.innerText.toLowerCase();
|
||||||
|
el.classList.toggle("hidden-by-search", q && !text.includes(q));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user