|
|
@@ -75,6 +75,16 @@
|
|
|
a{color:var(--ac)}
|
|
|
.empty{color:var(--mut);padding:40px;text-align:center}
|
|
|
.sub2{color:var(--mut);font-size:12px;margin:-2px 0 8px}
|
|
|
+ .sx{margin-left:auto;display:flex;gap:6px}
|
|
|
+ .pbtn{font-size:12px;padding:2px 10px;border-radius:7px;background:#fff;border:1px solid var(--line);color:var(--mut);cursor:pointer;font-family:inherit}
|
|
|
+ .pbtn:hover{border-color:var(--ac);color:var(--ac)}
|
|
|
+ .pmodal{background:var(--panel);border:1px solid var(--line);border-radius:12px;max-width:760px;width:90vw;max-height:86vh;overflow:auto;padding:16px 18px;cursor:auto}
|
|
|
+ .pmhead{display:flex;align-items:center;gap:10px;margin-bottom:6px}
|
|
|
+ .pmhead b{font-size:15px}
|
|
|
+ .pmmodel{font-size:12px;color:var(--ac);background:#eef4fb;padding:1px 8px;border-radius:7px}
|
|
|
+ .pmx{margin-left:auto;cursor:pointer;color:var(--mut);font-size:16px;line-height:1}
|
|
|
+ .pmlabel{font-size:12px;color:var(--mut);font-weight:600;margin:12px 0 4px}
|
|
|
+ .pmnote{font-size:12px;color:var(--warn);background:#faeeda;padding:6px 10px;border-radius:6px;margin-top:4px}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
@@ -102,11 +112,27 @@ function PostList({posts,sel,onSel}){
|
|
|
</div>;
|
|
|
}
|
|
|
|
|
|
-function Stage({n,title,sub,children}){
|
|
|
- return <div className="stage"><h3><span className="n">{n}</span>{title}</h3>
|
|
|
+function Stage({n,title,sub,extra,children}){
|
|
|
+ return <div className="stage"><h3><span className="n">{n}</span>{title}{extra&&<span className="sx">{extra}</span>}</h3>
|
|
|
<div className="body">{sub&&<div className="sub2">{sub}</div>}{children}</div></div>;
|
|
|
}
|
|
|
|
|
|
+function PromptBtn({label,onClick}){
|
|
|
+ return <button className="pbtn" onClick={onClick}>📄 {label||"提示词"}</button>;
|
|
|
+}
|
|
|
+
|
|
|
+function PromptModal({item,onClose}){
|
|
|
+ if(!item) return null;
|
|
|
+ return <div className="lb" onClick={onClose}>
|
|
|
+ <div className="pmodal" onClick={e=>e.stopPropagation()}>
|
|
|
+ <div className="pmhead"><b>{item.label} · 提示词</b><span className="pmmodel">{item.model}</span><span className="pmx" onClick={onClose}>✕</span></div>
|
|
|
+ {item.note&&<div className="pmnote">{item.note}</div>}
|
|
|
+ <div className="pmlabel">System(角色设定)</div><pre>{item.system}</pre>
|
|
|
+ <div className="pmlabel">User(输入模板)</div><pre>{item.user}</pre>
|
|
|
+ </div>
|
|
|
+ </div>;
|
|
|
+}
|
|
|
+
|
|
|
function KPCard({it,idx}){
|
|
|
const item=it.item||{}, deco=it.deconstruction||{}, payload=it.ingest_payload||{};
|
|
|
const types=(item.knowledge_types||[]).map(t=>t.toUpperCase()).join(" + ");
|
|
|
@@ -149,7 +175,7 @@ function KPCard({it,idx}){
|
|
|
</div>;
|
|
|
}
|
|
|
|
|
|
-function Pipeline({postId,onZoom}){
|
|
|
+function Pipeline({postId,onZoom,prompts,onPrompt}){
|
|
|
const [post,setPost]=useState(null);
|
|
|
const [items,setItems]=useState([]);
|
|
|
useEffect(()=>{ if(!postId)return; setPost(null);setItems([]);
|
|
|
@@ -171,7 +197,8 @@ function Pipeline({postId,onZoom}){
|
|
|
onClick={()=>onZoom(u)} onError={e=>e.target.style.display='none'}/>)}</div>
|
|
|
</Stage>
|
|
|
|
|
|
- <Stage n="2" title="筛选" sub="判断这篇帖子值不值得提取成创作知识">
|
|
|
+ <Stage n="2" title="筛选" sub="判断这篇帖子值不值得提取成创作知识"
|
|
|
+ extra={prompts.screen && <PromptBtn label="提示词" onClick={()=>onPrompt('screen')}/>}>
|
|
|
<div className="crit">
|
|
|
<div className="t">筛选标准(4 条都满足才通过)</div>
|
|
|
<ul>
|
|
|
@@ -190,7 +217,11 @@ function Pipeline({postId,onZoom}){
|
|
|
</Stage>
|
|
|
|
|
|
<Stage n="3" title={"知识点("+items.length+" 条)"}
|
|
|
- sub="每条 = 知识类型 + What/Why/How + 对应阶段 + 作用域">
|
|
|
+ sub="每条 = 知识类型 + What/Why/How + 对应阶段 + 作用域"
|
|
|
+ extra={<React.Fragment>
|
|
|
+ {prompts.split && <PromptBtn label="拆分提示词" onClick={()=>onPrompt('split')}/>}
|
|
|
+ {prompts.deconstruct && <PromptBtn label="解构提示词" onClick={()=>onPrompt('deconstruct')}/>}
|
|
|
+ </React.Fragment>}>
|
|
|
{items.length? items.map((it,i)=><KPCard key={i} it={it} idx={i+1}/>) : <div className="kv">无(被淘汰或未拆出)</div>}
|
|
|
</Stage>
|
|
|
</div>;
|
|
|
@@ -200,14 +231,18 @@ function App(){
|
|
|
const [posts,setPosts]=useState([]);
|
|
|
const [sel,setSel]=useState(null);
|
|
|
const [zoom,setZoom]=useState(null);
|
|
|
+ const [prompts,setPrompts]=useState({});
|
|
|
+ const [pm,setPm]=useState(null);
|
|
|
useEffect(()=>{ j("/api/posts").then(ps=>{setPosts(ps); if(ps[0])setSel(ps[0].id);}); },[]);
|
|
|
+ useEffect(()=>{ j("/api/prompts").then(r=>{const m={};(r.items||[]).forEach(it=>m[it.key]=it);setPrompts(m);}); },[]);
|
|
|
return <div>
|
|
|
<header>创作知识 <span className="sub">· 帖子知识流水线({posts.length} 帖)</span></header>
|
|
|
<div className="wrap">
|
|
|
<PostList posts={posts} sel={sel} onSel={setSel}/>
|
|
|
- <div className="main"><Pipeline postId={sel} onZoom={setZoom}/></div>
|
|
|
+ <div className="main"><Pipeline postId={sel} onZoom={setZoom} prompts={prompts} onPrompt={setPm}/></div>
|
|
|
</div>
|
|
|
{zoom&&<div className="lb" onClick={()=>setZoom(null)}><img src={zoom}/></div>}
|
|
|
+ {pm&&<PromptModal item={prompts[pm]} onClose={()=>setPm(null)}/>}
|
|
|
</div>;
|
|
|
}
|
|
|
ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
|