@leterhong/multichat 1.6.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.
Files changed (214) hide show
  1. package/.agents/plugins/marketplace.json +32 -0
  2. package/.agents/plugins/plugins/developer-toolkit/.codex-plugin/plugin.json +24 -0
  3. package/.agents/plugins/plugins/developer-toolkit/skills/code-review/SKILL.md +9 -0
  4. package/.agents/plugins/plugins/developer-toolkit/skills/code-review/agents/openai.yaml +3 -0
  5. package/.agents/plugins/plugins/developer-toolkit/skills/release-notes/SKILL.md +9 -0
  6. package/.agents/plugins/plugins/developer-toolkit/skills/release-notes/agents/openai.yaml +3 -0
  7. package/.agents/plugins/plugins/developer-toolkit/skills/security-review/SKILL.md +9 -0
  8. package/.agents/plugins/plugins/developer-toolkit/skills/security-review/agents/openai.yaml +3 -0
  9. package/.agents/plugins/plugins/developer-toolkit/skills/test-plan/SKILL.md +9 -0
  10. package/.agents/plugins/plugins/developer-toolkit/skills/test-plan/agents/openai.yaml +3 -0
  11. package/.agents/plugins/plugins/writing-toolkit/.codex-plugin/plugin.json +24 -0
  12. package/.agents/plugins/plugins/writing-toolkit/skills/meeting-notes/SKILL.md +9 -0
  13. package/.agents/plugins/plugins/writing-toolkit/skills/meeting-notes/agents/openai.yaml +3 -0
  14. package/.agents/plugins/plugins/writing-toolkit/skills/resume-tune/SKILL.md +9 -0
  15. package/.agents/plugins/plugins/writing-toolkit/skills/resume-tune/agents/openai.yaml +3 -0
  16. package/.agents/plugins/plugins/writing-toolkit/skills/weekly-report/SKILL.md +9 -0
  17. package/.agents/plugins/plugins/writing-toolkit/skills/weekly-report/agents/openai.yaml +3 -0
  18. package/.agents/skills/.gitkeep +1 -0
  19. package/.agents/skills/wanghong-handwritten-ppt/SKILL.md +172 -0
  20. package/.agents/skills/wanghong-handwritten-ppt/assets/HTML-PPT-LICENSE +21 -0
  21. package/.agents/skills/wanghong-handwritten-ppt/assets/NEAT-ANNOTATIONS-LICENSE +21 -0
  22. package/.agents/skills/wanghong-handwritten-ppt/assets/animations.css +138 -0
  23. package/.agents/skills/wanghong-handwritten-ppt/assets/base.css +150 -0
  24. package/.agents/skills/wanghong-handwritten-ppt/assets/deepseek-example.css +893 -0
  25. package/.agents/skills/wanghong-handwritten-ppt/assets/neat-annotations.css +152 -0
  26. package/.agents/skills/wanghong-handwritten-ppt/assets/preview-cover.png +0 -0
  27. package/.agents/skills/wanghong-handwritten-ppt/assets/preview-diagram.png +0 -0
  28. package/.agents/skills/wanghong-handwritten-ppt/assets/preview-summary.png +0 -0
  29. package/.agents/skills/wanghong-handwritten-ppt/assets/preview-table.png +0 -0
  30. package/.agents/skills/wanghong-handwritten-ppt/assets/runtime.js +960 -0
  31. package/.agents/skills/wanghong-handwritten-ppt/assets/template.css +198 -0
  32. package/.agents/skills/wanghong-handwritten-ppt/evals/evals.json +20 -0
  33. package/.agents/skills/wanghong-handwritten-ppt/examples/deepseek-v4-flash/index.html +414 -0
  34. package/.agents/skills/wanghong-handwritten-ppt/examples/deepseek-v4-flash/style.css +897 -0
  35. package/.agents/skills/wanghong-handwritten-ppt/references/prompts.md +170 -0
  36. package/.agents/skills/wanghong-handwritten-ppt/references/style-guide.md +133 -0
  37. package/.agents/skills/wanghong-handwritten-ppt/scripts/check_deck.py +41 -0
  38. package/.agents/skills/wanghong-handwritten-ppt/scripts/prepare_render_html.py +137 -0
  39. package/.agents/skills/wanghong-handwritten-ppt/scripts/render.sh +110 -0
  40. package/.agents/skills/wanghong-handwritten-ppt/templates/deck.html +94 -0
  41. package/.agents/skills/wanghong-handwritten-ppt/tests/test_font_contract.py +73 -0
  42. package/LICENSE +21 -0
  43. package/README.md +186 -0
  44. package/backend/adapters/README.md +77 -0
  45. package/backend/adapters/anthropic.ts +115 -0
  46. package/backend/adapters/base.ts +47 -0
  47. package/backend/adapters/index.ts +41 -0
  48. package/backend/adapters/lmstudio.ts +7 -0
  49. package/backend/adapters/moonshot.ts +7 -0
  50. package/backend/adapters/ollama.ts +7 -0
  51. package/backend/adapters/openai-compatible.ts +51 -0
  52. package/backend/adapters/openai.ts +7 -0
  53. package/backend/adapters/qwen.ts +112 -0
  54. package/backend/adapters/wenxin.ts +136 -0
  55. package/backend/adapters/zhipu.ts +93 -0
  56. package/backend/dist/adapters/anthropic.js +130 -0
  57. package/backend/dist/adapters/anthropic.js.map +1 -0
  58. package/backend/dist/adapters/base.js +38 -0
  59. package/backend/dist/adapters/base.js.map +1 -0
  60. package/backend/dist/adapters/index.js +39 -0
  61. package/backend/dist/adapters/index.js.map +1 -0
  62. package/backend/dist/adapters/lmstudio.js +8 -0
  63. package/backend/dist/adapters/lmstudio.js.map +1 -0
  64. package/backend/dist/adapters/moonshot.js +8 -0
  65. package/backend/dist/adapters/moonshot.js.map +1 -0
  66. package/backend/dist/adapters/ollama.js +8 -0
  67. package/backend/dist/adapters/ollama.js.map +1 -0
  68. package/backend/dist/adapters/openai-compatible.js +47 -0
  69. package/backend/dist/adapters/openai-compatible.js.map +1 -0
  70. package/backend/dist/adapters/openai.js +8 -0
  71. package/backend/dist/adapters/openai.js.map +1 -0
  72. package/backend/dist/adapters/qwen.js +103 -0
  73. package/backend/dist/adapters/qwen.js.map +1 -0
  74. package/backend/dist/adapters/wenxin.js +114 -0
  75. package/backend/dist/adapters/wenxin.js.map +1 -0
  76. package/backend/dist/adapters/zhipu.js +86 -0
  77. package/backend/dist/adapters/zhipu.js.map +1 -0
  78. package/backend/dist/extensions/demo-mcp-server.js +75 -0
  79. package/backend/dist/extensions/demo-mcp-server.js.map +1 -0
  80. package/backend/dist/extensions/importer.js +828 -0
  81. package/backend/dist/extensions/importer.js.map +1 -0
  82. package/backend/dist/extensions/manager.js +1154 -0
  83. package/backend/dist/extensions/manager.js.map +1 -0
  84. package/backend/dist/lib/catalog.js +30 -0
  85. package/backend/dist/lib/catalog.js.map +1 -0
  86. package/backend/dist/lib/context.js +59 -0
  87. package/backend/dist/lib/context.js.map +1 -0
  88. package/backend/dist/lib/errors.js +75 -0
  89. package/backend/dist/lib/errors.js.map +1 -0
  90. package/backend/dist/lib/provider-store.js +116 -0
  91. package/backend/dist/lib/provider-store.js.map +1 -0
  92. package/backend/dist/lib/redact.js +11 -0
  93. package/backend/dist/lib/redact.js.map +1 -0
  94. package/backend/dist/lib/runtime-store.js +28 -0
  95. package/backend/dist/lib/runtime-store.js.map +1 -0
  96. package/backend/dist/lib/sqlite-store.js +218 -0
  97. package/backend/dist/lib/sqlite-store.js.map +1 -0
  98. package/backend/dist/lib/sse.js +46 -0
  99. package/backend/dist/lib/sse.js.map +1 -0
  100. package/backend/dist/lib/ssrf.js +131 -0
  101. package/backend/dist/lib/ssrf.js.map +1 -0
  102. package/backend/dist/lib/store.js +84 -0
  103. package/backend/dist/lib/store.js.map +1 -0
  104. package/backend/dist/lib/usage.js +219 -0
  105. package/backend/dist/lib/usage.js.map +1 -0
  106. package/backend/dist/lib/util.js +38 -0
  107. package/backend/dist/lib/util.js.map +1 -0
  108. package/backend/dist/lib/workspace-store.js +152 -0
  109. package/backend/dist/lib/workspace-store.js.map +1 -0
  110. package/backend/dist/mcp.js +429 -0
  111. package/backend/dist/mcp.js.map +1 -0
  112. package/backend/dist/routes/agents.js +51 -0
  113. package/backend/dist/routes/agents.js.map +1 -0
  114. package/backend/dist/routes/assistants.js +32 -0
  115. package/backend/dist/routes/assistants.js.map +1 -0
  116. package/backend/dist/routes/chat.js +697 -0
  117. package/backend/dist/routes/chat.js.map +1 -0
  118. package/backend/dist/routes/control-plane.js +200 -0
  119. package/backend/dist/routes/control-plane.js.map +1 -0
  120. package/backend/dist/routes/conversations.js +66 -0
  121. package/backend/dist/routes/conversations.js.map +1 -0
  122. package/backend/dist/routes/extensions-import.js +22 -0
  123. package/backend/dist/routes/extensions-import.js.map +1 -0
  124. package/backend/dist/routes/import.js +85 -0
  125. package/backend/dist/routes/import.js.map +1 -0
  126. package/backend/dist/routes/mcp-servers.js +56 -0
  127. package/backend/dist/routes/mcp-servers.js.map +1 -0
  128. package/backend/dist/routes/meta.js +37 -0
  129. package/backend/dist/routes/meta.js.map +1 -0
  130. package/backend/dist/routes/mock.js +28 -0
  131. package/backend/dist/routes/mock.js.map +1 -0
  132. package/backend/dist/routes/plugins.js +41 -0
  133. package/backend/dist/routes/plugins.js.map +1 -0
  134. package/backend/dist/routes/providers.js +197 -0
  135. package/backend/dist/routes/providers.js.map +1 -0
  136. package/backend/dist/routes/runs.js +57 -0
  137. package/backend/dist/routes/runs.js.map +1 -0
  138. package/backend/dist/routes/skills.js +73 -0
  139. package/backend/dist/routes/skills.js.map +1 -0
  140. package/backend/dist/routes/tools.js +40 -0
  141. package/backend/dist/routes/tools.js.map +1 -0
  142. package/backend/dist/routes/usage.js +14 -0
  143. package/backend/dist/routes/usage.js.map +1 -0
  144. package/backend/dist/routes/workspaces.js +158 -0
  145. package/backend/dist/routes/workspaces.js.map +1 -0
  146. package/backend/dist/runtime/agent.js +518 -0
  147. package/backend/dist/runtime/agent.js.map +1 -0
  148. package/backend/dist/server.js +103 -0
  149. package/backend/dist/server.js.map +1 -0
  150. package/backend/dist/types.js +3 -0
  151. package/backend/dist/types.js.map +1 -0
  152. package/backend/extensions/demo-mcp-server.js +70 -0
  153. package/backend/extensions/importer.ts +709 -0
  154. package/backend/extensions/manager.ts +1055 -0
  155. package/backend/lib/catalog.ts +28 -0
  156. package/backend/lib/context.ts +61 -0
  157. package/backend/lib/errors.ts +78 -0
  158. package/backend/lib/provider-store.ts +116 -0
  159. package/backend/lib/redact.ts +9 -0
  160. package/backend/lib/runtime-store.ts +38 -0
  161. package/backend/lib/sqlite-store.ts +220 -0
  162. package/backend/lib/sse.ts +42 -0
  163. package/backend/lib/ssrf.ts +120 -0
  164. package/backend/lib/store.ts +79 -0
  165. package/backend/lib/usage.ts +212 -0
  166. package/backend/lib/util.ts +31 -0
  167. package/backend/lib/workspace-store.ts +150 -0
  168. package/backend/mcp.ts +417 -0
  169. package/backend/package.json +34 -0
  170. package/backend/prompts.json +82 -0
  171. package/backend/routes/agents.ts +55 -0
  172. package/backend/routes/assistants.ts +30 -0
  173. package/backend/routes/chat.ts +660 -0
  174. package/backend/routes/control-plane.ts +165 -0
  175. package/backend/routes/conversations.ts +62 -0
  176. package/backend/routes/extensions-import.ts +15 -0
  177. package/backend/routes/import.ts +72 -0
  178. package/backend/routes/mcp-servers.ts +36 -0
  179. package/backend/routes/meta.ts +38 -0
  180. package/backend/routes/mock.ts +25 -0
  181. package/backend/routes/plugins.ts +27 -0
  182. package/backend/routes/providers.ts +166 -0
  183. package/backend/routes/runs.ts +55 -0
  184. package/backend/routes/skills.ts +52 -0
  185. package/backend/routes/tools.ts +40 -0
  186. package/backend/routes/usage.ts +13 -0
  187. package/backend/routes/workspaces.ts +124 -0
  188. package/backend/runtime/agent.ts +474 -0
  189. package/backend/server.ts +106 -0
  190. package/backend/types.ts +126 -0
  191. package/bin/multichat.mjs +535 -0
  192. package/frontend/dist/assets/agentPicker-C39VzmYa.js +13 -0
  193. package/frontend/dist/assets/compare-BpaGD32J.js +1 -0
  194. package/frontend/dist/assets/conversations-COuLmg99.js +7 -0
  195. package/frontend/dist/assets/data-ZH7Dux1K.js +1 -0
  196. package/frontend/dist/assets/extensionImport-CnTWKtWm.js +28 -0
  197. package/frontend/dist/assets/importExport-B3mB-9zs.js +70 -0
  198. package/frontend/dist/assets/index-CASVCOUO.js +34 -0
  199. package/frontend/dist/assets/index-DSvbjNLc.css +1 -0
  200. package/frontend/dist/assets/init-ZPK483vB.js +1 -0
  201. package/frontend/dist/assets/jsx-runtime-ZSsbr0oW.js +9 -0
  202. package/frontend/dist/assets/modal-CgHocv3n.js +2 -0
  203. package/frontend/dist/assets/modelPicker-EUmmVnbj.js +14 -0
  204. package/frontend/dist/assets/pluginsUI-B6jGUQkx.js +16 -0
  205. package/frontend/dist/assets/render-DgAg1K8A.js +1 -0
  206. package/frontend/dist/assets/send-BJhZ_5YA.js +14 -0
  207. package/frontend/dist/assets/settings-DdnYwZl-.js +2 -0
  208. package/frontend/dist/assets/shell-DFV2khQl.js +1 -0
  209. package/frontend/dist/assets/workbench-BY2J4vWC.js +41 -0
  210. package/frontend/dist/icon-maskable.svg +4 -0
  211. package/frontend/dist/icon.svg +4 -0
  212. package/frontend/dist/index.html +21 -0
  213. package/frontend/dist/manifest.webmanifest +26 -0
  214. package/package.json +58 -0
