byteplan-cli 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/cli.js +4 -0
- package/src/commands/skills.js +279 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AbsoluteFill, useCurrentFrame, useVideoConfig, spring } from "remotion";
|
|
3
|
+
import { VisualConfig } from "../storyboard/types";
|
|
4
|
+
|
|
5
|
+
interface SummarySceneProps {
|
|
6
|
+
config: VisualConfig;
|
|
7
|
+
narration: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const COLORS = {
|
|
11
|
+
primary: "#667eea",
|
|
12
|
+
secondary: "#1E293B",
|
|
13
|
+
accent: "#764ba2",
|
|
14
|
+
background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)",
|
|
15
|
+
text: "#FFFFFF",
|
|
16
|
+
lightText: "#94A3B8",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const SummaryScene: React.FC<SummarySceneProps> = ({ config }) => {
|
|
20
|
+
const frame = useCurrentFrame();
|
|
21
|
+
const { fps } = useVideoConfig();
|
|
22
|
+
|
|
23
|
+
// 必须提供 points 数据
|
|
24
|
+
const points = config.points;
|
|
25
|
+
|
|
26
|
+
if (!points || points.length === 0) {
|
|
27
|
+
throw new Error("SummaryScene requires 'points' array in config");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 动画
|
|
31
|
+
const titleOpacity = spring({ frame, fps, config: { damping: 100 } });
|
|
32
|
+
const contentOpacity = spring({ frame: frame - 15, fps, config: { damping: 100 } });
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<AbsoluteFill style={{ background: COLORS.background }}>
|
|
36
|
+
{/* 装饰背景 */}
|
|
37
|
+
<div style={{
|
|
38
|
+
position: "absolute",
|
|
39
|
+
top: "50%",
|
|
40
|
+
left: "50%",
|
|
41
|
+
transform: "translate(-50%, -50%)",
|
|
42
|
+
width: 600,
|
|
43
|
+
height: 600,
|
|
44
|
+
borderRadius: "50%",
|
|
45
|
+
background: "radial-gradient(circle, rgba(255,106,0,0.1) 0%, transparent 60%)",
|
|
46
|
+
filter: "blur(80px)",
|
|
47
|
+
}} />
|
|
48
|
+
|
|
49
|
+
{/* 标题 */}
|
|
50
|
+
<div style={{
|
|
51
|
+
position: "absolute",
|
|
52
|
+
top: 100,
|
|
53
|
+
left: 0,
|
|
54
|
+
right: 0,
|
|
55
|
+
textAlign: "center",
|
|
56
|
+
opacity: titleOpacity,
|
|
57
|
+
}}>
|
|
58
|
+
<h2 style={{
|
|
59
|
+
fontSize: 56,
|
|
60
|
+
fontWeight: 700,
|
|
61
|
+
color: COLORS.text,
|
|
62
|
+
margin: 0,
|
|
63
|
+
}}>
|
|
64
|
+
{config.title}
|
|
65
|
+
</h2>
|
|
66
|
+
<div style={{
|
|
67
|
+
width: 100,
|
|
68
|
+
height: 4,
|
|
69
|
+
background: COLORS.primary,
|
|
70
|
+
margin: "20px auto",
|
|
71
|
+
}} />
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* 总结要点 */}
|
|
75
|
+
<div style={{
|
|
76
|
+
position: "absolute",
|
|
77
|
+
top: 220,
|
|
78
|
+
left: 200,
|
|
79
|
+
right: 200,
|
|
80
|
+
display: "grid",
|
|
81
|
+
gridTemplateColumns: "1fr 1fr",
|
|
82
|
+
gap: 30,
|
|
83
|
+
opacity: contentOpacity,
|
|
84
|
+
}}>
|
|
85
|
+
{points.map((point, index) => {
|
|
86
|
+
const delay = index * 6;
|
|
87
|
+
const itemOpacity = spring({ frame: frame - 25 - delay, fps, config: { damping: 100 } });
|
|
88
|
+
const itemScale = spring({ frame: frame - 25 - delay, fps, config: { damping: 100, stiffness: 80 } });
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
key={index}
|
|
93
|
+
style={{
|
|
94
|
+
display: "flex",
|
|
95
|
+
alignItems: "center",
|
|
96
|
+
gap: 20,
|
|
97
|
+
padding: "24px 32px",
|
|
98
|
+
background: "rgba(255,255,255,0.05)",
|
|
99
|
+
borderRadius: 16,
|
|
100
|
+
border: "1px solid rgba(255,255,255,0.1)",
|
|
101
|
+
opacity: itemOpacity,
|
|
102
|
+
transform: `scale(${itemScale})`,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{/* 序号 */}
|
|
106
|
+
<div style={{
|
|
107
|
+
width: 50,
|
|
108
|
+
height: 50,
|
|
109
|
+
borderRadius: "50%",
|
|
110
|
+
background: `linear-gradient(135deg, ${COLORS.primary}, ${COLORS.accent})`,
|
|
111
|
+
display: "flex",
|
|
112
|
+
alignItems: "center",
|
|
113
|
+
justifyContent: "center",
|
|
114
|
+
fontSize: 24,
|
|
115
|
+
fontWeight: 700,
|
|
116
|
+
flexShrink: 0,
|
|
117
|
+
}}>
|
|
118
|
+
{index + 1}
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{/* 文本 */}
|
|
122
|
+
<div style={{
|
|
123
|
+
fontSize: 26,
|
|
124
|
+
fontWeight: 500,
|
|
125
|
+
color: COLORS.text,
|
|
126
|
+
}}>
|
|
127
|
+
{point}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
{/* 感谢文字 */}
|
|
135
|
+
{config.source && (
|
|
136
|
+
<div style={{
|
|
137
|
+
position: "absolute",
|
|
138
|
+
bottom: 80,
|
|
139
|
+
left: 0,
|
|
140
|
+
right: 0,
|
|
141
|
+
textAlign: "center",
|
|
142
|
+
opacity: spring({ frame: frame - 50, fps, config: { damping: 100 } }),
|
|
143
|
+
}}>
|
|
144
|
+
<p style={{
|
|
145
|
+
fontSize: 24,
|
|
146
|
+
color: COLORS.lightText,
|
|
147
|
+
margin: 0,
|
|
148
|
+
}}>
|
|
149
|
+
感谢观看 · 数据来源:{config.source}
|
|
150
|
+
</p>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</AbsoluteFill>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AbsoluteFill, useCurrentFrame, useVideoConfig, spring } from "remotion";
|
|
3
|
+
import { VisualConfig } from "../storyboard/types";
|
|
4
|
+
|
|
5
|
+
interface TitleSceneProps {
|
|
6
|
+
config: VisualConfig;
|
|
7
|
+
narration: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const COLORS = {
|
|
11
|
+
primary: "#667eea", // BytePlan 紫
|
|
12
|
+
secondary: "#1E293B",
|
|
13
|
+
accent: "#764ba2",
|
|
14
|
+
background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)",
|
|
15
|
+
text: "#FFFFFF",
|
|
16
|
+
lightText: "#94A3B8",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const TitleScene: React.FC<TitleSceneProps> = ({ config }) => {
|
|
20
|
+
const frame = useCurrentFrame();
|
|
21
|
+
const { fps } = useVideoConfig();
|
|
22
|
+
|
|
23
|
+
// 动画效果
|
|
24
|
+
const titleOpacity = spring({ frame, fps, config: { damping: 100 } });
|
|
25
|
+
const titleScale = spring({ frame, fps, config: { damping: 100, stiffness: 50 } });
|
|
26
|
+
const subtitleOpacity = spring({ frame: frame - 15, fps, config: { damping: 100 } });
|
|
27
|
+
const logoOpacity = spring({ frame: frame - 30, fps, config: { damping: 100 } });
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<AbsoluteFill style={{ background: COLORS.background }}>
|
|
31
|
+
{/* 装饰元素 */}
|
|
32
|
+
<div style={{
|
|
33
|
+
position: "absolute",
|
|
34
|
+
top: "10%",
|
|
35
|
+
left: "5%",
|
|
36
|
+
width: 200,
|
|
37
|
+
height: 200,
|
|
38
|
+
borderRadius: "50%",
|
|
39
|
+
background: "radial-gradient(circle, rgba(102,126,234,0.2) 0%, transparent 70%)",
|
|
40
|
+
filter: "blur(40px)",
|
|
41
|
+
}} />
|
|
42
|
+
|
|
43
|
+
<div style={{
|
|
44
|
+
position: "absolute",
|
|
45
|
+
bottom: "10%",
|
|
46
|
+
right: "5%",
|
|
47
|
+
width: 300,
|
|
48
|
+
height: 300,
|
|
49
|
+
borderRadius: "50%",
|
|
50
|
+
background: "radial-gradient(circle, rgba(118,75,162,0.15) 0%, transparent 70%)",
|
|
51
|
+
filter: "blur(60px)",
|
|
52
|
+
}} />
|
|
53
|
+
|
|
54
|
+
{/* Logo 装饰 */}
|
|
55
|
+
<div style={{
|
|
56
|
+
position: "absolute",
|
|
57
|
+
top: 80,
|
|
58
|
+
left: 80,
|
|
59
|
+
opacity: logoOpacity,
|
|
60
|
+
display: "flex",
|
|
61
|
+
alignItems: "center",
|
|
62
|
+
gap: 16,
|
|
63
|
+
}}>
|
|
64
|
+
<div style={{
|
|
65
|
+
width: 60,
|
|
66
|
+
height: 60,
|
|
67
|
+
borderRadius: 12,
|
|
68
|
+
background: COLORS.primary,
|
|
69
|
+
display: "flex",
|
|
70
|
+
alignItems: "center",
|
|
71
|
+
justifyContent: "center",
|
|
72
|
+
fontSize: 32,
|
|
73
|
+
fontWeight: "bold",
|
|
74
|
+
color: "white",
|
|
75
|
+
}}>
|
|
76
|
+
B
|
|
77
|
+
</div>
|
|
78
|
+
<span style={{
|
|
79
|
+
color: COLORS.text,
|
|
80
|
+
fontSize: 24,
|
|
81
|
+
fontWeight: 500,
|
|
82
|
+
}}>
|
|
83
|
+
BytePlan
|
|
84
|
+
</span>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{/* 主标题 */}
|
|
88
|
+
<div style={{
|
|
89
|
+
display: "flex",
|
|
90
|
+
flexDirection: "column",
|
|
91
|
+
justifyContent: "center",
|
|
92
|
+
alignItems: "center",
|
|
93
|
+
height: "100%",
|
|
94
|
+
gap: 24,
|
|
95
|
+
}}>
|
|
96
|
+
<h1 style={{
|
|
97
|
+
fontSize: 72,
|
|
98
|
+
fontWeight: 700,
|
|
99
|
+
color: COLORS.text,
|
|
100
|
+
margin: 0,
|
|
101
|
+
opacity: titleOpacity,
|
|
102
|
+
transform: `scale(${titleScale})`,
|
|
103
|
+
textShadow: "0 4px 30px rgba(102,126,234,0.3)",
|
|
104
|
+
}}>
|
|
105
|
+
{config.title}
|
|
106
|
+
</h1>
|
|
107
|
+
|
|
108
|
+
<div style={{
|
|
109
|
+
width: 200,
|
|
110
|
+
height: 4,
|
|
111
|
+
background: `linear-gradient(90deg, transparent, ${COLORS.primary}, transparent)`,
|
|
112
|
+
opacity: subtitleOpacity,
|
|
113
|
+
}} />
|
|
114
|
+
|
|
115
|
+
{config.subtitle && (
|
|
116
|
+
<h2 style={{
|
|
117
|
+
fontSize: 32,
|
|
118
|
+
fontWeight: 400,
|
|
119
|
+
color: COLORS.lightText,
|
|
120
|
+
margin: 0,
|
|
121
|
+
opacity: subtitleOpacity,
|
|
122
|
+
letterSpacing: 2,
|
|
123
|
+
}}>
|
|
124
|
+
{config.subtitle}
|
|
125
|
+
</h2>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
</AbsoluteFill>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AbsoluteFill, Sequence, Audio, staticFile, useVideoConfig } from "remotion";
|
|
3
|
+
import { SceneFactory } from "../components/SceneFactory";
|
|
4
|
+
import storyboardData from "../storyboard/scenes.json";
|
|
5
|
+
|
|
6
|
+
export const AnalysisVideo: React.FC = () => {
|
|
7
|
+
const { fps } = useVideoConfig();
|
|
8
|
+
const scenes = Array.isArray(storyboardData) ? storyboardData : storyboardData.scenes;
|
|
9
|
+
|
|
10
|
+
let currentFrame = 0;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<AbsoluteFill style={{ backgroundColor: "#1a1a2e" }}>
|
|
14
|
+
{scenes.map((scene, index) => {
|
|
15
|
+
// 默认每个场景 5 秒,如果已有音频时长则使用
|
|
16
|
+
const durationSeconds = scene.audioDuration || 5;
|
|
17
|
+
const durationInFrames = Math.round(durationSeconds * fps);
|
|
18
|
+
|
|
19
|
+
const startFrame = currentFrame;
|
|
20
|
+
currentFrame += durationInFrames;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Sequence
|
|
24
|
+
key={scene.id}
|
|
25
|
+
from={startFrame}
|
|
26
|
+
durationInFrames={durationInFrames}
|
|
27
|
+
>
|
|
28
|
+
<AbsoluteFill>
|
|
29
|
+
<SceneFactory scene={scene} />
|
|
30
|
+
{scene.audioPath && (
|
|
31
|
+
<Audio src={staticFile(scene.audioPath.replace('/audio/', 'audio/'))} />
|
|
32
|
+
)}
|
|
33
|
+
</AbsoluteFill>
|
|
34
|
+
</Sequence>
|
|
35
|
+
);
|
|
36
|
+
})}
|
|
37
|
+
</AbsoluteFill>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Composition } from "remotion";
|
|
3
|
+
import { AnalysisVideo } from "./compositions/AnalysisVideo";
|
|
4
|
+
import storyboardData from "./storyboard/scenes.json";
|
|
5
|
+
|
|
6
|
+
// 支持直接的数组或 { scenes: [] } 格式
|
|
7
|
+
const scenes = Array.isArray(storyboardData) ? storyboardData : storyboardData.scenes;
|
|
8
|
+
|
|
9
|
+
// 计算总时长
|
|
10
|
+
const totalDuration = scenes.reduce(
|
|
11
|
+
(sum, scene) => sum + (scene.audioDuration || 5),
|
|
12
|
+
0
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const RemotionRoot: React.FC = () => {
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<Composition
|
|
19
|
+
id="AnalysisVideo"
|
|
20
|
+
component={AnalysisVideo}
|
|
21
|
+
durationInFrames={totalDuration * 30}
|
|
22
|
+
fps={30}
|
|
23
|
+
width={1920}
|
|
24
|
+
height={1080}
|
|
25
|
+
/>
|
|
26
|
+
</>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// 场景类型
|
|
2
|
+
export type SceneType = 'title' | 'bar_chart' | 'line_chart' | 'insight' | 'summary';
|
|
3
|
+
|
|
4
|
+
// 视觉配置
|
|
5
|
+
export interface VisualConfig {
|
|
6
|
+
title: string; // 必须提供标题
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
chartType?: string;
|
|
9
|
+
highlights?: string[]; // InsightScene 必需
|
|
10
|
+
points?: string[]; // SummaryScene 必需
|
|
11
|
+
dataKey?: string; // 图表场景必需
|
|
12
|
+
source?: string; // 数据来源(可选)
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 分镜场景
|
|
17
|
+
export interface StoryboardScene {
|
|
18
|
+
id: string;
|
|
19
|
+
type: SceneType;
|
|
20
|
+
description: string;
|
|
21
|
+
narration: string;
|
|
22
|
+
visualConfig: VisualConfig;
|
|
23
|
+
audioPath?: string;
|
|
24
|
+
audioDuration?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 分镜配置
|
|
28
|
+
export interface Storyboard {
|
|
29
|
+
scenes: StoryboardScene[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 营收数据
|
|
33
|
+
export interface RevenueData {
|
|
34
|
+
fiscal_year: string;
|
|
35
|
+
total_revenue: number;
|
|
36
|
+
year_over_year_growth?: number;
|
|
37
|
+
net_income?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 业务板块数据
|
|
41
|
+
export interface BusinessSegmentData {
|
|
42
|
+
segment: string;
|
|
43
|
+
revenue: number;
|
|
44
|
+
percentage: number;
|
|
45
|
+
year?: string;
|
|
46
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2020", "DOM"],
|
|
6
|
+
"jsx": "react",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"isolatedModules": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*", "remotion.config.ts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"strict": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"types": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["scripts/**/*"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: byteplan-word
|
|
3
|
+
description: 根据已有的分析数据(analysisPlan.json + analysis_report.md)生成 Word 报告。需要先使用 byteplan-analysis skill 完成数据分析。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BytePlan Word 报告 Skill
|
|
7
|
+
|
|
8
|
+
## 概述
|
|
9
|
+
|
|
10
|
+
此 Skill 根据已有的分析数据生成 Word 报告:
|
|
11
|
+
|
|
12
|
+
1. **检查数据** - 确认 analysisPlan.json 和 analysis_report.md 存在
|
|
13
|
+
2. **组装数据** - 将分析数据转换为 Word 格式
|
|
14
|
+
3. **生成 Word** - 使用 docx 库创建 Word 文档
|
|
15
|
+
|
|
16
|
+
**前置条件**:必须先使用 [byteplan-analysis](../byteplan-analysis/SKILL.md) skill 完成数据分析。
|
|
17
|
+
|
|
18
|
+
## 技术栈
|
|
19
|
+
|
|
20
|
+
使用 Node.js + docx 库,与项目其他 skill 保持一致:
|
|
21
|
+
- `docx` - Word 文档生成库
|
|
22
|
+
|
|
23
|
+
## 核心流程
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
用户输入"生成 Word"
|
|
27
|
+
↓
|
|
28
|
+
检查分析数据是否存在
|
|
29
|
+
↓
|
|
30
|
+
读取 analysisPlan.json
|
|
31
|
+
↓
|
|
32
|
+
读取 analysis_report.md
|
|
33
|
+
↓
|
|
34
|
+
组装报告数据
|
|
35
|
+
↓
|
|
36
|
+
pnpm run generate
|
|
37
|
+
↓
|
|
38
|
+
完成
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 前置条件
|
|
42
|
+
|
|
43
|
+
### 安装依赖
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cd skills/byteplan-word
|
|
47
|
+
pnpm install
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 检查分析数据
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
import { existsSync, readFileSync } from 'fs';
|
|
54
|
+
|
|
55
|
+
// 检查必要文件
|
|
56
|
+
if (!existsSync('analysisPlan.json')) {
|
|
57
|
+
throw new Error('缺少分析计划文件 analysisPlan.json,请先使用 /byteplan-analysis');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!existsSync('analysis_report.md')) {
|
|
61
|
+
throw new Error('缺少分析报告文件 analysis_report.md,请先使用 /byteplan-analysis');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('✅ 分析数据检查通过');
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 工作流程
|
|
68
|
+
|
|
69
|
+
### 步骤 1:读取分析计划
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
const plan = JSON.parse(readFileSync('analysisPlan.json', 'utf-8'));
|
|
73
|
+
|
|
74
|
+
console.log(`计划名称: ${plan.planName}`);
|
|
75
|
+
console.log(`任务数量: ${plan.tasks.length}`);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 步骤 2:读取分析报告
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
const reportMd = readFileSync('analysis_report.md', 'utf-8');
|
|
82
|
+
|
|
83
|
+
// 解析 Markdown 中的表格数据
|
|
84
|
+
const parsedData = parseMarkdownReport(reportMd);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 步骤 3:组装 Word 数据
|
|
88
|
+
|
|
89
|
+
根据分析计划中的任务,组装 Word 支持的数据格式:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
const wordData = {
|
|
93
|
+
title: plan.planName,
|
|
94
|
+
subtitle: plan.planDescription,
|
|
95
|
+
period: new Date().toLocaleDateString('zh-CN'),
|
|
96
|
+
sections: plan.tasks.map(t => t.taskName),
|
|
97
|
+
summary: extractSummary(parsedData),
|
|
98
|
+
kpis: extractKPIs(parsedData),
|
|
99
|
+
tables: extractTables(parsedData),
|
|
100
|
+
insights: extractInsights(parsedData),
|
|
101
|
+
recommendations: extractRecommendations(parsedData),
|
|
102
|
+
source: 'BytePlan 数据平台'
|
|
103
|
+
};
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 步骤 4:生成 Word
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
cd skills/byteplan-word
|
|
110
|
+
pnpm run generate -- -o analysis_report.docx -d word_data.json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Word 数据格式
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"title": "报告标题",
|
|
118
|
+
"subtitle": "副标题",
|
|
119
|
+
"period": "2026年3月",
|
|
120
|
+
"sections": ["一、核心指标概览", "二、数据分布分析"],
|
|
121
|
+
"summary": ["摘要1", "摘要2"],
|
|
122
|
+
"kpis": [
|
|
123
|
+
{ "label": "指标名称", "value": "数值", "unit": "单位" }
|
|
124
|
+
],
|
|
125
|
+
"barChart": {
|
|
126
|
+
"title": "图表标题",
|
|
127
|
+
"data": [
|
|
128
|
+
{ "name": "类别A", "value": 100 },
|
|
129
|
+
{ "name": "类别B", "value": 80 }
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"ratioData": {
|
|
133
|
+
"title": "占比标题",
|
|
134
|
+
"data": [
|
|
135
|
+
{ "name": "类别A", "value": 35 },
|
|
136
|
+
{ "name": "类别B", "value": 28 }
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
"rankingData": {
|
|
140
|
+
"title": "排行标题",
|
|
141
|
+
"data": [
|
|
142
|
+
{ "name": "项目A", "value": 100 },
|
|
143
|
+
{ "name": "项目B", "value": 85 }
|
|
144
|
+
],
|
|
145
|
+
"maxItems": 10
|
|
146
|
+
},
|
|
147
|
+
"tableData": {
|
|
148
|
+
"title": "明细表",
|
|
149
|
+
"columns": [
|
|
150
|
+
{ "key": "字段1", "label": "显示名" }
|
|
151
|
+
],
|
|
152
|
+
"rows": [
|
|
153
|
+
{ "字段1": "值1", "字段2": "值2" }
|
|
154
|
+
],
|
|
155
|
+
"maxRows": 20
|
|
156
|
+
},
|
|
157
|
+
"insights": [
|
|
158
|
+
{ "title": "关键发现", "content": "内容" }
|
|
159
|
+
],
|
|
160
|
+
"recommendations": [
|
|
161
|
+
{ "title": "建议", "content": "内容" }
|
|
162
|
+
],
|
|
163
|
+
"appendix": {
|
|
164
|
+
"说明": "补充内容"
|
|
165
|
+
},
|
|
166
|
+
"source": "BytePlan 数据平台"
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Word 文档结构
|
|
171
|
+
|
|
172
|
+
| 章节 | 内容 | 说明 |
|
|
173
|
+
|-----|------|------|
|
|
174
|
+
| 1 | 封面 | 报告标题、副标题、数据周期 |
|
|
175
|
+
| 2 | 目录 | 报告结构概览 |
|
|
176
|
+
| 3 | 执行摘要 | 核心发现汇总 |
|
|
177
|
+
| 4 | 核心指标 | 关键数据表格 |
|
|
178
|
+
| 5 | 数据分布 | 柱状图数据 |
|
|
179
|
+
| 6 | 占比分析 | 进度条形式 |
|
|
180
|
+
| 7 | 排行分析 | 排名列表 |
|
|
181
|
+
| 8 | 数据明细 | 数据表格 |
|
|
182
|
+
| 9 | 洞察发现 | 关键洞察 |
|
|
183
|
+
| 10 | 总结建议 | 结论和建议 |
|
|
184
|
+
| 11 | 附录 | 补充数据(可选) |
|
|
185
|
+
| 12 | 结尾页 | 报告结束 |
|
|
186
|
+
|
|
187
|
+
## 完整示例
|
|
188
|
+
|
|
189
|
+
### 1. 检查数据文件
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
if (!existsSync('analysisPlan.json') || !existsSync('analysis_report.md')) {
|
|
193
|
+
console.log('请先使用 /byteplan-analysis 完成数据分析');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 2. 读取并组装数据
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
const plan = JSON.parse(readFileSync('analysisPlan.json', 'utf-8'));
|
|
202
|
+
const report = readFileSync('analysis_report.md', 'utf-8');
|
|
203
|
+
|
|
204
|
+
// 从 Markdown 中提取数据
|
|
205
|
+
const data = extractDataFromMarkdown(report);
|
|
206
|
+
|
|
207
|
+
// 组装 Word 数据
|
|
208
|
+
const wordData = {
|
|
209
|
+
title: plan.planName,
|
|
210
|
+
subtitle: `分析主题: ${plan.planDescription}`,
|
|
211
|
+
period: new Date().toLocaleDateString('zh-CN'),
|
|
212
|
+
kpis: extractKPIs(data),
|
|
213
|
+
tables: extractTables(data),
|
|
214
|
+
insights: extractInsights(data),
|
|
215
|
+
// ...
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
writeFileSync('word_data.json', JSON.stringify(wordData, null, 2));
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### 3. 生成 Word
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
cd skills/byteplan-word
|
|
225
|
+
pnpm run generate -- -o analysis_report.docx -d ../word_data.json
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## 注意事项
|
|
229
|
+
|
|
230
|
+
- **前置条件**:必须先使用 `/byteplan-analysis` 完成数据分析
|
|
231
|
+
- **数据文件**:确保 `analysisPlan.json` 和 `analysis_report.md` 存在
|
|
232
|
+
- **数据转换**:自动从 Markdown 中提取表格和关键数据
|
|
233
|
+
- **包管理器**:使用 pnpm 安装依赖
|