byteplan-cli 1.0.2 → 1.2.2
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/package.json +7 -3
- package/skills/byteplan-analysis/SKILL.md +1078 -0
- package/skills/byteplan-api/API_REFERENCE.md +249 -0
- package/skills/byteplan-api/SKILL.md +96 -0
- package/skills/byteplan-api/package.json +16 -0
- package/skills/byteplan-api/scripts/api.js +973 -0
- package/skills/byteplan-excel/SKILL.md +212 -0
- package/skills/byteplan-excel/examples/margin-analysis.json +40 -0
- package/skills/byteplan-excel/package.json +12 -0
- package/skills/byteplan-excel/pnpm-lock.yaml +68 -0
- package/skills/byteplan-excel/scripts/generate_excel.js +156 -0
- package/skills/byteplan-html/SKILL.md +490 -0
- package/skills/byteplan-html/examples/example-output.html +184 -0
- package/skills/byteplan-html/examples/generate-ppt-style-html.js +611 -0
- package/skills/byteplan-html/examples/margin-contribution-analysis.json +152 -0
- package/skills/byteplan-html/package.json +18 -0
- package/skills/byteplan-html/scripts/generate_html.js +517 -0
- package/skills/byteplan-ppt/SKILL.md +394 -0
- package/skills/byteplan-ppt/examples/margin-contribution-analysis.json +152 -0
- package/skills/byteplan-ppt/package.json +16 -0
- package/skills/byteplan-ppt/pnpm-lock.yaml +138 -0
- package/skills/byteplan-ppt/scripts/check_ppt_overlap.js +318 -0
- package/skills/byteplan-ppt/scripts/generate_ppt.js +680 -0
- package/skills/byteplan-video/SKILL.md +606 -0
- package/skills/byteplan-video/examples/sample-video-data.json +82 -0
- package/skills/byteplan-video/remotion-project/package.json +22 -0
- package/skills/byteplan-video/remotion-project/pnpm-lock.yaml +1646 -0
- package/skills/byteplan-video/remotion-project/remotion.config.ts +6 -0
- package/skills/byteplan-video/remotion-project/scene_durations.json +32 -0
- package/skills/byteplan-video/remotion-project/scripts/generate_audio.js +279 -0
- package/skills/byteplan-video/remotion-project/src/DynamicReport.tsx +172 -0
- package/skills/byteplan-video/remotion-project/src/Root.tsx +51 -0
- package/skills/byteplan-video/remotion-project/src/SalesReport.tsx +107 -0
- package/skills/byteplan-video/remotion-project/src/index.tsx +4 -0
- package/skills/byteplan-video/remotion-project/src/scenes/ChartSlide.tsx +201 -0
- package/skills/byteplan-video/remotion-project/src/scenes/CoverSlide.tsx +61 -0
- package/skills/byteplan-video/remotion-project/src/scenes/EndSlide.tsx +60 -0
- package/skills/byteplan-video/remotion-project/src/scenes/InsightSlide.tsx +101 -0
- package/skills/byteplan-video/remotion-project/src/scenes/KpiSlide.tsx +84 -0
- package/skills/byteplan-video/remotion-project/src/scenes/RecommendationSlide.tsx +100 -0
- package/skills/byteplan-video/remotion-project/tsconfig.json +13 -0
- package/skills/byteplan-video/remotion-project/video_data.json +76 -0
- package/skills/byteplan-video/scripts/generate_video.js +270 -0
- package/skills/byteplan-video/templates/package.json +31 -0
- package/skills/byteplan-video/templates/pnpm-lock.yaml +2200 -0
- package/skills/byteplan-video/templates/remotion.config.ts +9 -0
- package/skills/byteplan-video/templates/scripts/generate-audio.ts +55 -0
- package/skills/byteplan-video/templates/src/components/BarChartScene.tsx +153 -0
- package/skills/byteplan-video/templates/src/components/InsightScene.tsx +135 -0
- package/skills/byteplan-video/templates/src/components/LineChartScene.tsx +214 -0
- package/skills/byteplan-video/templates/src/components/SceneFactory.tsx +34 -0
- package/skills/byteplan-video/templates/src/components/SummaryScene.tsx +155 -0
- package/skills/byteplan-video/templates/src/components/TitleScene.tsx +130 -0
- package/skills/byteplan-video/templates/src/compositions/AnalysisVideo.tsx +39 -0
- package/skills/byteplan-video/templates/src/index.tsx +28 -0
- package/skills/byteplan-video/templates/src/register-root.tsx +4 -0
- package/skills/byteplan-video/templates/src/storyboard/types.ts +46 -0
- package/skills/byteplan-video/templates/tsconfig.json +17 -0
- package/skills/byteplan-video/templates/tsconfig.scripts.json +13 -0
- package/skills/byteplan-word/SKILL.md +233 -0
- package/skills/byteplan-word/package.json +12 -0
- package/skills/byteplan-word/pnpm-lock.yaml +120 -0
- package/skills/byteplan-word/scripts/generate_word.js +548 -0
- package/src/api.js +78 -22
- package/src/cli.js +11 -0
- package/src/commands/skills.js +279 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PPT 元素重叠检查脚本
|
|
4
|
+
* 使用 jszip + xml2js 解析 PPTX 文件,检查元素位置重叠问题
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import JSZip from 'jszip';
|
|
8
|
+
import { parseString } from 'xml2js';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
// EMU 转 inch (1 inch = 914400 EMU)
|
|
13
|
+
const EMU_PER_INCH = 914400;
|
|
14
|
+
|
|
15
|
+
function emuToInch(emu) {
|
|
16
|
+
return emu / EMU_PER_INCH;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function inchToEmu(inch) {
|
|
20
|
+
return inch * EMU_PER_INCH;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getShapeBounds(shapeElement) {
|
|
24
|
+
// 从 XML 中提取位置和尺寸
|
|
25
|
+
const spPr = shapeElement['p:spPr']?.[0] || shapeElement;
|
|
26
|
+
const xfrm = spPr['a:xfrm']?.[0];
|
|
27
|
+
|
|
28
|
+
if (!xfrm) return null;
|
|
29
|
+
|
|
30
|
+
const off = xfrm['a:off']?.[0];
|
|
31
|
+
const ext = xfrm['a:ext']?.[0];
|
|
32
|
+
|
|
33
|
+
if (!off || !ext) return null;
|
|
34
|
+
|
|
35
|
+
const left = parseInt(off.$.x || 0);
|
|
36
|
+
const top = parseInt(off.$.y || 0);
|
|
37
|
+
const width = parseInt(ext.$.cx || 0);
|
|
38
|
+
const height = parseInt(ext.$.cy || 0);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
left, top, right: left + width, bottom: top + height,
|
|
42
|
+
width, height,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getShapeText(shapeElement) {
|
|
47
|
+
const txBody = shapeElement['p:txBody']?.[0];
|
|
48
|
+
if (!txBody) return null;
|
|
49
|
+
|
|
50
|
+
const bodyPr = txBody['a:bodyPr']?.[0];
|
|
51
|
+
const p = txBody['a:p']?.[0];
|
|
52
|
+
if (!p) return null;
|
|
53
|
+
|
|
54
|
+
const r = p['a:r']?.[0];
|
|
55
|
+
if (!r) return null;
|
|
56
|
+
|
|
57
|
+
const t = r['a:t']?.[0];
|
|
58
|
+
return t ? String(t) : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function boundsOverlap(bounds1, bounds2, tolerance = 10000) {
|
|
62
|
+
// 扩展边界以考虑容差
|
|
63
|
+
const b1 = {
|
|
64
|
+
left: bounds1.left - tolerance,
|
|
65
|
+
top: bounds1.top - tolerance,
|
|
66
|
+
right: bounds1.right + tolerance,
|
|
67
|
+
bottom: bounds1.bottom + tolerance,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const b2 = {
|
|
71
|
+
left: bounds2.left - tolerance,
|
|
72
|
+
top: bounds2.top - tolerance,
|
|
73
|
+
right: bounds2.right + tolerance,
|
|
74
|
+
bottom: bounds2.bottom + tolerance,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// 检查是否重叠
|
|
78
|
+
if (b1.right < b2.left || b2.right < b1.left) return false;
|
|
79
|
+
if (b1.bottom < b2.top || b2.bottom < b1.top) return false;
|
|
80
|
+
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function boundsContain(outer, inner, tolerance = 5000) {
|
|
85
|
+
return (
|
|
86
|
+
inner.left >= outer.left - tolerance &&
|
|
87
|
+
inner.top >= outer.top - tolerance &&
|
|
88
|
+
inner.right <= outer.right + tolerance &&
|
|
89
|
+
inner.bottom <= outer.bottom + tolerance
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function calculateOverlapRatio(bounds1, bounds2) {
|
|
94
|
+
const overlapLeft = Math.max(bounds1.left, bounds2.left);
|
|
95
|
+
const overlapTop = Math.max(bounds1.top, bounds2.top);
|
|
96
|
+
const overlapRight = Math.min(bounds1.right, bounds2.right);
|
|
97
|
+
const overlapBottom = Math.min(bounds1.bottom, bounds2.bottom);
|
|
98
|
+
|
|
99
|
+
if (overlapRight <= overlapLeft || overlapBottom <= overlapTop) return 0;
|
|
100
|
+
|
|
101
|
+
const overlapArea = (overlapRight - overlapLeft) * (overlapBottom - overlapTop);
|
|
102
|
+
const area1 = bounds1.width * bounds1.height;
|
|
103
|
+
const area2 = bounds2.width * bounds2.height;
|
|
104
|
+
|
|
105
|
+
const ratio1 = area1 > 0 ? overlapArea / area1 : 0;
|
|
106
|
+
const ratio2 = area2 > 0 ? overlapArea / area2 : 0;
|
|
107
|
+
|
|
108
|
+
return Math.max(ratio1, ratio2);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function parseSlide(slideXml) {
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
parseString(slideXml, { explicitArray: true }, (err, result) => {
|
|
114
|
+
if (err) reject(err);
|
|
115
|
+
else resolve(result);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function checkPptOverlap(pptFile) {
|
|
121
|
+
try {
|
|
122
|
+
const buffer = fs.readFileSync(pptFile);
|
|
123
|
+
const zip = await JSZip.loadAsync(buffer);
|
|
124
|
+
|
|
125
|
+
// 获取幻灯片尺寸
|
|
126
|
+
let slideWidth = inchToEmu(13.333);
|
|
127
|
+
let slideHeight = inchToEmu(7.5);
|
|
128
|
+
|
|
129
|
+
// 尝试从 presentation.xml 获取尺寸
|
|
130
|
+
const presentationXml = await zip.file('ppt/presentation.xml')?.async('string');
|
|
131
|
+
if (presentationXml) {
|
|
132
|
+
const presData = await parseSlide(presentationXml);
|
|
133
|
+
const sldSz = presData?.['p:presentation']?.['p:sldSz']?.[0]?.$;
|
|
134
|
+
if (sldSz) {
|
|
135
|
+
slideWidth = parseInt(sldSz.cx || slideWidth);
|
|
136
|
+
slideHeight = parseInt(sldSz.cy || slideHeight);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log(`📄 PPT 文件: ${pptFile}`);
|
|
141
|
+
console.log(`📐 幻灯片尺寸: ${emuToInch(slideWidth).toFixed(2)}" × ${emuToInch(slideHeight).toFixed(2)}"`);
|
|
142
|
+
|
|
143
|
+
// 获取所有幻灯片
|
|
144
|
+
const slideFiles = Object.keys(zip.files)
|
|
145
|
+
.filter(name => name.match(/^ppt\/slides\/slide\d+\.xml$/))
|
|
146
|
+
.sort();
|
|
147
|
+
|
|
148
|
+
console.log(`📊 幻灯片数量: ${slideFiles.length}`);
|
|
149
|
+
console.log();
|
|
150
|
+
|
|
151
|
+
const allOverlapIssues = [];
|
|
152
|
+
const allBoundaryIssues = [];
|
|
153
|
+
|
|
154
|
+
for (const slideFile of slideFiles) {
|
|
155
|
+
const slideNum = parseInt(slideFile.match(/slide(\d+)\.xml$/)[1]);
|
|
156
|
+
const slideXml = await zip.file(slideFile).async('string');
|
|
157
|
+
const slideData = await parseSlide(slideXml);
|
|
158
|
+
|
|
159
|
+
// 获取所有形状
|
|
160
|
+
const spTree = slideData?.['p:sld']?.['p:cSld']?.[0]?.['p:spTree']?.[0];
|
|
161
|
+
if (!spTree) continue;
|
|
162
|
+
|
|
163
|
+
const shapes = spTree['p:sp'] || [];
|
|
164
|
+
const shapeBounds = [];
|
|
165
|
+
|
|
166
|
+
shapes.forEach((shape, index) => {
|
|
167
|
+
const bounds = getShapeBounds(shape);
|
|
168
|
+
if (bounds) {
|
|
169
|
+
const text = getShapeText(shape);
|
|
170
|
+
shapeBounds.push({
|
|
171
|
+
index,
|
|
172
|
+
bounds,
|
|
173
|
+
text: text ? text.slice(0, 30) + (text.length > 30 ? '...' : '') : `[Shape ${index}]`,
|
|
174
|
+
isBackground: bounds.width > inchToEmu(12) && bounds.height > inchToEmu(6),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// 检查重叠
|
|
180
|
+
for (let i = 0; i < shapeBounds.length; i++) {
|
|
181
|
+
for (let j = i + 1; j < shapeBounds.length; j++) {
|
|
182
|
+
const shape1 = shapeBounds[i];
|
|
183
|
+
const shape2 = shapeBounds[j];
|
|
184
|
+
|
|
185
|
+
// 跳过背景形状
|
|
186
|
+
if (shape1.isBackground || shape2.isBackground) continue;
|
|
187
|
+
|
|
188
|
+
if (boundsOverlap(shape1.bounds, shape2.bounds)) {
|
|
189
|
+
// 检查容器关系
|
|
190
|
+
if (boundsContain(shape1.bounds, shape2.bounds)) continue;
|
|
191
|
+
if (boundsContain(shape2.bounds, shape1.bounds)) continue;
|
|
192
|
+
|
|
193
|
+
const overlapRatio = calculateOverlapRatio(shape1.bounds, shape2.bounds);
|
|
194
|
+
|
|
195
|
+
// 重叠比例小于 15% 可接受
|
|
196
|
+
if (overlapRatio < 0.15) continue;
|
|
197
|
+
|
|
198
|
+
allOverlapIssues.push({
|
|
199
|
+
slide: slideNum,
|
|
200
|
+
shape1: shape1.text,
|
|
201
|
+
shape2: shape2.text,
|
|
202
|
+
pos1: `(${emuToInch(shape1.bounds.left).toFixed(2)}", ${emuToInch(shape1.bounds.top).toFixed(2)}")`,
|
|
203
|
+
pos2: `(${emuToInch(shape2.bounds.left).toFixed(2)}", ${emuToInch(shape2.bounds.top).toFixed(2)}")`,
|
|
204
|
+
overlapRatio,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 检查边界
|
|
211
|
+
const tolerance = inchToEmu(0.1);
|
|
212
|
+
shapeBounds.forEach(shape => {
|
|
213
|
+
if (shape.isBackground) return;
|
|
214
|
+
|
|
215
|
+
const { bounds, text } = shape;
|
|
216
|
+
|
|
217
|
+
if (bounds.right > slideWidth + tolerance) {
|
|
218
|
+
allBoundaryIssues.push({
|
|
219
|
+
slide: slideNum,
|
|
220
|
+
type: '超出右边界',
|
|
221
|
+
shape: text,
|
|
222
|
+
right: `${emuToInch(bounds.right).toFixed(2)}"`,
|
|
223
|
+
limit: `${emuToInch(slideWidth).toFixed(2)}"`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (bounds.bottom > slideHeight + tolerance) {
|
|
228
|
+
allBoundaryIssues.push({
|
|
229
|
+
slide: slideNum,
|
|
230
|
+
type: '超出下边界',
|
|
231
|
+
shape: text,
|
|
232
|
+
bottom: `${emuToInch(bounds.bottom).toFixed(2)}"`,
|
|
233
|
+
limit: `${emuToInch(slideHeight).toFixed(2)}"`,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (bounds.left < -tolerance) {
|
|
238
|
+
allBoundaryIssues.push({
|
|
239
|
+
slide: slideNum,
|
|
240
|
+
type: '超出左边界',
|
|
241
|
+
shape: text,
|
|
242
|
+
left: `${emuToInch(bounds.left).toFixed(2)}"`,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (bounds.top < -tolerance) {
|
|
247
|
+
allBoundaryIssues.push({
|
|
248
|
+
slide: slideNum,
|
|
249
|
+
type: '超出上边界',
|
|
250
|
+
shape: text,
|
|
251
|
+
top: `${emuToInch(bounds.top).toFixed(2)}"`,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 输出重叠问题
|
|
258
|
+
if (allOverlapIssues.length > 0) {
|
|
259
|
+
console.log('⚠️ 发现元素重叠问题:');
|
|
260
|
+
console.log('-'.repeat(60));
|
|
261
|
+
allOverlapIssues.forEach(issue => {
|
|
262
|
+
console.log(` 幻灯片 ${issue.slide}:`);
|
|
263
|
+
console.log(` 元素1: "${issue.shape1}" 位置 ${issue.pos1}`);
|
|
264
|
+
console.log(` 元素2: "${issue.shape2}" 位置 ${issue.pos2}`);
|
|
265
|
+
console.log();
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
console.log('✅ 未发现元素重叠问题');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log();
|
|
272
|
+
|
|
273
|
+
// 输出边界问题
|
|
274
|
+
if (allBoundaryIssues.length > 0) {
|
|
275
|
+
console.log('⚠️ 发现边界问题:');
|
|
276
|
+
console.log('-'.repeat(60));
|
|
277
|
+
allBoundaryIssues.forEach(issue => {
|
|
278
|
+
console.log(` 幻灯片 ${issue.slide} - ${issue.type}:`);
|
|
279
|
+
console.log(` 元素: "${issue.shape}"`);
|
|
280
|
+
if (issue.limit) {
|
|
281
|
+
console.log(` 当前: ${issue.right || issue.bottom}, 限制: ${issue.limit}`);
|
|
282
|
+
} else {
|
|
283
|
+
console.log(` 当前位置: ${issue.left || issue.top}`);
|
|
284
|
+
}
|
|
285
|
+
console.log();
|
|
286
|
+
});
|
|
287
|
+
} else {
|
|
288
|
+
console.log('✅ 未发现边界问题');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 总结
|
|
292
|
+
console.log();
|
|
293
|
+
console.log('='.repeat(60));
|
|
294
|
+
const totalIssues = allOverlapIssues.length + allBoundaryIssues.length;
|
|
295
|
+
if (totalIssues === 0) {
|
|
296
|
+
console.log('✅ PPT 检查通过,未发现问题');
|
|
297
|
+
return true;
|
|
298
|
+
} else {
|
|
299
|
+
console.log(`❌ PPT 检查完成,发现 ${totalIssues} 个问题需要修复`);
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
} catch (err) {
|
|
303
|
+
console.error(`❌ 无法打开 PPT 文件: ${err.message}`);
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// CLI 入口
|
|
309
|
+
const pptFile = process.argv[2];
|
|
310
|
+
|
|
311
|
+
if (!pptFile) {
|
|
312
|
+
console.error('Usage: node check_ppt_overlap.js <ppt_file>');
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
checkPptOverlap(path.resolve(pptFile)).then(success => {
|
|
317
|
+
process.exit(success ? 0 : 1);
|
|
318
|
+
});
|