promptfigure 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/adapters/claude-code/SKILL.md +381 -0
- package/adapters/claude-code/install.mjs +9 -0
- package/adapters/codex/marketplace.json +14 -0
- package/adapters/codex/promptfigure/.codex-plugin/plugin.json +6 -0
- package/adapters/codex/promptfigure/skills/promptfigure-local/SKILL.md +381 -0
- package/bin/pf.mjs +1953 -0
- package/package.json +44 -0
- package/scripts/build-adapters.mjs +80 -0
- package/scripts/test-e2e.mjs +117 -0
- package/skill/promptfigure-local/SKILL.md +381 -0
- package/src/anchor.mjs +63 -0
- package/src/config.mjs +75 -0
- package/src/craft-rules.mjs +158 -0
- package/src/craft.mjs +600 -0
- package/src/doc/docx.mjs +69 -0
- package/src/doc/index.mjs +35 -0
- package/src/doc/para.mjs +54 -0
- package/src/doc/tex.mjs +161 -0
- package/src/doc/texbuild.mjs +158 -0
- package/src/docsearch.mjs +96 -0
- package/src/entity-pair.mjs +17 -0
- package/src/events.mjs +52 -0
- package/src/extract.mjs +124 -0
- package/src/figure-catalog.mjs +326 -0
- package/src/journal.mjs +67 -0
- package/src/ledger.mjs +43 -0
- package/src/next.mjs +125 -0
- package/src/plan.mjs +112 -0
- package/src/png-trim.mjs +217 -0
- package/src/quality.mjs +325 -0
- package/src/ratio.mjs +87 -0
- package/src/render.mjs +247 -0
- package/src/review.mjs +48 -0
- package/src/server.mjs +218 -0
- package/src/store.mjs +91 -0
- package/src/vectorize.mjs +54 -0
- package/tray/pf-tray.py +265 -0
- package/web/app.js +613 -0
- package/web/index.html +73 -0
- package/web/probe.html +36 -0
- package/web/style.css +208 -0
package/src/extract.mjs
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// extract.mjs — 🔴 多语言蒸馏抽取器(2026-09-22 第二轮:去写死、脚本无关):
|
|
2
|
+
// 第一版的候选实体正则只认英文大写词、分句只认 `.!?` —— 中文论文蒸馏出来是空的(数模小白
|
|
3
|
+
// 用不好的根因)。第二版把"写死的语言规则"降到最少:
|
|
4
|
+
// - splitSentences:终结标点覆盖中日韩/阿拉伯/天城文/亚美尼亚/高棉等主要文字,超长无标点段兜底
|
|
5
|
+
// - candidateEntities:按 Unicode script 切 run —— 带空格文字(拉丁/西里尔/希腊/阿拉伯/
|
|
6
|
+
// 天城文/韩语…)走词元路径(大写术语+缩写+高频词),无空格文字(汉字/假名/谚文/泰/高棉…)
|
|
7
|
+
// 走同 run 内 n-gram 频次路径。中文"X模型/X算法"后缀表只是众多启发式之一(对其他语言
|
|
8
|
+
// 自动失效、不碍事,不再为每种语言写后缀表)。
|
|
9
|
+
// 抽取是统计性的,会有误抽/漏抽 —— 蒸馏输出必须带"逐个核对原文出处"的核验任务(判断层归
|
|
10
|
+
// AI,插件只做确定性对账:checkEntitySource 拿实体回原文对账,编造出不了门)。
|
|
11
|
+
// 纯函数:CLI(distill/plan)与测试共用。
|
|
12
|
+
|
|
13
|
+
// ---- 分句(多语言通用)----
|
|
14
|
+
// 终结标点集:中日韩(。!?;…‥)拉丁(!?)阿拉伯(؟)乌尔都(۔)天城文(।॥)
|
|
15
|
+
// 亚美尼亚(։)格鲁吉亚/高棉(៖)埃塞俄比亚(።)高棉(។)
|
|
16
|
+
const TERM_PUNCT = "。!?;…‥!?؟۔।॥։៖።។";
|
|
17
|
+
export function splitSentences(text = "") {
|
|
18
|
+
const src = String(text || "");
|
|
19
|
+
if (!src.trim()) return [];
|
|
20
|
+
// 常见缩写句点保护(拉丁侧)+ 小数点保护
|
|
21
|
+
const ABBR = [/\bvs\./g, /\be\.g\./g, /\bi\.e\./g, /\bet al\./g, /\bFig\./g, /\bEq\./g, /\bapprox\./g, /\bNo\./g];
|
|
22
|
+
const masked = ABBR.reduce((t, re) => t.replace(re, (m) => m.replace(/\./g, "\u0001")), src)
|
|
23
|
+
.replace(/(\d)\.(\d)/g, "$1\u0001$2");
|
|
24
|
+
let parts = masked
|
|
25
|
+
.replace(/\s+/g, " ")
|
|
26
|
+
.split(new RegExp(`(?<=[${TERM_PUNCT}])\\s*|(?<=[.;])\\s+`))
|
|
27
|
+
.map((s) => s.replace(/\u0001/g, ".").trim())
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
// 兜底:泰语/老挝语等无句读符号的文字 —— 超长无标点段按空格粗切(~12 词一段)
|
|
30
|
+
const out = [];
|
|
31
|
+
for (const p of parts) {
|
|
32
|
+
if (p.length > 300 && p.includes(" ")) {
|
|
33
|
+
const toks = p.split(" ");
|
|
34
|
+
for (let i = 0; i < toks.length; i += 12) out.push(toks.slice(i, i + 12).join(" "));
|
|
35
|
+
} else out.push(p);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---- 候选实体 ----
|
|
41
|
+
// 中文后缀术语("X模型 / X算法 / X方法…")—— 数模论文命名实体的常见形态。
|
|
42
|
+
// 只对含汉字的 run 生效;其他语言自动不命中、不碍事(不为每种语言写后缀表)。
|
|
43
|
+
const ZH_SUFFIX = "(?:模型|算法|方法|网络|函数|矩阵|方程|指标|策略|机制|模块|系统|流程|准则|分布|检验|拟合|规划|仿真)";
|
|
44
|
+
// 无空格文字:连续序列整体当一个 run("灰色予測モデル"不再按汉/假名切碎)
|
|
45
|
+
const RUN_UNSPACED = "[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}\\p{Script=Thai}\\p{Script=Khmer}\\p{Script=Lao}\\p{Script=Myanmar}]+";
|
|
46
|
+
|
|
47
|
+
// 高频功能词黑名单(只兜中文——其他语言靠"≥2 次"频次门槛 + AI 逐个核对兜底)
|
|
48
|
+
const ZH_STOP = new Set([
|
|
49
|
+
"我们", "其中", "因此", "通过", "可以", "以及", "进行", "结果", "问题", "对于", "并且",
|
|
50
|
+
"然后", "如果", "得到", "使用", "基于", "不同", "如图", "所示", "本文", "首先", "其次",
|
|
51
|
+
"最后", "所以", "由于", "但是", "同时", "此外", "另外", "如下", "根据", "计算", "分析",
|
|
52
|
+
"建立", "考虑", "假设", "使得", "从而", "进而", "一个", "两个", "这种", "这些",
|
|
53
|
+
"为了", "需要", "可能", "应该", "表示", "对应", "分别", "之间", "之后", "上述", "该",
|
|
54
|
+
"情况下", "过程中", "基础上", "结果表明",
|
|
55
|
+
]);
|
|
56
|
+
const EN_STOP = new Set(["The","We","Figure","Table","Section","Equation","In","For","And","With","From","This","That","These","Those","Our","Where","When","If","As","By","On","To","An","A","It","Each","Both","All","Not","Can","May","One","Two","First","Second","Then","Thus","However","Note","Given","Since","While","After","Before","During","Over","Under","Between","Within","Without","Across","According","Based","Proposed","Method","Results","Experiment","Experiments","Training","Model","Models","Input","Output"]);
|
|
57
|
+
|
|
58
|
+
export function candidateEntities(text = "", { limit = 12 } = {}) {
|
|
59
|
+
const src = String(text || "");
|
|
60
|
+
if (!src.trim()) return [];
|
|
61
|
+
const counts = new Map();
|
|
62
|
+
const boosted = new Set(); // 后缀术语/缩写/大写术语:排序加权、出现即算
|
|
63
|
+
const add = (w, boost = false) => {
|
|
64
|
+
const k = String(w).trim();
|
|
65
|
+
if (!k || k.length < 2) return;
|
|
66
|
+
counts.set(k, (counts.get(k) || 0) + 1);
|
|
67
|
+
if (boost) boosted.add(k);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ---- 全文层(跨语言通用):缩写词 + 首字母大写多词术语(有大小写的文字)----
|
|
71
|
+
for (const m of src.matchAll(/\p{Lu}[\p{Lu}\p{Nd}-]{1,11}/gu)) add(m[0].replace(/[-]+$/, ""), true);
|
|
72
|
+
for (const m of src.matchAll(/\p{Lu}\p{Ll}{2,}(?:\s+(?:\p{Ll}{1,3}\s+)?\p{Lu}\p{Ll}{2,}){1,3}/gu)) add(m[0], true);
|
|
73
|
+
|
|
74
|
+
// ---- 按 script run 遍历 ----
|
|
75
|
+
// 无空格 run(汉字/假名/谚文/泰…连续段,"VAEモデル" 会拆成 VAE + モデル 两类):
|
|
76
|
+
// 中文后缀术语 + 同 run 内 n-gram 频次(n=2..6,≥2 次)
|
|
77
|
+
// 带空格 run(拉丁/西里尔/希腊/阿拉伯…):长词频次(≥5 字母且 ≥2 次,德/俄复合词)
|
|
78
|
+
for (const m of src.matchAll(new RegExp(RUN_UNSPACED + "|\\p{L}{5,}", "gu"))) {
|
|
79
|
+
const run = m[0];
|
|
80
|
+
if (new RegExp("^(?:" + RUN_UNSPACED + ")$", "u").test(run)) {
|
|
81
|
+
// 中文后缀术语(run 含汉字才试)
|
|
82
|
+
if (/\p{Script=Han}/u.test(run)) {
|
|
83
|
+
const LEAD_TRIM = new Set([..."了的是和与及对在是将把从用为以并而或于使给向都也又再比跟同这那有等先"]);
|
|
84
|
+
for (const mm of run.matchAll(new RegExp("[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}]{1,6}?" + ZH_SUFFIX, "gu"))) {
|
|
85
|
+
const full = mm[0];
|
|
86
|
+
const suf = full.match(new RegExp(ZH_SUFFIX + "$"))[0];
|
|
87
|
+
let pre = full.slice(0, full.length - suf.length).slice(-4);
|
|
88
|
+
while (pre.length > 1 && LEAD_TRIM.has(pre[0])) pre = pre.slice(1);
|
|
89
|
+
add(pre + suf, true);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (let n = 2; n <= 6; n++) {
|
|
93
|
+
for (let i = 0; i + n <= run.length; i++) {
|
|
94
|
+
const g = run.slice(i, i + n);
|
|
95
|
+
if (n <= 2 && ZH_STOP.has(g)) continue;
|
|
96
|
+
counts.set(g, (counts.get(g) || 0) + 1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
counts.set(run, (counts.get(run) || 0) + 1); // 带空格文字长词
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---- 排序与过滤 ----
|
|
105
|
+
const ranked = [...counts.entries()].filter(([w, c]) => {
|
|
106
|
+
if (EN_STOP.has(w)) return false;
|
|
107
|
+
if (boosted.has(w)) return true; // 后缀术语/缩写/大写术语:出现即算
|
|
108
|
+
if (ZH_STOP.has(w)) return false;
|
|
109
|
+
return c >= 2; // 统计 n-gram / 长词:≥2 次
|
|
110
|
+
}).sort((a, b) => {
|
|
111
|
+
const pa = boosted.has(a[0]) ? a[1] + 1000 : a[1];
|
|
112
|
+
const pb = boosted.has(b[0]) ? b[1] + 1000 : b[1];
|
|
113
|
+
return pb - pa || b[0].length - a[0].length;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// 去重包含:短词若是已入选长词的子串则丢掉("预测模型" ⊂ "灰色预测模型")
|
|
117
|
+
const picked = [];
|
|
118
|
+
for (const [w] of ranked) {
|
|
119
|
+
if (picked.some((p) => p.includes(w) || w.includes(p))) continue;
|
|
120
|
+
picked.push(w);
|
|
121
|
+
if (picked.length >= limit) break;
|
|
122
|
+
}
|
|
123
|
+
return picked;
|
|
124
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// figure-catalog.mjs — 图型目录(catalog):让宿主 AI 知道"能做什么图、每种图怎么想"
|
|
2
|
+
//
|
|
3
|
+
// 背景(用户实测反馈 2026-09-20):宿主 AI 不了解我们能画什么图,也不知道一张方法图
|
|
4
|
+
// 应该长什么样 —— craft 只有 4 个隐式 TYPE_TEMPLATES,AI 蒙着选。
|
|
5
|
+
// 对标来源(2026-09-21 两轮调研):
|
|
6
|
+
// · MatPlotAgent / figure-generation skill —— 图型目录化(10 种类型列出,AI 才知道选项)
|
|
7
|
+
// · figures4papers DESIGN_THEORY —— 语义化配色(蓝=提出方法/绿=增益/红=对照/灰=中性)
|
|
8
|
+
// · academic-figure-skill —— "一幅图一个核心信息"、审稿人 3 秒扫读
|
|
9
|
+
// · awesome-gpt-image-2(15.8k★)—— 每类图自带"避坑规则"(Prompt as Code 分类模板)
|
|
10
|
+
// · 🔴 2026-09-21 二轮(用户批评"提示词太笼统"后对标成品提示词):
|
|
11
|
+
// GPT-Image2-Skill research-paper-figures gallery —— 「布局契约」模式:
|
|
12
|
+
// 每个面板/区域单独描述 + 逐字引用标签 + 每元素指定配色
|
|
13
|
+
// paper-banana.org/prompts(End-to-End Segmentation Training Pipeline)——
|
|
14
|
+
// 编号阶段(Stage 1 — Input)+ 每阶段 2-4 条内容要点 + 指定阶段内画什么
|
|
15
|
+
// ("Show a 2x2 grid of tile thumbnails inside the stage")+ 风格句殿后
|
|
16
|
+
// ScholarViz 5-block template —— Objective / Composition / Entities / Style / Labeling
|
|
17
|
+
//
|
|
18
|
+
// 每个条目:
|
|
19
|
+
// id —— craft --figure-type 用的标识符
|
|
20
|
+
// name —— 中文名
|
|
21
|
+
// when —— 什么内容该选它(选型判断,给宿主 AI 看)
|
|
22
|
+
// think —— 动笔前该想清楚的构图问题(给宿主 AI 看)
|
|
23
|
+
// guide —— 进图模型提示词的版式指导段(布局契约语言:区域编号+区域内容+区域画法)
|
|
24
|
+
// avoid —— 该图型特有避坑(进提示词 Avoid 段之后)
|
|
25
|
+
// example —— 完整示例(intent/entities/structure/stages + craft 用法),可改变量直接用
|
|
26
|
+
|
|
27
|
+
export const FIGURE_CATALOG = [
|
|
28
|
+
{
|
|
29
|
+
id: "pipeline",
|
|
30
|
+
name: "方法流水线 / 处理流程",
|
|
31
|
+
when: "论文方法节的核心图:多步处理过程(数据预处理→模型→后处理)、训练/推理管线。论文里最常见的一种。",
|
|
32
|
+
think: "① 步骤顺序是什么?箭头方向必须与数据流向一致;② 每一步内部发生什么——拆成 2-4 条内容要点(PaperBanana:每阶段 2-4 条 bullet,多视觉截断);③ 每个阶段「框里画什么」——缩略图/小条形/形状母题要指定(如 Show a 2x2 grid of tile thumbnails inside the stage);④ 哪一步是本文贡献——它要视觉突出(主色+放大);⑤ 双栏图横排,单栏图竖排。",
|
|
33
|
+
guide:
|
|
34
|
+
"Method pipeline figure: a horizontal chain of NUMBERED stages (small numerals or \"Stage 1\"-style title prefixes), each stage a rounded rectangle carrying its title on top and its 2-4 content bullets inside, connected left-to-right by short labelled black arrows showing data flow direction. Where a stage specifies a visual (a thumbnail grid, a mini stacked bar, a shape motif), draw that visual INSIDE the stage box instead of describing it in words. The paper's contribution stage gets the primary hue and a slightly larger box. One reading orientation; stage outputs implied by small glyph shapes between boxes only when no explicit visual was specified.",
|
|
35
|
+
avoid: "no arrowheads pointing backwards unless a genuine feedback loop exists; do not invent stage names or bullet content beyond what was supplied; no empty title-only boxes (every stage carries its listed content); no isometric/3D boxes.",
|
|
36
|
+
example: {
|
|
37
|
+
intent: "Two-stage defect detection pipeline: a coarse filter discards most background patches, then a fine grader classifies the rest",
|
|
38
|
+
entities: "Input Image, Patch Sampler, Coarse Filter, Fine Grader, Defect Map",
|
|
39
|
+
stages: "Input Image | Show a strip of 3 rail-surface thumbnails || Patch Sampler + Coarse Filter | discard most background patches; keep candidate patches || Fine Grader | vision transformer classifier; output per-patch score || Defect Map | show a small heat-map thumbnail",
|
|
40
|
+
cmd: 'pf craft --figure-type pipeline --intent "Two-stage defect detection: coarse filter discards background, fine grader scores the rest" --entities "Input Image,Patch Sampler,Coarse Filter,Fine Grader,Defect Map" --stages "Input Image | Show a strip of 3 rail-surface thumbnails || Patch Sampler + Coarse Filter | discard background patches; keep candidates || Fine Grader | vision transformer scoring; per-patch score || Defect Map | show a small heat-map thumbnail" --preset double-column',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "architecture",
|
|
45
|
+
name: "模型 / 系统架构图",
|
|
46
|
+
when: "网络结构(encoder-decoder、attention 块、多分支)、系统组件图(客户端/服务/数据库)。与 pipeline 的区别:强调模块内部构造与连接,不是时间顺序。",
|
|
47
|
+
think: "① 模块嵌套关系(哪个块包含哪个块)用容器表达;② 每个关键模块内部画什么——子块/内部连接要逐个列出(对标 GPT-Image2-Skill No.79:左右两列 encoder-decoder,每块标签逐字写出);③ 并联分支要对称排布;④ 张量/接口标签只用用户给的,形状数字没有给就不写。",
|
|
48
|
+
guide:
|
|
49
|
+
"Model architecture diagram: nested containers for sub-modules (an outer container visually encloses its inner blocks with clear padding), parallel branches laid out symmetrically, every module block carries its quoted name label and, where supplied, 1-3 one-line content notes inside the block; connection lines labelled only with user-supplied names; encoder blocks tinted with one hue family, decoder with a second; skip-connections as thin curved lines over the main path.",
|
|
50
|
+
avoid: "do not print tensor shapes or parameter counts that were not supplied; do not invent layer names or internal sub-blocks beyond what was supplied; no empty blocks (each module shows its listed inner structure); avoid crossing lines where a rearrangement could avoid them.",
|
|
51
|
+
example: {
|
|
52
|
+
intent: "U-shaped encoder-decoder where skip connections carry multi-scale features into the bottleneck fusion gate",
|
|
53
|
+
entities: "Input Image, Encoder E1, Encoder E2, Bottleneck, Decoder D2, Decoder D1, Output Mask, Fusion Gate",
|
|
54
|
+
structure: "U-shape, skip connections from E1/E2 to D1/D2, Fusion Gate at the bottleneck",
|
|
55
|
+
cmd: 'pf craft --figure-type architecture --intent "U-shaped encoder-decoder: skip connections carry multi-scale features into the bottleneck fusion gate" --entities "Input Image,Encoder E1,Encoder E2,Bottleneck,Decoder D2,Decoder D1,Output Mask,Fusion Gate" --stages "Encoder E1 + E2 | show downsampling blocks over the Input Image; channel counts grow || Bottleneck + Fusion Gate | show multi-scale feature fusion; gates weighted skips || Decoder D2 + D1 | show upsampling blocks; skip connections from matching encoders; produces Output Mask" --structure "U-shape with skip connections"',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "flowchart",
|
|
60
|
+
name: "决策流程图",
|
|
61
|
+
when: "有判断分支的流程(是/否走不同路径)、算法步骤、实验流程(筛样→分组→测量→统计)。",
|
|
62
|
+
think: "① 判断点有几个?菱形表达;② 每个分支的出口标签是互斥的吗(是/否逐字写出);③ 每个处理节点发生了什么——1-2 条要点写进节点;④ 一屏内能读完吗——超过 10 个节点就考虑拆分或合并。",
|
|
63
|
+
guide:
|
|
64
|
+
"Decision flowchart: process steps as rounded rectangles, decision points as diamonds with short yes/no style exit labels (verbatim from user), one reading orientation, generous spacing so lines never touch text; each process node may list 1-2 content bullets beneath its title when supplied, so nodes never appear as empty titled boxes.",
|
|
65
|
+
avoid: "no orphan nodes (every node reachable); do not merge two different branches into one ambiguous arrow; labels under 4 words each; do not invent branch outcomes beyond the user's statements.",
|
|
66
|
+
example: {
|
|
67
|
+
intent: "Sample screening workflow: exclude low-quality recordings, split by diagnosis, two analysis arms",
|
|
68
|
+
entities: "Raw Records, Quality Check, Excluded, Diagnosis Split, Group A Analysis, Group B Analysis, Meta Report",
|
|
69
|
+
structure: "top-down flowchart, one diamond decision at Quality Check, two parallel branches below",
|
|
70
|
+
cmd: 'pf craft --figure-type flowchart --intent "Sample screening workflow: exclude low-quality recordings, split by diagnosis, two analysis arms" --entities "Raw Records,Quality Check,Excluded,Diagnosis Split,Group A Analysis,Group B Analysis,Meta Report" --stages "Raw Records | show a small stack-of-documents motif || Quality Check | decision diamond; exclude low-quality recordings || Diagnosis Split | two parallel branches; labelled yes/no exits || Group A / Group B Analysis | one rounded box per arm; same layout in both || Meta Report | show the final summary node; collects both arms" --structure "top-down, one decision diamond, two branches"',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "mechanism",
|
|
75
|
+
name: "机理 / 因果通路图",
|
|
76
|
+
when: "生物学通路、因果链、物理过程(A 激活 B、B 抑制 C)。核心是因果方向,不是流程步骤。",
|
|
77
|
+
think: "① 因果方向必须与文献/用户陈述一致——画错方向比不画更糟;② 每条连线上的动作标签逐字写(activates / inhibits / releases);③ 激活/抑制用什么视觉区分(实心箭头 vs 短横线端点)?④ 主通路和旁路要分清主次;⑤ 分子/实体的状态变化(磷酸化/释放)要不要画在节点内。",
|
|
78
|
+
guide:
|
|
79
|
+
"Mechanism / pathway diagram: entities as labelled nodes, activation as solid pointed arrows and inhibition as blunt-ended lines (only where the user stated the relation), each arrow carrying its action label verbatim where supplied; nodes may show a one-line state note beneath the label when supplied; one dominant pathway visually stronger (thicker line, primary hue), side branches visually quieter; stage numerals when sequential.",
|
|
80
|
+
avoid: "no arrow between two entities unless the user stated a genuine causal link; do not invent intermediate molecules/steps; no membrane-like decoration unless asked; no empty nodes (state notes included where supplied).",
|
|
81
|
+
example: {
|
|
82
|
+
intent: "Inflammatory signalling cascade: receptor activation triggers kinase cascade leading to cytokine release, with one negative-feedback loop",
|
|
83
|
+
entities: "Receptor, Kinase A, Kinase B, Transcription Factor, Cytokine, Feedback Inhibitor",
|
|
84
|
+
structure: "left-to-right cascade with a curved feedback line from Cytokine back to Kinase A",
|
|
85
|
+
cmd: 'pf craft --figure-type mechanism --intent "Inflammatory signalling cascade: receptor activation triggers kinase cascade leading to cytokine release, with one negative-feedback loop" --entities "Receptor,Kinase A,Kinase B,Transcription Factor,Cytokine,Feedback Inhibitor" --stages "Receptor | show ligand binding at the membrane receptor || Kinase A → Kinase B | show phosphorylation relay with solid activation arrows; label each arrow phosphorylate || Transcription Factor | show translocation into the nucleus; drives Cytokine release || Feedback Inhibitor | show curved blunt-ended inhibition back to Kinase A" --structure "left-to-right cascade, curved feedback loop"',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "teaser",
|
|
90
|
+
name: "图形摘要 / 概念主图(teaser)",
|
|
91
|
+
when: "第一页 graphical abstract、开头 overview 图:一图讲清全文最核心的一件事。读者 3 秒扫读即懂——信息极简。",
|
|
92
|
+
think: "① 全文唯一想让读者记住的一句话是什么——它就是构图中心;② 能砍掉的元素全砍掉(academic-figure-skill:一幅图一个核心信息);③ 只留 3-6 个视觉元素,留白 ≥ 1/3;④ 输入→输出的「变换」用哪个视觉隐喻表达。",
|
|
93
|
+
guide:
|
|
94
|
+
"Graphical abstract / teaser figure: ONE communication idea as the focal centre, at most 3-6 visual elements, strong empty space, the input-to-output transformation shown as a single clean visual metaphor with its label; reads correctly even at thumbnail size.",
|
|
95
|
+
avoid: "no multi-panel structure; no small annotation text; no secondary ideas competing with the focal one; minimal labels only.",
|
|
96
|
+
example: {
|
|
97
|
+
intent: "One-line value proposition: degraded photos restored to gallery quality by a single model pass",
|
|
98
|
+
entities: "Degraded Photo, Single-Pass Restoration Model, Gallery-Quality Result",
|
|
99
|
+
structure: "three-element horizontal composition, model box at centre slightly larger, generous whitespace",
|
|
100
|
+
cmd: 'pf craft --figure-type teaser --intent "Degraded photos restored to gallery quality in a single pass" --entities "Degraded Photo,Single-Pass Restoration Model,Gallery-Quality Result" --stages "Degraded Photo | show a faded scratched photo thumbnail || Single-Pass Restoration Model | show a single rounded box in the primary hue; centre focal || Gallery-Quality Result | show a vivid sharp photo thumbnail" --structure "horizontal, centre focal"',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "comparison",
|
|
105
|
+
name: "对比图(ours vs baseline / before-after)",
|
|
106
|
+
when: "方法对比、改前改后、消融示意。视觉核心是\"差异\",不是双方完整细节。",
|
|
107
|
+
think: "① 对比的维度是什么(质量/速度/结构)?② 对齐排版——同维度水平对齐读者才扫得出差异;③ 两侧用同一组「编号槽位」组织,读者逐槽对比;④ 我们的方法视觉突出(主色),对照安静(灰);⑤ 中间加分隔或 VS 留白,不要共用边框。",
|
|
108
|
+
guide:
|
|
109
|
+
"Comparison figure: two aligned panels (left = baseline in neutral grey tones, right = ours in the primary hue), built from the SAME numbered slots inside both panels (Slot 1, Slot 2, ...) so the reader compares slot-by-slot, same internal layout in both so differences pop, a slim divider or whitespace gap between panels; differences emphasised, similarities quiet.",
|
|
110
|
+
avoid: "do not use red-green as the only contrast pair; do not give the baseline deliberately ugly styling; keep panel titles short and parallel; do not put different content in matched slots.",
|
|
111
|
+
example: {
|
|
112
|
+
intent: "Before/after comparison of a deblurring method on a face photo, ours keeps eye detail sharp",
|
|
113
|
+
entities: "Blurred Input, Baseline Result, Ours Result",
|
|
114
|
+
structure: "three aligned panels left-to-right, ours panel slightly larger and blue-tinted",
|
|
115
|
+
cmd: 'pf craft --figure-type comparison --intent "Deblurring before/after: baseline leaves residual blur, ours keeps eye detail sharp" --entities "Blurred Input,Baseline Result,Ours Result" --stages "Blurred Input | show the shared left thumbnail; identical crop in both panels || Baseline Result | grey panel; residual blur around eyes || Ours Result | primary-hue panel; sharp eye detail" --structure "three aligned panels"',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "dataflow",
|
|
120
|
+
name: "数据流 / 形态变换图",
|
|
121
|
+
when: "数据在系统里形态怎么变(文本→token→向量→聚类)、数据集组织与流动。强调\"形态\",常配小字形示意。",
|
|
122
|
+
think: "① 每个阶段的形态用什么小图形示意(波浪线=文本、点阵=token、方块阵列=张量)——逐阶段指定;② 每阶段发生了什么——1-3 条要点;③ 流动主线唯一;④ 数据量变化(变多/变少)可用粗细或宽度暗示。",
|
|
123
|
+
guide:
|
|
124
|
+
"Data-flow figure: one continuous flow spine; each stage a numbered node carrying its quoted label and 1-3 content bullets, with its data-shape glyph drawn beneath the node (wavy lines for text, dot grids for tokens, tile arrays for tensors — specify per stage); flow width hints at volume only where the user stated it.",
|
|
125
|
+
avoid: "do not invent dimensionalities (no \"768-d\" unless supplied); glyphs stay schematic, never photorealistic; one spine only; no shapeless empty nodes.",
|
|
126
|
+
example: {
|
|
127
|
+
intent: "Document processing flow: raw text becomes tokens, then embeddings, then clustered topics",
|
|
128
|
+
entities: "Raw Text, Tokenizer, Tokens, Encoder, Embeddings, Clustering, Topic Groups",
|
|
129
|
+
structure: "single left-to-right spine with shape glyphs under each stage",
|
|
130
|
+
cmd: 'pf craft --figure-type dataflow --intent "Document processing flow: raw text becomes tokens, then embeddings, then clustered topics" --entities "Raw Text,Tokenizer,Tokens,Encoder,Embeddings,Clustering,Topic Groups" --stages "Raw Text | show a wavy-line glyph; raw text paragraphs || Tokenizer | show a dot-grid glyph; splits text into tokens || Encoder | show a tile-array glyph; embeddings per token || Clustering | show grouped dot clusters; Topic Groups emerge" --structure "single left-to-right spine"',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "hierarchy",
|
|
135
|
+
name: "层级 / 分类树图",
|
|
136
|
+
when: "类别体系、目录结构、组织关系、数据集标注层级。核心是包含关系,不是流动。",
|
|
137
|
+
think: "① 几层?每层几个节点?超宽就换横向树;② 每个节点除了名字还有没有一句话说明——有就写进节点;③ 同层节点视觉等权;④ 根/主干加粗或主色,叶节点安静。",
|
|
138
|
+
guide:
|
|
139
|
+
"Hierarchy tree diagram: root at top (or left for wide trees), levels clearly separated, same-level nodes visually equal width, each node carrying its quoted label and an optional one-line content note; containment or membership implied by tree lines only, root branch emphasised with the primary hue.",
|
|
140
|
+
avoid: "no curved decorative branches; do not vary node size by importance unless asked; max ~4 levels visible; no empty nodes.",
|
|
141
|
+
example: {
|
|
142
|
+
intent: "Taxonomy of evaluation metrics organised into three families with sub-metrics",
|
|
143
|
+
entities: "Metrics, Fidelity, Perceptual, Task-Based, PSNR Family, LPIPS Family, Accuracy Family",
|
|
144
|
+
structure: "three-level tree, root at top, three mid nodes, leaves below",
|
|
145
|
+
cmd: 'pf craft --figure-type hierarchy --intent "Taxonomy of evaluation metrics organised into three families with sub-metrics" --entities "Metrics,Fidelity,Perceptual,Task-Based,PSNR Family,LPIPS Family,Accuracy Family" --stages "Metrics (root) | show the root node in the primary hue; spans all families || Fidelity | PSNR Family leaf; pixel-wise fidelity || Perceptual | LPIPS Family leaf; learned perceptual distance || Task-Based | Accuracy Family leaf; downstream task scores" --structure "three-level tree"',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "timeline",
|
|
150
|
+
name: "时间线 / 发展历程图",
|
|
151
|
+
when: "领域发展脉络、版本演进、项目里程碑。核心是时间顺序 + 关键转折点。",
|
|
152
|
+
think: "① 里程碑几个?超过 7 个就合并;② 转折点(范式变化)视觉强调;③ 时间轴只有一条,从左到右或从上到下。",
|
|
153
|
+
guide: null, // timeline 属于低频图型,不进 craft 模板路由,仅作选型参考与示例
|
|
154
|
+
example: {
|
|
155
|
+
intent: "Evolution of diffusion models from pixel-space to latent-space to rectified flow",
|
|
156
|
+
entities: "Pixel Diffusion, Latent Diffusion, Rectified Flow",
|
|
157
|
+
structure: "left-to-right timeline with three milestone nodes and a connecting axis",
|
|
158
|
+
cmd: 'pf craft --figure-type flowchart --intent "Evolution from pixel diffusion to rectified flow" --entities "Pixel Diffusion,Latent Diffusion,Rectified Flow" --structure "left-to-right timeline with a horizontal axis line"',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: "zoomin",
|
|
163
|
+
name: "总览 + 局部放大图",
|
|
164
|
+
when: "整体架构太大看不清细节时:一张总览 + 从总览引出的放大框。常见于 CV 方法图。",
|
|
165
|
+
think: "① 放大哪一处(必须是最关键的模块);② 放大框内部画什么——模块的子块与连接要逐个列出(这是放大框存在的意义,空放大框=白画);③ 总览与放大框用虚线引导线连接;④ 总览对应区域用虚线框标注。",
|
|
166
|
+
guide:
|
|
167
|
+
"Overview plus zoom-in figure: full pipeline at smaller scale on top (numbered stages), a dashed callout box magnifying the key module — INSIDE the callout, draw the module's internal sub-blocks and their connections explicitly as listed; thin dashed leader lines connect the callout to its dashed-highlighted source region; the magnified panel is the visual centre.",
|
|
168
|
+
avoid: "only ONE magnified region unless user asked for more; leader lines must not cross each other; zoom panel must not cover the overview; no empty callout (its listed internals are always drawn).",
|
|
169
|
+
example: {
|
|
170
|
+
intent: "Full detector pipeline with the attention module magnified to show its internal heads",
|
|
171
|
+
entities: "Input Image, Backbone, Attention Module, Detection Head, Multi-Head Split",
|
|
172
|
+
structure: "overview pipeline top, magnified attention panel below-right connected by dashed leaders",
|
|
173
|
+
cmd: 'pf craft --figure-type zoomin --intent "Detector overview with the attention module magnified to show its internal heads" --entities "Input Image,Backbone,Attention Module,Detection Head,Multi-Head Split" --stages "Overview pipeline | show Input Image → Backbone → Attention Module → Detection Head at small scale || Attention callout | show the Multi-Head Split explicitly: parallel head boxes feeding a concat block; dashed leaders to the Attention Module in the overview" --structure "overview top, zoom panel bottom-right, dashed leaders"',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: "scene",
|
|
178
|
+
name: "场景插图 / 应用示意",
|
|
179
|
+
when: "应用背景图(部署在手术机器人上的系统)、人文示意、封面配图。允许更丰富的视觉,但仍要克制。",
|
|
180
|
+
think: "① 场景里的\"产品/方法\"在哪——用主色或焦点光引到它;② 场景元素服务主题,与主题无关的装饰全删(NEGATIVE_LIST 的 object icons 条款就是为这类图准备的例外出口);③ 人物可剪影化,避免写实人脸。",
|
|
181
|
+
guide:
|
|
182
|
+
"Application scene illustration: the deployed system or method highlighted as the focal element (primary hue or focus lighting), supporting scene elements in muted tones, clean flat vector style maintained; human figures as simple silhouettes unless the user asked otherwise.",
|
|
183
|
+
avoid: "keep palette restraint (scene is not an excuse for rainbow); no photorealistic faces; no brand logos; the focal method stays legible at thumbnail size.",
|
|
184
|
+
example: {
|
|
185
|
+
intent: "Rail-inspection robot deployed in a metro tunnel, scanner beam highlighting a crack",
|
|
186
|
+
entities: "Metro Tunnel, Inspection Robot, Scanner Beam, Surface Crack",
|
|
187
|
+
structure: "wide tunnel scene, robot at right third with a teal scanner beam to the crack",
|
|
188
|
+
cmd: 'pf craft --figure-type scene --intent "Rail-inspection robot scanning a crack in a metro tunnel" --entities "Metro Tunnel,Inspection Robot,Scanner Beam,Surface Crack" --stages "Metro Tunnel | show a wide low-light tunnel backdrop; muted tones || Inspection Robot | show the robot at the right third in the primary hue; scanner beam emitter || Scanner Beam → Surface Crack | show a teal beam landing on a visible crack on the rail" --structure "wide scene, robot right third"',
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "result-style",
|
|
193
|
+
name: "数据图风格示意(⚠️ 非精确数据图)",
|
|
194
|
+
when: "示意性质的统计图(概念对比柱、趋势示意)。⚠️ 要保留精确数值的结果图禁止 AI 重画——AI 画数字必错,引导用户改绘图脚本(figures4papers / academic-figure-skill 路线)。",
|
|
195
|
+
think: "① 这张图是\"示意\"还是\"承载精确数值\"?后者停手,走改脚本路线;② 示意图里坐标轴只是视觉暗示,刻度文字不写数字;③ 相对大小即信息(A 明显高于 B);④ 每根柱/每条线对应哪个系列——逐个指定颜色。",
|
|
196
|
+
guide:
|
|
197
|
+
"Schematic data figure: implied axes as thin lines with NO numeric tick labels, bars/curves encoding RELATIVE magnitude only, each series named and colour-assigned individually (focal series in the primary hue, all other series muted grey); clear value difference between series is the message.",
|
|
198
|
+
avoid: "no numeric axis labels, no invented values printed on bars; no 3D bars; if precise numbers matter, do not use AI rendering — modify the plotting script instead.",
|
|
199
|
+
example: {
|
|
200
|
+
intent: "Schematic bar comparison: our method clearly higher than three baselines",
|
|
201
|
+
entities: "Ours, Baseline A, Baseline B, Baseline C",
|
|
202
|
+
structure: "four vertical bars, Ours tallest and blue, baselines muted grey",
|
|
203
|
+
cmd: 'pf craft --figure-type result-style --intent "Schematic bar comparison: our method clearly higher than three baselines" --entities "Ours,Baseline A,Baseline B,Baseline C" --stages "Ours | show the tallest bar in primary blue || Baseline A / B / C | show muted grey bars; clearly lower than ours" --structure "four vertical bars, no tick numbers"',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "multi-panel",
|
|
208
|
+
name: "多面板组合图 (a)(b)(c)",
|
|
209
|
+
when: "多个子图并列成一张期刊组合图:方法总览+局部细节、多个子实验并列、消融分组展示。期刊论文最常见的主图形态(academic-figure-skill / nature-figure 路线)。",
|
|
210
|
+
think: "① 每个 panel 只讲一个子信息——panel 顺序与正文引用顺序一致;② panel 间字号/线宽/配色必须完全一致(割裂感是组合图头号死因);③ (a)(b)(c) 编号统一放各 panel 左上角,粗体小写;④ 图例共享还是各自带?逐个指定;⑤ panel 尺寸一致或按内容权重明确分配(如 左 1/3 + 右 2/3)。",
|
|
211
|
+
guide:
|
|
212
|
+
"Multi-panel composite figure: labelled panels arranged on a clean grid, each panel carrying a bold lowercase letter label ((a), (b), (c)) at its top-left corner; every panel internally uses the same font size, stroke weight and colour assignments; a single shared legend when series repeat across panels; panel widths stated by the user are honoured exactly; thin separators or whitespace between panels, never decorative frames.",
|
|
213
|
+
avoid: "no numeric axis tick labels or invented measured values in any panel; do not vary font size or palette between panels; no empty panels (each panel shows its supplied content); panel labels must not float far from their panels; do not repeat the same legend twice.",
|
|
214
|
+
example: {
|
|
215
|
+
intent: "Composite figure: overview of the two-stage method plus a zoom of the fine grader",
|
|
216
|
+
entities: "Full Pipeline, Coarse Filter, Fine Grader, Score Map",
|
|
217
|
+
stages: "Panel (a) Full Pipeline | show the two-stage chain end to end with Coarse Filter and Fine Grader || Panel (b) Fine Grader | zoom into grader internals; attention blocks || Panel (c) Score Map | show a small heat-map thumbnail",
|
|
218
|
+
cmd: 'pf craft --figure-type multi-panel --preset double-column --intent "Composite figure: two-stage method overview plus a zoom of the fine grader" --entities "Full Pipeline,Coarse Filter,Fine Grader,Score Map" --stages "Panel (a) Full Pipeline | show the two-stage chain end to end with Coarse Filter and Fine Grader || Panel (b) Fine Grader | zoom into internals; attention blocks || Panel (c) Score Map | show a small heat-map thumbnail" --structure "three panels: a wide left panel, two stacked right panels"',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
// —— 配色体系:数据 + 拼接(🔴 2026-09-21 用户定规:提示词不许写死配色)——
|
|
224
|
+
// 缺省 Okabe-Ito(colorblind-safe,Nature Methods / Wong 2011);用户 --colors 或
|
|
225
|
+
// 风格卡里出现 ≥2 个 hex 就整体换用用户的 —— 阶段配色行与调色板句全部由模板拼接生成。
|
|
226
|
+
export const OKABE_ITO_ROLES =
|
|
227
|
+
"deep blue #0072B2 = the proposed method / primary emphasis; sky blue #56B4E9 = supporting modules and secondary inputs; " +
|
|
228
|
+
"bluish green #009E73 = outputs, gains and success path; orange #E69F00 = intermediate steps and pending items; " +
|
|
229
|
+
"vermillion #D55E00 = discarded elements, baselines and contrast; neutral grey #B0B0B0 = context and shared structure";
|
|
230
|
+
|
|
231
|
+
export const SEMANTIC_COLOR_DEFAULT =
|
|
232
|
+
"Palette — Okabe-Ito colorblind-safe scientific palette (Nature Methods / Wong 2011), use EXACTLY these hues and no others: " +
|
|
233
|
+
OKABE_ITO_ROLES + ". " +
|
|
234
|
+
"Every block gets an explicit assignment: fills are a very light tint of that hue (roughly 10-12% opacity on white), " +
|
|
235
|
+
"borders are the full-strength hue at consistent 1.5pt weight, text is near-black #1A1A1A. " +
|
|
236
|
+
"Never print hex codes as visible text on the figure; never pair red-vs-green as the only distinction.";
|
|
237
|
+
|
|
238
|
+
// 缺省阶段/区块配色序列(craft 按序循环取色并写死到每个阶段上)
|
|
239
|
+
export const STAGE_COLOR_CYCLE = [
|
|
240
|
+
{ name: "deep blue #0072B2", tint: "very light blue tint" },
|
|
241
|
+
{ name: "sky blue #56B4E9", tint: "very light sky-blue tint" },
|
|
242
|
+
{ name: "bluish green #009E73", tint: "very light green tint" },
|
|
243
|
+
{ name: "orange #E69F00", tint: "very light orange tint" },
|
|
244
|
+
{ name: "reddish purple #CC79A7", tint: "very light purple tint" },
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
// "deep red #C0392B" → "very light red tint"(抠掉 hex 剩下的词当色相名);纯 hex → 兜底措辞
|
|
248
|
+
function tintOf(name) {
|
|
249
|
+
const hueWords = name.replace(/#[0-9a-fA-F]{3,8}\b/g, "").trim().toLowerCase();
|
|
250
|
+
return hueWords ? `very light ${hueWords} tint` : "very light tint of this hue";
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 用户给的色板令牌(数组或逗号/分号分隔串)→ [{name, tint}];坏令牌丢弃
|
|
254
|
+
export function normalizeColorTokens(colors) {
|
|
255
|
+
const raw = Array.isArray(colors)
|
|
256
|
+
? colors
|
|
257
|
+
: String(colors || "").split(/[,;\n]/);
|
|
258
|
+
const out = [];
|
|
259
|
+
for (const item of raw) {
|
|
260
|
+
const s = String(item).trim().replace(/\s+/g, " ");
|
|
261
|
+
if (!s) continue;
|
|
262
|
+
if (!/#[0-9a-fA-F]{3,8}\b/.test(s) && !/[a-zA-Z]/.test(s)) continue; // 必须有名或 hex
|
|
263
|
+
out.push({ name: s, tint: tintOf(s) });
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 从风格卡/风格提示里抠"标签 + hex"对(≥2 个才算完整色板,避免误抓单个强调色)
|
|
269
|
+
export function extractPaletteFromStyle(text) {
|
|
270
|
+
const s = String(text || "");
|
|
271
|
+
if (!s) return [];
|
|
272
|
+
const re = /([A-Za-z][A-Za-z0-9 ()-]{0,28}?)\s*#([0-9a-fA-F]{6})\b/g;
|
|
273
|
+
const seen = new Set();
|
|
274
|
+
const out = [];
|
|
275
|
+
let m;
|
|
276
|
+
while ((m = re.exec(s))) {
|
|
277
|
+
const hex = "#" + m[2].toUpperCase();
|
|
278
|
+
if (seen.has(hex)) continue;
|
|
279
|
+
seen.add(hex);
|
|
280
|
+
const label = m[1].replace(/\b(use|exactly|hues?|palette|colour|color|border|fill|text|and|the|a|of|in|on|is|=)\b/gi, " ").replace(/\s+/g, " ").trim();
|
|
281
|
+
out.push({ name: (label ? label + " " : "") + hex, tint: tintOf(label || "") });
|
|
282
|
+
}
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 色板解析优先级:--colors 显式 > 风格卡里的 hex 组 > 缺省 Okabe-Ito
|
|
287
|
+
export function buildPalette({ colors, styleHints } = {}) {
|
|
288
|
+
const explicit = normalizeColorTokens(colors);
|
|
289
|
+
if (explicit.length >= 2) {
|
|
290
|
+
return { cycle: explicit, custom: true, source: "user-specified palette" };
|
|
291
|
+
}
|
|
292
|
+
const fromStyle = extractPaletteFromStyle(styleHints);
|
|
293
|
+
if (fromStyle.length >= 2) {
|
|
294
|
+
return { cycle: fromStyle, custom: true, source: "document style-card palette" };
|
|
295
|
+
}
|
|
296
|
+
return { cycle: STAGE_COLOR_CYCLE, custom: false, source: "Okabe-Ito (default)" };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 调色板句:用户色板 = 按显著度拼色名;缺省 = 带角色语义映射的 Okabe-Ito 全句
|
|
300
|
+
export function paletteSentence(palette) {
|
|
301
|
+
const fillRule =
|
|
302
|
+
"Every block gets an explicit assignment: fills are a very light tint of that hue (roughly 10-12% opacity on white), " +
|
|
303
|
+
"borders are the full-strength hue at consistent 1.5pt weight, text is near-black #1A1A1A. " +
|
|
304
|
+
"Never print hex codes as visible text on the figure; never pair red-vs-green as the only distinction.";
|
|
305
|
+
if (palette.custom) {
|
|
306
|
+
return (
|
|
307
|
+
"Palette — use EXACTLY these hues and no others, in this order of prominence: " +
|
|
308
|
+
palette.cycle.map((c) => c.name).join("; ") + ". " + fillRule
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return SEMANTIC_COLOR_DEFAULT;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 选型决策速查(给宿主 AI:拿到一段内容先问这几个问题,答案直接映射到图型 id)
|
|
315
|
+
export const TYPE_DECISION = [
|
|
316
|
+
"内容是按时间/顺序推进的多步过程?→ pipeline(有判断分支就 flowchart)",
|
|
317
|
+
"内容强调模块内部构造与连接(谁包含谁、谁连谁)?→ architecture(关键模块看不清 → zoomin)",
|
|
318
|
+
"内容是因果链(A 激活/抑制 B)?→ mechanism(方向必须与陈述一致,画错比不画更糟)",
|
|
319
|
+
"内容是\"改前 vs 改后\"或\"我们 vs 对照\"?→ comparison(差异是主角)",
|
|
320
|
+
"内容是形态在变(文本→向量→聚类)?→ dataflow(数据量变化用视觉宽度暗示)",
|
|
321
|
+
"内容是包含/分类体系?→ hierarchy(是发展脉络才用 timeline)",
|
|
322
|
+
"要一图讲清全文唯一核心信息(第一页/摘要)?→ teaser(元素 ≤ 6,砍到不能再砍)",
|
|
323
|
+
"要画应用场景/人文背景?→ scene(方法仍是焦点,装饰全删)",
|
|
324
|
+
"要画统计对比但只是示意?→ result-style(⚠️ 精确数值图不许 AI 画,改绘图脚本)",
|
|
325
|
+
"拿不准 → pf types show <id> 看该图型的构图思考与完整示例,照着改变量",
|
|
326
|
+
];
|
package/src/journal.mjs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// journal.mjs — 期刊/会议画幅规范映射(2026-09-22 竞品差距①:academic-figure-skill 1.6k★
|
|
2
|
+
// 把 Nature/Cell/Science 的尺寸·字号·导出规范做成先验,我们从 craft --journal 一句话注入)。
|
|
3
|
+
//
|
|
4
|
+
// 为什么进提示词而不是进质量门:图模型吃的是"布局契约语言",期刊规范的本质是
|
|
5
|
+
// "这张图最终会被缩到多宽印刷"——字号/线宽/信息密度都必须按这个物理宽度反推。
|
|
6
|
+
// 纯函数:CLI 与测试共用。
|
|
7
|
+
export const JOURNAL_SPECS = {
|
|
8
|
+
nature: {
|
|
9
|
+
col1: "89 mm", col2: "183 mm", minPt: 5,
|
|
10
|
+
text: "Nature-family figure: final print width is 89 mm (single column) or 183 mm (double column). Minimum 5 pt lettering at final size, sans-serif (Helvetica/Arial-like), avoid hairline strokes thinner than 0.5 pt, colour-safe for common colour-vision deficiency (no red-green-only contrasts), generous white space over decoration.",
|
|
11
|
+
},
|
|
12
|
+
science: {
|
|
13
|
+
col1: "55 mm", col2: "120 mm", minPt: 6,
|
|
14
|
+
text: "Science-family figure: single column ≈55 mm, two columns ≈120 mm. Keep all lettering ≥6 pt at final size, line weights legible after ~50% reduction, restrained palette, every panel self-explanatory.",
|
|
15
|
+
},
|
|
16
|
+
ieee: {
|
|
17
|
+
col1: "88 mm", col2: "181 mm", minPt: 8,
|
|
18
|
+
text: "IEEE figure: column width ≈88 mm (single) or ≈181 mm (full page). Fonts ≥8 pt at final size (Times/New-Century-like is common but any clean serif/sans works), avoid coloured backgrounds, high contrast for grayscale printing.",
|
|
19
|
+
},
|
|
20
|
+
elsevier: {
|
|
21
|
+
col1: "90 mm", col2: "190 mm", minPt: 7,
|
|
22
|
+
text: "Elsevier journal figure: single column ≈90 mm, full width ≈190 mm. Minimum 7 pt at final size, sans-serif preferred, consistent line weights, colours must survive grayscale conversion.",
|
|
23
|
+
},
|
|
24
|
+
thesis: {
|
|
25
|
+
col1: "150 mm", col2: "150 mm", minPt: 9,
|
|
26
|
+
text: "Thesis/report figure: text width ≈150 mm, figure is read on paper or screen at 100% — medium density, lettering ≥9 pt, no shrink-dependent details.",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// 主读语言无关——注入句永远英文(进图模型提示词),CLI 提示用中文。
|
|
31
|
+
export function journalSentence(journal = "") {
|
|
32
|
+
const key = String(journal || "").trim().toLowerCase();
|
|
33
|
+
const spec = JOURNAL_SPECS[key];
|
|
34
|
+
if (!spec) return null;
|
|
35
|
+
return `Print-size contract (${key}): ${spec.text}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 版式预设联动:期刊 + preset 缺省画布比有冲突时给提示(宽幅内容 vs 单栏窄幅)
|
|
39
|
+
export function journalPresetHint(journal = "", preset = "") {
|
|
40
|
+
const key = String(journal || "").trim().toLowerCase();
|
|
41
|
+
if (!JOURNAL_SPECS[key]) return null;
|
|
42
|
+
if (preset === "double-column" || preset === "slide") {
|
|
43
|
+
return `--journal ${key} 双栏宽 ≈${JOURNAL_SPECS[key].col2}:字号按这个物理宽度反推,双栏图缩印后字会小,标签别太密`;
|
|
44
|
+
}
|
|
45
|
+
if (preset === "single-column") {
|
|
46
|
+
return `--journal ${key} 单栏宽 ≈${JOURNAL_SPECS[key].col1}:内容必须极简,字号按 ${JOURNAL_SPECS[key].col1} 印刷宽度可读来设计`;
|
|
47
|
+
}
|
|
48
|
+
return `--journal ${key}:单栏 ≈${JOURNAL_SPECS[key].col1} / 双栏 ≈${JOURNAL_SPECS[key].col2},配合 --preset double-column 或 single-column 使用(字号按最终印刷宽度反推)`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---- 字号物理核验(2026-09-22 用户拍板补差距③)----
|
|
52
|
+
// 痛点:--journal 只能把规格写进提示词,渲染完没人验证字号是否真被遵守。
|
|
53
|
+
// 确定性换算:PNG 像素宽 × 印刷宽(mm) → mm/px;最小字号(pt)×0.3528(mm/pt) → 最小文字高度(px)。
|
|
54
|
+
// qa 任务包把这个数打出来,AI 量图上最小文字的像素高即可判定——不需要开图片编辑器量 mm。
|
|
55
|
+
// 纯函数;参数缺失或期刊未知时返回 null(不核验)。
|
|
56
|
+
const PT_MM = 0.3528; // 1 pt = 0.3528 mm
|
|
57
|
+
export function fontCheck({ journal = "", preset = "", pngW = 0 } = {}) {
|
|
58
|
+
const key = String(journal || "").trim().toLowerCase();
|
|
59
|
+
const spec = JOURNAL_SPECS[key];
|
|
60
|
+
const w = Number(pngW) || 0;
|
|
61
|
+
if (!spec || !spec.minPt || !w) return null;
|
|
62
|
+
const colMm = parseFloat(preset === "double-column" ? spec.col2 : spec.col1);
|
|
63
|
+
if (!colMm) return null;
|
|
64
|
+
const mmPerPx = colMm / w;
|
|
65
|
+
const minTextPx = Math.ceil((spec.minPt * PT_MM) / mmPerPx);
|
|
66
|
+
return { journal: key, preset: preset || "(default single)", colMm, mmPerPx: +mmPerPx.toFixed(4), minPt: spec.minPt, minTextPx };
|
|
67
|
+
}
|
package/src/ledger.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// ledger.mjs — 执行留证账本(2026-09-23 用户拍板"让用户的 AI 明确执行才放行")
|
|
2
|
+
// 原则:放行条件不是提示词约定,是事件流水对账——宿主 AI 必须真的执行过前置步骤
|
|
3
|
+
// (读文档 / 排练渲染 / 领核验任务包),events.jsonl 里查得到记录才放行。
|
|
4
|
+
// 🔴 只读 events.jsonl,不写;写入一律走 appendEvent(append-only 契约不变)。
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
import { appendEvent, readEvents } from "./events.mjs";
|
|
7
|
+
|
|
8
|
+
export const EVIDENCE_WINDOW_MS = 24 * 3600 * 1000; // 会话级窗口:24h 内的执行记录有效
|
|
9
|
+
|
|
10
|
+
/** 记录一条执行留证(同步落盘 —— die/退出前必须已经写进去,异步会丢;无项目上下文时静默跳过) */
|
|
11
|
+
export function logEvidence(docId, type, data = {}) {
|
|
12
|
+
try {
|
|
13
|
+
appendEvent(docId, type, { ...data, ts: new Date().toISOString() });
|
|
14
|
+
} catch { /* 无项目上下文时跳过 */ }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 24h 内是否有过指定类型的执行记录。
|
|
19
|
+
* @param {string} docId 项目 docId
|
|
20
|
+
* @param {string[]} types 事件类型(任一命中即 true)
|
|
21
|
+
* @param {{filter?: (e: object) => boolean, windowMs?: number}} opts
|
|
22
|
+
*/
|
|
23
|
+
export function hasEvidence(docId, types, { filter = null, windowMs = EVIDENCE_WINDOW_MS } = {}) {
|
|
24
|
+
const since = Date.now() - windowMs;
|
|
25
|
+
const evs = readEvents(docId, 500);
|
|
26
|
+
return evs.some((e) => {
|
|
27
|
+
if (!types.includes(e.type)) return false;
|
|
28
|
+
const t = new Date(e.ts || e.t || 0).getTime();
|
|
29
|
+
if (t < since) return false;
|
|
30
|
+
return filter ? filter(e) : true;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 最近一条指定类型的执行记录(没有则 null)——用于对比排练内容与当前是否一致 */
|
|
35
|
+
export function lastEvidence(docId, type, { filter = null } = {}) {
|
|
36
|
+
const evs = readEvents(docId, 500).filter((e) => e.type === type && (!filter || filter(e)));
|
|
37
|
+
return evs.length ? evs[evs.length - 1] : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 提示词指纹(sha256 前 16 位)——排练事件与真实渲染对账用 */
|
|
41
|
+
export function shaOf(s) {
|
|
42
|
+
return crypto.createHash("sha256").update(String(s ?? "").trim(), "utf8").digest("hex").slice(0, 16);
|
|
43
|
+
}
|