| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- <!doctype html>
- <html lang="zh">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title>创作知识的解构</title>
- <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
- <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
- <style>
- *{box-sizing:border-box}
- html,body{margin:0;padding:0;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;font-size:13px;color:#111827;background:#f5f5f7;height:100vh;overflow:hidden}
- .app{height:100vh;display:flex;flex-direction:column}
- header.top{padding:11px 18px 9px;background:#fff;border-bottom:1px solid #e5e7eb;flex:none}
- header.top h1{margin:0 0 7px;font-size:16px;font-weight:600}
- .picker{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
- .picker .lab{font-size:11.5px;color:#9ca3af;margin-right:2px}
- .pill{font-size:12px;padding:4px 11px;border-radius:14px;border:1px solid #e5e7eb;background:#fff;color:#6b7280;cursor:pointer}
- .pill:hover{background:#f3f4f6}
- .pill.on{background:#1e293b;color:#fff;border-color:#1e293b;font-weight:600}
- .pill .n{font-size:10px;opacity:.7;margin-left:4px}
- .scroll{flex:1;overflow:auto;background:#f5f5f7;padding-bottom:40px}
- details.src{margin:10px 18px 0;background:#fff;border:1px solid #e2e8f0;border-radius:6px}
- details.src summary{padding:8px 14px;cursor:pointer;font-size:12px;color:#475569;list-style:none}
- details.src summary::before{content:'▶ ';color:#94a3b8;font-size:10px}
- details.src[open] summary::before{content:'▼ '}
- .srcbody{padding:4px 16px 12px;font-size:12px}
- .srcbody a{color:#2563eb;word-break:break-all}
- .srcprompt{color:#0e7490;font-size:11.5px;cursor:pointer}.srcprompt:hover{text-decoration:underline}
- .srcimgs{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
- .srcimgs img{width:80px;height:80px;object-fit:cover;border-radius:4px;border:1px solid #e2e8f0;cursor:zoom-in}
- .barhint{margin:8px 18px 0;font-size:11.5px;color:#6b7280}
- .ribbon{margin:11px 18px 0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:10px 14px}
- .rcap{font-size:11.5px;color:#6b7280;margin-bottom:8px}
- .rsteps{display:flex;align-items:stretch;flex-wrap:wrap}
- .rstep{flex:1;min-width:118px;border:1px solid #e2e8f0;border-radius:8px;padding:8px 9px;cursor:pointer;background:#f8fafc}
- .rstep:hover{background:#ecfeff;border-color:#99d5d0}
- .rstep.auto{cursor:default;background:#f1f5f9;opacity:.9}.rstep.auto:hover{background:#f1f5f9;border-color:#e2e8f0}
- .rtop{display:flex;align-items:center;gap:7px}
- .rn{width:18px;height:18px;border-radius:50%;background:#0e7490;color:#fff;font-size:11px;display:flex;align-items:center;justify-content:center;flex:none}
- .rstep.auto .rn{background:#94a3b8}
- .rlab{font-size:13.5px;font-weight:600}
- .rdesc{font-size:11.5px;color:#6b7280;margin:5px 0 7px;line-height:1.5}
- .rbtn{font-size:11.5px;color:#0e7490}.rstep.auto .rbtn{color:#94a3b8}
- .rarrow{display:flex;align-items:center;color:#cbd5e1;font-size:18px;padding:0 4px;flex:none}
- .pmwhy{font-size:12.5px;color:#92400e;background:#fffbeb;border:1px solid #fde68a;border-radius:6px;padding:8px 11px;margin-bottom:12px;line-height:1.6}
- .pmwhy b{color:#78350f}
- .lbtn{padding:3px 9px;border:1px solid #cbd5e1;border-radius:5px;background:#fff;cursor:pointer;font-size:11.5px;color:#334155;font-family:inherit}
- .lbtn:hover{background:#f1f5f9}.lbtn.on{background:#fef3c7;border-color:#d97706;color:#92400e;font-weight:600}
- /* 颗卡 */
- .kcard{margin:12px 18px 0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden}
- .khead{display:flex;align-items:center;gap:8px;padding:9px 14px;border-bottom:1px solid #eef2f7;flex-wrap:wrap}
- .kno{font-size:11px;font-weight:700;color:#fff;background:#475569;border-radius:4px;padding:1px 7px}
- .ktype{font-size:11px;font-weight:700;border-radius:4px;padding:1px 8px}
- .ty-how{background:#cffafe;color:#155e75}.ty-what{background:#fae8ff;color:#86198f}.ty-why{background:#fef9c3;color:#854d0e}
- .ktitle{font-size:14px;font-weight:600}
- .krole{font-size:10.5px;padding:1px 6px;border-radius:3px;background:#f1f5f9;color:#64748b}
- .kref{font-size:11px;color:#0e7490;cursor:pointer}.kref:hover{text-decoration:underline}
- .srcbtn{cursor:pointer;color:#0e7490;margin-left:3px;user-select:none}.srcbtn:hover{color:#155e75}
- .srccard{border:1px solid #e2e8f0;border-radius:6px;padding:8px 10px;margin:8px 0;background:#f8fafc}
- .srccard .ci{font-size:11.5px;color:#6b7280;font-weight:600;margin-bottom:5px}
- .srccard img{max-width:100%;max-height:360px;border-radius:5px;border:1px solid #e2e8f0;cursor:zoom-in}
- .srccard video{max-width:100%;max-height:300px;border-radius:5px}
- .srcread{font-size:12.5px;color:#1f2937;line-height:1.6;margin-top:6px;background:#fff;border-left:3px solid #0e7490;padding:6px 9px}
- .kbiz{font-size:11px;padding:1px 7px;border-radius:3px;background:#fef3c7;color:#92400e}
- .kacts{margin-left:auto;display:flex;gap:6px}
- .kbody{padding:6px 14px 13px}
- /* how 工序表 */
- .twrap{overflow:auto;border:1px solid #e5e7eb;border-radius:6px;margin-top:8px}
- table.proc{width:max-content;min-width:100%;border-collapse:collapse;font-size:12px}
- table.proc th,table.proc td{border:1px solid #e5e7eb;padding:6px 8px;vertical-align:top;line-height:1.5}
- table.proc thead th{font-weight:600;text-align:center;font-size:12px;position:sticky;top:0;z-index:3}
- table.proc thead tr:nth-child(2) th{top:30px}
- td.idx{width:30px;text-align:center;color:#6b7280;background:#f8fafc}
- th.c-intent,td.intent{width:210px;min-width:210px;max-width:210px;white-space:pre-wrap}
- th.c-cstage,td.cstage{width:54px;text-align:center;background:#ecfeff}
- th.c-act,td.act{min-width:110px;max-width:140px;word-break:break-word;background:#ecfeff}
- th.c-dir,td.dir{min-width:230px;max-width:330px;white-space:pre-wrap;word-break:break-word;background:#ecfeff}
- th.c-out,td.out{min-width:160px;max-width:240px;white-space:pre-wrap;background:#ecfeff}
- th.c-scope,td.scope{width:92px;min-width:92px;max-width:92px;word-break:break-word}
- th.g-frame{background:#0e7490;color:#fff}
- th.g-scope{background:#7c3aed;color:#fff}
- th.cstage-h{cursor:pointer}th.cstage-h:hover{background:#0e7490}
- th.c-idx{background:#475569;color:#fff;vertical-align:middle}
- thead tr:nth-child(2) th.c-intent,thead tr:nth-child(2) th.c-cstage,thead tr:nth-child(2) th.c-act,thead tr:nth-child(2) th.c-dir,thead tr:nth-child(2) th.c-out{background:#0891b2;color:#fff}
- thead tr:nth-child(2) th.s-substance{background:#dc2626;color:#fff}thead tr:nth-child(2) th.s-form{background:#2563eb;color:#fff}
- thead tr:nth-child(2) th.s-feeling{background:#db2777;color:#fff}thead tr:nth-child(2) th.s-effect{background:#16a34a;color:#fff}thead tr:nth-child(2) th.s-intent{background:#d97706;color:#fff}
- td.scope.s-substance{background:#fef2f2}td.scope.s-form{background:#eff6ff}td.scope.s-feeling{background:#fdf2f8}td.scope.s-effect{background:#f0fdf4}td.scope.s-intent{background:#fffbeb}
- tr.step>td{border-top:1.5px solid #cbd5e1}
- .cstage-chip{display:inline-block;font-size:11px;padding:1px 6px;border-radius:10px;background:#ede9fe;color:#5b21b6}
- .act-chip{display:inline-block;font-size:11.5px;padding:1px 7px;border-radius:4px;background:#fff7ed;color:#9a3412;border:1px solid #fed7aa}
- .dir-txt{font-size:11.5px;color:#33312c;line-height:1.65}.out-txt{font-size:11.5px;color:#166534}
- .in-txt{font-size:11.5px;color:#1e40af;line-height:1.6}
- .cue{font-size:11px;color:#7c2d12;background:#fff7ed;border-left:2px solid #fb923c;padding:3px 7px;margin-top:4px;border-radius:0 4px 4px 0}
- .cue b{color:#9a3412}
- .kindb{font-size:10.5px;font-weight:700;padding:1px 8px;border-radius:9px;margin-right:8px;vertical-align:middle}
- .kind-子集{background:#dbeafe;color:#1e40af}.kind-多维关系{background:#dcfce7;color:#166534}.kind-序列{background:#fef3c7;color:#92400e}
- .dimrule{font-size:10.5px;padding:1px 8px;border-radius:9px;background:#ecfccb;color:#3f6212;margin-right:8px;vertical-align:middle}
- .ksec-f.key{background:#fde68a;color:#92400e;font-weight:700}
- .glossbtn{background:#ede9fe!important;color:#6d28d9!important;border-color:#ddd6fe!important}
- .from{font-size:11px;color:#9ca3af}
- /* what/why 自由小节 */
- .def{font-size:13px;background:#fdf4ff;border-left:3px solid #a21caf;border-radius:0 6px 6px 0;padding:8px 12px;margin:6px 0 10px}
- .ksec{margin:9px 0;padding-left:11px;border-left:3px solid #e2e8f0}
- .ksec-h{display:flex;align-items:center;gap:7px;margin-bottom:4px}
- .ksec-t{font-size:13px;font-weight:600;color:#334155}
- .ksec-f{font-size:10.5px;padding:1px 7px;border-radius:9px;background:#f1f5f9;color:#64748b}
- .ksec-c{font-size:12.5px;line-height:1.65;color:#1f2937;margin-bottom:4px}
- table.elem{border-collapse:collapse;width:100%;font-size:12.5px}
- table.elem td{border:1px solid #f0e6f5;padding:6px 10px;text-align:left;vertical-align:top}
- table.elem td.el{font-weight:600;color:#86198f;background:#fdf4ff;width:130px}
- /* why 论点卡 */
- .why-row{display:flex;gap:10px;margin:7px 0}
- .why-lab{font-size:11px;font-weight:700;border-radius:4px;padding:2px 8px;flex:none;height:20px}
- .wl-claim{background:#fef9c3;color:#854d0e}.wl-basis{background:#e0e7ff;color:#3730a3}.wl-impact{background:#dcfce7;color:#166534}
- .why-txt{font-size:12.5px;line-height:1.65;color:#1f2937}
- .why-exp{font-size:12.5px;line-height:1.72;color:#1f2937;background:#fdf4ff;border-left:3px solid #a21caf;border-radius:0 6px 6px 0;padding:9px 13px;margin:6px 0}
- /* 颗级作用域 */
- .kscopes{margin-top:9px;display:flex;gap:5px;flex-wrap:wrap;align-items:center}
- .kscopes .lab{font-size:11px;color:#9ca3af}
- .sv{display:inline-block;cursor:pointer;font-size:11.5px;padding:1px 5px;border-radius:3px;border:1px solid rgba(0,0,0,.08);background:rgba(255,255,255,.7);margin:1px 0}
- .sv:hover{box-shadow:0 0 0 1px #1e293b}
- .sv .k{font-size:11.5px;opacity:.85;font-weight:700;margin-right:4px}
- table.proc th.c-scope{font-size:13.5px}
- .sv .mk{font-size:9px;padding:0 3px;border-radius:2px;margin-left:3px;vertical-align:top}
- .mk-new{background:#fde68a;color:#92400e}.mk-reuse{background:#bbf7d0;color:#166534}
- .sv.s-substance{background:#fef2f2}.sv.s-form{background:#eff6ff}.sv.s-feeling{background:#fdf2f8}.sv.s-effect{background:#f0fdf4}.sv.s-intent{background:#fffbeb}
- body.hl .sv .mk-new{outline:2px solid #d97706}
- /* drawer + modal */
- .ov{position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:100}
- .drawer{position:fixed;right:0;top:0;bottom:0;width:440px;max-width:92vw;background:#fff;box-shadow:-4px 0 16px rgba(0,0,0,.15);z-index:101;overflow-y:auto}
- .drawer h2{margin:0;padding:13px 16px;background:#1e293b;color:#fff;font-size:14px;display:flex;align-items:center}
- .drawer h2 .x{margin-left:auto;cursor:pointer;font-size:18px}
- .dc{padding:14px 16px;font-size:12.5px;line-height:1.6}.dc .row{margin:6px 0}.dc b{color:#374151}
- .verdict{display:inline-block;padding:2px 9px;border-radius:5px;font-weight:600;font-size:12px}
- .v-new{background:#fef3c7;color:#92400e}.v-reuse{background:#dcfce7;color:#166534}
- .nn{margin:5px 0;padding:7px 10px;border:1px solid #e5e7eb;border-radius:5px;background:#f8fafc}
- .nn .nm{font-weight:600}.nn .sc{float:right;color:#6b7280;font-family:ui-monospace,monospace}
- .nn .pth{color:#9ca3af;font-size:11px;font-family:ui-monospace,monospace;word-break:break-all}
- .modal{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:110;display:flex;align-items:center;justify-content:center;padding:20px}
- .mbox{background:#fff;width:760px;max-width:94vw;max-height:86vh;border-radius:10px;overflow:hidden;display:flex;flex-direction:column}
- .mbox h2{margin:0;padding:13px 18px;background:#0f766e;color:#fff;font-size:14px;display:flex;align-items:center}
- .mbox h2 .x{margin-left:auto;cursor:pointer;font-size:18px}
- .mbox .body{padding:15px 18px;overflow:auto}.mbox pre{margin:0;font:12.5px/1.7 ui-monospace,Menlo,monospace;white-space:pre-wrap;color:#1f2937}
- /* 提示词 Markdown 渲染 */
- .md{font-size:13px;line-height:1.7;color:#1f2937}
- .md h3{font-size:16px;margin:18px 0 8px;padding-bottom:5px;border-bottom:2px solid #0f766e;color:#0f766e}
- .md h4{font-size:14px;margin:15px 0 6px;color:#0f766e}
- .md h5,.md h6{font-size:13px;margin:12px 0 5px;color:#115e59}
- .md p{margin:8px 0}
- .md strong{color:#9a3412;font-weight:700}
- .md code{background:#f1f5f9;color:#be123c;padding:1px 5px;border-radius:4px;font:12px ui-monospace,Menlo,monospace}
- .md pre{background:#0f172a;border-radius:8px;padding:12px 14px;overflow-x:auto;margin:10px 0}
- .md pre code{background:none;color:#e2e8f0;padding:0;font-size:12px;line-height:1.6}
- .md blockquote{margin:10px 0;padding:8px 12px;background:#f0fdfa;border-left:3px solid #14b8a6;border-radius:0 6px 6px 0;color:#134e4a}
- .md ul,.md ol{margin:8px 0;padding-left:22px}.md li{margin:4px 0}
- .md hr{border:none;border-top:1px dashed #cbd5e1;margin:14px 0}
- .md table{border-collapse:collapse;width:100%;margin:10px 0;font-size:12.5px}
- .md th,.md td{border:1px solid #cbd5e1;padding:6px 9px;text-align:left;vertical-align:top}
- .md th{background:#0f766e;color:#fff;font-weight:600;white-space:nowrap}
- .md tbody tr:nth-child(even){background:#f8fafc}
- .md a{color:#0e7490;text-decoration:underline}
- .md .ptag{font-size:14px;font-weight:700;color:#0f766e;margin:16px 0 7px;padding:4px 10px;background:#ccfbf1;border-radius:6px;display:inline-block}
- .md .ptag-ex{font-size:12px;font-weight:600;color:#92400e;background:#fef3c7;margin:11px 0 5px;padding:2px 9px}
- .md .ptag-sub{font-weight:400;font-size:11.5px;opacity:.8;margin-left:7px}
- .lb{position:fixed;inset:0;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;z-index:120;cursor:zoom-out}.lb img{max-width:92vw;max-height:92vh;border-radius:6px}
- .empty{color:#9ca3af;padding:50px;text-align:center}
- </style>
- </head>
- <body>
- <div id="root"></div>
- <script type="text/x-jsx" id="app-src">
- const {useState,useEffect}=React;
- const j=u=>fetch(u,{cache:"no-store"}).then(r=>r.json());
- const SCOPES=[["substance","实质"],["form","形式"],["feeling","感受"],["effect","作用"],["intent","意图"]];
- const SC_CN={substance:"实质",form:"形式",feeling:"感受",effect:"作用",intent:"意图"};
- const TYPECN={how:"How",what:"What",why:"Why"};
- const CSTAGE_DOC=`## 创作阶段:写死的 5 个(每步选 1 个)
- how 工序每一步命中其中**一个**;不在这 5 个里的值会被代码丢弃(受控词表,不开放)。
- | 阶段 | 在干什么 |
- |---|---|
- | **定向** | 定主题 / 方向 / 受众——还没开始具体创作 |
- | **构思** | 生成核心创意 / 钩子 / 角度 / 冲突 |
- | **结构** | 搭骨架 / 大纲 / 顺序 |
- | **成文** | 把结构写成具体文字 / 镜头 |
- | **打磨** | 修改 / 精炼 / 优化 / 定稿 |
- (区别于「动作」:动作是模型从帖子里实时抽的、不固定;创作阶段是定死的 5 选 1。)`;
- const GLOSSARY=`## 一篇帖子能拆出三种知识(平级的)
- 这三种各帮你做一个不同的决定,没有谁更重要。
- | 标签 | 大白话 | 它帮你决定 |
- |---|---|---|
- | **怎么做** (How) | 一步步把东西做出来的流程 | 按什么**顺序**做 |
- | **是什么** (What) | 某东西由什么组成 / 有哪些种类 | 用**哪些料** |
- | **为什么** (Why) | 背后的道理、标准、平台规则 | 该**留什么、砍什么** |
- ## 「是什么」按结构分三种
- - **子集**:像菜单,几个**互不隶属**的项,**归其一 or 挑着用**(例:7 种叙事结构挑一种、构图六法按场景挑)。
- - 每个项后面那句 **👉 什么时候用** 就是「**选择线索**」——告诉你哪种情况该挑它。没有它,就只能背、不会选。
- - **多维关系**:像配方,几个部件**都要、合起来才完整、无先后**(例:人物 = 生理+社会+心理)。
- - **序列**:几个部件**都要、且有前后位置**(例:故事五部分、黄金三段式)。只是位置顺序——后段若**消费**前段产出、换序就崩,那就成 how 工序了。
- ## 「为什么」是什么
- - **一段「阐述」**:把原帖讲这条原理的内容**忠实整合**成一段——这条原理**是什么 + 为什么成立**。
- - 解构阶段只**还原原理本身**,不加工"该怎么用"(那是下游应用的事)。`;
- const STEPS=[
- {key:"read",label:"读懂帖子",desc:"把图、视频读成文字",
- why:"创作知识常在图里、不在正文;这一步只忠实转写、不判断,避免一步干两件事。",
- src:["/prompt-read/extract.txt","/prompt-read/extract_video.txt"]},
- {key:"gate",label:"判断是否是创作知识",desc:"判这帖是不是『图文/视频创作』知识:两次判断+分歧裁决,不是就整帖跳过",
- why:"靠模型语义理解判、不靠关键词(换措辞就绕过);两视角(一个判'是创作'、一个专挑'是制作/越界'的茬)+ 边界倾向排除,把『用工具做视频/游戏设计』挡在门外,省下后面整条拆解。",
- src:["/prompt-read/gate_admit.txt","/prompt-read/gate_refute.txt","/prompt-read/gate_tiebreak.txt"]},
- {key:"frame",label:"拆出知识",desc:"从文字里拆出一条条知识,理清每条是 怎么做 / 是什么 / 为什么",
- why:"先判类型再拆——三类成形不同,默认 how 会把纯 what 硬掰成假 how;how 先吸进步骤里的 what/why,吸不进的才独立成一条。",
- src:["/prompt-skill/phase1-frame.md"]},
- {key:"howgate",label:"校验how的严谨性",desc:"每条『怎么做』再验一次:步骤真有先后依赖,还是离散知识被硬串成的假工序?两次判断+裁决,判假就拆回 是什么 / 为什么",
- why:"模型有『教程=步骤』的先验,爱把几块离散知识(如 前提/人物/冲突)硬串成假工序。这步逐条核验:后步是否真消费前步产出、有没有『定义式拆解』(后步产出其实是前步里已点名的成分);假的当场拆成 what/why,真链原样保留。",
- src:["/prompt-read/gate_how_admit.txt","/prompt-read/gate_how_refute.txt","/prompt-read/gate_how_tiebreak.txt"]},
- {key:"scope",label:"给知识归类",desc:"标好 讲什么 / 起什么作用 / 哪个创作环节 → 标签收成规范名词 → 和已有分类库对上号",
- why:"这 5 类标签最容易串味,单独写厚;标签先名词化(动宾→名词)再做向量定位,既规范又能挂上分类库。",
- src:["/prompt-skill/phase2-scope.md","/prompt-read/normalize_scope.txt"]},
- {key:"store",label:"存进知识库",desc:"整理成统一格式存起来,供以后调用",
- why:"纯拼接 + 校验的确定性活,程序自动做——没有提示词,让 AI 来干只会帮倒忙。",
- src:null}
- ];
- const STEPMAP={}; STEPS.forEach(s=>STEPMAP[s.key]=s);
- // 极简 Markdown 渲染(自带、无外部依赖):标题/表格/代码/引用/列表/加粗/行内码/链接
- function mdToHtml(src){
- var escc=function(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");};
- var inline=function(t){
- t=escc(t);
- t=t.replace(/`([^`]+)`/g,'<code>$1</code>');
- t=t.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>');
- t=t.replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank">$1</a>');
- return t;
- };
- var L=(src||"").split("\n"), out=[], i=0;
- var isStruct=function(s){return /^```/.test(s)||/^\s*#{1,6}\s/.test(s)||/^\s*>\s?/.test(s)||/^\s*[-*]\s+/.test(s)||/^\s*\d+\.\s+/.test(s)||/^\s*([-*_])(\s*\1){2,}\s*$/.test(s)||/^\s*\|/.test(s)||/^\s*<\/?[^<>]+>/.test(s);};
- while(i<L.length){
- var ln=L[i];
- if(/^```/.test(ln)){ var b=[]; i++; while(i<L.length&&!/^```/.test(L[i])){b.push(L[i]);i++;} i++; out.push("<pre><code>"+escc(b.join("\n"))+"</code></pre>"); continue; }
- if(/^\s*\|/.test(ln)&&i+1<L.length&&/^\s*\|?[\s:|-]*-[\s:|-]*\|?\s*$/.test(L[i+1])){
- var head=ln, rows=[]; i+=2;
- while(i<L.length&&/^\s*\|/.test(L[i])&&L[i].trim()!==""){rows.push(L[i]);i++;}
- var cells=function(r){r=r.trim().replace(/^\|/,"").replace(/\|$/,"");return r.split("|").map(function(c){return c.trim();});};
- var th=cells(head).map(function(c){return "<th>"+inline(c)+"</th>";}).join("");
- var tb=rows.map(function(r){return "<tr>"+cells(r).map(function(c){return "<td>"+inline(c)+"</td>";}).join("")+"</tr>";}).join("");
- out.push("<table><thead><tr>"+th+"</tr></thead><tbody>"+tb+"</tbody></table>"); continue;
- }
- if(/^\s*([-*_])(\s*\1){2,}\s*$/.test(ln)){ out.push("<hr/>"); i++; continue; }
- var tag=ln.match(/^\s*<(\/?)([^<>\/][^<>]*)>\s*(.*)$/); // 行首伪标签(如 <工作要点> / <example> 描述)→ 当小节标题
- if(tag){ if(tag[1]){i++;continue;} var nm=tag[2], rest=tag[3].trim(), ex=/^example$/i.test(nm);
- out.push('<div class="ptag'+(ex?' ptag-ex':'')+'">'+inline(ex?'示例':nm)+(rest?'<span class="ptag-sub">'+inline(rest)+'</span>':'')+"</div>"); i++; continue; }
- var h=ln.match(/^\s*(#{1,6})\s+(.*)$/);
- if(h){ var lv=Math.min(h[1].length+2,6); out.push("<h"+lv+">"+inline(h[2])+"</h"+lv+">"); i++; continue; }
- if(/^\s*>\s?/.test(ln)){ var q=[]; while(i<L.length&&/^\s*>\s?/.test(L[i])){q.push(inline(L[i].replace(/^\s*>\s?/,"")));i++;} out.push("<blockquote>"+q.join("<br/>")+"</blockquote>"); continue; }
- if(/^\s*[-*]\s+/.test(ln)){ var ul=[]; while(i<L.length&&/^\s*[-*]\s+/.test(L[i])){ul.push("<li>"+inline(L[i].replace(/^\s*[-*]\s+/,""))+"</li>");i++;} out.push("<ul>"+ul.join("")+"</ul>"); continue; }
- if(/^\s*\d+\.\s+/.test(ln)){ var ol=[]; while(i<L.length&&/^\s*\d+\.\s+/.test(L[i])){ol.push("<li>"+inline(L[i].replace(/^\s*\d+\.\s+/,""))+"</li>");i++;} out.push("<ol>"+ol.join("")+"</ol>"); continue; }
- if(ln.trim()===""){ i++; continue; }
- var p=[]; while(i<L.length&&L[i].trim()!==""&&!isStruct(L[i])){p.push(inline(L[i]));i++;} out.push("<p>"+p.join("<br/>")+"</p>");
- }
- return out.join("\n");
- }
- function Ribbon({onOpen}){
- return <div className="ribbon">
- <div className="rcap">这套系统怎么「从帖子拆解知识」——点任一步看它的提示词</div>
- <div className="rsteps">{STEPS.map((s,i)=><React.Fragment key={s.key}>
- {i>0&&<span className="rarrow">→</span>}
- <div className={"rstep"+(s.src?"":" auto")} onClick={()=>onOpen(s.key)}>
- <div className="rtop"><span className="rn">{i+1}</span><span className="rlab">{s.label}</span></div>
- <div className="rdesc">{s.desc}</div>
- <div className="rbtn">{s.src?"📄 看提示词":"⚙️ 程序自动"}</div>
- </div></React.Fragment>)}</div>
- </div>;
- }
- function ScopeChip({x,onScope,showType}){
- return <span className={"sv s-"+x.scope_type} onClick={()=>onScope(x)}>{showType&&<span className="k">{SC_CN[x.scope_type]||x.scope_type}</span>}{x.value}<span className={"mk "+(x.link==="复用"?"mk-reuse":"mk-new")}>{x.link==="复用"?"复":"新"}</span></span>;
- }
- function KScopes({list,onScope}){
- if(!list||!list.length) return null;
- return <div className="kscopes"><span className="lab">作用域(5 棵树):</span>
- {list.map((x,i)=><ScopeChip key={i} x={{...x}} onScope={onScope} showType={true}/>)}</div>;
- }
- function HowTable({k,onScope,onPrompt,onSource,onCstage}){
- const steps=k.steps||[];
- const scOf=(s,t)=>(s.作用域||[]).filter(x=>x.scope_type===t);
- return <div className="twrap"><table className="proc">
- <thead>
- <tr><th className="c-idx" rowSpan="2">#</th>
- <th className="g-frame" colSpan="5">② 拆框架</th>
- <th className="g-scope" colSpan="5">③ 作用域</th></tr>
- <tr><th className="c-intent">输入</th><th className="c-cstage cstage-h" title="看 5 个写死的创作阶段" onClick={()=>onCstage&&onCstage()}>创作<br/>阶段 ▸</th><th className="c-act">动作<br/>(模型推导)</th><th className="c-dir">方法</th><th className="c-out">产出</th>
- {SCOPES.map(([kk,cn])=><th key={kk} className={"c-scope s-"+kk}>{cn}</th>)}</tr>
- </thead>
- <tbody>{steps.map((s,i)=><tr className="step" key={i}>
- <td className="idx">{i+1}</td>
- <td className="intent"><span className="in-txt">{s.input}</span>{s.出处&&s.出处.length>0&&<span className="srcbtn" title={"看原文出处 "+s.出处.join(" ")} onClick={()=>onSource(s.出处)}>🔍</span>}</td>
- <td className="cstage">{s.创作阶段&&<span className="cstage-chip">{s.创作阶段}</span>}</td>
- <td className="act">{s.动作&&<span className="act-chip">{s.动作}</span>}</td>
- <td className="dir"><span className="dir-txt">{s.directive}</span></td>
- <td className="out"><span className="out-txt">{s.output}</span></td>
- {SCOPES.map(([kk])=><td key={kk} className={"scope s-"+kk}>{scOf(s,kk).map((x,j)=><ScopeChip key={j} x={x} onScope={onScope}/>)}</td>)}
- </tr>)}</tbody>
- </table></div>;
- }
- function WhatCard({k,onScope,onGloss}){
- return <div>
- {k.概要&&<div className="def">
- {k.kind&&<span className={"kindb kind-"+k.kind}>{k.kind}</span>}
- {k.维度拆分规则&&<span className="dimrule" title="维度拆分规则(仅子集)">按「{k.维度拆分规则}」拆</span>}
- 概要:{k.概要}</div>}
- {(k.body||[]).length>0&&<table className="elem"><tbody>{k.body.map((it,i)=><tr key={i}>
- <td className="el">{it.item_name}</td><td>{it.item_desc}</td></tr>)}</tbody></table>}
- <KScopes list={k.作用域} onScope={onScope}/>
- </div>;
- }
- function WhyCard({k,onScope}){
- return <div>
- {k.阐述&&<div className="why-exp">{k.阐述}</div>}
- <KScopes list={k.作用域} onScope={onScope}/>
- </div>;
- }
- function KnowledgeCard({k,idx,onScope,onPrompt,onJson,onSource,payload,how_titles,onGloss,onCstage}){
- const par=k.parent;
- return <div className="kcard" id={"k-"+k.id}>
- <div className="khead">
- <span className="kno">第{idx+1}颗知识</span>
- <span className={"ktype ty-"+k.type}>{TYPECN[k.type]}</span>
- <span className="ktitle">{k.title}</span>
- {k.出处&&k.出处.length>0&&<span className="kref" title={"看原文出处 "+k.出处.join(" ")} onClick={()=>onSource(k.出处)}>🔍 出处 {k.出处.join(" ")}</span>}
- {par&&<span className="kref" onClick={()=>{var e=document.getElementById("k-"+par.how_id);if(e)e.scrollIntoView({behavior:"smooth",block:"center"});}}>↑ 出自「{how_titles[par.how_id]||par.how_id}」第{par.step}步</span>}
- {(k.业务阶段||[]).map((b,i)=><span key={i} className="kbiz">业务阶段:{b}</span>)}
- <span className="kacts">{payload&&<button className="lbtn" onClick={()=>onJson(payload)}>⚙️ 入库 payload</button>}</span>
- </div>
- <div className="kbody">
- {k.type==="how"&&<HowTable k={k} onScope={onScope} onPrompt={onPrompt} onSource={onSource} onCstage={onCstage}/>}
- {k.type==="what"&&<WhatCard k={k} onScope={onScope} onGloss={onGloss}/>}
- {k.type==="why"&&<WhyCard k={k} onScope={onScope}/>}
- </div>
- </div>;
- }
- function App(){
- const [items,setItems]=useState([]);
- const [sel,setSel]=useState(0);
- const [scope,setScope]=useState(null);
- const [runs,setRuns]=useState([]); // web/runs/index.json 清单(搜索批次)
- const [run,setRun]=useState(null); // 选中的 run slug;null=默认样本 frameworks.json
- const [pm,setPm]=useState(null);
- const [json,setJson]=useState(null);
- const [zoom,setZoom]=useState(null);
- const [src,setSrc]=useState(null);
- const [hl,setHl]=useState(false);
- const [gloss,setGloss]=useState(false);
- const [cstage,setCstage]=useState(false);
- useEffect(()=>{j("/runs/index.json").then(r=>setRuns(Array.isArray(r)?r:[])).catch(()=>setRuns([]));},[]);
- useEffect(()=>{
- const fw=run?("/runs/"+run+".json"):"/frameworks.json";
- const pls=run?("/runs/"+run+".payloads.json"):"/payloads.json";
- Promise.all([j(fw),j(pls).catch(()=>[])]).then(([f,pl])=>{
- setSel(0);setScope(null);
- setItems((f.posts||[]).map(p=>({...p,payloads:(pl||[]).filter(x=>(x.source||{}).id===(p.source_id||("xhs_"+p.post_id)))})));});},[run]);
- useEffect(()=>{document.body.classList.toggle("hl",hl);},[hl]);
- function openPrompt(key){
- const st=STEPMAP[key]; if(!st) return;
- if(!st.src){ setPm({label:st.label,why:st.why,text:"(这一步是程序自动完成的,没有提示词。)"}); return; }
- setPm({label:st.label,why:st.why,text:"加载中…"});
- const multi=st.src.length>1;
- Promise.all(st.src.map(u=>fetch(u,{cache:"no-store"}).then(r=>r.ok?r.text():"(未找到 "+u+")").catch(()=>"(加载失败 "+u+")")
- .then(t=>multi?("## 〔"+u.split("/").pop()+"〕\n\n"+t):t)))
- .then(txts=>setPm({label:st.label,why:st.why,text:txts.join("\n\n")}));
- }
- if(!items.length) return <div className="empty">加载中…</div>;
- const it=items[sel];
- const ks=it.knowledges||[];
- const how_titles={}; ks.forEach(k=>{if(k.type==="how")how_titles[k.id]=k.title;});
- const payloadOf=k=>(it.payloads||[]).find(p=>p.title===k.title);
- const media=it.media||{}; const imgs=media.images||[]; const videoUrl=media.video_url;
- const cardsById={}; (it.cards||[]).forEach(c=>cardsById[c.index]=c);
- const openSource=list=>{var cs=(list||[]).map(s=>{var m=String(s).match(/\d+/);return m?cardsById[parseInt(m[0])]:null;}).filter(Boolean);if(cs.length)setSrc({cards:cs});};
- const fmtT=s=>{s=Math.floor(s||0);return Math.floor(s/60)+":"+String(s%60).padStart(2,"0");};
- return <div className="app">
- <header className="top">
- <h1>创作知识的解构</h1>
- {runs.length>0 && <div className="picker"><span className="lab">批次:</span>
- <select className="runsel" value={run||""} onChange={e=>setRun(e.target.value||null)}>
- <option value="">样本(frameworks)</option>
- {runs.map((r,i)=><option key={i} value={r.slug}>{(r.queries||[]).join("+")||r.slug}({r.count}帖)</option>)}
- </select>
- </div>}
- <div className="picker"><span className="lab">帖子:</span>
- {items.map((x,i)=><div key={i} className={"pill"+(sel===i?" on":"")} onClick={()=>{setSel(i);setScope(null);}}>{x.title.slice(0,16)}<span className="n">{(x.knowledges||[]).length}颗</span></div>)}
- </div>
- </header>
- <div className="scroll">
- <Ribbon onOpen={openPrompt}/>
- <details className="src" open><summary>原帖:{it.title}(这一帖的原料)· {it.platform}{videoUrl?" · 视频":(imgs.length?` · ${imgs.length} 图`:"")}</summary>
- <div className="srcbody"><a href={it.url} target="_blank" rel="noreferrer">{it.url}</a>
- {videoUrl
- ? <div style={{marginTop:8}}><video src={videoUrl} controls style={{maxWidth:"380px",maxHeight:"320px",borderRadius:"6px",border:"1px solid #e2e8f0"}}/></div>
- : <div className="srcimgs">{imgs.map((u,i)=><img key={i} src={u} title={u} onClick={()=>setZoom(u)} onError={e=>{var ph="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='80' height='80'><rect width='80' height='80' fill='%23f1f5f9' stroke='%23cbd5e1' stroke-dasharray='4'/></svg>"; if(e.target.src.slice(0,4)!=='data')e.target.src=ph;}}/>)}</div>}
- </div>
- </details>
- <div className="barhint" style={{display:"flex",gap:8,alignItems:"center"}}>
- <span>这一帖拆出 <b>{ks.length}</b> 颗知识(how 工序表 / what 知识卡 / why 论点卡);组件颗标「出自某 how 第 N 步」可点跳。</span>
- <button className="lbtn glossbtn" onClick={()=>setGloss(true)} style={{marginLeft:"auto"}}>❓ 名词说明</button>
- <button className={"lbtn"+(hl?" on":"")} onClick={()=>setHl(!hl)}>高亮「新值」</button>
- </div>
- {ks.length===0 && <div className="kcard"><div className="kbody" style={{padding:"20px 16px",color:"#9ca3af",fontSize:"13px"}}>🚫 这帖<b style={{color:"#6b7280"}}>没有可提取的创作知识</b>(纯作品 / 展示 / 无关)——① 读懂时 <code>is_empty=true</code>,后面拆颗/归类/入库已自动跳过。</div></div>}
- {ks.map((k,i)=><KnowledgeCard key={i} k={k} idx={i} onScope={setScope} onPrompt={openPrompt} onJson={setJson} onSource={openSource} payload={payloadOf(k)} how_titles={how_titles} onGloss={()=>setGloss(true)} onCstage={()=>setCstage(true)}/>)}
- </div>
- {scope&&<div><div className="ov" onClick={()=>setScope(null)}></div>
- <div className="drawer"><h2>作用域定位详情<span className="x" onClick={()=>setScope(null)}>×</span></h2>
- <div className="dc">
- <div className="row"><b>提取候选值</b>:{scope.candidate||scope.value}</div>
- <div className="row"><b>归属树</b>:<span className={"sv s-"+scope.scope_type} style={{cursor:"default"}}>{SC_CN[scope.scope_type]||scope.scope_type}</span>(在这棵树里找最近邻)</div>
- <div className="row"><b>判定</b>:<span className={"verdict "+(scope.link==="复用"?"v-reuse":"v-new")}>{scope.link==="复用"?"复用现有节点":"新建(树里没有)"}</span> = 最近邻 {scope.score}</div>
- <div className="row"><b>最终入库值</b>:{scope.value}</div>
- <h3 style={{margin:"12px 0 6px",fontSize:12.5,color:"#475569"}}>分类树最近邻 top-{(scope.top||[]).length} · 【{SC_CN[scope.scope_type]||scope.scope_type}】树(火山余弦)</h3>
- {(scope.top||[]).map((h,i)=><div className="nn" key={i}><span className="sc">{h.score}</span><span className="nm">{h.name}</span><div className="pth">【{SC_CN[scope.scope_type]||scope.scope_type}】 {h.path}</div></div>)}
- {(!scope.top||!scope.top.length)&&<div style={{color:"#9ca3af"}}>无最近邻</div>}
- </div></div></div>}
- {pm&&<div className="modal" onClick={()=>setPm(null)}><div className="mbox" onClick={e=>e.stopPropagation()}>
- <h2>📄 {pm.label} · 这一步的提示词<span className="x" onClick={()=>setPm(null)}>×</span></h2>
- <div className="body">
- <div className="pmwhy"><b>为什么这么做:</b>{pm.why}</div>
- {pm.text==="加载中…"?<pre>{pm.text}</pre>:<div className="md" dangerouslySetInnerHTML={{__html:mdToHtml(pm.text)}}/>}</div></div></div>}
- {json&&<div className="modal" onClick={()=>setJson(null)}><div className="mbox" onClick={e=>e.stopPropagation()}>
- <h2>⚙️ ingest payload(给机器看)<span className="x" onClick={()=>setJson(null)}>×</span></h2>
- <div className="body"><pre>{JSON.stringify(json,null,2)}</pre></div></div></div>}
- {src&&<div className="modal" onClick={()=>setSrc(null)}><div className="mbox" onClick={e=>e.stopPropagation()}>
- <h2>🔍 原文出处(① 读到的卡片)<span className="x" onClick={()=>setSrc(null)}>×</span></h2>
- <div className="body">{src.cards.map((c,i)=><div className="srccard" key={i}>
- <div className="ci">卡片{c.index}{c.start!=null?(" · "+fmtT(c.start)+"–"+fmtT(c.end)):""}</div>
- {c.image_url&&<img src={c.image_url} onClick={()=>setZoom(c.image_url)} onError={e=>e.target.style.display='none'}/>}
- {c.video_url&&<video src={c.video_url+(c.start!=null?("#t="+Math.floor(c.start)+","+Math.floor(c.end)):"")} controls/>}
- {c.content?<div className="srcread">① 在这张卡读到的:{c.content}</div>:<div style={{color:"#9ca3af",fontSize:"12px",marginTop:5}}>(这张卡没读到要点)</div>}
- </div>)}</div></div></div>}
- {cstage&&<div className="modal" onClick={()=>setCstage(false)}><div className="mbox" onClick={e=>e.stopPropagation()}>
- <h2>📐 创作阶段 · 写死的 5 个<span className="x" onClick={()=>setCstage(false)}>×</span></h2>
- <div className="body"><div className="md" dangerouslySetInnerHTML={{__html:mdToHtml(CSTAGE_DOC)}}/></div></div></div>}
- {gloss&&<div className="modal" onClick={()=>setGloss(false)}><div className="mbox" onClick={e=>e.stopPropagation()}>
- <h2>❓ 名词说明 · 用大白话讲清楚<span className="x" onClick={()=>setGloss(false)}>×</span></h2>
- <div className="body"><div className="md" dangerouslySetInnerHTML={{__html:mdToHtml(GLOSSARY)}}/></div></div></div>}
- {zoom&&<div className="lb" onClick={()=>setZoom(null)}><img src={zoom}/></div>}
- </div>;
- }
- ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
- </script>
- <script>
- (function(){var src=document.getElementById("app-src").textContent;
- var out=Babel.transform(src,{presets:[["react",{runtime:"classic"}]]}).code;(0,eval)(out);})();
- </script>
- </body>
- </html>
|