index.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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. :root{
  12. --bg:#f3efe4; --panel:#fff; --panel2:#faf7ef; --line:#e7e2d4; --line2:#ece7d9;
  13. --fg:#2c2c2a; --mut:#6f6e68; --tert:#9a978d; --ac:#185fa5;
  14. }
  15. *{box-sizing:border-box}
  16. body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.6 -apple-system,"PingFang SC",Segoe UI,Roboto,sans-serif}
  17. header{padding:12px 20px;background:var(--panel);border-bottom:1px solid var(--line);display:flex;gap:10px;align-items:baseline}
  18. header h0{font-size:16px;font-weight:650;margin:0}
  19. header .sub{color:var(--mut);font-weight:400;font-size:12.5px}
  20. .pbtn{margin-left:auto;background:#1e293b;color:#fff;border:0;border-radius:7px;padding:6px 13px;font-size:12.5px;cursor:pointer;font-family:inherit}
  21. .pbtn:hover{background:#334155}
  22. .legend{background:var(--panel2);border-bottom:1px solid var(--line);padding:8px 20px;font-size:11.5px;color:var(--mut);display:flex;gap:18px;flex-wrap:wrap;align-items:center}
  23. .legend .grp{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
  24. .legend .gl{font-weight:700;color:var(--fg)}
  25. .legend .it{white-space:nowrap}
  26. .wrap{display:flex;height:calc(100vh - 50px - 38px)}
  27. .side{width:320px;background:var(--panel);border-right:1px solid var(--line);overflow:auto;flex:none}
  28. .sidecap{padding:9px 14px;font-size:11px;color:var(--tert);font-weight:600;border-bottom:1px solid var(--line);background:var(--panel2);position:sticky;top:0}
  29. .main{flex:1;overflow:auto;padding:18px 26px}
  30. .pitem{padding:11px 14px;border-bottom:1px solid var(--line);cursor:pointer}
  31. .pitem:hover{background:#f7f3ea}
  32. .pitem.sel{background:#eef4fb;box-shadow:inset 3px 0 0 var(--ac)}
  33. .pitem .ftitle{font-size:13.5px;font-weight:600;line-height:1.45}
  34. .pitem .r{display:flex;gap:5px;align-items:center;margin-top:5px;flex-wrap:wrap}
  35. .pbg{font-size:10.5px;font-weight:700;padding:1px 6px;border-radius:3px;background:#ffe4e6;color:#be123c}
  36. .pitem .src{font-size:11px;color:var(--tert);margin-top:3px}
  37. .chip{font-size:11px;padding:1px 8px;border-radius:10px;white-space:nowrap}
  38. .c-stage{background:#ede9fe;color:#5b21b6}
  39. .c-type{background:#e6f1fb;color:#185fa5}
  40. .c-step{font-size:11px;padding:0 7px;border-radius:4px;background:#eef2f7;color:#475569;font-weight:600}
  41. .sc{font-size:11px;padding:1px 8px;border-radius:4px;margin:0 4px 4px 0;display:inline-block}
  42. .sc-实质{background:#fee2e2;color:#991b1b}.sc-形式{background:#dbeafe;color:#1e40af}
  43. .sc-感受{background:#fce7f3;color:#9d174d}.sc-作用{background:#dcfce7;color:#166534}.sc-意图{background:#fef3c7;color:#92400e}
  44. .dh{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:2px}
  45. .dh h1{font-size:20px;margin:0;font-weight:700}
  46. .dh .acts{margin-left:auto;display:flex;gap:8px;align-items:center}
  47. .dh a{color:var(--ac);text-decoration:none;font-size:12.5px}
  48. .minib{background:#eef2f7;color:#334155;border:1px solid var(--line);border-radius:6px;padding:4px 10px;font-size:12px;cursor:pointer;font-family:inherit}
  49. .minib:hover{background:#e2e8f0}
  50. .ptitle{color:var(--mut);font-size:12.5px;margin-bottom:12px}
  51. .meta{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-bottom:16px}
  52. /* 下游字段名作主标签 */
  53. .flab{display:inline-block;font-size:11px;font-weight:700;color:#fff;background:#0f766e;border-radius:5px;padding:1px 9px;margin-bottom:7px}
  54. .block{margin-bottom:18px}
  55. .purpose{background:var(--panel);border:1px solid var(--line);border-left:3px solid #0f766e;border-radius:8px;padding:11px 14px;font-size:14.5px;font-weight:600;line-height:1.55}
  56. .want{background:var(--panel2);border:1px dashed var(--line);border-radius:8px;padding:8px 12px;font-size:13px}
  57. .want b{color:var(--ac)}
  58. .step{background:var(--panel);border:1px solid var(--line);border-radius:10px;margin-bottom:12px;overflow:hidden}
  59. .step-h{display:flex;align-items:center;gap:9px;padding:9px 14px;background:var(--panel2);border-bottom:1px solid var(--line2)}
  60. .step-n{background:#0f766e;color:#fff;width:auto;min-width:46px;height:22px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:11px;flex:none;padding:0 8px;font-weight:600}
  61. .step-intent{font-size:14px;font-weight:600;flex:1}
  62. .step-intent .vlabel{color:var(--tert);font-weight:400;font-size:12px}
  63. .step-tags{padding:8px 14px 0;display:flex;gap:5px;flex-wrap:wrap;align-items:center}
  64. .rowlab{display:flex;gap:8px;padding:9px 14px 0}
  65. .rl{font-size:10px;font-weight:700;border-radius:4px;padding:1px 7px;flex:none;height:18px;line-height:16px}
  66. .rl-how{background:#e6f1fb;color:#185fa5}
  67. .rl-out{background:#dcfce7;color:#15803d}
  68. .directive{font-size:13px;line-height:1.72;white-space:pre-wrap;color:#33312c;flex:1}
  69. .outtxt{font-size:12.5px;line-height:1.6;color:#166534;flex:1}
  70. .from{font-size:11px;color:var(--tert);margin-left:auto}
  71. details.box{margin-top:14px;border:1px solid var(--line);border-radius:8px;background:var(--panel)}
  72. details.box>summary{padding:9px 14px;cursor:pointer;font-size:12.5px;color:var(--mut);list-style:none}
  73. details.box>summary::before{content:'▶ ';color:var(--tert)}
  74. details.box[open]>summary::before{content:'▼ '}
  75. .box .bd{padding:4px 14px 12px}
  76. .drop{font-size:12px;color:var(--mut);margin:4px 0;line-height:1.6}
  77. .imgs{display:flex;flex-wrap:wrap;gap:7px;padding:4px 14px 12px}
  78. .imgs img{width:96px;height:96px;object-fit:cover;border-radius:6px;border:1px solid var(--line);cursor:zoom-in}
  79. .lb{position:fixed;inset:0;background:rgba(0,0,0,.82);display:flex;align-items:center;justify-content:center;z-index:50;cursor:zoom-out}
  80. .lb img{max-width:92vw;max-height:92vh;border-radius:8px}
  81. .empty{color:var(--tert);padding:40px;text-align:center}
  82. .ov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:60}
  83. .modal{background:#fff;width:780px;max-width:92vw;max-height:86vh;border-radius:10px;overflow:hidden;display:flex;flex-direction:column}
  84. .modal h2{margin:0;padding:13px 18px;background:#1e293b;color:#fff;font-size:14px;display:flex;align-items:center}
  85. .modal h2 .x{margin-left:auto;cursor:pointer;font-size:18px}
  86. .modal .mbody{padding:14px 18px;overflow:auto}
  87. .modal pre{margin:0;font:12px/1.62 ui-monospace,"SF Mono",Menlo,monospace;color:#1f2937;white-space:pre-wrap}
  88. .modal .fk{font-size:11px;color:var(--tert);margin:10px 0 3px;font-weight:700}
  89. .modal .fv{font-size:13px;color:#1f2937;margin-bottom:4px}
  90. .modal .cwrap{background:#f7f7f5;border:1px solid var(--line);border-radius:7px;padding:11px 13px;margin-top:4px}
  91. </style>
  92. </head>
  93. <body>
  94. <div id="root"></div>
  95. <script type="text/babel">
  96. const {useState,useEffect} = React;
  97. const j = u => fetch(u).then(r=>r.json());
  98. const PROMPT = `# 创作知识提取(需求驱动 · 完整框架粒度)
  99. 产物:一颗知识 = 一个完整创作框架
  100. = 一个 purpose(把某种输入一路做成一个可交付成品)+ 一条不可跳步的 steps 链。
  101. 一帖通常只出 1-2 颗。绝不把框架内部的步骤/要点拆成独立知识。
  102. ## 提取四步
  103. 1. 数闭环:找帖里所有“输入→可交付成品”的完整链,几条算几颗。
  104. 2. 去重叠:若某链成品其实是另一条链的中间产物,并入后者。
  105. 3. 压清单:所有“N选1 / 并列要素”压进所属 step,不按项数拆颗、不拆成多步。
  106. 4. 定步数:每颗按“几次不可省的形态转换”定 steps(2-6),动词只用 决策/分析/构建/撰写/打磨。
  107. ## 一颗的结构
  108. - title:6-12字名词短语,以“框架/创作/写作”收尾。
  109. - purpose:「按「A→B→C」N步,将X写成可直接交付的Y」。
  110. - 每 step:intent="<双字动词>,<任务>";directive=把怎么做的全部细节压进来(清单+判据+正反例+阈值+反模式);output=交付物验收规格。
  111. ## 只要创作,剔除制作(按“设计决策 vs 工艺执行”判,不看词面)
  112. 保留:景别/角度/运镜/构图/字幕/配乐/结构/时长。剔除:器材/打光/剪辑软件操作/调色参数/导出。
  113. ## 标签
  114. 知识类型(What/Why/How)、阶段(灵感/选题/脚本)、作用域(实质/形式/感受/作用/意图,可多选)。`;
  115. // 把一颗框架拍平成下游消费的 content 文本(与 id 1005 同款)
  116. function buildContent(fw){
  117. let s = "目标:" + (fw.purpose||"") + "\n";
  118. (fw.steps||[]).forEach((st,i)=>{
  119. s += "步骤"+(i+1)+"(目的:"+(st.intent||"")+")\n";
  120. s += " 指引:"+(st.directive||"")+"\n";
  121. s += " 产出:"+(st.output||"")+"\n";
  122. });
  123. return s.replace(/\n+$/,"");
  124. }
  125. function Scopes({list}){ return (list||[]).map((s,i)=><span key={i} className={"sc sc-"+s}>{s}</span>); }
  126. function Tags({stage,type,scopes}){ return <React.Fragment>
  127. {type&&<span className="chip c-type">类型·{type}</span>}
  128. {stage&&<span className="chip c-stage">阶段·{stage}</span>}
  129. <Scopes list={scopes}/>
  130. </React.Fragment>; }
  131. function Legend(){
  132. return <div className="legend">
  133. <div className="grp"><span className="gl">怎么读:</span><span className="it">左边一颗知识 → 右边是它下发给下游的格式:<b>目标</b> + <b>步骤</b>(目的/指引/产出)。点右上「下游记录」看实际 content。</span></div>
  134. <div className="grp"><span className="gl">类型</span><span className="it"><b>What</b>是什么</span><span className="it"><b>Why</b>为什么</span><span className="it"><b>How</b>怎么做</span></div>
  135. <div className="grp"><span className="gl">阶段</span><span className="it">灵感·找方向</span><span className="it">选题·定写什么</span><span className="it">脚本·组织表达</span></div>
  136. <div className="grp"><span className="gl">作用域</span><span className="sc sc-实质">实质讲什么</span><span className="sc sc-形式">形式怎么呈现</span><span className="sc sc-感受">感受勾情绪</span><span className="sc sc-作用">作用起功能</span><span className="sc sc-意图">意图创作者目的</span></div>
  137. </div>;
  138. }
  139. function PostList({posts,sel,onSel}){
  140. return <div className="side">
  141. <div className="sidecap">知识列表 · 每条 = 一颗完整创作框架</div>
  142. {posts.map(p=>{ const fw=p.framework||{};
  143. return <div key={p.post_id} className={"pitem"+(sel===p.post_id?" sel":"")} onClick={()=>onSel(p.post_id)}>
  144. <div className="ftitle">{fw.title||p.title}</div>
  145. <div className="r"><span className="pbg">小红书</span><span className="chip c-type">{fw["知识类型"]}</span><span className="chip c-stage">{fw["阶段跨度"]}</span><span className="c-step">{(fw.steps||[]).length}步</span></div>
  146. <div className="src">原帖:{p.title}</div>
  147. </div>;
  148. })}
  149. </div>;
  150. }
  151. function Step({s,i}){
  152. return <div className="step">
  153. <div className="step-h">
  154. <span className="step-n">步骤{i+1}</span>
  155. <span className="step-intent"><span className="vlabel">目的:</span>{s.intent}</span>
  156. </div>
  157. <div className="step-tags">
  158. {s["阶段"]&&<span className="chip c-stage">阶段·{s["阶段"]}</span>}
  159. {s["类型"]&&<span className="chip c-type">类型·{s["类型"]}</span>}
  160. <Scopes list={s["作用域"]}/>
  161. {s["出处"]&&s["出处"].length>0 && <span className="from">来源 {s["出处"].join(" · ")}</span>}
  162. </div>
  163. <div className="rowlab"><span className="rl rl-how">指引</span><div className="directive">{s.directive}</div></div>
  164. {s.output && <div className="rowlab" style={{paddingBottom:12}}><span className="rl rl-out">产出</span><div className="outtxt">{s.output}</div></div>}
  165. </div>;
  166. }
  167. function Detail({post,onZoom,onRecord}){
  168. if(!post) return <div className="empty">← 左边选一颗知识</div>;
  169. const fw=post.framework||{};
  170. const imgs=[]; for(let n=1;n<=(post.img_count||0);n++) imgs.push(post.img_base+"/image_"+n+".webp");
  171. return <div>
  172. <div className="dh">
  173. <h1>{fw.title}</h1>
  174. <div className="acts">
  175. <button className="minib" onClick={()=>onRecord(post)}>📥 下游记录</button>
  176. <a href={post.url} target="_blank" rel="noreferrer">🔗 原帖</a>
  177. </div>
  178. </div>
  179. <div className="ptitle">原帖:{post.title} · 小红书</div>
  180. <div className="meta"><Tags stage={fw["阶段跨度"]} type={fw["知识类型"]} scopes={fw["作用域并集"]}/></div>
  181. <div className="block">
  182. <span className="flab">目标</span>
  183. <div className="purpose">{fw.purpose}</div>
  184. </div>
  185. {fw["想要"] && <div className="block"><span className="flab">想要</span><div className="want">{fw["想要"]}</div></div>}
  186. <div className="block">
  187. <span className="flab">步骤({(fw.steps||[]).length})· 目的 / 指引 / 产出</span>
  188. {(fw.steps||[]).map((s,i)=><Step key={i} s={s} i={i}/>)}
  189. </div>
  190. {post.dropped&&post.dropped.length>0 && <details className="box">
  191. <summary>被剔除的「制作 / 素材」({post.dropped.length})— 不算创作知识</summary>
  192. <div className="bd">{post.dropped.map((d,i)=><div className="drop" key={i}>• {typeof d==="string"?d:(d.item||JSON.stringify(d))}{d.reason?(" — "+d.reason):""}</div>)}</div>
  193. </details>}
  194. <details className="box">
  195. <summary>原帖图片({imgs.length})· 知识从这里提炼</summary>
  196. <div className="imgs">{imgs.map((u,i)=><img key={i} src={u} onClick={()=>onZoom(u)} onError={e=>e.target.style.display='none'}/>)}</div>
  197. </details>
  198. </div>;
  199. }
  200. function RecordModal({post,onClose}){
  201. const fw=post.framework||{};
  202. const content=buildContent(fw);
  203. const record={ title: fw.title, purpose: fw.purpose, content };
  204. return <div className="ov" onClick={onClose}>
  205. <div className="modal" onClick={e=>e.stopPropagation()}>
  206. <h2>下游消费记录(这颗知识下发给「脚本构建 Agent」的样子)<span className="x" onClick={onClose}>×</span></h2>
  207. <div className="mbody">
  208. <div className="fk">title</div><div className="fv">{record.title}</div>
  209. <div className="fk">purpose</div><div className="fv">{record.purpose}</div>
  210. <div className="fk">content(拍平后的全文,下游逐字消费)</div>
  211. <div className="cwrap"><pre>{content}</pre></div>
  212. </div>
  213. </div>
  214. </div>;
  215. }
  216. function PromptModal({onClose}){
  217. return <div className="ov" onClick={onClose}>
  218. <div className="modal" onClick={e=>e.stopPropagation()}>
  219. <h2>这套知识是怎么提取出来的(提示词)<span className="x" onClick={onClose}>×</span></h2>
  220. <div className="mbody"><pre>{PROMPT}</pre></div>
  221. </div>
  222. </div>;
  223. }
  224. function App(){
  225. const [posts,setPosts]=useState([]);
  226. const [sel,setSel]=useState(null);
  227. const [zoom,setZoom]=useState(null);
  228. const [pm,setPm]=useState(false);
  229. const [rec,setRec]=useState(null);
  230. useEffect(()=>{ j("/frameworks.json").then(d=>{const ps=d.posts||[];setPosts(ps); if(ps[0])setSel(ps[0].post_id);}); },[]);
  231. const post=posts.find(p=>p.post_id===sel);
  232. return <div>
  233. <header>
  234. <h0>创作知识的搜索和解构</h0>
  235. <span className="sub">· 一帖一颗完整框架({posts.length} 颗)· 已按下游消费格式呈现</span>
  236. <button className="pbtn" onClick={()=>setPm(true)}>📄 提取提示词</button>
  237. </header>
  238. <Legend/>
  239. <div className="wrap">
  240. <PostList posts={posts} sel={sel} onSel={setSel}/>
  241. <div className="main">{posts.length? <Detail post={post} onZoom={setZoom} onRecord={setRec}/> : <div className="empty">加载中…</div>}</div>
  242. </div>
  243. {zoom&&<div className="lb" onClick={()=>setZoom(null)}><img src={zoom}/></div>}
  244. {pm&&<PromptModal onClose={()=>setPm(false)}/>}
  245. {rec&&<RecordModal post={rec} onClose={()=>setRec(null)}/>}
  246. </div>;
  247. }
  248. ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
  249. </script>
  250. </body>
  251. </html>