|
|
@@ -30,6 +30,21 @@
|
|
|
.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:148px;border:1px solid #e2e8f0;border-radius:8px;padding:9px 11px;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}
|
|
|
/* 颗卡 */
|
|
|
@@ -85,7 +100,8 @@
|
|
|
.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:9px;opacity:.6;font-weight:700;margin-right:3px}
|
|
|
+ .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}
|
|
|
@@ -116,21 +132,45 @@
|
|
|
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 PROMPTS={
|
|
|
- read:{title:"① 读懂内容 · 提示词",text:`把帖子的图片/视频忠实读成文字,作为后面拆颗的原料。\n· 只记录帖子真实讲了什么——不评价、不替它发挥、不补帖子没有的例子。\n· 图里的要点、视频里的讲解都转写成文字。\n· 这一步只读、不判断、不拆分归类。`},
|
|
|
- frame:{title:"② 判颗 + 类型闸 + 三 lane 成形 · 提示词",text:`颗是单位,一帖 = N 颗(可混 how/what/why)。先判类型闸,再按类型成形。\n\n【拆颗顺序】\n有 how 工序?→ 抽 how 骨架;how 步骤里嵌的 what/why 既显示在步骤里,又【单列成组件颗】(parent 指回第 N 步);how 没覆盖的独立 what/why 也单列;没 how 就是纯 what/why。\n别硬造假 how("7 种叙事结构"当菜单或单列成 what,别掰成 how 框架)。\n\n【三类成形】\n· How:目标 + 不可跳步 steps(目的/怎么做/产出,原帖例子原文保留)\n· What:界定 + 构成要素[{要素,说明}]\n· Why:主张 + 依据 + 对创作的影响\n\n【组件判据】脱离这个 how 还独立成立、能单独检索复用吗?是→单列成颗。\n\n【轻标签】知识类型 / 业务阶段(颗级) / 创作阶段(仅how逐步) / 动作(仅how逐步)。`},
|
|
|
- scope:{title:"③ 作用域 · 提示词(重)",text:`给每颗标作用域:5 类里相关的几类,每类一个具体短值,再回扣分类树。how 逐步标、what/why 颗级标。\n\n【五类+互斥边界】实质=讲什么(对象);形式=怎么呈现(结构/手法);感受=勾什么情绪;作用=起什么功能(段内干的活);意图=图什么(最终目的)。\n· 实质 vs 作用:「赛道共识」实质、「撕裂共识」作用。\n· 作用 vs 意图:「制造悬念」作用、「引爆传播」意图。\n· 感受 vs 意图:「羞耻感」观众感受、「引爆传播」作者意图。\n\n【值】短词、原子、不跨类重复、不用泛词;判不准就留空。\n【回扣】每个值用火山 embedding 找最近邻:≥0.90 复用树原名,否则新值(丰富树)。点作用域值看回扣详情。`}
|
|
|
-};
|
|
|
+const STEPS=[
|
|
|
+ {key:"read",label:"读懂帖子",desc:"把图、视频读成文字",
|
|
|
+ why:"创作知识常在图里、不在正文;这一步只忠实转写、不判断,避免一步干两件事。",
|
|
|
+ src:["/prompt-read/extract.txt"]},
|
|
|
+ {key:"frame",label:"拆出知识",desc:"从文字里拆出一条条知识,理清每条是 怎么做 / 是什么 / 为什么",
|
|
|
+ why:"先判类型再拆——三类成形不同,默认 how 会把纯 what 硬掰成假 how;how 先吸进步骤里的 what/why,吸不进的才独立成一条。",
|
|
|
+ src:["/prompt-skill/phase1-frame.md"]},
|
|
|
+ {key:"scope",label:"给知识归类",desc:"标好 讲什么 / 起什么作用 / 哪个创作环节,再和已有分类库对上号",
|
|
|
+ why:"这 5 类标签最容易串味,又要中途调向量回扣工具;单独一步写厚,避免塞进一个提示词复杂到死循环。",
|
|
|
+ src:["/prompt-skill/phase2-scope.md"]},
|
|
|
+ {key:"store",label:"存进知识库",desc:"整理成统一格式存起来,供以后调用",
|
|
|
+ why:"纯拼接 + 校验的确定性活,程序自动做——没有提示词,让 AI 来干只会帮倒忙。",
|
|
|
+ src:null}
|
|
|
+];
|
|
|
+const STEPMAP={}; STEPS.forEach(s=>STEPMAP[s.key]=s);
|
|
|
|
|
|
-function ScopeChip({x,onScope}){
|
|
|
- return <span className={"sv s-"+x.scope_type} onClick={()=>onScope(x)}>{x.value}<span className={"mk "+(x.link==="复用"?"mk-reuse":"mk-new")}>{x.link==="复用"?"复":"新"}</span></span>;
|
|
|
+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">作用域:</span>
|
|
|
- {list.map((x,i)=><ScopeChip key={i} x={{...x}} onScope={onScope}/>)}</div>;
|
|
|
+ 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}){
|
|
|
@@ -179,9 +219,8 @@ function KnowledgeCard({k,idx,onScope,onPrompt,onJson,payload,how_titles}){
|
|
|
<span className="kno">第{idx+1}颗</span>
|
|
|
<span className={"ktype ty-"+k.type}>{TYPECN[k.type]}</span>
|
|
|
<span className="ktitle">{k.title}</span>
|
|
|
- <span className="krole">{k.role}</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>)}
|
|
|
+ {(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">
|
|
|
@@ -196,13 +235,20 @@ function App(){
|
|
|
const [items,setItems]=useState([]);
|
|
|
const [sel,setSel]=useState(0);
|
|
|
const [scope,setScope]=useState(null);
|
|
|
- const [prompt,setPrompt]=useState(null);
|
|
|
+ const [pm,setPm]=useState(null);
|
|
|
const [json,setJson]=useState(null);
|
|
|
const [zoom,setZoom]=useState(null);
|
|
|
const [hl,setHl]=useState(false);
|
|
|
useEffect(()=>{Promise.all([j("/frameworks.json"),j("/payloads.json").catch(()=>[])]).then(([f,pl])=>{
|
|
|
setItems((f.posts||[]).map(p=>({...p,payloads:(pl||[]).filter(x=>(x.source||{}).id==="xhs_"+p.post_id)})));});},[]);
|
|
|
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:"加载中…"});
|
|
|
+ Promise.all(st.src.map(u=>fetch(u,{cache:"no-store"}).then(r=>r.ok?r.text():"(未找到 "+u+")").catch(()=>"(加载失败 "+u+")")))
|
|
|
+ .then(txts=>setPm({label:st.label,why:st.why,text:txts.join("\n\n──────\n\n")}));
|
|
|
+ }
|
|
|
if(!items.length) return <div className="empty">加载中…</div>;
|
|
|
const it=items[sel];
|
|
|
const ks=it.knowledges||[];
|
|
|
@@ -217,7 +263,8 @@ function App(){
|
|
|
</div>
|
|
|
</header>
|
|
|
<div className="scroll">
|
|
|
- <details className="src"><summary>① 读懂 · 原帖:{it.title} <span className="srcprompt" onClick={e=>{e.preventDefault();e.stopPropagation();setPrompt("read");}}>▸ 读懂提示词</span></summary>
|
|
|
+ <Ribbon onOpen={openPrompt}/>
|
|
|
+ <details className="src"><summary>原帖:{it.title}(这一帖的原料)</summary>
|
|
|
<div className="srcbody"><a href={it.url} target="_blank" rel="noreferrer">{it.url}</a> · {it.platform}
|
|
|
<div className="srcimgs">{imgs.map((u,i)=><img key={i} src={u} onClick={()=>setZoom(u)} onError={e=>e.target.style.display='none'}/>)}</div></div>
|
|
|
</details>
|
|
|
@@ -225,7 +272,8 @@ function App(){
|
|
|
<span>这一帖拆出 <b>{ks.length}</b> 颗知识(how 工序表 / what 要素表 / why 论点卡);组件颗标「出自某 how 第 N 步」可点跳。</span>
|
|
|
<button className={"lbtn"+(hl?" on":"")} onClick={()=>setHl(!hl)} style={{marginLeft:"auto"}}>高亮「新值」</button>
|
|
|
</div>
|
|
|
- {ks.map((k,i)=><KnowledgeCard key={i} k={k} idx={i} onScope={setScope} onPrompt={setPrompt} onJson={setJson} payload={payloadOf(k)} how_titles={how_titles}/>)}
|
|
|
+ {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} payload={payloadOf(k)} how_titles={how_titles}/>)}
|
|
|
</div>
|
|
|
{scope&&<div><div className="ov" onClick={()=>setScope(null)}></div>
|
|
|
<div className="drawer"><h2>作用域回扣详情<span className="x" onClick={()=>setScope(null)}>×</span></h2>
|
|
|
@@ -237,9 +285,11 @@ function App(){
|
|
|
{(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>)}
|
|
|
{(!scope.top||!scope.top.length)&&<div style={{color:"#9ca3af"}}>无最近邻</div>}
|
|
|
</div></div></div>}
|
|
|
- {prompt&&<div className="modal" onClick={()=>setPrompt(null)}><div className="mbox" onClick={e=>e.stopPropagation()}>
|
|
|
- <h2>📄 {PROMPTS[prompt].title}<span className="x" onClick={()=>setPrompt(null)}>×</span></h2>
|
|
|
- <div className="body"><pre>{PROMPTS[prompt].text}</pre></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>
|
|
|
+ <pre>{pm.text}</pre></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>}
|