foliko 1.1.92 → 2.0.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 (212) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/CLAUDE.md +56 -30
  3. package/REFACTORING_PLAN.md +645 -0
  4. package/docs/architecture.md +131 -0
  5. package/docs/migration.md +57 -0
  6. package/docs/public-api.md +138 -0
  7. package/docs/usage.md +385 -0
  8. package/examples/ambient-example.js +20 -137
  9. package/examples/basic.js +21 -48
  10. package/examples/bootstrap.js +16 -74
  11. package/examples/mcp-example.js +6 -29
  12. package/examples/skill-example.js +6 -19
  13. package/examples/workflow.js +8 -56
  14. package/package.json +8 -4
  15. package/plugins/README.md +49 -0
  16. package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
  17. package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
  18. package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
  19. package/plugins/ambient/README.md +14 -0
  20. package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
  21. package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
  22. package/plugins/{ambient-agent → ambient}/index.js +2 -2
  23. package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
  24. package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
  25. package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
  26. package/plugins/core/default/bootstrap.js +202 -0
  27. package/plugins/core/default/config.js +220 -0
  28. package/plugins/core/default/index.js +58 -0
  29. package/plugins/core/mcp/index.js +1 -0
  30. package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
  31. package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
  32. package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
  33. package/plugins/{session-plugin.js → core/session/index.js} +9 -73
  34. package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
  35. package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
  36. package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
  37. package/plugins/{think-plugin.js → core/think/index.js} +24 -91
  38. package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
  39. package/plugins/default-plugins.js +6 -720
  40. package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
  41. package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
  42. package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
  43. package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
  44. package/plugins/install/README.md +9 -0
  45. package/plugins/{install-plugin.js → install/index.js} +3 -3
  46. package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
  47. package/plugins/{web-plugin.js → io/web/index.js} +11 -113
  48. package/plugins/memory/README.md +13 -0
  49. package/plugins/{memory-plugin.js → memory/index.js} +4 -18
  50. package/plugins/messaging/email/README.md +19 -0
  51. package/plugins/{email → messaging/email}/index.js +2 -2
  52. package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +3 -3
  53. package/plugins/{qq-plugin.js → messaging/qq/index.js} +5 -16
  54. package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +3 -3
  55. package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +15 -15
  56. package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
  57. package/plugins/{tools-plugin.js → tools/index.js} +68 -116
  58. package/plugins/trading/README.md +15 -0
  59. package/plugins/{gate-trading.js → trading/index.js} +8 -8
  60. package/{examples → sandbox}/test-concurrent-chat.js +2 -2
  61. package/{examples → sandbox}/test-long-chat.js +2 -2
  62. package/{examples → sandbox}/test-session-chat.js +2 -2
  63. package/{examples → sandbox}/test-web-plugin.js +1 -1
  64. package/{examples → sandbox}/test-weixin-feishu.js +2 -2
  65. package/src/agent/base.js +56 -0
  66. package/src/{core/agent-chat.js → agent/chat.js} +11 -11
  67. package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
  68. package/src/agent/index.js +111 -0
  69. package/src/agent/main.js +337 -0
  70. package/src/agent/prompt.js +78 -0
  71. package/src/agent/sub.js +198 -0
  72. package/src/agent/worker.js +104 -0
  73. package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
  74. package/{cli/src → src/cli}/commands/chat.js +25 -21
  75. package/{cli/src → src/cli}/index.js +1 -0
  76. package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
  77. package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
  78. package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
  79. package/src/{core → common}/constants.js +3 -0
  80. package/src/common/errors.js +402 -0
  81. package/src/{utils → common}/logger.js +33 -0
  82. package/src/{utils/chat-queue.js → common/queue.js} +2 -2
  83. package/src/config/plugin-config.js +50 -0
  84. package/src/context/agent.js +32 -0
  85. package/src/context/compaction-prompts.js +170 -0
  86. package/src/context/compaction-utils.js +191 -0
  87. package/src/context/compressor.js +413 -0
  88. package/src/context/index.js +9 -0
  89. package/src/{core/context-manager.js → context/manager.js} +1 -1
  90. package/src/context/request.js +50 -0
  91. package/src/context/session.js +33 -0
  92. package/src/context/storage.js +30 -0
  93. package/src/executors/mcp-client.js +153 -0
  94. package/src/executors/mcp-desc.js +236 -0
  95. package/src/executors/mcp-executor.js +91 -956
  96. package/src/{core → framework}/command-registry.js +1 -1
  97. package/src/framework/framework.js +300 -0
  98. package/src/framework/index.js +18 -0
  99. package/src/framework/lifecycle.js +203 -0
  100. package/src/framework/loader.js +78 -0
  101. package/src/framework/registry.js +86 -0
  102. package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
  103. package/src/index.js +130 -15
  104. package/src/llm/index.js +26 -0
  105. package/src/llm/provider.js +212 -0
  106. package/src/llm/registry.js +11 -0
  107. package/src/{core/token-counter.js → llm/tokens.js} +4 -37
  108. package/src/{core/plugin-base.js → plugin/base.js} +10 -136
  109. package/src/plugin/index.js +14 -0
  110. package/src/plugin/loader.js +101 -0
  111. package/src/plugin/manager.js +261 -0
  112. package/src/{core → session}/branch-summary-auto.js +2 -2
  113. package/src/{core/chat-session.js → session/chat.js} +2 -2
  114. package/src/session/index.js +7 -0
  115. package/src/{core/session-manager.js → session/session.js} +2 -2
  116. package/src/session/ttl.js +92 -0
  117. package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
  118. package/src/tool/executor.js +85 -0
  119. package/src/tool/index.js +15 -0
  120. package/src/tool/registry.js +143 -0
  121. package/src/{core/tool-router.js → tool/router.js} +17 -124
  122. package/src/tool/schema.js +108 -0
  123. package/src/utils/data-splitter.js +1 -1
  124. package/src/utils/download.js +1 -1
  125. package/src/utils/index.js +6 -6
  126. package/src/utils/message-validator.js +1 -1
  127. package/tests/core/context-storage.test.js +46 -0
  128. package/tests/core/llm.test.js +54 -0
  129. package/tests/core/plugin.test.js +42 -0
  130. package/tests/core/tool.test.js +60 -0
  131. package/tests/setup.js +10 -0
  132. package/tests/smoke.test.js +58 -0
  133. package/vitest.config.js +9 -0
  134. package/cli/src/daemon.js +0 -149
  135. package/docs/CONTEXT_DESIGN.md +0 -1596
  136. package/docs/ai-sdk-optimization.md +0 -655
  137. package/docs/features.md +0 -120
  138. package/docs/qq-bot.md +0 -976
  139. package/docs/quick-reference.md +0 -160
  140. package/docs/user-manual.md +0 -1391
  141. package/images/geometric_shapes.jpg +0 -0
  142. package/images/sunset_mountain_lake.jpg +0 -0
  143. package/skills/poster-guide/SKILL.md +0 -792
  144. package/src/capabilities/index.js +0 -11
  145. package/src/core/agent.js +0 -808
  146. package/src/core/context-compressor.js +0 -959
  147. package/src/core/enhanced-context-compressor.js +0 -210
  148. package/src/core/framework.js +0 -1422
  149. package/src/core/index.js +0 -30
  150. package/src/core/plugin-manager.js +0 -961
  151. package/src/core/provider-registry.js +0 -159
  152. package/src/core/provider.js +0 -156
  153. package/src/core/request-context.js +0 -98
  154. package/src/core/subagent.js +0 -442
  155. package/src/core/system-prompt-builder.js +0 -120
  156. package/src/core/tool-executor.js +0 -202
  157. package/src/core/tool-registry.js +0 -517
  158. package/src/core/worker-agent.js +0 -192
  159. package/src/executors/executor-base.js +0 -58
  160. package/src/utils/error-boundary.js +0 -363
  161. package/src/utils/error.js +0 -374
  162. package/system.md +0 -1645
  163. package/website_v2/README.md +0 -57
  164. package/website_v2/SPEC.md +0 -1
  165. package/website_v2/docs/api.html +0 -128
  166. package/website_v2/docs/configuration.html +0 -147
  167. package/website_v2/docs/plugin-development.html +0 -129
  168. package/website_v2/docs/project-structure.html +0 -89
  169. package/website_v2/docs/skill-development.html +0 -85
  170. package/website_v2/index.html +0 -489
  171. package/website_v2/scripts/main.js +0 -93
  172. package/website_v2/styles/animations.css +0 -8
  173. package/website_v2/styles/docs.css +0 -83
  174. package/website_v2/styles/main.css +0 -417
  175. package/xhs_auth.json +0 -268
  176. package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
  177. /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
  178. /package/plugins/{email → messaging/email}/constants.js +0 -0
  179. /package/plugins/{email → messaging/email}/handlers.js +0 -0
  180. /package/plugins/{email → messaging/email}/monitor.js +0 -0
  181. /package/plugins/{email → messaging/email}/parser.js +0 -0
  182. /package/plugins/{email → messaging/email}/reply.js +0 -0
  183. /package/plugins/{email → messaging/email}/utils.js +0 -0
  184. /package/{examples → sandbox}/test-chat.js +0 -0
  185. /package/{examples → sandbox}/test-mcp.js +0 -0
  186. /package/{examples → sandbox}/test-reload.js +0 -0
  187. /package/{examples → sandbox}/test-telegram.js +0 -0
  188. /package/{examples → sandbox}/test-tg-bot.js +0 -0
  189. /package/{examples → sandbox}/test-tg-simple.js +0 -0
  190. /package/{examples → sandbox}/test-tg.js +0 -0
  191. /package/{examples → sandbox}/test-think.js +0 -0
  192. /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
  193. /package/{cli/src → src/cli}/commands/daemon.js +0 -0
  194. /package/{cli/src → src/cli}/commands/list.js +0 -0
  195. /package/{cli/src → src/cli}/commands/plugin.js +0 -0
  196. /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
  197. /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
  198. /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
  199. /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
  200. /package/{cli/src → src/cli}/utils/ansi.js +0 -0
  201. /package/{cli/src → src/cli}/utils/config.js +0 -0
  202. /package/{cli/src → src/cli}/utils/markdown.js +0 -0
  203. /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
  204. /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
  205. /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
  206. /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
  207. /package/src/{utils/event-emitter.js → common/events.js} +0 -0
  208. /package/src/{utils → common}/id.js +0 -0
  209. /package/src/{utils → common}/retry.js +0 -0
  210. /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
  211. /package/src/{core/session-entry.js → session/entry.js} +0 -0
  212. /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
