index.html 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <!doctype html>
  2. <html lang="zh">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <title>创作知识的解构</title>
  7. <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
  8. <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
  9. <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  10. <style>
  11. *{box-sizing:border-box}
  12. 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}
  13. .app{height:100vh;display:flex;flex-direction:column}
  14. header.top{padding:11px 18px 9px;background:#fff;border-bottom:1px solid #e5e7eb;flex:none}
  15. header.top h1{margin:0 0 7px;font-size:16px;font-weight:600}
  16. .picker{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
  17. .picker .lab{font-size:11.5px;color:#9ca3af;margin-right:2px}
  18. .pill{font-size:12px;padding:4px 11px;border-radius:14px;border:1px solid #e5e7eb;background:#fff;color:#6b7280;cursor:pointer}
  19. .pill:hover{background:#f3f4f6}
  20. .pill.on{background:#1e293b;color:#fff;border-color:#1e293b;font-weight:600}
  21. .pill .n{font-size:10px;opacity:.7;margin-left:4px}
  22. .scroll{flex:1;overflow:auto;background:#f5f5f7;padding-bottom:40px}
  23. details.src{margin:10px 18px 0;background:#fff;border:1px solid #e2e8f0;border-radius:6px}
  24. details.src summary{padding:8px 14px;cursor:pointer;font-size:12px;color:#475569;list-style:none}
  25. details.src summary::before{content:'▶ ';color:#94a3b8;font-size:10px}
  26. details.src[open] summary::before{content:'▼ '}
  27. .srcbody{padding:4px 16px 12px;font-size:12px}
  28. .srcbody a{color:#2563eb;word-break:break-all}
  29. .srcprompt{color:#0e7490;font-size:11.5px;cursor:pointer}.srcprompt:hover{text-decoration:underline}
  30. .srcimgs{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
  31. .srcimgs img{width:80px;height:80px;object-fit:cover;border-radius:4px;border:1px solid #e2e8f0;cursor:zoom-in}
  32. .barhint{margin:8px 18px 0;font-size:11.5px;color:#6b7280}
  33. .ribbon{margin:11px 18px 0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:10px 14px}
  34. .rcap{font-size:11.5px;color:#6b7280;margin-bottom:8px}
  35. .rsteps{display:flex;align-items:stretch;flex-wrap:wrap}
  36. .rstep{flex:1;min-width:148px;border:1px solid #e2e8f0;border-radius:8px;padding:9px 11px;cursor:pointer;background:#f8fafc}
  37. .rstep:hover{background:#ecfeff;border-color:#99d5d0}
  38. .rstep.auto{cursor:default;background:#f1f5f9;opacity:.9}.rstep.auto:hover{background:#f1f5f9;border-color:#e2e8f0}
  39. .rtop{display:flex;align-items:center;gap:7px}
  40. .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}
  41. .rstep.auto .rn{background:#94a3b8}
  42. .rlab{font-size:13.5px;font-weight:600}
  43. .rdesc{font-size:11.5px;color:#6b7280;margin:5px 0 7px;line-height:1.5}
  44. .rbtn{font-size:11.5px;color:#0e7490}.rstep.auto .rbtn{color:#94a3b8}
  45. .rarrow{display:flex;align-items:center;color:#cbd5e1;font-size:18px;padding:0 4px;flex:none}
  46. .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}
  47. .pmwhy b{color:#78350f}
  48. .lbtn{padding:3px 9px;border:1px solid #cbd5e1;border-radius:5px;background:#fff;cursor:pointer;font-size:11.5px;color:#334155;font-family:inherit}
  49. .lbtn:hover{background:#f1f5f9}.lbtn.on{background:#fef3c7;border-color:#d97706;color:#92400e;font-weight:600}
  50. /* 颗卡 */
  51. .kcard{margin:12px 18px 0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden}
  52. .khead{display:flex;align-items:center;gap:8px;padding:9px 14px;border-bottom:1px solid #eef2f7;flex-wrap:wrap}
  53. .kno{font-size:11px;font-weight:700;color:#fff;background:#475569;border-radius:4px;padding:1px 7px}
  54. .ktype{font-size:11px;font-weight:700;border-radius:4px;padding:1px 8px}
  55. .ty-how{background:#cffafe;color:#155e75}.ty-what{background:#fae8ff;color:#86198f}.ty-why{background:#fef9c3;color:#854d0e}
  56. .ktitle{font-size:14px;font-weight:600}
  57. .krole{font-size:10.5px;padding:1px 6px;border-radius:3px;background:#f1f5f9;color:#64748b}
  58. .kref{font-size:11px;color:#0e7490;cursor:pointer}.kref:hover{text-decoration:underline}
  59. .kbiz{font-size:11px;padding:1px 7px;border-radius:3px;background:#fef3c7;color:#92400e}
  60. .kacts{margin-left:auto;display:flex;gap:6px}
  61. .kbody{padding:6px 14px 13px}
  62. /* how 工序表 */
  63. .twrap{overflow:auto;border:1px solid #e5e7eb;border-radius:6px;margin-top:8px}
  64. table.proc{width:max-content;min-width:100%;border-collapse:collapse;font-size:12px}
  65. table.proc th,table.proc td{border:1px solid #e5e7eb;padding:6px 8px;vertical-align:top;line-height:1.5}
  66. table.proc thead th{font-weight:600;text-align:center;font-size:12px;position:sticky;top:0;z-index:3}
  67. table.proc thead tr:nth-child(2) th{top:30px}
  68. td.idx{width:30px;text-align:center;color:#6b7280;background:#f8fafc}
  69. th.c-intent,td.intent{width:210px;min-width:210px;max-width:210px;white-space:pre-wrap}
  70. th.c-cstage,td.cstage{width:54px;text-align:center;background:#ecfeff}
  71. th.c-act,td.act{min-width:110px;max-width:140px;word-break:break-word;background:#ecfeff}
  72. th.c-dir,td.dir{min-width:230px;max-width:330px;white-space:pre-wrap;word-break:break-word;background:#ecfeff}
  73. th.c-out,td.out{min-width:160px;max-width:240px;white-space:pre-wrap;background:#ecfeff}
  74. th.c-scope,td.scope{width:92px;min-width:92px;max-width:92px;word-break:break-word}
  75. th.g-frame{background:#0e7490;color:#fff;cursor:pointer}th.g-frame:hover{background:#155e75}
  76. th.g-scope{background:#7c3aed;color:#fff;cursor:pointer}th.g-scope:hover{background:#6d28d9}
  77. th.c-idx{background:#475569;color:#fff;vertical-align:middle}
  78. 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}
  79. thead tr:nth-child(2) th.s-substance{background:#dc2626;color:#fff}thead tr:nth-child(2) th.s-form{background:#2563eb;color:#fff}
  80. 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}
  81. 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}
  82. tr.step>td{border-top:1.5px solid #cbd5e1}
  83. .cstage-chip{display:inline-block;font-size:11px;padding:1px 6px;border-radius:10px;background:#ede9fe;color:#5b21b6}
  84. .act-chip{display:inline-block;font-size:11.5px;padding:1px 7px;border-radius:4px;background:#fff7ed;color:#9a3412;border:1px solid #fed7aa}
  85. .dir-txt{font-size:11.5px;color:#33312c;line-height:1.65}.out-txt{font-size:11.5px;color:#166534}
  86. .from{font-size:11px;color:#9ca3af}
  87. /* what 要素表 */
  88. .def{font-size:13px;background:#fdf4ff;border-left:3px solid #a21caf;border-radius:0 6px 6px 0;padding:8px 12px;margin:6px 0 10px}
  89. table.elem{border-collapse:collapse;width:100%;font-size:12.5px}
  90. table.elem th,table.elem td{border:1px solid #f0e6f5;padding:6px 10px;text-align:left;vertical-align:top}
  91. table.elem th{background:#fae8ff;color:#86198f;font-weight:600;width:130px}
  92. table.elem td.el{font-weight:600;color:#86198f;background:#fdf4ff;width:130px}
  93. /* why 论点卡 */
  94. .why-row{display:flex;gap:10px;margin:7px 0}
  95. .why-lab{font-size:11px;font-weight:700;border-radius:4px;padding:2px 8px;flex:none;height:20px}
  96. .wl-claim{background:#fef9c3;color:#854d0e}.wl-basis{background:#e0e7ff;color:#3730a3}.wl-impact{background:#dcfce7;color:#166534}
  97. .why-txt{font-size:12.5px;line-height:1.65;color:#1f2937}
  98. /* 颗级作用域 */
  99. .kscopes{margin-top:9px;display:flex;gap:5px;flex-wrap:wrap;align-items:center}
  100. .kscopes .lab{font-size:11px;color:#9ca3af}
  101. .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}
  102. .sv:hover{box-shadow:0 0 0 1px #1e293b}
  103. .sv .k{font-size:11.5px;opacity:.85;font-weight:700;margin-right:4px}
  104. table.proc th.c-scope{font-size:13.5px}
  105. .sv .mk{font-size:9px;padding:0 3px;border-radius:2px;margin-left:3px;vertical-align:top}
  106. .mk-new{background:#fde68a;color:#92400e}.mk-reuse{background:#bbf7d0;color:#166534}
  107. .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}
  108. body.hl .sv .mk-new{outline:2px solid #d97706}
  109. /* drawer + modal */
  110. .ov{position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:100}
  111. .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}
  112. .drawer h2{margin:0;padding:13px 16px;background:#1e293b;color:#fff;font-size:14px;display:flex;align-items:center}
  113. .drawer h2 .x{margin-left:auto;cursor:pointer;font-size:18px}
  114. .dc{padding:14px 16px;font-size:12.5px;line-height:1.6}.dc .row{margin:6px 0}.dc b{color:#374151}
  115. .verdict{display:inline-block;padding:2px 9px;border-radius:5px;font-weight:600;font-size:12px}
  116. .v-new{background:#fef3c7;color:#92400e}.v-reuse{background:#dcfce7;color:#166534}
  117. .nn{margin:5px 0;padding:7px 10px;border:1px solid #e5e7eb;border-radius:5px;background:#f8fafc}
  118. .nn .nm{font-weight:600}.nn .sc{float:right;color:#6b7280;font-family:ui-monospace,monospace}
  119. .nn .pth{color:#9ca3af;font-size:11px;font-family:ui-monospace,monospace;word-break:break-all}
  120. .modal{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:110;display:flex;align-items:center;justify-content:center;padding:20px}
  121. .mbox{background:#fff;width:760px;max-width:94vw;max-height:86vh;border-radius:10px;overflow:hidden;display:flex;flex-direction:column}
  122. .mbox h2{margin:0;padding:13px 18px;background:#0f766e;color:#fff;font-size:14px;display:flex;align-items:center}
  123. .mbox h2 .x{margin-left:auto;cursor:pointer;font-size:18px}
  124. .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}
  125. .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}
  126. .empty{color:#9ca3af;padding:50px;text-align:center}
  127. </style>
  128. </head>
  129. <body>
  130. <div id="root"></div>
  131. <script type="text/x-jsx" id="app-src">
  132. const {useState,useEffect}=React;
  133. const j=u=>fetch(u,{cache:"no-store"}).then(r=>r.json());
  134. const SCOPES=[["substance","实质"],["form","形式"],["feeling","感受"],["effect","作用"],["intent","意图"]];
  135. const SC_CN={substance:"实质",form:"形式",feeling:"感受",effect:"作用",intent:"意图"};
  136. const TYPECN={how:"How 工序",what:"What 构成",why:"Why 原理"};
  137. const STEPS=[
  138. {key:"read",label:"读懂帖子",desc:"把图、视频读成文字",
  139. why:"创作知识常在图里、不在正文;这一步只忠实转写、不判断,避免一步干两件事。",
  140. src:["/prompt-read/extract.txt"]},
  141. {key:"frame",label:"拆出知识",desc:"从文字里拆出一条条知识,理清每条是 怎么做 / 是什么 / 为什么",
  142. why:"先判类型再拆——三类成形不同,默认 how 会把纯 what 硬掰成假 how;how 先吸进步骤里的 what/why,吸不进的才独立成一条。",
  143. src:["/prompt-skill/phase1-frame.md"]},
  144. {key:"scope",label:"给知识归类",desc:"标好 讲什么 / 起什么作用 / 哪个创作环节,再和已有分类库对上号",
  145. why:"这 5 类标签最容易串味,又要中途调向量回扣工具;单独一步写厚,避免塞进一个提示词复杂到死循环。",
  146. src:["/prompt-skill/phase2-scope.md"]},
  147. {key:"store",label:"存进知识库",desc:"整理成统一格式存起来,供以后调用",
  148. why:"纯拼接 + 校验的确定性活,程序自动做——没有提示词,让 AI 来干只会帮倒忙。",
  149. src:null}
  150. ];
  151. const STEPMAP={}; STEPS.forEach(s=>STEPMAP[s.key]=s);
  152. function Ribbon({onOpen}){
  153. return <div className="ribbon">
  154. <div className="rcap">这套系统怎么「从帖子拆解知识」——点任一步看它的提示词</div>
  155. <div className="rsteps">{STEPS.map((s,i)=><React.Fragment key={s.key}>
  156. {i>0&&<span className="rarrow">→</span>}
  157. <div className={"rstep"+(s.src?"":" auto")} onClick={()=>onOpen(s.key)}>
  158. <div className="rtop"><span className="rn">{i+1}</span><span className="rlab">{s.label}</span></div>
  159. <div className="rdesc">{s.desc}</div>
  160. <div className="rbtn">{s.src?"📄 看提示词":"⚙️ 程序自动"}</div>
  161. </div></React.Fragment>)}</div>
  162. </div>;
  163. }
  164. function ScopeChip({x,onScope,showType}){
  165. 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>;
  166. }
  167. function KScopes({list,onScope}){
  168. if(!list||!list.length) return null;
  169. return <div className="kscopes"><span className="lab">作用域(5 棵树):</span>
  170. {list.map((x,i)=><ScopeChip key={i} x={{...x}} onScope={onScope} showType={true}/>)}</div>;
  171. }
  172. function HowTable({k,onScope,onPrompt}){
  173. const steps=k.steps||[];
  174. const scOf=(s,t)=>(s.作用域||[]).filter(x=>x.scope_type===t);
  175. return <div className="twrap"><table className="proc">
  176. <thead>
  177. <tr><th className="c-idx" rowSpan="2">#</th>
  178. <th className="g-frame" colSpan="5" onClick={()=>onPrompt("frame")}>② 拆框架 + 轻标签 ▸ 提示词</th>
  179. <th className="g-scope" colSpan="5" onClick={()=>onPrompt("scope")}>③ 作用域 ▸ 提示词</th></tr>
  180. <tr><th className="c-intent">目的</th><th className="c-cstage">创作<br/>阶段</th><th className="c-act">动作</th><th className="c-dir">怎么做</th><th className="c-out">产出</th>
  181. {SCOPES.map(([kk,cn])=><th key={kk} className={"c-scope s-"+kk}>{cn}</th>)}</tr>
  182. </thead>
  183. <tbody>{steps.map((s,i)=><tr className="step" key={i}>
  184. <td className="idx">{i+1}</td>
  185. <td className="intent">{s.intent}</td>
  186. <td className="cstage">{s.创作阶段&&<span className="cstage-chip">{s.创作阶段}</span>}</td>
  187. <td className="act">{s.动作&&<span className="act-chip">{s.动作}</span>}</td>
  188. <td className="dir"><span className="dir-txt">{s.directive}</span></td>
  189. <td className="out"><span className="out-txt">{s.output}</span></td>
  190. {SCOPES.map(([kk])=><td key={kk} className={"scope s-"+kk}>{scOf(s,kk).map((x,j)=><ScopeChip key={j} x={x} onScope={onScope}/>)}</td>)}
  191. </tr>)}</tbody>
  192. </table></div>;
  193. }
  194. function WhatCard({k,onScope}){
  195. return <div>
  196. {k.界定&&<div className="def">界定:{k.界定}</div>}
  197. <table className="elem"><thead><tr><th>要素</th><th>说明</th></tr></thead>
  198. <tbody>{(k.构成||[]).map((c,i)=><tr key={i}><td className="el">{c.要素}</td><td>{c.说明}</td></tr>)}</tbody></table>
  199. <KScopes list={k.作用域} onScope={onScope}/>
  200. </div>;
  201. }
  202. function WhyCard({k,onScope}){
  203. return <div>
  204. {k.主张&&<div className="why-row"><span className="why-lab wl-claim">主张</span><span className="why-txt">{k.主张}</span></div>}
  205. {k.依据&&<div className="why-row"><span className="why-lab wl-basis">依据</span><span className="why-txt">{k.依据}</span></div>}
  206. {k["对创作的影响"]&&<div className="why-row"><span className="why-lab wl-impact">影响</span><span className="why-txt">{k["对创作的影响"]}</span></div>}
  207. <KScopes list={k.作用域} onScope={onScope}/>
  208. </div>;
  209. }
  210. function KnowledgeCard({k,idx,onScope,onPrompt,onJson,payload,how_titles}){
  211. const par=k.parent;
  212. return <div className="kcard" id={"k-"+k.id}>
  213. <div className="khead">
  214. <span className="kno">第{idx+1}颗</span>
  215. <span className={"ktype ty-"+k.type}>{TYPECN[k.type]}</span>
  216. <span className="ktitle">{k.title}</span>
  217. {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>}
  218. {(k.业务阶段||[]).map((b,i)=><span key={i} className="kbiz">业务阶段:{b}</span>)}
  219. <span className="kacts">{payload&&<button className="lbtn" onClick={()=>onJson(payload)}>⚙️ 入库 payload</button>}</span>
  220. </div>
  221. <div className="kbody">
  222. {k.type==="how"&&<HowTable k={k} onScope={onScope} onPrompt={onPrompt}/>}
  223. {k.type==="what"&&<WhatCard k={k} onScope={onScope}/>}
  224. {k.type==="why"&&<WhyCard k={k} onScope={onScope}/>}
  225. </div>
  226. </div>;
  227. }
  228. function App(){
  229. const [items,setItems]=useState([]);
  230. const [sel,setSel]=useState(0);
  231. const [scope,setScope]=useState(null);
  232. const [pm,setPm]=useState(null);
  233. const [json,setJson]=useState(null);
  234. const [zoom,setZoom]=useState(null);
  235. const [hl,setHl]=useState(false);
  236. useEffect(()=>{Promise.all([j("/frameworks.json"),j("/payloads.json").catch(()=>[])]).then(([f,pl])=>{
  237. setItems((f.posts||[]).map(p=>({...p,payloads:(pl||[]).filter(x=>(x.source||{}).id==="xhs_"+p.post_id)})));});},[]);
  238. useEffect(()=>{document.body.classList.toggle("hl",hl);},[hl]);
  239. function openPrompt(key){
  240. const st=STEPMAP[key]; if(!st) return;
  241. if(!st.src){ setPm({label:st.label,why:st.why,text:"(这一步是程序自动完成的,没有提示词。)"}); return; }
  242. setPm({label:st.label,why:st.why,text:"加载中…"});
  243. Promise.all(st.src.map(u=>fetch(u,{cache:"no-store"}).then(r=>r.ok?r.text():"(未找到 "+u+")").catch(()=>"(加载失败 "+u+")")))
  244. .then(txts=>setPm({label:st.label,why:st.why,text:txts.join("\n\n──────\n\n")}));
  245. }
  246. if(!items.length) return <div className="empty">加载中…</div>;
  247. const it=items[sel];
  248. const ks=it.knowledges||[];
  249. const how_titles={}; ks.forEach(k=>{if(k.type==="how")how_titles[k.id]=k.title;});
  250. const payloadOf=k=>(it.payloads||[]).find(p=>p.title===k.title);
  251. const imgs=[]; for(let n=1;n<=(it.img_count||0);n++) imgs.push(it.img_base+"/image_"+n+".webp");
  252. return <div className="app">
  253. <header className="top">
  254. <h1>创作知识的解构</h1>
  255. <div className="picker"><span className="lab">帖子:</span>
  256. {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>)}
  257. </div>
  258. </header>
  259. <div className="scroll">
  260. <Ribbon onOpen={openPrompt}/>
  261. <details className="src"><summary>原帖:{it.title}(这一帖的原料)</summary>
  262. <div className="srcbody"><a href={it.url} target="_blank" rel="noreferrer">{it.url}</a> · {it.platform}
  263. <div className="srcimgs">{imgs.map((u,i)=><img key={i} src={u} onClick={()=>setZoom(u)} onError={e=>e.target.style.display='none'}/>)}</div></div>
  264. </details>
  265. <div className="barhint" style={{display:"flex",gap:8,alignItems:"center"}}>
  266. <span>这一帖拆出 <b>{ks.length}</b> 颗知识(how 工序表 / what 要素表 / why 论点卡);组件颗标「出自某 how 第 N 步」可点跳。</span>
  267. <button className={"lbtn"+(hl?" on":"")} onClick={()=>setHl(!hl)} style={{marginLeft:"auto"}}>高亮「新值」</button>
  268. </div>
  269. {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>}
  270. {ks.map((k,i)=><KnowledgeCard key={i} k={k} idx={i} onScope={setScope} onPrompt={openPrompt} onJson={setJson} payload={payloadOf(k)} how_titles={how_titles}/>)}
  271. </div>
  272. {scope&&<div><div className="ov" onClick={()=>setScope(null)}></div>
  273. <div className="drawer"><h2>作用域回扣详情<span className="x" onClick={()=>setScope(null)}>×</span></h2>
  274. <div className="dc">
  275. <div className="row"><b>提取候选值</b>:{scope.candidate||scope.value}</div>
  276. <div className="row"><b>判定</b>:<span className={"verdict "+(scope.link==="复用"?"v-reuse":"v-new")}>{scope.link==="复用"?"复用现有节点":"新建(树里没有)"}</span> = 最近邻 {scope.score}</div>
  277. <div className="row"><b>最终入库值</b>:{scope.value}</div>
  278. <h3 style={{margin:"12px 0 6px",fontSize:12.5,color:"#475569"}}>分类树最近邻 top-{(scope.top||[]).length}(火山余弦)</h3>
  279. {(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">{h.path}</div></div>)}
  280. {(!scope.top||!scope.top.length)&&<div style={{color:"#9ca3af"}}>无最近邻</div>}
  281. </div></div></div>}
  282. {pm&&<div className="modal" onClick={()=>setPm(null)}><div className="mbox" onClick={e=>e.stopPropagation()}>
  283. <h2>📄 {pm.label} · 这一步的提示词<span className="x" onClick={()=>setPm(null)}>×</span></h2>
  284. <div className="body">
  285. <div className="pmwhy"><b>为什么这么做:</b>{pm.why}</div>
  286. <pre>{pm.text}</pre></div></div></div>}
  287. {json&&<div className="modal" onClick={()=>setJson(null)}><div className="mbox" onClick={e=>e.stopPropagation()}>
  288. <h2>⚙️ ingest payload(给机器看)<span className="x" onClick={()=>setJson(null)}>×</span></h2>
  289. <div className="body"><pre>{JSON.stringify(json,null,2)}</pre></div></div></div>}
  290. {zoom&&<div className="lb" onClick={()=>setZoom(null)}><img src={zoom}/></div>}
  291. </div>;
  292. }
  293. ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
  294. </script>
  295. <script>
  296. (function(){var src=document.getElementById("app-src").textContent;
  297. var out=Babel.transform(src,{presets:[["react",{runtime:"classic"}]]}).code;(0,eval)(out);})();
  298. </script>
  299. </body>
  300. </html>