@@ -0,0 +1,170 @@
1
+ # 王虹学术手写 PPT:GPT Image 与 HTML 完整提示词
2
+
3
+ 这套提示词用于复刻一种 16:9 的 Notability 学术报告手写页:暖白纸面、细而工整的深蓝手写字、克制的荧光标记、手绘箭头与图表,以及非常清楚的逻辑链。
4
+
5
+ ## 统一风格提示词
6
+
7
+ ```text
8
+ Use case: productivity-visual
9
+ Asset type: exact 16:9 technical presentation slide
10
+
11
+ Create one handwritten Chinese technical presentation slide on a flat warm off-white Notability-style digital note page. The slide should feel like a mathematician's carefully prepared conference lecture notes: calm, precise, information-dense, logically ordered, and genuinely handwritten.
12
+
13
+ Use authentic fine dark-navy digital handwriting, slightly irregular baselines, natural pen-pressure variation, thin hand-drawn boxes, arrows, axes, tables and diagrams. Keep generous outer margins. Use vivid cobalt blue only for derivation lines and axes, magenta-red for conclusions or recommendation boxes, fluorescent yellow for one short priority phrase, fluorescent green for a confirmed structure or recommended option, and coral pink for an exception or loss.
14
+
15
+ The page must be a clean flat slide, with no stage, projector, camera perspective, paper edge or photo texture. Avoid commercial presentation design: no rounded cards, no gradient, no shadow, no glossy icon, no decorative background, no brand logo, no footer, no page number, no watermark.
16
+
17
+ Render every Chinese character, Latin word and number exactly as supplied. Use small neat handwriting and thin strokes. Keep bright highlights narrow and imperfect, like a real highlighter. One slide answers one question. Preserve large empty areas where the logic does not need content.
18
+ ```
19
+
20
+ ## 第 1 页:封面
21
+
22
+ ```text
23
+ Create an exact 16:9 handwritten Chinese presentation cover on a flat warm off-white Notability page.
24
+
25
+ Place one single-line title near the upper third in fine dark-navy handwriting. Draw one thin vivid magenta-red hand-drawn underline directly beneath it. Put the author, occasion and date as three small centered handwritten lines with generous vertical spacing. Keep the entire lower half almost empty.
26
+
27
+ Text, verbatim:
28
+ "DeepSeek-V4-Flash:极限量化与本地部署"
29
+ "AI学习的老章"
30
+ "AI 技术分享"
31
+ "August 2, 2026"
32
+
33
+ Constraints: exact text; one-line title; huge whitespace; no subtitle; no icon; no logo; no border; no shadow; no gradient; no typed font; no watermark; no extra text.
34
+ ```
35
+
36
+ ## 第 2 页:正式版开源
37
+
38
+ ```text
39
+ Create an exact 16:9 handwritten Chinese technical slide in the same warm off-white Notability academic style.
40
+
41
+ Title at top left with a thin cobalt-blue underline:
42
+ "DeepSeek-V4-Flash 正式版开源"
43
+
44
+ Write one small sentence under the title:
45
+ "模型结构、参数规模保持不变"
46
+
47
+ In the center, draw a three-step horizontal process with thin hand-drawn boxes and cobalt arrows:
48
+ "Preview" → "重新后训练" → "正式版"
49
+
50
+ Highlight "重新后训练" with one narrow fluorescent-yellow marker stroke. Highlight "正式版" with fluorescent green. Under the middle box, write three short stacked notes:
51
+ "回答问题"
52
+ "调用工具"
53
+ "完成任务"
54
+
55
+ Near the bottom, draw one thin magenta-red conclusion box containing:
56
+ "多项基准超过 V4-Pro Preview"
57
+
58
+ Keep the slide quiet and sparse. Use fine dark-navy handwriting, thin hand-drawn arrows, no extra decoration and no extra text.
59
+ ```
60
+
61
+ ## 第 3 页:成本—智能斩杀线
62
+
63
+ ```text
64
+ Create an exact 16:9 handwritten Chinese technical slide in a clean Notability academic lecture style.
65
+
66
+ Title at top left with a thin cobalt-blue underline:
67
+ "理想模型,应该站在左上角"
68
+
69
+ On the left, write three lines:
70
+ "横轴:完成一次任务的成本"
71
+ "纵轴:模型的智能得分"
72
+ "越往左越便宜,越往上越聪明"
73
+
74
+ Highlight the word "成本" with coral pink and the phrase "智能得分" with fluorescent green.
75
+
76
+ On the right, draw a hand-drawn scatter chart. The horizontal axis points right and is labeled "任务成本". The vertical axis points up and is labeled "智能得分". Place a magenta dot near the upper-left and label it "DeepSeek-V4-Flash". Add a small curved magenta arrow pointing toward this dot and write "DeepSeek 斩杀线". Place one blue dot around the middle-right labeled "模型 A" and one green dot near the lower-right labeled "模型 B".
77
+
78
+ At the bottom left, add a thin magenta conclusion box:
79
+ "左上角 = 低成本 + 高能力"
80
+
81
+ Use thin dark-navy handwriting and simple hand-drawn geometry. No extra text, no commercial chart styling, no grid background.
82
+ ```
83
+
84
+ ## 第 4 页:本地部署门槛
85
+
86
+ ```text
87
+ Create an exact 16:9 handwritten Chinese technical slide in a warm off-white Notability academic style.
88
+
89
+ Title at top left with a thin cobalt-blue underline:
90
+ "本地部署门槛,终于降下来了"
91
+
92
+ Across the upper-middle, place three large handwritten facts with small labels underneath:
93
+ "304B" / "参数规模"
94
+ "原生 8-bit" / "官方精度"
95
+ "167GB" / "模型文件"
96
+
97
+ Highlight "原生 8-bit" with fluorescent green and "167GB" with fluorescent yellow. Keep "304B" plain dark navy.
98
+
99
+ On the right, draw a simple hand-drawn rounded boundary labeled "本地". Inside it, draw a tiny server tower. Add "CPU →" on the left, "↔ SSD" on the right, and a two-way arrow to "内存 (RAM)" below.
100
+
101
+ Under the three facts, write:
102
+ "企业最关心:能力、成本、数据"
103
+
104
+ Near the bottom center, draw one thin magenta conclusion box:
105
+ "本地可控"
106
+
107
+ Use fine dark-navy handwriting, thin cobalt lines and very large whitespace. No icon set, no cards, no shadow, no extra text.
108
+ ```
109
+
110
+ ## 从文章继续生成后续页面
111
+
112
+ ```text
113
+ Continue the same exact 16:9 handwritten Notability academic slide system for every remaining page.
114
+
115
+ For each page:
116
+ 1. Answer one clear question.
117
+ 2. Use 3 to 6 short handwritten lines, or one diagram/table plus a short explanation.
118
+ 3. Put the causal chain in visible reading order from left to right or top to bottom.
119
+ 4. Use only one main diagram type: flow, coordinate chart, comparison table, memory scale, architecture sketch, command block or summary formula.
120
+ 5. Use yellow and green highlights only on short phrases. Use one magenta conclusion box at most.
121
+ 6. Preserve the same handwriting scale, line weight, warm off-white background, outer margins and color meanings.
122
+ 7. Render all supplied text verbatim and add no new claims.
123
+
124
+ Constraints: no commercial template, no large decorative title, no gradients, no shadows, no icons, no photos, no page number, no watermark, no extra text.
125
+ ```
126
+
127
+ ## HTML 完整实现提示词
128
+
129
+ ```text
130
+ 请把提供的中文文章制作成一套完整的 16:9 HTML 幻灯片,视觉参考 Notability 制作的数学学术报告手写页。
131
+
132
+ 内容组织:
133
+ - 先提炼一个总问题、3 到 6 个核心判断,以及支撑判断的数据、流程、例子和陷阱。
134
+ - 拆成 12 到 24 页,每页只回答一个问题。
135
+ - 推荐结构:封面 → 变化发生了什么 → 为什么重要 → 证据与推导 → 怎么做 → 选择与陷阱 → 总结 → 结束。
136
+ - 每页只保留投影需要的关键词,其余解释写入隐藏的演讲备注。
137
+
138
+ 视觉系统:
139
+ 1. 画布固定 1920×1080 和 16:9,背景使用淡暖白。没有页眉、页脚、页码、品牌装饰和卡片阴影。
140
+ 2. 中文、英文与数字固定使用 HanziPen SC,字形必须与 `assets/preview-cover.png` 一致。
141
+ 3. 主文字用深蓝色。蓝色用于推导线和坐标轴,玫红用于结论框和推荐项,绿色用于已确认结构,荧光黄用于极少量关键词,珊瑚粉用于例外或剩余部分。
142
+ 4. 框、线、箭头和坐标轴保留轻微手画抖动。关键词高亮只覆盖文字本身。
143
+ 5. 禁止圆角卡片、渐变、阴影、照片背景、装饰图标、粗大数字墙和统一商业网格。
144
+
145
+ 封面:
146
+ - 单行标题位于画面上方约三分之一。
147
+ - 标题下方画一条细玫红横线。
148
+ - 作者、场合和日期分三行居中。
149
+ - 下半页保持大面积留白。
150
+
151
+ 正文:
152
+ - 优先使用左文右图、流程、坐标轴、对比表、公式总结。
153
+ - 标题比正文大一档,正文保持小而清楚。
154
+ - 结论使用 2px 到 3px 的玫红细框。
155
+ - SVG 图表线条使用圆头,并加入极轻微位移滤镜。
156
+
157
+ 使用 neat-annotations:
158
+ - 本地引入 neat-annotations.css。
159
+ - 中文可以直接写入 data-note,例如 data-note="甜点档"。
160
+ - 将 --ann-font 固定为 HanziPen SC,--ann-label-max-width 设置为 180px 到 240px。
161
+ - 标注采用绝对定位,因此在目标内容四周预留空间。
162
+ - 重要信息同时写在正文中,标注只做视觉提示。
163
+
164
+ 交互与导出:
165
+ - 支持左右键、空格翻页和全屏。
166
+ - 每页写一段隐藏讲稿备注。
167
+ - 逐页导出为 1920×1080 PNG。
168
+ - 用浏览器检查全部页面的中文、溢出、遮挡、翻页和 16:9 比例。
169
+ - 生成一张全部页面的总览图,人工检查整套风格是否统一。
170
+ ```
@@ -0,0 +1,133 @@
1
+ # 王虹手写 PPT 风格拆解
2
+
3
+ 这套风格的核心是“现场推导笔记”:让观众沿着颜色、框线和图示跟随一条清楚的逻辑链。
4
+
5
+ ## 一、真实风格特征
6
+
7
+ 1. **封面极简**
8
+ - 单行标题放在画面上方约三分之一处。
9
+ - 标题用深蓝色细笔,下面一条玫红色手绘横线。
10
+ - 作者、场合、日期分三行居中。
11
+ - 下半页保持大面积空白。
12
+
13
+ 2. **正文像数字白板笔记**
14
+ - 背景接近纯白或淡暖白,纸张纹理很弱。
15
+ - 主体笔迹以深蓝或黑色为主,字小而工整,行距宽松。
16
+ - 没有页眉、页脚、页码、品牌标识和装饰性背景。
17
+
18
+ 3. **颜色承担逻辑功能**
19
+ - 蓝色:标题下划线、公式、坐标轴、推导主线。
20
+ - 玫红:结论、警告、重点框、推荐项。
21
+ - 绿色:正向定义、成立条件、关键结构。
22
+ - 荧光黄:一个词或一段短语的瞬时强调。
23
+ - 每页可以出现多种颜色,颜色都要对应具体含义。
24
+
25
+ 4. **图文排布自由,逻辑很规整**
26
+ - 常见结构是左边文字、右边小图,也会在顶部放公式、下方放推论。
27
+ - 直线、坐标轴、框、箭头都保留手画的不均匀感。
28
+ - 内容密度较高,每页围绕一个问题逐层展开。
29
+
30
+ 5. **强调方式克制**
31
+ - 定理、目标、结论用细线框。
32
+ - 关键词用窄条高亮,避免整段铺色。
33
+ - 重要词用短下划线,避免粗体和大字号。
34
+
35
+ ## 二、GPT Image 通用提示词
36
+
37
+ ```text
38
+ Use case: productivity-visual
39
+ Asset type: exact 16:9 technical presentation slide
40
+ Primary request: Create one handwritten Chinese technical slide in the visual language of a real Notability academic lecture page. The page should feel like a mathematician's carefully prepared live derivation notes: dense, calm, clear, and handmade.
41
+
42
+ Scene/backdrop: flat warm off-white digital note page; no stage; no projector frame; no photo perspective; nearly textureless background
43
+
44
+ Style/medium: authentic fine digital handwriting; small dark navy body text; slightly irregular baselines; thin hand-drawn boxes, arrows, axes and diagrams; natural pen pressure variation; no typed font
45
+
46
+ Composition/framing: exact 16:9 landscape; one logical question per slide; compact handwritten notes on the left; one small explanatory diagram on the right; one thin result box near the bottom; generous outer margins; free-form alignment
47
+
48
+ Color system:
49
+ - deep navy for body text and formulas
50
+ - vivid cobalt blue for derivation lines and axes
51
+ - magenta-red for conclusions, warnings and recommendation boxes
52
+ - fluorescent yellow for one short high-priority phrase
53
+ - fluorescent green for a positive definition or confirmed structure
54
+ - coral pink for exceptions or the small remaining part
55
+
56
+ Text (verbatim):
57
+ "{标题或首句}"
58
+ "{要点 1}"
59
+ "{要点 2}"
60
+ "{要点 3}"
61
+ "{结论}"
62
+
63
+ Diagram: {说明图的对象、方向、标签、颜色和关系}
64
+
65
+ Constraints: render all Chinese and Latin text exactly; use small neat handwriting; thin strokes; bright highlights only on short phrases; no commercial cards; no grid template; no icons; no gradients; no shadows; no glossy design; no footer; no page number; no watermark; no extra text
66
+ ```
67
+
68
+ ### 封面专用提示词
69
+
70
+ ```text
71
+ Create an exact 16:9 handwritten Chinese presentation cover on a flat warm off-white Notability page.
72
+
73
+ Place one single-line title near the upper third in fine dark navy handwriting. Draw one thin vivid magenta-red hand-drawn underline directly beneath it. Put the author, occasion and date as three small centered handwritten lines with generous vertical spacing. Keep the entire lower half almost empty.
74
+
75
+ Text (verbatim):
76
+ "DeepSeek-V4-Flash:极限量化与本地部署"
77
+ "AI学习的老章"
78
+ "AI 技术分享"
79
+ "August 2, 2026"
80
+
81
+ Constraints: exact text; one-line title; huge whitespace; no subtitle; no icon; no logo; no border; no shadow; no gradient; no typed font; no watermark; no extra text
82
+ ```
83
+
84
+ ## 三、HTML 实现提示词
85
+
86
+ ```text
87
+ 请把文章制作成一套 16:9 HTML 幻灯片,视觉参考 Notability 制作的数学学术报告手写页。
88
+
89
+ 整体要求:
90
+ 1. 画布固定为 16:9,背景使用淡暖白。页面没有页眉、页脚、页码、品牌装饰和卡片阴影。
91
+ 2. 中文、英文与数字固定使用 HanziPen SC,字形以 `assets/preview-cover.png` 为验收基准。
92
+ 3. 正文字号控制在投影可读范围内,标题比正文大一档,避免商业演示常见的超大标题。
93
+ 4. 主文字用深蓝色。蓝色用于推导线和坐标轴,玫红用于结论框和推荐项,绿色用于已确认结构,荧光黄用于极少量关键词,珊瑚粉用于例外或剩余部分。
94
+ 5. 每页只回答一个问题。允许左文右图、上式下图、定义加图解等自由布局。框、线、箭头和坐标轴都保留轻微手画抖动。
95
+ 6. 禁止圆角卡片、渐变、阴影、照片背景、装饰图标、粗大数字墙和统一网格。
96
+
97
+ 使用 neat-annotations:
98
+ - 本地引入 neat-annotations.css。
99
+ - 在要标注的词外包一层 span,使用 ann、方向类和颜色类。
100
+ - data-note 可以直接放中文,例如 data-note="甜点档"。
101
+ - 将 --ann-font 固定为 HanziPen SC,--ann-label-max-width 设为 180px 到 240px。
102
+ - 标注采用绝对定位,因此被标注内容四周必须预留空间。
103
+ - 重要信息要同时出现在正文里,标注只承担视觉提示。
104
+
105
+ 封面布局:
106
+ - 单行标题位于画面上方约三分之一。
107
+ - 标题下方画一条细玫红横线。
108
+ - 作者“AI学习的老章”、场合和日期分三行居中。
109
+ - 下半页保持大面积留白。
110
+
111
+ 正文布局建议:
112
+ - 左侧 3 到 5 行手写要点,右侧一个手绘示意图。
113
+ - 结论用 2px 到 3px 玫红细框。
114
+ - 关键词高亮只覆盖文字本身。
115
+ - SVG 线条使用轻微位移滤镜,保留手绘感。
116
+
117
+ 交互与导出:
118
+ - 支持左右键翻页和全屏。
119
+ - 逐页导出为 1920×1080 PNG。
120
+ - 逐页检查中文标注、溢出、遮挡和 16:9 比例。
121
+ ```
122
+
123
+ ## 四、neat-annotations 中文支持结论
124
+
125
+ 可以使用中文,已经用中文 `data-note` 做了实际页面测试。
126
+
127
+ - 标注文字来自 `data-note`,中文字符可以正常显示。
128
+ - 八个箭头方向、颜色、高亮和嵌套标注与文字语言无关。
129
+ - 项目所有文字统一使用 HanziPen SC。渲染前确认字体已安装,渲染后与预览封面核对字形。
130
+ - 标注处于目标元素外侧,不会自动占据页面空间。中文短语较长时要加大最大宽度,并预留上下左右的空白。
131
+ - 标注属于视觉提示。关键结论仍要写在正文中。
132
+
133
+ 参考:[neat-annotations 项目](https://github.com/syabro/neat-annotations) · [项目演示页](https://neat-annotations.syabro.com/) · [王虹报告现场报道](https://m.21jingji.com/article/20260728/herald/620fe6808829148b183085b0c8979608.html)
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env python3
2
+ import re
3
+ import sys
4
+ from pathlib import Path
5
+
6
+
7
+ def main() -> int:
8
+ if len(sys.argv) != 2:
9
+ print("usage: check_deck.py <index.html>", file=sys.stderr)
10
+ return 2
11
+
12
+ path = Path(sys.argv[1]).expanduser().resolve()
13
+ if not path.is_file():
14
+ print(f"error: {path} not found", file=sys.stderr)
15
+ return 2
16
+
17
+ text = path.read_text(encoding="utf-8")
18
+ slides = len(re.findall(r'<section\s+class="[^"]*\bslide\b', text))
19
+ notes = len(re.findall(r'<(?:aside|div)\s+class="notes"', text))
20
+ missing = []
21
+
22
+ for required in ("base.css", "runtime.js", "neat-annotations.css"):
23
+ if required not in text:
24
+ missing.append(required)
25
+
26
+ if slides < 1:
27
+ print("error: no slides found")
28
+ return 1
29
+ if notes != slides:
30
+ print(f"error: {slides} slides but {notes} notes")
31
+ return 1
32
+ if missing:
33
+ print("error: missing resources: " + ", ".join(missing))
34
+ return 1
35
+
36
+ print(f"ok: {slides} slides, {notes} notes, required resources present")
37
+ return 0
38
+
39
+
40
+ if __name__ == "__main__":
41
+ raise SystemExit(main())
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import struct
6
+ from pathlib import Path
7
+
8
+
9
+ FONT_ALIAS = "Wanghong Preview Hand"
10
+ DEMO_FONT_POSTSCRIPT = "HanziPenSC-W3"
11
+
12
+
13
+ def _u16(data: bytes, offset: int) -> int:
14
+ return struct.unpack_from(">H", data, offset)[0]
15
+
16
+
17
+ def _u32(data: bytes, offset: int) -> int:
18
+ return struct.unpack_from(">I", data, offset)[0]
19
+
20
+
21
+ def postscript_names(font: Path) -> set[str]:
22
+ data = font.read_bytes()
23
+ if data[:4] == b"ttcf":
24
+ face_count = _u32(data, 8)
25
+ face_offsets = [_u32(data, 12 + index * 4) for index in range(face_count)]
26
+ else:
27
+ face_offsets = [0]
28
+
29
+ names: set[str] = set()
30
+ for face_offset in face_offsets:
31
+ table_count = _u16(data, face_offset + 4)
32
+ name_offset = None
33
+ for index in range(table_count):
34
+ record = face_offset + 12 + index * 16
35
+ if data[record : record + 4] == b"name":
36
+ name_offset = _u32(data, record + 8)
37
+ break
38
+ if name_offset is None:
39
+ continue
40
+
41
+ record_count = _u16(data, name_offset + 2)
42
+ string_offset = name_offset + _u16(data, name_offset + 4)
43
+ for index in range(record_count):
44
+ record = name_offset + 6 + index * 12
45
+ platform_id = _u16(data, record)
46
+ name_id = _u16(data, record + 6)
47
+ if name_id != 6:
48
+ continue
49
+ length = _u16(data, record + 8)
50
+ offset = _u16(data, record + 10)
51
+ raw = data[string_offset + offset : string_offset + offset + length]
52
+ encoding = "utf-16-be" if platform_id in {0, 3} else "mac_roman"
53
+ try:
54
+ names.add(raw.decode(encoding))
55
+ except UnicodeDecodeError:
56
+ continue
57
+ return names
58
+
59
+
60
+ def validate_font_file(font: Path) -> None:
61
+ try:
62
+ names = postscript_names(font)
63
+ except (OSError, struct.error, ValueError) as error:
64
+ raise ValueError("无法读取指定字体文件") from error
65
+ if DEMO_FONT_POSTSCRIPT not in names:
66
+ raise ValueError("指定文件与预览封面使用的字体不一致")
67
+
68
+
69
+ def inject_font(source: Path, font: Path, output: Path) -> None:
70
+ validate_font_file(font)
71
+ html = source.read_text(encoding="utf-8")
72
+ if "<head" not in html or "</head>" not in html:
73
+ raise ValueError("HTML 缺少 head 元素")
74
+
75
+ head_end = html.find(">", html.find("<head"))
76
+ if head_end < 0:
77
+ raise ValueError("HTML head 元素不完整")
78
+
79
+ base_uri = source.resolve().parent.as_uri().rstrip("/") + "/"
80
+ font_uri = font.resolve().as_uri()
81
+ injection = f"""
82
+ <base href="{base_uri}">
83
+ <style id="wanghong-preview-font-lock">
84
+ @font-face {{
85
+ font-family: "{FONT_ALIAS}";
86
+ src: url("{font_uri}");
87
+ font-style: normal;
88
+ font-weight: 100 900;
89
+ }}
90
+ :root {{
91
+ --hand: "{FONT_ALIAS}";
92
+ --hand-font: "{FONT_ALIAS}";
93
+ --ann-font: "{FONT_ALIAS}";
94
+ --font-sans: "{FONT_ALIAS}";
95
+ --font-display: "{FONT_ALIAS}";
96
+ --font-mono: "{FONT_ALIAS}";
97
+ --font-serif: "{FONT_ALIAS}";
98
+ }}
99
+ html, body, .deck, .deck *, .deck svg text {{
100
+ font-family: "{FONT_ALIAS}" !important;
101
+ }}
102
+ </style>
103
+ <script>
104
+ window.addEventListener('DOMContentLoaded', async () => {{
105
+ try {{
106
+ await document.fonts.load('32px "{FONT_ALIAS}"', '王虹学术手写');
107
+ await document.fonts.ready;
108
+ document.documentElement.dataset.wanghongFontReady =
109
+ document.fonts.check('32px "{FONT_ALIAS}"', '王虹学术手写') ? 'yes' : 'no';
110
+ }} catch (error) {{
111
+ document.documentElement.dataset.wanghongFontReady = 'no';
112
+ }}
113
+ }});
114
+ </script>
115
+ """
116
+ output.write_text(html[: head_end + 1] + injection + html[head_end + 1 :], encoding="utf-8")
117
+
118
+
119
+ def main() -> int:
120
+ parser = argparse.ArgumentParser(
121
+ description="为王虹 PPT 静态渲染注入预览封面的指定字体"
122
+ )
123
+ parser.add_argument("source", type=Path)
124
+ parser.add_argument("font", type=Path)
125
+ parser.add_argument("output", type=Path)
126
+ args = parser.parse_args()
127
+
128
+ if not args.source.is_file():
129
+ parser.error(f"HTML 不存在: {args.source}")
130
+ if not args.font.is_file():
131
+ parser.error(f"字体文件不存在: {args.font}")
132
+ inject_font(args.source, args.font, args.output)
133
+ return 0
134
+
135
+
136
+ if __name__ == "__main__":
137
+ raise SystemExit(main())
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
5
+ FILE="${1:-}"
6
+ COUNT="${2:-all}"
7
+ OUT="${3:-}"
8
+ FONT_FILE="${4:-${WANGHONG_FONT_PATH:-}}"
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+
11
+ if [[ -z "$FILE" || ! -f "$FILE" ]]; then
12
+ echo "usage: render.sh <html-file> [N|all] [out-dir] [font-file]" >&2
13
+ exit 1
14
+ fi
15
+
16
+ if [[ ! -x "$CHROME" ]]; then
17
+ echo "error: Google Chrome not found" >&2
18
+ exit 1
19
+ fi
20
+
21
+ if [[ -z "$FONT_FILE" ]]; then
22
+ for candidate in \
23
+ "$HOME/Library/Fonts/Hanzipen.ttc" \
24
+ "$HOME/Library/Fonts/HanziPen.ttc" \
25
+ "/Library/Fonts/Hanzipen.ttc" \
26
+ "/Library/Fonts/HanziPen.ttc" \
27
+ "/System/Library/Fonts/Supplemental/Hanzipen.ttc" \
28
+ "/System/Library/Fonts/Supplemental/HanziPen.ttc"; do
29
+ if [[ -f "$candidate" ]]; then
30
+ FONT_FILE="$candidate"
31
+ break
32
+ fi
33
+ done
34
+ fi
35
+ if [[ -z "$FONT_FILE" && -d "/System/Library/AssetsV2" ]]; then
36
+ FONT_FILE="$(find /System/Library/AssetsV2 \
37
+ \( -path '*/com_apple_MobileAsset_Font7/*/AssetData/Hanzipen.ttc' \
38
+ -o -path '*/com_apple_MobileAsset_Font8/*/AssetData/Hanzipen.ttc' \) \
39
+ -type f -print -quit 2>/dev/null || true)"
40
+ fi
41
+ if [[ -z "$FONT_FILE" || ! -f "$FONT_FILE" ]]; then
42
+ echo "error: 找不到预览封面使用的字体文件" >&2
43
+ echo "download: 在 macOS 字体册下载“翩翩体-简”" >&2
44
+ echo "usage: render.sh <html-file> [N|all] [out-dir] [font-file]" >&2
45
+ exit 1
46
+ fi
47
+
48
+ STEM="$(basename "${FILE%.*}")"
49
+
50
+ if [[ "$COUNT" == "all" ]]; then
51
+ COUNT="$(python3 - "$FILE" <<'PY'
52
+ import re
53
+ import sys
54
+ from pathlib import Path
55
+
56
+ html = Path(sys.argv[1]).read_text(encoding="utf-8")
57
+ print(len(re.findall(r'<section\s+class="[^"]*\bslide\b', html)))
58
+ PY
59
+ )"
60
+ fi
61
+
62
+ if [[ -z "$COUNT" || "$COUNT" -lt 1 ]]; then
63
+ echo "error: no slides found" >&2
64
+ exit 1
65
+ fi
66
+
67
+ if [[ -z "$OUT" ]]; then
68
+ OUT="$(dirname "$FILE")/${STEM}-png"
69
+ fi
70
+ mkdir -p "$OUT"
71
+
72
+ RENDER_DIR="$(mktemp -d "/tmp/wanghong-ppt-render.XXXXXX")"
73
+ RENDER_HTML="$RENDER_DIR/deck.html"
74
+ trap 'rm -f "$RENDER_HTML"; rmdir "$RENDER_DIR"' EXIT
75
+ python3 "$SCRIPT_DIR/prepare_render_html.py" "$FILE" "$FONT_FILE" "$RENDER_HTML"
76
+
77
+ if ! FONT_CHECK="$("$CHROME" \
78
+ --headless=new \
79
+ --allow-file-access-from-files \
80
+ --disable-gpu \
81
+ --hide-scrollbars \
82
+ --no-sandbox \
83
+ --virtual-time-budget=4000 \
84
+ --dump-dom \
85
+ "file://$RENDER_HTML#/1")"; then
86
+ echo "error: Chrome 字体预检失败" >&2
87
+ exit 1
88
+ fi
89
+ if [[ "$FONT_CHECK" != *'data-wanghong-font-ready="yes"'* ]]; then
90
+ echo "error: 预览封面字体加载失败" >&2
91
+ exit 1
92
+ fi
93
+
94
+ for i in $(seq 1 "$COUNT"); do
95
+ target="$OUT/${STEM}_$(printf '%02d' "$i").png"
96
+ "$CHROME" \
97
+ --headless=new \
98
+ --allow-file-access-from-files \
99
+ --disable-gpu \
100
+ --hide-scrollbars \
101
+ --no-sandbox \
102
+ --virtual-time-budget=4000 \
103
+ --window-size=1920,1080 \
104
+ --screenshot="$target" \
105
+ "file://$RENDER_HTML#/$i" >/dev/null 2>&1
106
+ [[ -s "$target" ]] || { echo "error: failed to render slide $i" >&2; exit 1; }
107
+ echo "rendered: $target"
108
+ done
109
+
110
+ echo "done: $COUNT slide(s)"