@@ -1,621 +0,0 @@
1
- # Poster Designer Plugin
2
-
3
- 画报/Banner 图绘制插件 - 基于 Node.js paper.js 实现,支持创建海报、宣传图、banner 图并导出为图片。
4
-
5
- ## 功能特性
6
-
7
- ### 预设尺寸
8
-
9
- - **海报尺寸**: A4海报、方形海报、16:9海报
10
- - **Banner尺寸**: 网站Banner、电商Banner、移动端Banner、小横幅
11
- - **社交媒体**: Instagram、Facebook、Twitter 等平台尺寸
12
- - **宣传图**: 多种常用宣传图尺寸
13
-
14
- ### 绘图功能
15
-
16
- - 背景:纯色背景、线性渐变、径向渐变
17
- - 图形:矩形、圆形/椭圆、线条、多边形
18
- - 文字:普通文字、艺术文字(描边、渐变、阴影)
19
- - 图片:添加图片、设为背景
20
- - 圆角支持:矩形和图片都支持圆角裁剪
21
-
22
- ### 导出功能
23
-
24
- - PNG 格式(默认)
25
- - JPEG 格式(支持质量参数)
26
- - Base64 编码导出
27
- - 自动生成文件路径
28
-
29
- ## 安装
30
-
31
- ```bash
32
- npm install poster-designer
33
- ```
34
-
35
- ## 使用方法
36
-
37
- ### 1. 创建画布
38
-
39
- ```javascript
40
- // 使用预设尺寸
41
- create_canvas({ preset: 'poster_square' });
42
-
43
- // 自定义尺寸
44
- create_canvas({ width: 1920, height: 1080, background: '#1a1a2e' });
45
- ```
46
-
47
- ### 2. 添加背景
48
-
49
- ```javascript
50
- // 纯色背景
51
- add_background({ color: '#1a1a2e' });
52
-
53
- // 渐变背景
54
- add_background({
55
- gradient: {
56
- type: 'linear',
57
- colors: ['#ff6b6b', '#4ecdc4'],
58
- direction: 45,
59
- },
60
- });
61
- ```
62
-
63
- ### 3. 添加元素
64
-
65
- ```javascript
66
- // 矩形
67
- add_rectangle({
68
- x: 100,
69
- y: 100,
70
- width: 300,
71
- height: 200,
72
- fill: '#4ecdc4',
73
- radius: 20,
74
- });
75
-
76
- // 圆形
77
- add_circle({
78
- cx: 500,
79
- cy: 500,
80
- rx: 100,
81
- fill: '#ff6b6b',
82
- });
83
-
84
- // 文字
85
- add_text({
86
- text: 'Hello World',
87
- x: 100,
88
- y: 100,
89
- fontSize: 48,
90
- color: '#ffffff',
91
- shadow: {
92
- color: 'rgba(0,0,0,0.5)',
93
- blur: 10,
94
- offsetX: 2,
95
- offsetY: 2,
96
- },
97
- });
98
-
99
- // 艺术文字
100
- add_art_text({
101
- text: 'FOLIKO',
102
- x: 1000,
103
- y: 1000,
104
- fontSize: 120,
105
- gradient: {
106
- colors: ['#ff6b6b', '#feca57'],
107
- direction: 45,
108
- },
109
- strokeColor: '#ffffff',
110
- strokeWidth: 3,
111
- });
112
-
113
- // 图片
114
- add_image({
115
- src: '/path/to/image.png',
116
- x: 200,
117
- y: 200,
118
- width: 500,
119
- height: 300,
120
- rounded: 10,
121
- });
122
- ```
123
-
124
- ### 4. 导出
125
-
126
- ```javascript
127
- // 导出为文件
128
- export_canvas({
129
- filename: 'my-poster',
130
- format: 'png',
131
- outputDir: '/app',
132
- });
133
-
134
- // 导出为 Base64
135
- export_base64({ format: 'png' });
136
- ```
137
-
138
- ### 5. 一键生成海报
139
-
140
- ```javascript
141
- // 使用预设模板快速生成
142
- generate_poster({
143
- template: 'modern', // simple, modern, business, social
144
- title: 'FOLIKO AI',
145
- subtitle: '智能助手新体验',
146
- background: '#1a1a2e',
147
- accentColor: '#e94560',
148
- output: 'foliko-poster',
149
- });
150
- ```
151
-
152
- ## 可用工具
153
-
154
- | 工具名称 | 描述 |
155
- | ---------------------- | -------------------- |
156
- | `list_presets` | 列出所有可用尺寸预设 |
157
- | `create_canvas` | 创建新画布 |
158
- | `add_background` | 添加背景色/渐变 |
159
- | `add_rectangle` | 添加矩形 |
160
- | `add_circle` | 添加圆形/椭圆 |
161
- | `add_line` | 添加线条 |
162
- | `add_polygon` | 绘制多边形 |
163
- | `add_text` | 添加文字 |
164
- | `add_art_text` | 添加艺术文字 |
165
- | `add_image` | 添加图片 |
166
- | `set_background_image` | 设置背景图片 |
167
- | `export_canvas` | 导出为图片文件 |
168
- | `export_base64` | 导出为 Base64 |
169
- | `get_canvas_info` | 获取画布信息 |
170
- | `clear_canvas` | 清除画布 |
171
- | `generate_poster` | 一键生成海报 |
172
-
173
- ## 预设尺寸列表
174
-
175
- ### 海报
176
-
177
- | Key | 尺寸 | 名称 |
178
- | --------------- | --------- | --------------- |
179
- | `poster_a4` | 2480×3508 | A4海报 (300dpi) |
180
- | `poster_square` | 2000×2000 | 方形海报 |
181
- | `poster_16_9` | 1920×1080 | 16:9海报 |
182
-
183
- ### Banner
184
-
185
- | Key | 尺寸 | 名称 |
186
- | ----------------- | -------- | ------------ |
187
- | `banner_1920x500` | 1920×500 | 网站Banner |
188
- | `banner_1200x400` | 1200×400 | 电商Banner |
189
- | `banner_750x300` | 750×300 | 移动端Banner |
190
- | `banner_468x60` | 468×60 | 小横幅 |
191
-
192
- ### 社交媒体
193
-
194
- | Key | 尺寸 | 名称 |
195
- | ------------------ | --------- | --------------- |
196
- | `social_instagram` | 1080×1080 | Instagram正方形 |
197
- | `social_story` | 1080×1920 | Instagram Story |
198
- | `social_facebook` | 1200×630 | Facebook封面 |
199
- | `social_twitter` | 1600×900 | Twitter封面 |
200
-
201
- ### 宣传图
202
-
203
- | Key | 尺寸 | 名称 |
204
- | --------------- | ------- | ---------- |
205
- | `promo_900x500` | 900×500 | 宣传图 |
206
- | `promo_500x500` | 500×500 | 正方宣传图 |
207
-
208
- ## 模板示例
209
-
210
- ### Modern(现代风格)
211
-
212
- ```
213
- 特点:几何装饰元素,大标题居中,渐变强调色
214
- 适用:科技产品、活动宣传
215
- ```
216
-
217
- ### Business(商务风格)
218
-
219
- ```
220
- 特点:简洁线条,专业配色,稳重排版
221
- 适用:企业宣传、商务推广
222
- ```
223
-
224
- ### Social(社交风格)
225
-
226
- ```
227
- 特点:圆形框架,居中头像效果,活力配色
228
- 适用:社交媒体头像、个人品牌
229
- ```
230
-
231
- ### Simple(简约风格)
232
-
233
- ```
234
- 特点:纯色背景,大留白,简洁文字
235
- 适用:极简主义、文艺风格
236
- ```
237
-
238
- ## 依赖
239
-
240
- - `paper.js`: 图片处理
241
-
242
- ## 许可证
243
-
244
- MIT License
245
-
246
- paper.js nodejs使用的例子:
247
-
248
- ```javascript
249
- import paper from 'paper';
250
- import path from 'path';
251
- import { fileURLToPath } from 'url';
252
- import fs from 'fs-extra';
253
- import { createCanvas, registerFont } from 'canvas';
254
- import { initDefaultFont, registerFontFile } from '../utils/font-manager.js';
255
- const __filename = fileURLToPath(import.meta.url);
256
- const __dirname = path.dirname(__filename);
257
-
258
- initDefaultFont();
259
- /**
260
- * 渲染器类 - 使用 Paper.js 渲染
261
- */
262
- export class Renderer {
263
- constructor(config = {}) {
264
- this.width = config.width || 1920;
265
- this.height = config.height || 1080;
266
- this.fps = config.fps || 30;
267
- this.quality = config.quality || 'high';
268
- this.canvas = null;
269
- this.project = null;
270
- this.paper = null; // Paper.js 实例引用
271
- this.initialized = false;
272
- }
273
-
274
- /**
275
- * 初始化渲染器
276
- */
277
- async init() {
278
- if (this.initialized) return;
279
-
280
- // 创建离屏 Canvas(用于最终输出)
281
- this.canvas = paper.createCanvas(this.width, this.height);
282
-
283
- // 创建新的 Paper.js Project(实例化)
284
- // 每个 Renderer 都有自己独立的 project
285
- this.project = new paper.Project(this.canvas);
286
-
287
- // 保存 paper 引用(用于访问 Paper.js 的类,如 paper.Point, paper.Path 等)
288
- this.paper = paper;
289
-
290
- this.initialized = true;
291
- }
292
-
293
- /**
294
- * 获取 Paper.js 实例(用于传递给元素)
295
- * @returns {Object} Paper.js 实例对象,包含 project 和 paper 类
296
- */
297
- getPaperInstance() {
298
- return {
299
- project: this.project,
300
- paper: this.paper, // 用于访问 Paper.js 的类(Point, Path, Color 等)
301
- };
302
- }
303
-
304
- /**
305
- * 设置尺寸
306
- */
307
- setSize(width, height) {
308
- this.width = width;
309
- this.height = height;
310
- if (this.canvas) {
311
- this.canvas.width = width;
312
- this.canvas.height = height;
313
- }
314
- if (this.project && this.project.view) {
315
- this.project.view.viewSize = new this.paper.Size(width, height);
316
- }
317
- }
318
-
319
- /**
320
- * 清空场景
321
- */
322
- clear() {
323
- // 清空当前 project 的 activeLayer
324
- if (this.project && this.project.activeLayer) {
325
- this.project.activeLayer.removeChildren();
326
- }
327
- }
328
-
329
- /**
330
- * 渲染一帧 - 使用 Paper.js
331
- * @param {Array} layers - 图层数组
332
- * @param {number} time - 当前时间(秒)
333
- * @param {string} backgroundColor - 背景颜色
334
- */
335
- async renderFrame(layers, time, backgroundColor = '#000000') {
336
- if (!this.initialized) {
337
- await this.init();
338
- }
339
-
340
- // 先清空 canvas 的 2D context(确保 canvas 有初始状态)
341
- const ctx = this.canvas.getContext('2d');
342
- ctx.fillStyle = backgroundColor;
343
- ctx.fillRect(0, 0, this.width, this.height);
344
-
345
- // 清空 Paper.js 场景(复用 project 时只需要清空子元素)
346
- if (this.project && this.project.activeLayer) {
347
- this.project.activeLayer.removeChildren();
348
- }
349
-
350
- // 绘制背景(使用 Paper.js)
351
- // 只有当背景不是透明时才绘制
352
- if (
353
- backgroundColor &&
354
- backgroundColor !== 'transparent' &&
355
- backgroundColor !== 'rgba(0,0,0,0)'
356
- ) {
357
- const bgRect = new this.paper.Path.Rectangle({
358
- rectangle: new this.paper.Rectangle(0, 0, this.width, this.height),
359
- fillColor: backgroundColor,
360
- });
361
- bgRect.sendToBack();
362
- }
363
-
364
- // 按 zIndex 排序图层
365
- const sortedLayers = [...layers].sort((a, b) => a.zIndex - b.zIndex);
366
-
367
- // 收集所有已渲染的元素
368
- const renderedElements = [];
369
-
370
- // 获取 Paper.js 实例(传递给图层和元素)
371
- const paperInstance = this.getPaperInstance();
372
-
373
- // 渲染所有图层到 Paper.js
374
- for (let i = 0; i < sortedLayers.length; i++) {
375
- const layer = sortedLayers[i];
376
- if (layer.isActiveAtTime(time)) {
377
- // 使用 Paper.js 渲染(支持异步)
378
- // 传递 paperInstance 给图层,图层再传递给元素
379
- const result = layer.render(this.project.activeLayer, time, paperInstance);
380
- if (result && typeof result.then === 'function') {
381
- await result;
382
- }
383
-
384
- // 收集图层中已渲染的元素
385
- if (layer.getRenderedElements && typeof layer.getRenderedElements === 'function') {
386
- const elements = layer.getRenderedElements();
387
- renderedElements.push(...elements);
388
- }
389
- }
390
- }
391
-
392
- // 创建 onFrame 事件对象
393
- const frameEvent = {
394
- count: Math.floor(time * this.fps), // 帧计数
395
- time: time, // 当前时间(秒)
396
- delta: 1 / this.fps, // 帧间隔(秒)
397
- };
398
-
399
- // 触发元素的 onFrame 回调
400
- for (const element of renderedElements) {
401
- if (element.onFrame) {
402
- // 使用元素保存的 _paperItem 引用,并传递 paperInstance
403
- element._callOnFrame(frameEvent, element._paperItem, paperInstance);
404
- }
405
- }
406
-
407
- // 触发全局的 Paper.js onFrame 事件(如果已注册,用于向后兼容)
408
- if (this.project.view && typeof this.project.view.onFrame === 'function') {
409
- try {
410
- this.project.view.onFrame(frameEvent);
411
- } catch (e) {
412
- console.warn('[Renderer] 全局 onFrame 事件触发失败:', e);
413
- }
414
- }
415
-
416
- // 更新视图并绘制到 canvas
417
- // Paper.js 在 Node.js 环境中需要调用 view.update() 来更新视图
418
- // 然后使用 view.draw() 将内容绘制到 canvas
419
- this.project.view.update();
420
-
421
- // 检查 project 中的项目数量
422
- const itemCount = this.project.activeLayer.children.length;
423
- if (itemCount > 0) {
424
- // 强制绘制
425
- this.project.view.draw();
426
- }
427
-
428
- return this.canvas;
429
- }
430
-
431
- /**
432
- * 将 BGRA 格式转换为 RGBA 格式
433
- * @param {Buffer} bgraBuffer - BGRA 格式的 Buffer
434
- * @returns {Buffer} RGBA 格式的 Buffer
435
- */
436
- convertBGRAtoRGBA(bgraBuffer) {
437
- const rgbaBuffer = Buffer.allocUnsafe(bgraBuffer.length);
438
- // 每 4 个字节为一组:BGRA -> RGBA
439
- for (let i = 0; i < bgraBuffer.length; i += 4) {
440
- rgbaBuffer[i] = bgraBuffer[i + 2]; // R = B
441
- rgbaBuffer[i + 1] = bgraBuffer[i + 1]; // G = G
442
- rgbaBuffer[i + 2] = bgraBuffer[i]; // B = R
443
- rgbaBuffer[i + 3] = bgraBuffer[i + 3]; // A = A
444
- }
445
- return rgbaBuffer;
446
- }
447
-
448
- /**
449
- * 获取 Canvas 缓冲区
450
- * @param {string} format - 格式:'raw'(原始 RGBA 数据)或 'png'(PNG 图片)
451
- * @returns {Buffer|null} Canvas 缓冲区
452
- */
453
- getCanvasBuffer(format = 'raw') {
454
- if (!this.canvas) return null;
455
-
456
- if (format === 'raw') {
457
- // 使用 toBuffer('raw') 获取原始数据,性能提升 5-10倍
458
- // 注意:node-canvas 的 toBuffer('raw') 在不同平台上可能返回不同格式:
459
- // - Windows: BGRA 格式
460
- // - Linux/Mac: RGBA 格式
461
- // FFmpeg 需要 RGBA 格式,所以如果是 BGRA 需要转换
462
- let buffer = this.canvas.toBuffer('raw');
463
-
464
- // 验证 buffer 大小是否正确
465
- const expectedSize = this.width * this.height * 4;
466
- if (buffer.length !== expectedSize) {
467
- console.warn(
468
- `[Renderer] Buffer size mismatch: expected ${expectedSize}, got ${buffer.length}`
469
- );
470
- // 如果大小不匹配,回退到 PNG 格式
471
- return this.canvas.toBuffer('image/png');
472
- }
473
-
474
- // 检测平台:Windows 上通常是 BGRA,需要转换为 RGBA
475
- const platform = process.platform;
476
- const isWindows = platform === 'win32';
477
-
478
- if (isWindows) {
479
- // Windows 上 toBuffer('raw') 返回 BGRA,需要转换为 RGBA
480
- buffer = this.convertBGRAtoRGBA(buffer);
481
- }
482
- // Linux/Mac 上通常是 RGBA,不需要转换
483
-
484
- return buffer;
485
- } else {
486
- // PNG 格式(用于保存帧文件或转场处理)
487
- return this.canvas.toBuffer('image/png');
488
- }
489
- }
490
-
491
- /**
492
- * 获取 Canvas
493
- */
494
- getCanvas() {
495
- return this.canvas;
496
- }
497
-
498
- /**
499
- * 导出动画帧序列(类似 paper.view.exportFrames)
500
- * @param {Array} layers - 图层数组
501
- * @param {Object} options - 导出选项
502
- * @param {number} options.amount - 要导出的总帧数
503
- * @param {string} options.directory - 保存帧的目录
504
- * @param {number} options.fps - 帧率(默认 30)
505
- * @param {number} options.startTime - 开始时间(秒,默认 0)
506
- * @param {number} options.endTime - 结束时间(秒)
507
- * @param {string} options.backgroundColor - 背景颜色(默认 '#000000')
508
- * @param {Function} options.onProgress - 进度回调函数 (current, total) => {}
509
- * @param {Function} options.onComplete - 完成回调函数
510
- * @returns {Promise<string[]>} 返回所有导出帧的文件路径数组
511
- */
512
- async exportFrames(layers, options = {}) {
513
- const {
514
- amount,
515
- directory = './frames',
516
- fps = this.fps || 30,
517
- startTime = 0,
518
- endTime,
519
- backgroundColor = '#000000',
520
- onProgress,
521
- onComplete,
522
- } = options;
523
-
524
- if (!this.initialized) {
525
- await this.init();
526
- }
527
-
528
- // 确保目录存在
529
- await fs.ensureDir(directory);
530
-
531
- // 计算实际帧数和时间范围
532
- let totalFrames = amount;
533
- let actualEndTime = endTime;
534
-
535
- if (endTime !== undefined) {
536
- // 如果指定了结束时间,根据帧率计算帧数
537
- totalFrames = Math.ceil((endTime - startTime) * fps);
538
- } else if (amount === undefined) {
539
- // 如果都没有指定,使用默认值
540
- totalFrames = 30; // 默认 1 秒
541
- actualEndTime = startTime + 1;
542
- } else {
543
- // 如果只指定了帧数,计算结束时间
544
- actualEndTime = startTime + totalFrames / fps;
545
- }
546
-
547
- const framePaths = [];
548
-
549
- // 按 zIndex 排序图层
550
- const sortedLayers = [...layers].sort((a, b) => a.zIndex - b.zIndex);
551
-
552
- for (let frame = 0; frame < totalFrames; frame++) {
553
- const time = startTime + frame / fps;
554
- const frameNumber = frame + 1;
555
-
556
- // 渲染帧
557
- await this.renderFrame(sortedLayers, time, backgroundColor);
558
-
559
- // 保存帧
560
- const framePath = path.join(
561
- directory,
562
- `frame_${frameNumber.toString().padStart(4, '0')}.png`
563
- );
564
- const buffer = this.getCanvasBuffer();
565
- await fs.writeFile(framePath, buffer);
566
- framePaths.push(framePath);
567
-
568
- // 调用进度回调
569
- if (onProgress && typeof onProgress === 'function') {
570
- onProgress(frameNumber, totalFrames);
571
- }
572
- }
573
-
574
- // 调用完成回调
575
- if (onComplete && typeof onComplete === 'function') {
576
- onComplete(framePaths);
577
- }
578
-
579
- return framePaths;
580
- }
581
-
582
- /**
583
- * 销毁渲染器
584
- */
585
- destroy() {
586
- // 清理 Paper.js 项目
587
- if (this.project) {
588
- try {
589
- this.project.clear();
590
- // 移除所有图层和项目
591
- if (this.project.activeLayer) {
592
- this.project.activeLayer.removeChildren();
593
- }
594
- // 移除项目
595
- if (this.project && this.project.remove) {
596
- this.project.remove();
597
- }
598
- } catch (error) {
599
- // 忽略清理错误
600
- }
601
- }
602
-
603
- // 清理 Canvas
604
- if (this.canvas) {
605
- try {
606
- // 清空 canvas 内容
607
- const ctx = this.canvas.getContext('2d');
608
- if (ctx) {
609
- ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
610
- }
611
- } catch (error) {
612
- // 忽略清理错误
613
- }
614
- this.canvas = null;
615
- }
616
-
617
- this.project = null;
618
- this.initialized = false;
619
- }
620
- }
621
- ```
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes