@cyber-dash-tech/revela 0.1.16 → 0.2.1
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 +87 -90
- package/README.zh-CN.md +90 -93
- package/lib/agents/research-prompt.ts +12 -2
- package/lib/commands/help.ts +2 -1
- package/lib/commands/pptx.ts +75 -0
- package/lib/document-materials/extract.ts +373 -0
- package/lib/pptx/export.ts +974 -0
- package/package.json +2 -1
- package/plugin.ts +8 -1
- package/tools/extract-document-materials.ts +18 -0
package/README.zh-CN.md
CHANGED
|
@@ -2,35 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **中文**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="800" />
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
Revela 是一个 [OpenCode](https://opencode.ai) 插件,可以把你当前使用的 agent 变成 HTML 幻灯片生成器。
|
|
12
|
-
|
|
12
|
+
在当前会话中启用之后,agent 可以完成调研、结构设计、HTML 写作、QA 和导出。
|
|
13
13
|
|
|
14
|
-
**[在线演示 — AI 权力转移](https://cyber-dash-tech.github.io/revela/assets/html/ai-power-shift.html)**
|
|
14
|
+
**[在线演示 — AI 权力转移](https://cyber-dash-tech.github.io/revela/assets/html/ai-power-shift.html)**
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## 它能做什么
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- `/revela enable` 会把演示文稿生成专用的 system prompt 注入到当前 agent
|
|
20
|
+
- 通过 `/revela enable` 向当前 agent 注入演示文稿专用 system prompt
|
|
23
21
|
- prompt 由 3 层组成:核心 skill、当前 domain、当前 design
|
|
24
|
-
-
|
|
25
|
-
-
|
|
22
|
+
- 支持工作区文档扫描,以及 `.pdf`、`.docx`、`.pptx`、`.xlsx` 的透明文本提取
|
|
23
|
+
- agent 每次写入 `decks/*.html` 时自动执行布局 QA
|
|
24
|
+
- 支持导出成 PDF 和可编辑 PPTX
|
|
25
|
+
- design 和 domain 的切换都在本地完成,不消耗 LLM token
|
|
26
|
+
|
|
27
|
+
Revela 是一种工作模式,不是独立 agent。
|
|
26
28
|
|
|
27
29
|
---
|
|
28
30
|
|
|
29
31
|
## 环境要求
|
|
30
32
|
|
|
31
33
|
- [OpenCode](https://opencode.ai)
|
|
32
|
-
- Bun
|
|
33
|
-
- [Google Chrome](https://www.google.com/chrome/) 或 Chromium
|
|
34
|
+
- Bun 运行时 `>= 1.0.0`
|
|
35
|
+
- [Google Chrome](https://www.google.com/chrome/) 或 Chromium,用于 QA、PDF 导出和 PPTX 导出
|
|
34
36
|
- Git,用于源码安装
|
|
35
37
|
|
|
36
38
|
---
|
|
@@ -48,19 +50,13 @@ Revela 是一种工作模式,不是一个独立聊天 agent。
|
|
|
48
50
|
}
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
然后重启 OpenCode。
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
如果想全局安装,可以把同样配置写到 `~/.config/opencode/opencode.json`。
|
|
54
56
|
|
|
55
57
|
### 本地 wrapper 安装
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- Bun 插件安装不稳定或被网络环境阻塞
|
|
60
|
-
- 你在中国大陆网络环境下使用 OpenCode
|
|
61
|
-
- 你希望直接运行本地源码仓库
|
|
62
|
-
|
|
63
|
-
源码安装方式:
|
|
59
|
+
如果 Bun 安装被网络阻塞、不稳定,或者你想直接运行本地源码仓库,建议使用本地 wrapper。
|
|
64
60
|
|
|
65
61
|
```bash
|
|
66
62
|
git clone https://github.com/cyber-dash-tech/revela
|
|
@@ -74,41 +70,44 @@ npm install
|
|
|
74
70
|
export { default } from "/absolute/path/to/revela/index.ts";
|
|
75
71
|
```
|
|
76
72
|
|
|
77
|
-
|
|
73
|
+
如果使用本地 wrapper,记得把 `opencode.json` 里的 `@cyber-dash-tech/revela` `plugin` 配置删掉,否则 OpenCode 仍可能尝试用 Bun 安装。
|
|
78
74
|
|
|
79
75
|
### 中国大陆网络说明
|
|
80
76
|
|
|
81
|
-
OpenCode 的 npm 插件安装依赖 Bun,而 Bun 可能不会遵循 npm mirror
|
|
77
|
+
OpenCode 的 npm 插件安装依赖 Bun,而 Bun 可能不会遵循 npm mirror 设置。如果直接安装失败,优先使用上面的本地 wrapper 方案。
|
|
82
78
|
|
|
83
79
|
---
|
|
84
80
|
|
|
85
81
|
## 快速开始
|
|
86
82
|
|
|
87
|
-
|
|
83
|
+
先在当前会话中启用 Revela:
|
|
88
84
|
|
|
89
|
-
```
|
|
90
|
-
|
|
85
|
+
```text
|
|
86
|
+
/revela enable
|
|
91
87
|
```
|
|
92
88
|
|
|
93
|
-
|
|
89
|
+
如有需要,先切换 design 或 domain:
|
|
94
90
|
|
|
95
91
|
```text
|
|
96
|
-
/revela
|
|
92
|
+
/revela designs
|
|
93
|
+
/revela designs summit
|
|
94
|
+
/revela domains deeptech-investment
|
|
97
95
|
```
|
|
98
96
|
|
|
99
|
-
然后直接给 agent
|
|
97
|
+
然后直接给 agent 一个 deck 任务:
|
|
100
98
|
|
|
101
99
|
```text
|
|
102
|
-
Create a 6-slide HTML deck on humanoid robotics supply chains.
|
|
100
|
+
Create a 6-slide HTML deck on humanoid robotics supply chains. Cite the main market drivers, use the active design faithfully, and save the result to decks/humanoid-robotics.html.
|
|
103
101
|
```
|
|
104
102
|
|
|
105
|
-
|
|
103
|
+
需要导出时:
|
|
106
104
|
|
|
107
105
|
```text
|
|
108
106
|
/revela pdf decks/humanoid-robotics.html
|
|
107
|
+
/revela pptx decks/humanoid-robotics.html
|
|
109
108
|
```
|
|
110
109
|
|
|
111
|
-
|
|
110
|
+
完成后关闭演示文稿模式:
|
|
112
111
|
|
|
113
112
|
```text
|
|
114
113
|
/revela disable
|
|
@@ -134,6 +133,7 @@ Create a 6-slide HTML deck on humanoid robotics supply chains. Use the summit de
|
|
|
134
133
|
/revela domains-rm <name> 删除已安装 domain
|
|
135
134
|
|
|
136
135
|
/revela pdf <file> 将 HTML deck 导出为同目录 PDF
|
|
136
|
+
/revela pptx <file> 将 HTML deck 导出为同目录可编辑 PPTX
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
所有 `/revela` 命令都在本地执行,不消耗 LLM token。
|
|
@@ -146,11 +146,12 @@ Create a 6-slide HTML deck on humanoid robotics supply chains. Use the summit de
|
|
|
146
146
|
|
|
147
147
|
这份 prompt 由 3 层组成:
|
|
148
148
|
|
|
149
|
-
1. `skill/SKILL.md
|
|
150
|
-
2. 当前 active domain
|
|
151
|
-
3. 当前 active design
|
|
149
|
+
1. `skill/SKILL.md` - 核心幻灯片生成流程
|
|
150
|
+
2. 当前 active domain - 行业结构与术语
|
|
151
|
+
3. 当前 active design - 视觉系统、layout、component 和图表规则
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
持久化配置保存在 `~/.config/revela/config.json`。
|
|
154
|
+
是否启用 Revela 则只在当前会话生效。
|
|
154
155
|
|
|
155
156
|
---
|
|
156
157
|
|
|
@@ -159,60 +160,54 @@ Create a 6-slide HTML deck on humanoid robotics supply chains. Use the summit de
|
|
|
159
160
|
启用 Revela 后,agent 可以使用:
|
|
160
161
|
|
|
161
162
|
- `revela-workspace-scan` 扫描工作区中的 PDF、Office 文件、CSV、Markdown 和文本文件
|
|
162
|
-
- `revela-research`
|
|
163
|
-
- `revela-research-save`
|
|
163
|
+
- `revela-research` 子代理做定向网页调研
|
|
164
|
+
- `revela-research-save` 把结构化 findings 写入 `researches/<topic>/`
|
|
165
|
+
|
|
166
|
+
支持提取文本的入口:
|
|
164
167
|
|
|
165
|
-
|
|
168
|
+
- 在对话里 `@` 引用或直接粘贴文件
|
|
169
|
+
- 启用 Revela 后通过 `read` 工具访问文件
|
|
170
|
+
|
|
171
|
+
支持提取的文件类型:
|
|
166
172
|
|
|
167
173
|
- `.pdf`
|
|
168
174
|
- `.docx`
|
|
169
175
|
- `.pptx`
|
|
170
176
|
- `.xlsx`
|
|
171
177
|
|
|
172
|
-
|
|
178
|
+
这些提取过程对主 agent 是透明的。
|
|
173
179
|
|
|
174
180
|
---
|
|
175
181
|
|
|
176
182
|
## 布局 QA 与合规检查
|
|
177
183
|
|
|
178
|
-
每次 agent 写入 `decks/*.html` 时,Revela 都会自动在 `1920x1080`
|
|
179
|
-
|
|
184
|
+
每次 agent 写入 `decks/*.html` 时,Revela 都会自动在 `1920x1080` 下运行一轮基于 Puppeteer 的 QA。
|
|
185
|
+
报告会立刻返回,便于 agent 继续修正。
|
|
180
186
|
|
|
181
|
-
当前 QA
|
|
187
|
+
当前 QA 维度:
|
|
182
188
|
|
|
183
189
|
| 维度 | 检查内容 |
|
|
184
190
|
|---|---|
|
|
185
191
|
| `overflow` | 元素是否超出 slide canvas |
|
|
186
|
-
| `balance` |
|
|
192
|
+
| `balance` | 是否过稀、重心偏移、底部留白过大 |
|
|
187
193
|
| `symmetry` | 并列列之间的高度或密度是否明显失衡 |
|
|
188
194
|
| `rhythm` | 垂直堆叠元素之间的间距节奏是否不稳定 |
|
|
189
195
|
| `compliance` | 是否使用了 active design 之外的 class 或新增 CSS 规则 |
|
|
190
196
|
|
|
191
|
-
每张 slide
|
|
192
|
-
|
|
193
|
-
- `slide-qa="true"`:适用于内容型页面,执行完整 QA
|
|
194
|
-
- `slide-qa="false"`:适用于封面、目录、引用、总结、结尾等结构型页面
|
|
197
|
+
每张 slide 都必须声明 `slide-qa="true"` 或 `slide-qa="false"`。
|
|
195
198
|
|
|
196
|
-
`
|
|
199
|
+
- `slide-qa="true"` 适用于内容型页面,执行完整 QA
|
|
200
|
+
- `slide-qa="false"` 适用于封面、目录、引用、总结、结尾等结构型页面
|
|
197
201
|
|
|
198
202
|
也可以手动调用 `revela-qa` 工具执行 QA。
|
|
199
203
|
|
|
200
204
|
---
|
|
201
205
|
|
|
202
|
-
##
|
|
203
|
-
|
|
204
|
-
使用 `/revela designs <name>` 切换。
|
|
205
|
-
|
|
206
|
-
| 名称 | 说明 | 预览 |
|
|
207
|
-
|---|---|---|
|
|
208
|
-
| `aurora` | 深色 executive 风格,信息密度更高,适合结构化商业表达和 ECharts 数据可视化 |  |
|
|
209
|
-
| `summit` | 年报式 editorial 风格,适合图像丰富、叙事感更强的商业表达 |  |
|
|
210
|
-
|
|
211
|
-
---
|
|
206
|
+
## Designs 与 Domains
|
|
212
207
|
|
|
213
|
-
|
|
208
|
+
用 `/revela designs` 和 `/revela domains` 查看你当前环境里实际安装的内容。
|
|
214
209
|
|
|
215
|
-
|
|
210
|
+
仓库内置的 domains:
|
|
216
211
|
|
|
217
212
|
| 名称 | 说明 |
|
|
218
213
|
|---|---|
|
|
@@ -220,11 +215,18 @@ Revela 会在主 agent 处理这些文件前,先透明地完成文本提取。
|
|
|
220
215
|
| `deeptech-investment` | VC / 投资分析:市场规模、技术成熟度、护城河与投资逻辑 |
|
|
221
216
|
| `consulting` | 战略咨询:go/no-go 判断、战略设计与 belief-change 报告 |
|
|
222
217
|
|
|
218
|
+
仓库中的 design 示例:
|
|
219
|
+
|
|
220
|
+
| 名称 | 说明 | 预览 |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `summit` | 年报式 editorial 风格,适合图像更丰富、叙事感更强的商业表达 |  |
|
|
223
|
+
| `monet` | 更轻、更安静的 serif 视觉系统,适合带有 art direction 气质的商业叙事 | 仓库内含 `DESIGN.md` |
|
|
224
|
+
|
|
223
225
|
---
|
|
224
226
|
|
|
225
227
|
## 自定义 Designs
|
|
226
228
|
|
|
227
|
-
自定义 design 是一个包含 `DESIGN.md`
|
|
229
|
+
自定义 design 是一个包含 `DESIGN.md` 的文件夹,并带有 frontmatter:
|
|
228
230
|
|
|
229
231
|
```yaml
|
|
230
232
|
---
|
|
@@ -235,58 +237,42 @@ version: 1.0.0
|
|
|
235
237
|
---
|
|
236
238
|
```
|
|
237
239
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
### Marker 体系
|
|
241
|
-
|
|
242
|
-
对于较大的 design,建议使用当前 marker 格式:
|
|
240
|
+
对于较大的 design,建议使用 marker 体系:
|
|
243
241
|
|
|
244
242
|
```html
|
|
245
243
|
<!-- @design:foundation:start -->
|
|
246
|
-
|
|
244
|
+
Foundation rules
|
|
247
245
|
<!-- @design:foundation:end -->
|
|
248
246
|
|
|
249
247
|
<!-- @design:rules:start -->
|
|
250
|
-
|
|
248
|
+
Design rules
|
|
251
249
|
<!-- @design:rules:end -->
|
|
252
250
|
|
|
253
251
|
<!-- @design:layouts:start -->
|
|
254
252
|
<!-- @layout:cover:start qa=false -->
|
|
255
|
-
Layout
|
|
253
|
+
Layout details
|
|
256
254
|
<!-- @layout:cover:end -->
|
|
257
|
-
|
|
258
|
-
<!-- @layout:two-col:start qa=true -->
|
|
259
|
-
Layout 详情...
|
|
260
|
-
<!-- @layout:two-col:end -->
|
|
261
255
|
<!-- @design:layouts:end -->
|
|
262
256
|
|
|
263
257
|
<!-- @design:components:start -->
|
|
264
258
|
<!-- @component:card:start -->
|
|
265
|
-
Component
|
|
259
|
+
Component details
|
|
266
260
|
<!-- @component:card:end -->
|
|
267
|
-
|
|
268
|
-
<!-- @component:stat-card:start -->
|
|
269
|
-
Component HTML + CSS...
|
|
270
|
-
<!-- @component:stat-card:end -->
|
|
271
261
|
<!-- @design:components:end -->
|
|
272
262
|
|
|
273
263
|
<!-- @design:chart-rules:start -->
|
|
274
|
-
|
|
264
|
+
Chart rules
|
|
275
265
|
<!-- @design:chart-rules:end -->
|
|
276
266
|
```
|
|
277
267
|
|
|
278
|
-
Prompt
|
|
268
|
+
Prompt 注入规则:
|
|
279
269
|
|
|
280
270
|
- 常驻注入:`@design:foundation`、`@design:rules`、layout index、component index
|
|
281
271
|
- 按需获取:单个 `@layout:*`、单个 `@component:*`、`@design:chart-rules`
|
|
282
272
|
|
|
283
273
|
如果 design 没有 marker,Revela 会退回到整份 `DESIGN.md` 全量注入。
|
|
284
274
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
Revela 会从 design 中提取允许使用的 CSS class vocabulary,并在 QA 的 compliance 维度里做校验。如果 agent 发明了新的 class 或 CSS rule,QA 会直接报出。
|
|
288
|
-
|
|
289
|
-
### 安装自定义 Design
|
|
275
|
+
安装自定义 design:
|
|
290
276
|
|
|
291
277
|
```text
|
|
292
278
|
/revela designs-add github:your-org/your-design
|
|
@@ -298,31 +284,42 @@ Revela 会从 design 中提取允许使用的 CSS class vocabulary,并在 QA
|
|
|
298
284
|
|
|
299
285
|
## 自定义 Domains
|
|
300
286
|
|
|
301
|
-
自定义 domain 是一个包含 `INDUSTRY.md`
|
|
287
|
+
自定义 domain 是一个包含 `INDUSTRY.md` 的文件夹。
|
|
302
288
|
|
|
303
289
|
```text
|
|
304
290
|
/revela domains-add github:your-org/your-domain
|
|
305
291
|
```
|
|
306
292
|
|
|
307
|
-
`INDUSTRY.md`
|
|
293
|
+
`INDUSTRY.md` 是为兼容历史版本保留的文件名。
|
|
308
294
|
|
|
309
295
|
---
|
|
310
296
|
|
|
311
|
-
##
|
|
297
|
+
## 导出
|
|
312
298
|
|
|
313
|
-
|
|
299
|
+
PDF 导出:
|
|
314
300
|
|
|
315
301
|
```text
|
|
316
302
|
/revela pdf decks/my-deck.html
|
|
317
303
|
```
|
|
318
304
|
|
|
319
|
-
|
|
305
|
+
可编辑 PPTX 导出:
|
|
306
|
+
|
|
307
|
+
```text
|
|
308
|
+
/revela pptx decks/my-deck.html
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
两种导出都会把结果写到源 HTML deck 同目录。
|
|
320
312
|
|
|
321
313
|
---
|
|
322
314
|
|
|
323
|
-
##
|
|
315
|
+
## 开发
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
bun test
|
|
319
|
+
bun run typecheck
|
|
320
|
+
```
|
|
324
321
|
|
|
325
|
-
|
|
322
|
+
开启详细日志:
|
|
326
323
|
|
|
327
324
|
```bash
|
|
328
325
|
REVELA_DEBUG=1 opencode
|
|
@@ -332,4 +329,4 @@ REVELA_DEBUG=1 opencode
|
|
|
332
329
|
|
|
333
330
|
## 许可证
|
|
334
331
|
|
|
335
|
-
MIT
|
|
332
|
+
MIT - 见 [LICENSE](LICENSE)
|
|
@@ -37,8 +37,17 @@ Given a research brief specifying your topic and axis, you will:
|
|
|
37
37
|
Use the **\`revela-workspace-scan\`** tool in a single call to discover all document
|
|
38
38
|
files in the workspace (PDF, Word, Excel, PowerPoint, CSV, text).
|
|
39
39
|
|
|
40
|
-
Then
|
|
41
|
-
|
|
40
|
+
Then select the files relevant to your research axis.
|
|
41
|
+
|
|
42
|
+
For every selected file, call **\`revela-extract-document-materials\`** first.
|
|
43
|
+
- \`pptx\`, \`docx\`, and \`xlsx\` will produce a manifest plus extracted text and any available embedded materials
|
|
44
|
+
- unsupported file types will be skipped automatically
|
|
45
|
+
|
|
46
|
+
After that, use the \`read\` tool on:
|
|
47
|
+
- the original relevant file when you want the plain extracted text
|
|
48
|
+
- the generated manifest and extracted image/table files when visual or tabular evidence matters
|
|
49
|
+
|
|
50
|
+
For PDFs and Office formats, the Revela plugin extracts text transparently — just call \`read\` normally.
|
|
42
51
|
|
|
43
52
|
---
|
|
44
53
|
|
|
@@ -125,6 +134,7 @@ Gaps:
|
|
|
125
134
|
- **NEVER** ask the user for information you can find through search or workspace files
|
|
126
135
|
- **NEVER** use the raw \`write\` tool — always use \`revela-research-save\`
|
|
127
136
|
- **NEVER** fabricate image URLs — only record URLs you actually found
|
|
137
|
+
- **Always** call \`revela-extract-document-materials\` for every selected workspace file before deciding which extracted materials to read next
|
|
128
138
|
- **Always** include source attribution on every data point
|
|
129
139
|
- **Always** use tables for comparative data (more useful than bullets for presentations)
|
|
130
140
|
- **Preserve** raw data — the primary agent will select what to include in slides
|
package/lib/commands/help.ts
CHANGED
|
@@ -34,6 +34,7 @@ export async function handleHelp(
|
|
|
34
34
|
`\`/revela domains-add <url>\` — install a domain from URL / github:user/repo\n` +
|
|
35
35
|
`\`/revela designs-rm <name>\` — remove an installed design\n` +
|
|
36
36
|
`\`/revela domains-rm <name>\` — remove an installed domain\n` +
|
|
37
|
-
`\`/revela pdf <file>\` — export HTML slide deck to PDF`
|
|
37
|
+
`\`/revela pdf <file>\` — export HTML slide deck to PDF\n` +
|
|
38
|
+
`\`/revela pptx <file>\` — export HTML slide deck to PPTX`
|
|
38
39
|
)
|
|
39
40
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/commands/pptx.ts
|
|
3
|
+
*
|
|
4
|
+
* Handler for `/revela pptx <file_path>` — exports an HTML slide deck to PPTX.
|
|
5
|
+
*
|
|
6
|
+
* Output: same directory and base name as the input, with .pptx extension.
|
|
7
|
+
* Example: decks/my-deck.html → decks/my-deck.pptx
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { resolve } from "path"
|
|
11
|
+
import { exportToPptx } from "../pptx/export"
|
|
12
|
+
|
|
13
|
+
function formatSecs(ms: number): string {
|
|
14
|
+
return `${(ms / 1000).toFixed(1)}s`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function handlePptx(
|
|
18
|
+
filePath: string,
|
|
19
|
+
send: (text: string) => Promise<void>,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
if (!filePath) {
|
|
22
|
+
await send(
|
|
23
|
+
"**Usage:** `/revela pptx <file_path>`\n\n" +
|
|
24
|
+
"Example: `/revela pptx decks/my-deck.html`"
|
|
25
|
+
)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const abs = resolve(filePath)
|
|
30
|
+
await send(`Exporting \`${abs}\` to PPTX...`)
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
let lastSlideUpdate = 0
|
|
34
|
+
let longDeckThreshold: number | null = null
|
|
35
|
+
|
|
36
|
+
const result = await exportToPptx(filePath, {
|
|
37
|
+
onProgress: async (progress) => {
|
|
38
|
+
if (progress.kind === "stage") {
|
|
39
|
+
await send(progress.message)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const current = progress.current ?? 0
|
|
44
|
+
const total = progress.total ?? 0
|
|
45
|
+
if (!total) return
|
|
46
|
+
|
|
47
|
+
if (longDeckThreshold === null) {
|
|
48
|
+
longDeckThreshold = total >= 8 ? (total > 20 ? 5 : 2) : -1
|
|
49
|
+
}
|
|
50
|
+
if (longDeckThreshold < 0) return
|
|
51
|
+
|
|
52
|
+
const shouldSend = current === 1 || current === total || current - lastSlideUpdate >= longDeckThreshold
|
|
53
|
+
if (!shouldSend) return
|
|
54
|
+
|
|
55
|
+
lastSlideUpdate = current
|
|
56
|
+
await send(`Editable export progress: slide ${current}/${total}`)
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
await send(
|
|
61
|
+
`**PPTX exported successfully**\n\n` +
|
|
62
|
+
`- Output: \`${result.outputPath}\`\n` +
|
|
63
|
+
`- Slides: ${result.slideCount}\n` +
|
|
64
|
+
`- Time: ${formatSecs(result.durationMs)}\n` +
|
|
65
|
+
`- Prepare: ${formatSecs(result.timingsMs.prepareMs)}\n` +
|
|
66
|
+
`- Page setup: ${formatSecs(result.timingsMs.pageSetupMs)}\n` +
|
|
67
|
+
`- Slide export: ${formatSecs(result.timingsMs.slideExportMs)}\n` +
|
|
68
|
+
`- Merge: ${formatSecs(result.timingsMs.mergeMs)}\n` +
|
|
69
|
+
`- Write: ${formatSecs(result.timingsMs.writeMs)}`
|
|
70
|
+
)
|
|
71
|
+
} catch (e) {
|
|
72
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
73
|
+
await send(`**PPTX export failed**\n\n\`\`\`\n${msg}\n\`\`\``)
|
|
74
|
+
}
|
|
75
|
+
}
|