|
|
@@ -3,269 +3,254 @@
|
|
|
<head>
|
|
|
<meta charset="utf-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
-<title>创作知识的搜索和解构</title>
|
|
|
+<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>
|
|
|
- :root{
|
|
|
- --bg:#f3efe4; --panel:#fff; --panel2:#faf7ef; --line:#e7e2d4; --line2:#ece7d9;
|
|
|
- --fg:#2c2c2a; --mut:#6f6e68; --tert:#9a978d; --ac:#185fa5;
|
|
|
- }
|
|
|
*{box-sizing:border-box}
|
|
|
- body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.6 -apple-system,"PingFang SC",Segoe UI,Roboto,sans-serif}
|
|
|
- header{padding:12px 20px;background:var(--panel);border-bottom:1px solid var(--line);display:flex;gap:10px;align-items:baseline}
|
|
|
- header h0{font-size:16px;font-weight:650;margin:0}
|
|
|
- header .sub{color:var(--mut);font-weight:400;font-size:12.5px}
|
|
|
- .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}
|
|
|
- .pbtn:hover{background:#334155}
|
|
|
- .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}
|
|
|
- .legend .grp{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
|
|
|
- .legend .gl{font-weight:700;color:var(--fg)}
|
|
|
- .legend .it{white-space:nowrap}
|
|
|
- .wrap{display:flex;height:calc(100vh - 50px - 38px)}
|
|
|
- .side{width:320px;background:var(--panel);border-right:1px solid var(--line);overflow:auto;flex:none}
|
|
|
- .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}
|
|
|
- .main{flex:1;overflow:auto;padding:18px 26px}
|
|
|
- .pitem{padding:11px 14px;border-bottom:1px solid var(--line);cursor:pointer}
|
|
|
- .pitem:hover{background:#f7f3ea}
|
|
|
- .pitem.sel{background:#eef4fb;box-shadow:inset 3px 0 0 var(--ac)}
|
|
|
- .pitem .ftitle{font-size:13.5px;font-weight:600;line-height:1.45}
|
|
|
- .pitem .r{display:flex;gap:5px;align-items:center;margin-top:5px;flex-wrap:wrap}
|
|
|
- .pbg{font-size:10.5px;font-weight:700;padding:1px 6px;border-radius:3px;background:#ffe4e6;color:#be123c}
|
|
|
- .pitem .src{font-size:11px;color:var(--tert);margin-top:3px}
|
|
|
- .chip{font-size:11px;padding:1px 8px;border-radius:10px;white-space:nowrap}
|
|
|
- .c-stage{background:#ede9fe;color:#5b21b6}
|
|
|
- .c-type{background:#e6f1fb;color:#185fa5}
|
|
|
- .c-step{font-size:11px;padding:0 7px;border-radius:4px;background:#eef2f7;color:#475569;font-weight:600}
|
|
|
- .sc{font-size:11px;padding:1px 8px;border-radius:4px;margin:0 4px 4px 0;display:inline-block}
|
|
|
- .sc-实质{background:#fee2e2;color:#991b1b}.sc-形式{background:#dbeafe;color:#1e40af}
|
|
|
- .sc-感受{background:#fce7f3;color:#9d174d}.sc-作用{background:#dcfce7;color:#166534}.sc-意图{background:#fef3c7;color:#92400e}
|
|
|
- .dh{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:2px}
|
|
|
- .dh h1{font-size:20px;margin:0;font-weight:700}
|
|
|
- .dh .acts{margin-left:auto;display:flex;gap:8px;align-items:center}
|
|
|
- .dh a{color:var(--ac);text-decoration:none;font-size:12.5px}
|
|
|
- .minib{background:#eef2f7;color:#334155;border:1px solid var(--line);border-radius:6px;padding:4px 10px;font-size:12px;cursor:pointer;font-family:inherit}
|
|
|
- .minib:hover{background:#e2e8f0}
|
|
|
- .ptitle{color:var(--mut);font-size:12.5px;margin-bottom:12px}
|
|
|
- .meta{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-bottom:16px}
|
|
|
- /* 下游字段名作主标签 */
|
|
|
- .flab{display:inline-block;font-size:11px;font-weight:700;color:#fff;background:#0f766e;border-radius:5px;padding:1px 9px;margin-bottom:7px}
|
|
|
- .block{margin-bottom:18px}
|
|
|
- .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}
|
|
|
- .want{background:var(--panel2);border:1px dashed var(--line);border-radius:8px;padding:8px 12px;font-size:13px}
|
|
|
- .want b{color:var(--ac)}
|
|
|
- .step{background:var(--panel);border:1px solid var(--line);border-radius:10px;margin-bottom:12px;overflow:hidden}
|
|
|
- .step-h{display:flex;align-items:center;gap:9px;padding:9px 14px;background:var(--panel2);border-bottom:1px solid var(--line2)}
|
|
|
- .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}
|
|
|
- .step-intent{font-size:14px;font-weight:600;flex:1}
|
|
|
- .step-intent .vlabel{color:var(--tert);font-weight:400;font-size:12px}
|
|
|
- .step-tags{padding:8px 14px 0;display:flex;gap:5px;flex-wrap:wrap;align-items:center}
|
|
|
- .rowlab{display:flex;gap:8px;padding:9px 14px 0}
|
|
|
- .rl{font-size:10px;font-weight:700;border-radius:4px;padding:1px 7px;flex:none;height:18px;line-height:16px}
|
|
|
- .rl-how{background:#e6f1fb;color:#185fa5}
|
|
|
- .rl-out{background:#dcfce7;color:#15803d}
|
|
|
- .directive{font-size:13px;line-height:1.72;white-space:pre-wrap;color:#33312c;flex:1}
|
|
|
- .outtxt{font-size:12.5px;line-height:1.6;color:#166534;flex:1}
|
|
|
- .from{font-size:11px;color:var(--tert);margin-left:auto}
|
|
|
- details.box{margin-top:14px;border:1px solid var(--line);border-radius:8px;background:var(--panel)}
|
|
|
- details.box>summary{padding:9px 14px;cursor:pointer;font-size:12.5px;color:var(--mut);list-style:none}
|
|
|
- details.box>summary::before{content:'▶ ';color:var(--tert)}
|
|
|
- details.box[open]>summary::before{content:'▼ '}
|
|
|
- .box .bd{padding:4px 14px 12px}
|
|
|
- .drop{font-size:12px;color:var(--mut);margin:4px 0;line-height:1.6}
|
|
|
- .imgs{display:flex;flex-wrap:wrap;gap:7px;padding:4px 14px 12px}
|
|
|
- .imgs img{width:96px;height:96px;object-fit:cover;border-radius:6px;border:1px solid var(--line);cursor:zoom-in}
|
|
|
- .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}
|
|
|
- .lb img{max-width:92vw;max-height:92vh;border-radius:8px}
|
|
|
- .empty{color:var(--tert);padding:40px;text-align:center}
|
|
|
- .ov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:60}
|
|
|
- .modal{background:#fff;width:780px;max-width:92vw;max-height:86vh;border-radius:10px;overflow:hidden;display:flex;flex-direction:column}
|
|
|
- .modal h2{margin:0;padding:13px 18px;background:#1e293b;color:#fff;font-size:14px;display:flex;align-items:center}
|
|
|
- .modal h2 .x{margin-left:auto;cursor:pointer;font-size:18px}
|
|
|
- .modal .mbody{padding:14px 18px;overflow:auto}
|
|
|
- .modal pre{margin:0;font:12px/1.62 ui-monospace,"SF Mono",Menlo,monospace;color:#1f2937;white-space:pre-wrap}
|
|
|
- .modal .fk{font-size:11px;color:var(--tert);margin:10px 0 3px;font-weight:700}
|
|
|
- .modal .fv{font-size:13px;color:#1f2937;margin-bottom:4px}
|
|
|
- .modal .cwrap{background:#f7f7f5;border:1px solid var(--line);border-radius:7px;padding:11px 13px;margin-top:4px}
|
|
|
+ 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}
|
|
|
+ .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}
|
|
|
+ .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;cursor:pointer}th.g-frame:hover{background:#155e75}
|
|
|
+ th.g-scope{background:#7c3aed;color:#fff;cursor:pointer}th.g-scope:hover{background:#6d28d9}
|
|
|
+ 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}
|
|
|
+ .from{font-size:11px;color:#9ca3af}
|
|
|
+ /* what 要素表 */
|
|
|
+ .def{font-size:13px;background:#fdf4ff;border-left:3px solid #a21caf;border-radius:0 6px 6px 0;padding:8px 12px;margin:6px 0 10px}
|
|
|
+ table.elem{border-collapse:collapse;width:100%;font-size:12.5px}
|
|
|
+ table.elem th,table.elem td{border:1px solid #f0e6f5;padding:6px 10px;text-align:left;vertical-align:top}
|
|
|
+ table.elem th{background:#fae8ff;color:#86198f;font-weight:600;width:130px}
|
|
|
+ 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}
|
|
|
+ /* 颗级作用域 */
|
|
|
+ .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:9px;opacity:.6;font-weight:700;margin-right:3px}
|
|
|
+ .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}
|
|
|
+ .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/babel">
|
|
|
-const {useState,useEffect} = React;
|
|
|
-const j = u => fetch(u).then(r=>r.json());
|
|
|
+<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 TYPECN={how:"How 工序",what:"What 构成",why:"Why 原理"};
|
|
|
|
|
|
-const PROMPT = `# 创作知识提取(需求驱动 · 完整框架粒度)
|
|
|
+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 复用树原名,否则新值(丰富树)。点作用域值看回扣详情。`}
|
|
|
+};
|
|
|
|
|
|
-产物:一颗知识 = 一个完整创作框架
|
|
|
-= 一个 purpose(把某种输入一路做成一个可交付成品)+ 一条不可跳步的 steps 链。
|
|
|
-一帖通常只出 1-2 颗。绝不把框架内部的步骤/要点拆成独立知识。
|
|
|
-
|
|
|
-## 提取四步
|
|
|
-1. 数闭环:找帖里所有“输入→可交付成品”的完整链,几条算几颗。
|
|
|
-2. 去重叠:若某链成品其实是另一条链的中间产物,并入后者。
|
|
|
-3. 压清单:所有“N选1 / 并列要素”压进所属 step,不按项数拆颗、不拆成多步。
|
|
|
-4. 定步数:每颗按“几次不可省的形态转换”定 steps(2-6),动词只用 决策/分析/构建/撰写/打磨。
|
|
|
-
|
|
|
-## 一颗的结构
|
|
|
-- title:6-12字名词短语,以“框架/创作/写作”收尾。
|
|
|
-- purpose:「按「A→B→C」N步,将X写成可直接交付的Y」。
|
|
|
-- 每 step:intent="<双字动词>,<任务>";directive=把怎么做的全部细节压进来(清单+判据+正反例+阈值+反模式);output=交付物验收规格。
|
|
|
-
|
|
|
-## 只要创作,剔除制作(按“设计决策 vs 工艺执行”判,不看词面)
|
|
|
-保留:景别/角度/运镜/构图/字幕/配乐/结构/时长。剔除:器材/打光/剪辑软件操作/调色参数/导出。
|
|
|
-
|
|
|
-## 标签
|
|
|
-知识类型(What/Why/How)、阶段(灵感/选题/脚本)、作用域(实质/形式/感受/作用/意图,可多选)。`;
|
|
|
-
|
|
|
-// 把一颗框架拍平成下游消费的 content 文本(与 id 1005 同款)
|
|
|
-function buildContent(fw){
|
|
|
- let s = "目标:" + (fw.purpose||"") + "\n";
|
|
|
- (fw.steps||[]).forEach((st,i)=>{
|
|
|
- s += "步骤"+(i+1)+"(目的:"+(st.intent||"")+")\n";
|
|
|
- s += " 指引:"+(st.directive||"")+"\n";
|
|
|
- s += " 产出:"+(st.output||"")+"\n";
|
|
|
- });
|
|
|
- return s.replace(/\n+$/,"");
|
|
|
+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 Scopes({list}){ return (list||[]).map((s,i)=><span key={i} className={"sc sc-"+s}>{s}</span>); }
|
|
|
-function Tags({stage,type,scopes}){ return <React.Fragment>
|
|
|
- {type&&<span className="chip c-type">类型·{type}</span>}
|
|
|
- {stage&&<span className="chip c-stage">阶段·{stage}</span>}
|
|
|
- <Scopes list={scopes}/>
|
|
|
-</React.Fragment>; }
|
|
|
-
|
|
|
-function Legend(){
|
|
|
- return <div className="legend">
|
|
|
- <div className="grp"><span className="gl">怎么读:</span><span className="it">左边一颗知识 → 右边是它下发给下游的格式:<b>目标</b> + <b>步骤</b>(目的/指引/产出)。点右上「下游记录」看实际 content。</span></div>
|
|
|
- <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>
|
|
|
- <div className="grp"><span className="gl">阶段</span><span className="it">灵感·找方向</span><span className="it">选题·定写什么</span><span className="it">脚本·组织表达</span></div>
|
|
|
- <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>
|
|
|
- </div>;
|
|
|
+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>;
|
|
|
}
|
|
|
|
|
|
-function PostList({posts,sel,onSel}){
|
|
|
- return <div className="side">
|
|
|
- <div className="sidecap">知识列表 · 每条 = 一颗完整创作框架</div>
|
|
|
- {posts.map(p=>{ const fw=p.framework||{};
|
|
|
- return <div key={p.post_id} className={"pitem"+(sel===p.post_id?" sel":"")} onClick={()=>onSel(p.post_id)}>
|
|
|
- <div className="ftitle">{fw.title||p.title}</div>
|
|
|
- <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>
|
|
|
- <div className="src">原帖:{p.title}</div>
|
|
|
- </div>;
|
|
|
- })}
|
|
|
- </div>;
|
|
|
+function HowTable({k,onScope,onPrompt}){
|
|
|
+ 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" onClick={()=>onPrompt("frame")}>② 拆框架 + 轻标签 ▸ 提示词</th>
|
|
|
+ <th className="g-scope" colSpan="5" onClick={()=>onPrompt("scope")}>③ 作用域 ▸ 提示词</th></tr>
|
|
|
+ <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>
|
|
|
+ {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">{s.intent}</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 Step({s,i}){
|
|
|
- return <div className="step">
|
|
|
- <div className="step-h">
|
|
|
- <span className="step-n">步骤{i+1}</span>
|
|
|
- <span className="step-intent"><span className="vlabel">目的:</span>{s.intent}</span>
|
|
|
- </div>
|
|
|
- <div className="step-tags">
|
|
|
- {s["阶段"]&&<span className="chip c-stage">阶段·{s["阶段"]}</span>}
|
|
|
- {s["类型"]&&<span className="chip c-type">类型·{s["类型"]}</span>}
|
|
|
- <Scopes list={s["作用域"]}/>
|
|
|
- {s["出处"]&&s["出处"].length>0 && <span className="from">来源 {s["出处"].join(" · ")}</span>}
|
|
|
- </div>
|
|
|
- <div className="rowlab"><span className="rl rl-how">指引</span><div className="directive">{s.directive}</div></div>
|
|
|
- {s.output && <div className="rowlab" style={{paddingBottom:12}}><span className="rl rl-out">产出</span><div className="outtxt">{s.output}</div></div>}
|
|
|
+function WhatCard({k,onScope}){
|
|
|
+ return <div>
|
|
|
+ {k.界定&&<div className="def">界定:{k.界定}</div>}
|
|
|
+ <table className="elem"><thead><tr><th>要素</th><th>说明</th></tr></thead>
|
|
|
+ <tbody>{(k.构成||[]).map((c,i)=><tr key={i}><td className="el">{c.要素}</td><td>{c.说明}</td></tr>)}</tbody></table>
|
|
|
+ <KScopes list={k.作用域} onScope={onScope}/>
|
|
|
</div>;
|
|
|
}
|
|
|
-
|
|
|
-function Detail({post,onZoom,onRecord}){
|
|
|
- if(!post) return <div className="empty">← 左边选一颗知识</div>;
|
|
|
- const fw=post.framework||{};
|
|
|
- const imgs=[]; for(let n=1;n<=(post.img_count||0);n++) imgs.push(post.img_base+"/image_"+n+".webp");
|
|
|
+function WhyCard({k,onScope}){
|
|
|
return <div>
|
|
|
- <div className="dh">
|
|
|
- <h1>{fw.title}</h1>
|
|
|
- <div className="acts">
|
|
|
- <button className="minib" onClick={()=>onRecord(post)}>📥 下游记录</button>
|
|
|
- <a href={post.url} target="_blank" rel="noreferrer">🔗 原帖</a>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div className="ptitle">原帖:{post.title} · 小红书</div>
|
|
|
- <div className="meta"><Tags stage={fw["阶段跨度"]} type={fw["知识类型"]} scopes={fw["作用域并集"]}/></div>
|
|
|
-
|
|
|
- <div className="block">
|
|
|
- <span className="flab">目标</span>
|
|
|
- <div className="purpose">{fw.purpose}</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- {fw["想要"] && <div className="block"><span className="flab">想要</span><div className="want">{fw["想要"]}</div></div>}
|
|
|
-
|
|
|
- <div className="block">
|
|
|
- <span className="flab">步骤({(fw.steps||[]).length})· 目的 / 指引 / 产出</span>
|
|
|
- {(fw.steps||[]).map((s,i)=><Step key={i} s={s} i={i}/>)}
|
|
|
- </div>
|
|
|
-
|
|
|
- {post.dropped&&post.dropped.length>0 && <details className="box">
|
|
|
- <summary>被剔除的「制作 / 素材」({post.dropped.length})— 不算创作知识</summary>
|
|
|
- <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>
|
|
|
- </details>}
|
|
|
-
|
|
|
- <details className="box">
|
|
|
- <summary>原帖图片({imgs.length})· 知识从这里提炼</summary>
|
|
|
- <div className="imgs">{imgs.map((u,i)=><img key={i} src={u} onClick={()=>onZoom(u)} onError={e=>e.target.style.display='none'}/>)}</div>
|
|
|
- </details>
|
|
|
+ {k.主张&&<div className="why-row"><span className="why-lab wl-claim">主张</span><span className="why-txt">{k.主张}</span></div>}
|
|
|
+ {k.依据&&<div className="why-row"><span className="why-lab wl-basis">依据</span><span className="why-txt">{k.依据}</span></div>}
|
|
|
+ {k["对创作的影响"]&&<div className="why-row"><span className="why-lab wl-impact">影响</span><span className="why-txt">{k["对创作的影响"]}</span></div>}
|
|
|
+ <KScopes list={k.作用域} onScope={onScope}/>
|
|
|
</div>;
|
|
|
}
|
|
|
|
|
|
-function RecordModal({post,onClose}){
|
|
|
- const fw=post.framework||{};
|
|
|
- const content=buildContent(fw);
|
|
|
- const record={ title: fw.title, purpose: fw.purpose, content };
|
|
|
- return <div className="ov" onClick={onClose}>
|
|
|
- <div className="modal" onClick={e=>e.stopPropagation()}>
|
|
|
- <h2>下游消费记录(这颗知识下发给「脚本构建 Agent」的样子)<span className="x" onClick={onClose}>×</span></h2>
|
|
|
- <div className="mbody">
|
|
|
- <div className="fk">title</div><div className="fv">{record.title}</div>
|
|
|
- <div className="fk">purpose</div><div className="fv">{record.purpose}</div>
|
|
|
- <div className="fk">content(拍平后的全文,下游逐字消费)</div>
|
|
|
- <div className="cwrap"><pre>{content}</pre></div>
|
|
|
- </div>
|
|
|
+function KnowledgeCard({k,idx,onScope,onPrompt,onJson,payload,how_titles}){
|
|
|
+ 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>
|
|
|
+ <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>)}
|
|
|
+ <span className="kacts">{payload&&<button className="lbtn" onClick={()=>onJson(payload)}>⚙️ 入库 payload</button>}</span>
|
|
|
</div>
|
|
|
- </div>;
|
|
|
-}
|
|
|
-
|
|
|
-function PromptModal({onClose}){
|
|
|
- return <div className="ov" onClick={onClose}>
|
|
|
- <div className="modal" onClick={e=>e.stopPropagation()}>
|
|
|
- <h2>这套知识是怎么提取出来的(提示词)<span className="x" onClick={onClose}>×</span></h2>
|
|
|
- <div className="mbody"><pre>{PROMPT}</pre></div>
|
|
|
+ <div className="kbody">
|
|
|
+ {k.type==="how"&&<HowTable k={k} onScope={onScope} onPrompt={onPrompt}/>}
|
|
|
+ {k.type==="what"&&<WhatCard k={k} onScope={onScope}/>}
|
|
|
+ {k.type==="why"&&<WhyCard k={k} onScope={onScope}/>}
|
|
|
</div>
|
|
|
</div>;
|
|
|
}
|
|
|
|
|
|
function App(){
|
|
|
- const [posts,setPosts]=useState([]);
|
|
|
- const [sel,setSel]=useState(null);
|
|
|
+ const [items,setItems]=useState([]);
|
|
|
+ const [sel,setSel]=useState(0);
|
|
|
+ const [scope,setScope]=useState(null);
|
|
|
+ const [prompt,setPrompt]=useState(null);
|
|
|
+ const [json,setJson]=useState(null);
|
|
|
const [zoom,setZoom]=useState(null);
|
|
|
- const [pm,setPm]=useState(false);
|
|
|
- const [rec,setRec]=useState(null);
|
|
|
- useEffect(()=>{ j("/frameworks.json").then(d=>{const ps=d.posts||[];setPosts(ps); if(ps[0])setSel(ps[0].post_id);}); },[]);
|
|
|
- const post=posts.find(p=>p.post_id===sel);
|
|
|
- return <div>
|
|
|
- <header>
|
|
|
- <h0>创作知识的搜索和解构</h0>
|
|
|
- <span className="sub">· 一帖一颗完整框架({posts.length} 颗)· 已按下游消费格式呈现</span>
|
|
|
- <button className="pbtn" onClick={()=>setPm(true)}>📄 提取提示词</button>
|
|
|
+ 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]);
|
|
|
+ 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 imgs=[]; for(let n=1;n<=(it.img_count||0);n++) imgs.push(it.img_base+"/image_"+n+".webp");
|
|
|
+ return <div className="app">
|
|
|
+ <header className="top">
|
|
|
+ <h1>创作知识的解构</h1>
|
|
|
+ <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>
|
|
|
- <Legend/>
|
|
|
- <div className="wrap">
|
|
|
- <PostList posts={posts} sel={sel} onSel={setSel}/>
|
|
|
- <div className="main">{posts.length? <Detail post={post} onZoom={setZoom} onRecord={setRec}/> : <div className="empty">加载中…</div>}</div>
|
|
|
+ <div className="scroll">
|
|
|
+ <details className="src"><summary>① 读懂 · 原帖:{it.title} <span className="srcprompt" onClick={e=>{e.preventDefault();e.stopPropagation();setPrompt("read");}}>▸ 读懂提示词</span></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>
|
|
|
+ <div className="barhint" style={{display:"flex",gap:8,alignItems:"center"}}>
|
|
|
+ <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}/>)}
|
|
|
</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={"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}(火山余弦)</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">{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>}
|
|
|
+ {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>}
|
|
|
{zoom&&<div className="lb" onClick={()=>setZoom(null)}><img src={zoom}/></div>}
|
|
|
- {pm&&<PromptModal onClose={()=>setPm(false)}/>}
|
|
|
- {rec&&<RecordModal post={rec} onClose={()=>setRec(null)}/>}
|
|
|
</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>
|