hanseol-dev 5.0.3-dev.9 → 5.0.4-dev.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/dist/agents/common/sub-agent.d.ts +2 -4
- package/dist/agents/common/sub-agent.d.ts.map +1 -1
- package/dist/agents/common/sub-agent.js +12 -11
- package/dist/agents/common/sub-agent.js.map +1 -1
- package/dist/agents/office/excel-agent.d.ts.map +1 -1
- package/dist/agents/office/excel-agent.js +1 -1
- package/dist/agents/office/excel-agent.js.map +1 -1
- package/dist/agents/office/excel-create-agent.d.ts.map +1 -1
- package/dist/agents/office/excel-create-agent.js +24 -4
- package/dist/agents/office/excel-create-agent.js.map +1 -1
- package/dist/agents/office/excel-create-prompts.d.ts +3 -3
- package/dist/agents/office/excel-create-prompts.d.ts.map +1 -1
- package/dist/agents/office/excel-create-prompts.js +83 -20
- package/dist/agents/office/excel-create-prompts.js.map +1 -1
- package/dist/agents/office/index.d.ts.map +1 -1
- package/dist/agents/office/index.js.map +1 -1
- package/dist/agents/office/powerpoint-agent.d.ts.map +1 -1
- package/dist/agents/office/powerpoint-agent.js +7 -1
- package/dist/agents/office/powerpoint-agent.js.map +1 -1
- package/dist/agents/office/powerpoint-create-agent.d.ts.map +1 -1
- package/dist/agents/office/powerpoint-create-agent.js +257 -281
- package/dist/agents/office/powerpoint-create-agent.js.map +1 -1
- package/dist/agents/office/powerpoint-create-prompts.d.ts +3 -2
- package/dist/agents/office/powerpoint-create-prompts.d.ts.map +1 -1
- package/dist/agents/office/powerpoint-create-prompts.js +284 -250
- package/dist/agents/office/powerpoint-create-prompts.js.map +1 -1
- package/dist/agents/office/prompts.d.ts +4 -4
- package/dist/agents/office/prompts.d.ts.map +1 -1
- package/dist/agents/office/prompts.js +2 -2
- package/dist/agents/office/word-agent.d.ts.map +1 -1
- package/dist/agents/office/word-agent.js +1 -1
- package/dist/agents/office/word-agent.js.map +1 -1
- package/dist/agents/office/word-create-agent.d.ts.map +1 -1
- package/dist/agents/office/word-create-agent.js +2 -4
- package/dist/agents/office/word-create-agent.js.map +1 -1
- package/dist/agents/office/word-create-prompts.d.ts +3 -3
- package/dist/agents/office/word-create-prompts.d.ts.map +1 -1
- package/dist/agents/office/word-create-prompts.js +167 -43
- package/dist/agents/office/word-create-prompts.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/prompts/agents/planning.d.ts.map +1 -1
- package/dist/prompts/agents/planning.js +7 -0
- package/dist/prompts/agents/planning.js.map +1 -1
- package/dist/tools/office/common/utils.d.ts +2 -0
- package/dist/tools/office/common/utils.d.ts.map +1 -1
- package/dist/tools/office/common/utils.js +4 -0
- package/dist/tools/office/common/utils.js.map +1 -1
- package/dist/tools/office/excel-client.d.ts +6 -0
- package/dist/tools/office/excel-client.d.ts.map +1 -1
- package/dist/tools/office/excel-client.js +77 -6
- package/dist/tools/office/excel-client.js.map +1 -1
- package/dist/tools/office/excel-tools/index.js +3 -3
- package/dist/tools/office/excel-tools/index.js.map +1 -1
- package/dist/tools/office/excel-tools/launch.d.ts.map +1 -1
- package/dist/tools/office/excel-tools/launch.js +3 -1
- package/dist/tools/office/excel-tools/launch.js.map +1 -1
- package/dist/tools/office/excel-tools/sheet-builders.d.ts +1 -0
- package/dist/tools/office/excel-tools/sheet-builders.d.ts.map +1 -1
- package/dist/tools/office/excel-tools/sheet-builders.js +79 -10
- package/dist/tools/office/excel-tools/sheet-builders.js.map +1 -1
- package/dist/tools/office/powerpoint-client.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-client.js +21 -18
- package/dist/tools/office/powerpoint-client.js.map +1 -1
- package/dist/tools/office/powerpoint-tools/effects.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-tools/effects.js.map +1 -1
- package/dist/tools/office/powerpoint-tools/launch.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-tools/launch.js +3 -1
- package/dist/tools/office/powerpoint-tools/launch.js.map +1 -1
- package/dist/tools/office/powerpoint-tools/media.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-tools/media.js.map +1 -1
- package/dist/tools/office/powerpoint-tools/notes.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-tools/notes.js.map +1 -1
- package/dist/tools/office/powerpoint-tools/sections.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-tools/sections.js.map +1 -1
- package/dist/tools/office/powerpoint-tools/shapes.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-tools/shapes.js.map +1 -1
- package/dist/tools/office/powerpoint-tools/slides.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-tools/slides.js.map +1 -1
- package/dist/tools/office/powerpoint-tools/tables.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-tools/tables.js.map +1 -1
- package/dist/tools/office/powerpoint-tools/text.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-tools/text.js.map +1 -1
- package/dist/tools/office/word-client.d.ts +16 -0
- package/dist/tools/office/word-client.d.ts.map +1 -1
- package/dist/tools/office/word-client.js +295 -39
- package/dist/tools/office/word-client.js.map +1 -1
- package/dist/tools/office/word-tools/launch.d.ts.map +1 -1
- package/dist/tools/office/word-tools/launch.js +3 -1
- package/dist/tools/office/word-tools/launch.js.map +1 -1
- package/dist/tools/office/word-tools/section-builders.d.ts +2 -0
- package/dist/tools/office/word-tools/section-builders.d.ts.map +1 -1
- package/dist/tools/office/word-tools/section-builders.js +230 -50
- package/dist/tools/office/word-tools/section-builders.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const PPT_DESIGN_PROMPT = `You are an elite presentation design consultant AND structure planner. Output ONLY valid JSON — no markdown, no explanation, no code fences.
|
|
2
2
|
|
|
3
|
-
Given the user's instruction, produce a JSON object
|
|
3
|
+
Given the user's instruction, produce a JSON object with a visual design system and a detailed slide plan.
|
|
4
4
|
|
|
5
5
|
{
|
|
6
6
|
"design": {
|
|
@@ -22,170 +22,87 @@ Given the user's instruction, produce a JSON object that combines a cohesive vis
|
|
|
22
22
|
]
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
═══ DESIGN SYSTEM
|
|
26
|
-
•
|
|
27
|
-
•
|
|
28
|
-
•
|
|
29
|
-
•
|
|
30
|
-
•
|
|
31
|
-
•
|
|
32
|
-
•
|
|
33
|
-
•
|
|
34
|
-
• design.mood: One of modern-minimal, bold-energetic, corporate-elegant, warm-friendly, academic-clean
|
|
35
|
-
• design.design_notes: 1-2 sentence visual approach description
|
|
36
|
-
|
|
37
|
-
• slides[].type: One of "title", "content", "closing"
|
|
38
|
-
• slides[].title: Slide title in user's language (max 60 chars)
|
|
39
|
-
• slides[].content_direction: The ACTUAL TEXT AND DATA to display on this slide (6-10 sentences).
|
|
25
|
+
═══ DESIGN SYSTEM ═══
|
|
26
|
+
• primary_color: Deep main color for headers and key elements
|
|
27
|
+
• accent_color: Vibrant contrast color for highlights and CTAs
|
|
28
|
+
• background_color: Page background (near white, near black, or subtle tint)
|
|
29
|
+
• text_color: Main text color (must contrast with background)
|
|
30
|
+
• accent_light: Light tint for subtle section backgrounds
|
|
31
|
+
• gradient_end: Paired gradient color for primary
|
|
32
|
+
• font_title / font_body: System fonts only (Segoe UI, Arial, Georgia, Calibri, Malgun Gothic)
|
|
33
|
+
• mood: One of modern-minimal, bold-energetic, corporate-elegant, warm-friendly, academic-clean
|
|
40
34
|
|
|
41
35
|
═══ COLOR PALETTE — CREATIVE PSYCHOLOGY ═══
|
|
42
|
-
⚠
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
• Finance/Investment: navy + gold (#1A237E, #F4A300) — authority, wealth, stability
|
|
47
|
-
• Education/Training: teal + orange (#00796B, #FF7043) — growth, warmth, curiosity
|
|
48
|
-
• Marketing/Creative: coral + purple (#FF6B6B, #7C3AED) — boldness, creativity, passion
|
|
49
|
-
• Environment/Sustainability: deep green + amber (#1B5E20, #FFA000) — nature, urgency
|
|
50
|
-
• Government/Policy: dark blue + red (#1A237E, #C62828) — authority, importance
|
|
36
|
+
⚠ EVERY presentation must have a UNIQUE palette matching the topic's emotional tone.
|
|
37
|
+
Think about color psychology: trust (blues), growth (greens), urgency (reds/oranges),
|
|
38
|
+
innovation (purples/cyans), warmth (corals/browns), elegance (charcoals/rose), wealth (navy/gold).
|
|
39
|
+
Pick DEEP saturated colors for primary, VIBRANT contrasting for accent.
|
|
51
40
|
⚠ Choose colors that MATCH the specific topic. Generic blue = LAZY.
|
|
52
41
|
⚠ Ensure accent_color has HIGH contrast with primary_color.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
• modern-minimal: Clean lines, subtle shadows, generous whitespace, thin borders, smooth radius (12-16px)
|
|
57
|
-
• bold-energetic: Strong gradients, deep shadows, thick accent bars, high contrast, punchy colors
|
|
58
|
-
• corporate-elegant: Refined spacing, subtle gradients, structured grids, sophisticated feel
|
|
59
|
-
• warm-friendly: Rounded corners (20px+), soft shadows, inviting spacing, gentle transitions
|
|
60
|
-
• academic-clean: Tight grids, minimal decoration, clear hierarchy, data-first design
|
|
61
|
-
|
|
62
|
-
═══ SLIDE STRUCTURE RULES ═══
|
|
63
|
-
⚠ First slide MUST be type "title"
|
|
64
|
-
⚠ Last slide MUST be type "closing" with "감사합니다"/"Thank You" as title
|
|
42
|
+
|
|
43
|
+
═══ SLIDE STRUCTURE ═══
|
|
44
|
+
⚠ First slide MUST be type "title". Last slide MUST be type "closing".
|
|
65
45
|
⚠ Minimum 10, maximum 13 slides total. Aim for 10-12.
|
|
66
|
-
⚠
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
•
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
⚠
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
BAD content_direction (MULTIPLE visuals = OVERFLOW):
|
|
113
|
-
✗ "테이블로 수익 모델 + 수익 그래프 + 파트너십 + 라이선싱" ← 4 sections!
|
|
114
|
-
✗ "원형 차트 + 투자 조건 테이블 + 단계적 계획 + 바 차트" ← 4 sections!
|
|
115
|
-
|
|
116
|
-
═══ SPLITTING DENSE TOPICS INTO MULTIPLE SLIDES ═══
|
|
117
|
-
⚠ If a user topic has 4+ sub-items, you MUST split it into 2 slides:
|
|
118
|
-
• "비즈니스모델" → "수익 모델" (pricing table) + "수익 성장 전망" (bar chart)
|
|
119
|
-
• "시장분석" → "시장 규모" (3 big metrics) + "고객 세분화" (pie chart or cards)
|
|
120
|
-
• "투자조건" → "투자 조건" (table) + "자금 사용 계획" (pie or bar chart)
|
|
121
|
-
• "경쟁우위" → "경쟁사 비교" (comparison table) + "핵심 차별화" (3 card grid)
|
|
122
|
-
• "경쟁분석" → "경쟁사 비교" (comparison table ONLY) + "SWOT 분석" (2×2 grid ONLY)
|
|
123
|
-
• "AI 솔루션" → "핵심 기능" (3 card grid) + "진단 프로세스" (process flow)
|
|
124
|
-
• "제품/플랫폼" → "핵심 기능" (3 card grid with feature descriptions)
|
|
125
|
-
• "마케팅/GTM 전략" → "마케팅 채널 전략" (3 cards) + "성과 지표" (metrics or table)
|
|
126
|
-
• "로드맵" → "단기 로드맵" (3 milestone cards) + "장기 비전" (3 milestone cards)
|
|
127
|
-
• "팀 소개" (4+ members) → "창업진 소개" (2-3 people) + "핵심 팀원" (2-3 people)
|
|
128
|
-
|
|
129
|
-
═══ CONTENT DENSITY — FILL THE SLIDE ═══
|
|
130
|
-
⚠⚠⚠ Each slide is 1920×1080px. Content MUST fill 85-95% of vertical space. EMPTY slides = FAILURE.
|
|
131
|
-
⚠ content_direction MUST contain 6-10 sentences of ACTUAL DATA and details.
|
|
132
|
-
⚠ Each item: title + 3 SHORT bullets (max 25 chars each) with specific numbers.
|
|
133
|
-
⚠ Tables: 3-4 data rows × 3-4 columns. Fill EVERY cell with real data. Column headers MUST be descriptive — NEVER generic "Column 1".
|
|
134
|
-
⚠ Timeline/Roadmap: 3 milestones (MAX 3) with dates, descriptions, and key metrics.
|
|
135
|
-
⚠ Process/Flow: 3-4 steps (MAX 4) — each step with short title + 1-sentence description.
|
|
136
|
-
⚠ Data/metrics: 3 big numbers with trend indicators AND 1-sentence context.
|
|
137
|
-
⚠ Cards: 3-4 cards, each with title + 3 bullets (MAX 3, keep short) + stat metric.
|
|
138
|
-
⚠ Donut/Pie charts: 3-4 segments with labels + summary.
|
|
139
|
-
⚠ The more SPECIFIC DATA you include in content_direction, the better the slide will look.
|
|
140
|
-
|
|
141
|
-
═══ content_direction QUALITY RULES ═══
|
|
142
|
-
⚠ content_direction = REAL CONTENT the viewer will read. NOT layout instructions.
|
|
143
|
-
Include: specific numbers, names, descriptions, bullet point text, table data.
|
|
144
|
-
MUST be DETAILED — 6-10 sentences with specific data. Sparse directions produce empty slides.
|
|
145
|
-
Each item/card/row MUST include: title + 3-4 supporting details with specific data.
|
|
146
|
-
Optionally end with ONE short layout hint (e.g., "Layout: 3-column cards" or "Layout: comparison table").
|
|
147
|
-
|
|
148
|
-
✓ GOOD: "총매출 1,250억원(전년비 15%↑), 영업이익 180억원(14.4%), 순이익 95억원(7.6%). 사업부별: 클라우드 45%(562억), AI솔루션 30%(375억), 컨설팅 25%(312억). 클라우드가 전년비 32% 성장하며 최대 성장 동력. Layout: bar chart"
|
|
149
|
-
✓ GOOD: "3 core services: (1) 클라우드 인프라 — 하이브리드 클라우드 구축, AWS/Azure 호환, 99.9% SLA 보장, 50+ 기업 고객 (2) AI 솔루션 — 자연어처리/영상분석, 진단 정확도 98.2%, 월 100만건 처리 (3) 컨설팅 — 디지털 전환 자문, 평균 ROI 250%, 12개월 효과. Layout: 3-column cards"
|
|
150
|
-
✗ BAD: "클라우드, AI, 컨설팅 3개 서비스" ← Too sparse!
|
|
151
|
-
✗ BAD: "왼쪽에 개요 텍스트, 오른쪽에 카드" ← Layout instruction, NOT content!
|
|
152
|
-
✗ BAD: "#accent_light 배경, #primary 글씨" ← CSS instruction, NOT content!
|
|
46
|
+
⚠ Current year: \${new Date().getFullYear()}.
|
|
47
|
+
|
|
48
|
+
═══ TITLE & CLOSING FORMAT ═══
|
|
49
|
+
⚠ Title slide:
|
|
50
|
+
• title: Company/topic name ONLY — max 3-4 words (rendered at 96px)
|
|
51
|
+
✓ "Acme Corp" ✓ "프로젝트 알파" ✗ "Acme Corp - 2025 혁신 전략 보고서" (too long)
|
|
52
|
+
• content_direction: The actual subtitle/tagline TEXT (1-2 lines, under 120 chars)
|
|
53
|
+
⚠ Closing slide:
|
|
54
|
+
• title: "감사합니다" (Korean) / "Thank You" (English)
|
|
55
|
+
• content_direction: Company/topic name
|
|
56
|
+
|
|
57
|
+
═══ SLIDE STRUCTURE STRATEGY ═══
|
|
58
|
+
⚠ Do NOT follow a fixed template. Instead, analyze the user's topic and determine the most logical slide flow yourself.
|
|
59
|
+
⚠ Think about what information the audience NEEDS and in what ORDER.
|
|
60
|
+
⚠ General principles:
|
|
61
|
+
- Start with context/background before diving into details
|
|
62
|
+
- Build a narrative arc: setup → evidence → insight → action
|
|
63
|
+
- Each slide should have a DISTINCT purpose — no redundant slides
|
|
64
|
+
- End with actionable conclusions or key takeaways before closing
|
|
65
|
+
⚠ The slide structure should feel CUSTOM-TAILORED to the specific topic, not a generic template.
|
|
66
|
+
|
|
67
|
+
═══ content_direction = THE #1 PRIORITY ═══
|
|
68
|
+
⚠⚠⚠ content_direction is the REAL DATA AND TEXT that will appear on the slide.
|
|
69
|
+
⚠ Each content_direction MUST be 6-10 sentences of SPECIFIC DATA:
|
|
70
|
+
- Include: numbers, percentages, names, descriptions, dates, comparisons
|
|
71
|
+
- The MORE specific data you provide, the better the slide will look
|
|
72
|
+
- Each item/section: title + 3-4 supporting details with real numbers
|
|
73
|
+
⚠ content_direction MUST describe ONE focused topic per slide.
|
|
74
|
+
If a topic has 4+ sub-items, SPLIT into 2 slides.
|
|
75
|
+
⚠ NEVER include layout/CSS instructions in content_direction. Just the DATA.
|
|
76
|
+
✓ GOOD: "2024년 국내 시장 규모 4.8조 원. 전년 대비 23% 성장. 주요 성장 동력: 신규 고객 유입 35% 증가, 기존 고객 유지율 89%, 프리미엄 세그먼트 매출 비중 41%. 2027년 시장 전망 7.2조 원."
|
|
77
|
+
✓ GOOD: "핵심 제품 A: 월간 구독형 서비스. 기본 플랜 월 29만원, 프로 플랜 월 59만원, 엔터프라이즈 맞춤 견적. 주요 기능: 실시간 분석 대시보드, 자동 보고서 생성, API 연동. 도입 기업 120개사, 평균 고객 만족도 94.2%."
|
|
78
|
+
✗ BAD: "3개 서비스 소개. Layout: cards" ← Too sparse, has layout hint!
|
|
79
|
+
✗ BAD: "왼쪽에 텍스트, 오른쪽에 차트" ← Layout instruction!
|
|
80
|
+
|
|
81
|
+
═══ SLIDE CONTENT VARIETY ═══
|
|
82
|
+
⚠ Each slide's content should naturally suggest a DIFFERENT visual treatment.
|
|
83
|
+
Vary what you write about across slides:
|
|
84
|
+
- Some slides: key metrics/numbers (naturally displayed as large metric spotlights)
|
|
85
|
+
- Some slides: comparison data (naturally shown as tables or side-by-side)
|
|
86
|
+
- Some slides: step-by-step processes (naturally shown as flows)
|
|
87
|
+
- Some slides: category breakdowns (naturally shown as charts or card grids)
|
|
88
|
+
- Some slides: timeline/roadmap items (naturally shown as milestone sequences)
|
|
89
|
+
- Some slides: detailed feature descriptions (naturally shown as rich cards)
|
|
90
|
+
⚠ Don't make every slide a list of items. Mix data-heavy slides with narrative slides.
|
|
91
|
+
⚠ AVOID having 3+ consecutive slides with the same content structure (e.g., all lists of 3-4 items).
|
|
153
92
|
|
|
154
93
|
═══ OVERVIEW / AGENDA SLIDES ═══
|
|
155
|
-
⚠
|
|
156
|
-
|
|
157
|
-
If
|
|
158
|
-
|
|
159
|
-
═══ LAYOUT VARIETY — MANDATORY ═══
|
|
160
|
-
⚠⚠⚠ You MUST vary the visual approach. No more than 2 slides with the same layout hint.
|
|
161
|
-
Available layouts (use at least 5 different types across the presentation):
|
|
162
|
-
- "2×2 grid": 4 cards in 2 rows × 2 columns (MAX 2 slides)
|
|
163
|
-
- "bar chart": CSS vertical/horizontal bars showing data trends
|
|
164
|
-
- "donut chart": conic-gradient pie/donut with metric labels
|
|
165
|
-
- "comparison table": styled table with 3-4 rows, colored headers
|
|
166
|
-
- "process flow": horizontal step boxes connected by arrows (→)
|
|
167
|
-
- "big numbers": 2-3 large metric spotlights (72-96px) with trend indicators
|
|
168
|
-
- "2-column split": left panel + right content
|
|
169
|
-
- "timeline": 3-4 milestone steps horizontal with dates
|
|
170
|
-
- "progress bars": horizontal bars showing completion percentages
|
|
171
|
-
- "hero stat": one large central metric with supporting context
|
|
172
|
-
⚠ Each content_direction MUST end with a "Layout:" hint specifying the visual type.
|
|
173
|
-
⚠ If your plan has 3+ slides all using "cards/grid" layout, REWRITE to use charts, tables, flows instead.
|
|
174
|
-
⚠ A 10-slide deck MUST have: at least 2 chart/data-viz slides, at least 1 table slide, at least 1 process/flow slide.
|
|
175
|
-
⚠ NEVER plan a presentation where every slide is cards — that is LAZY and BORING.
|
|
94
|
+
⚠ AVOID overview/agenda/TOC slides — they waste space and add no real content.
|
|
95
|
+
Instead, jump straight into substantive content after the title slide.
|
|
96
|
+
If absolutely needed: MAXIMUM 5 items with short titles only.
|
|
176
97
|
|
|
177
98
|
═══ HARD RULES ═══
|
|
178
|
-
⚠ First slide MUST be type "title"
|
|
179
|
-
⚠ Last slide MUST be type "closing" with "감사합니다"/"Thank You" as title
|
|
180
99
|
⚠ ALL titles and content_direction MUST be in the SAME language as the user's instruction
|
|
181
|
-
⚠ content_direction
|
|
182
|
-
⚠
|
|
183
|
-
⚠
|
|
184
|
-
⚠
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
Output ONLY the JSON object. No preamble, no markdown fences, no explanation.`;
|
|
100
|
+
⚠ content_direction with NO real data = FAILURE
|
|
101
|
+
⚠ HARD MAXIMUM: 13 slides. Slides beyond 13 are DISCARDED.
|
|
102
|
+
⚠ NEVER use "스크린샷", "screenshot", "이미지", "사진", "placeholder" in content_direction
|
|
103
|
+
⚠ Do NOT create a separate "연락처" slide — closing handles this.
|
|
104
|
+
|
|
105
|
+
Output ONLY the JSON object.`;
|
|
189
106
|
export function extractLayoutHint(contentDirection) {
|
|
190
107
|
const match = contentDirection.match(/Layout:\s*(.+?)$/im);
|
|
191
108
|
if (!match)
|
|
@@ -324,8 +241,8 @@ ${layoutCss}
|
|
|
324
241
|
• Complete HTML: <!DOCTYPE html> through </html>. ALL styling in <style>.
|
|
325
242
|
• NO <img>, NO external resources, NO JavaScript, NO external fonts.
|
|
326
243
|
• System fonts ONLY. CSS for visuals: gradients, shapes, shadows, borders.
|
|
327
|
-
• Title:
|
|
328
|
-
• Content fills 85-95% of 1080px height. Empty space = FAILURE.
|
|
244
|
+
• Title: 42-48px bold. Body: 26-32px. MINIMUM any text: 24px. If text needs to shrink below 24px, remove content instead.
|
|
245
|
+
• Content fills 85-95% of 1080px height. Empty space = FAILURE. MAX ~1000 chars of visible text.
|
|
329
246
|
• NEVER use justify-content:center on .content (creates dead space). Use stretch/space-evenly.
|
|
330
247
|
• Use gradients, box-shadow (0 4px 20px rgba(0,0,0,0.06)), border-radius (12-20px).
|
|
331
248
|
• Generate REAL professional content. No placeholders. Specific numbers and data.
|
|
@@ -334,28 +251,120 @@ ${layoutCss}
|
|
|
334
251
|
|
|
335
252
|
Output the complete HTML now.`;
|
|
336
253
|
}
|
|
337
|
-
export function
|
|
254
|
+
export function buildFreeHtmlPrompt(title, contentDirection, design, slideIndex, totalSlides, language) {
|
|
255
|
+
const langRule = language === 'ko'
|
|
256
|
+
? 'ALL visible text MUST be in Korean. Write naturally in Korean. Never use Chinese characters (漢字).'
|
|
257
|
+
: 'ALL visible text MUST be in English.';
|
|
258
|
+
const variants = [
|
|
259
|
+
`Background: ${design.background_color}. Elements use white background with box-shadow.`,
|
|
260
|
+
`Top accent bar (height:4px, linear-gradient(90deg, ${design.accent_color}, ${design.primary_color})). Background: ${design.background_color}.`,
|
|
261
|
+
`Subtle gradient background: linear-gradient(150deg, ${design.background_color} 0%, ${design.accent_light}40 100%). Stronger shadows.`,
|
|
262
|
+
];
|
|
263
|
+
const styleGuide = variants[slideIndex % variants.length];
|
|
264
|
+
return `You are a world-class presentation designer. Create ONE slide as a complete HTML page.
|
|
265
|
+
Output ONLY the complete HTML (<!DOCTYPE html> to </html>). No explanation, no markdown fences.
|
|
266
|
+
|
|
267
|
+
═══ SLIDE ${slideIndex + 1} OF ${totalSlides} ═══
|
|
268
|
+
Title: "${title}"
|
|
269
|
+
|
|
270
|
+
═══ CONTENT TO VISUALIZE ═══
|
|
271
|
+
${contentDirection}
|
|
272
|
+
|
|
273
|
+
═══ DESIGN SYSTEM ═══
|
|
274
|
+
Primary: ${design.primary_color} | Accent: ${design.accent_color} | BG: ${design.background_color}
|
|
275
|
+
Text: ${design.text_color} | Light: ${design.accent_light} | Gradient: ${design.gradient_end}
|
|
276
|
+
Title Font: ${design.font_title} | Body Font: ${design.font_body} | Mood: ${design.mood}
|
|
277
|
+
|
|
278
|
+
═══ STYLE FOR THIS SLIDE ═══
|
|
279
|
+
${styleGuide}
|
|
280
|
+
|
|
281
|
+
═══ YOUR CREATIVE MISSION ═══
|
|
282
|
+
Design the BEST possible visual layout for THIS specific content.
|
|
283
|
+
Think like a professional designer: what visual structure best communicates this information?
|
|
284
|
+
|
|
285
|
+
You have FULL CREATIVE FREEDOM. Choose the most appropriate visual approach:
|
|
286
|
+
• Card grids (2×2, 1×3, 1×4) with icons, stats, bullet details
|
|
287
|
+
• Styled data tables (<table>) with colored headers and alternating rows
|
|
288
|
+
• CSS bar charts: vertical bars using flex-end alignment + height percentages
|
|
289
|
+
• Donut/pie charts: conic-gradient on border-radius:50% elements
|
|
290
|
+
• Horizontal progress bars with labeled tracks and percentage fills
|
|
291
|
+
• Step-by-step process flows with numbered circles + arrow (→) connectors
|
|
292
|
+
• Timeline layouts with dated milestone cards in a horizontal row
|
|
293
|
+
• Dashboard panels: 2-3 large metric spotlights (80-100px numbers)
|
|
294
|
+
• 2-column split: left summary/data + right detailed content or bullets
|
|
295
|
+
• Feature showcases with emoji/icon badges
|
|
296
|
+
• Itinerary/schedule: day-by-day breakdown with locations and activities
|
|
297
|
+
• Ranking/leaderboard: ordered items with visual indicators
|
|
298
|
+
• Comparison matrices: side-by-side analysis with visual scoring
|
|
299
|
+
• SWOT or quadrant grids for strategic analysis
|
|
300
|
+
• Package/pricing comparison: side-by-side product cards with highlights
|
|
301
|
+
• Any OTHER CSS-only visual that serves the content perfectly
|
|
302
|
+
|
|
303
|
+
⚠ Choose the layout that BEST fits THIS content. Match layout to content type: schedules for timelines, charts for financial data, comparison matrices for competitive analysis, card grids for feature showcases. Be creative and appropriate.
|
|
304
|
+
|
|
305
|
+
═══ MANDATORY CSS BOILERPLATE (copy into <style>) ═══
|
|
306
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
307
|
+
html, body { width:1920px; height:1080px; overflow:hidden; font-family:"${design.font_body}","${design.font_title}","Segoe UI","Malgun Gothic",Arial,sans-serif; word-break:keep-all; overflow-wrap:break-word; }
|
|
308
|
+
body { display:flex; flex-direction:column; padding:60px 80px; height:1080px; background:${design.background_color}; color:${design.text_color}; font-size:26px; }
|
|
309
|
+
|
|
310
|
+
═══ HTML STRUCTURE ═══
|
|
311
|
+
body has EXACTLY 2 direct children:
|
|
312
|
+
1. .slide-title (flex:0 0 auto) — h1 with title + accent bar
|
|
313
|
+
.slide-title h1 { font-size:48px; font-weight:700; color:${design.primary_color}; font-family:"${design.font_title}","Segoe UI",sans-serif; }
|
|
314
|
+
Below h1: <div style="width:80px;height:3px;background:${design.accent_color};border-radius:2px;margin-top:12px"></div>
|
|
315
|
+
2. .content (flex:1) — stretches to fill ALL remaining vertical space
|
|
316
|
+
⚠ .content class name is REQUIRED.
|
|
317
|
+
⚠ ALL layout elements MUST be DIRECT children of .content — no wrapper divs.
|
|
318
|
+
|
|
319
|
+
═══ DESIGN RULES ═══
|
|
320
|
+
• ${langRule}
|
|
321
|
+
• Complete HTML: <!DOCTYPE html> through </html>. ALL styling in <style>.
|
|
322
|
+
• NO <img>, NO external resources, NO JavaScript, NO external fonts.
|
|
323
|
+
• System fonts ONLY. CSS for visuals: gradients, shapes, shadows, borders.
|
|
324
|
+
• Title: 42-48px bold. Body: 28-32px. MINIMUM any visible text: 24px (except page numbers at 12px).
|
|
325
|
+
• ⚠ Korean text is WIDER and DENSER than English — use FEWER items with LARGER fonts. Each card/element: MAX 2-3 short lines.
|
|
326
|
+
• ⚠ If you need to shrink text below 24px, you have TOO MUCH content. Remove sections or shorten text instead.
|
|
327
|
+
• ⚠ ABSOLUTELY NO OVERFLOW — content MUST fit within 1080px total height. It is BETTER to have 20% empty space than 1px of clipping. Limit yourself to 3-4 major content elements max.
|
|
328
|
+
• The available height for .content is approximately 900px (1080 - 60px top padding - 60px bottom padding - ~60px title). Design within this constraint.
|
|
329
|
+
• ⚠ MAXIMUM visible text: ~800 characters. More than this WILL cause overflow. Be concise — short labels, brief bullet points (max 8-10 words each).
|
|
330
|
+
• NEVER use position:absolute for layout (ok for page numbers). Use flexbox/grid.
|
|
331
|
+
• NEVER use justify-content:center on .content — it creates dead space. Use stretch/space-evenly.
|
|
332
|
+
• Use gradients, box-shadow (0 4px 20px rgba(0,0,0,0.06)), border-radius (12-20px).
|
|
333
|
+
• Cards/elements: white (#fff) background with subtle shadow on light slides.
|
|
334
|
+
• Table headers: dark background (${design.primary_color}) with white text.
|
|
335
|
+
• Generate REAL professional content from the direction. No placeholders.
|
|
336
|
+
• ⚠ NEVER use bracket placeholders like [Team Name], [Email], [YYYY], [Author]. Instead, INVENT realistic fictional content for ALL fields — names, emails, dates, numbers, etc.
|
|
337
|
+
• If user specified a year, USE THAT YEAR. Default to ${new Date().getFullYear()} only when no year given.
|
|
338
|
+
• Page number: bottom-right "${slideIndex + 1}" (12px, opacity 0.4, position:absolute ok for this).
|
|
339
|
+
|
|
340
|
+
Output the complete HTML now.`;
|
|
341
|
+
}
|
|
342
|
+
export function validateSlideHtml(html, _layoutType) {
|
|
338
343
|
if (!html.includes('<!DOCTYPE') && !html.includes('<!doctype')) {
|
|
339
344
|
return { pass: false, feedback: 'Missing <!DOCTYPE html> declaration. Start with <!DOCTYPE html><html>.' };
|
|
340
345
|
}
|
|
341
346
|
if (!html.includes('<html') || !html.includes('</html>')) {
|
|
342
347
|
return { pass: false, feedback: 'Missing <html> or </html> tags. Output must be a complete HTML document.' };
|
|
343
348
|
}
|
|
344
|
-
const layoutFeedback = checkLayoutCompliance(html, layoutType);
|
|
345
|
-
if (layoutFeedback) {
|
|
346
|
-
return { pass: false, feedback: layoutFeedback };
|
|
347
|
-
}
|
|
348
349
|
if (/<img\s/i.test(html)) {
|
|
349
350
|
return { pass: false, feedback: 'Forbidden: <img> tags are not allowed. Use CSS gradients, shapes, and backgrounds instead.' };
|
|
350
351
|
}
|
|
352
|
+
if (/<script[\s>]/i.test(html)) {
|
|
353
|
+
return { pass: false, feedback: 'Forbidden: <script> tags are not allowed. This is a static slide — no JavaScript.' };
|
|
354
|
+
}
|
|
351
355
|
const absCount = (html.match(/position\s*:\s*absolute/gi) || []).length;
|
|
352
356
|
if (absCount > 6) {
|
|
353
|
-
return { pass: false, feedback: `Too many position:absolute (${absCount}). Use flexbox/grid for main layout
|
|
357
|
+
return { pass: false, feedback: `Too many position:absolute (${absCount}). Use flexbox/grid for main layout.` };
|
|
358
|
+
}
|
|
359
|
+
const externalUrls = html.match(/url\(\s*['"]?https?:\/\//gi) || [];
|
|
360
|
+
if (externalUrls.length > 0) {
|
|
361
|
+
return { pass: false, feedback: 'Forbidden: External URLs detected in CSS url(). No external resources allowed.' };
|
|
354
362
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
363
|
+
const scaleMatches = html.match(/transform\s*:[^;]*scale\(\s*([\d.]+)/gi) || [];
|
|
364
|
+
for (const m of scaleMatches) {
|
|
365
|
+
const val = parseFloat(m.replace(/.*scale\(\s*/i, ''));
|
|
366
|
+
if (val > 0 && val < 0.9) {
|
|
367
|
+
return { pass: false, feedback: `Forbidden: transform:scale(${val}) shrinks content. Use full 1920×1080 layout without scaling.` };
|
|
359
368
|
}
|
|
360
369
|
}
|
|
361
370
|
const placeholderPatterns = [
|
|
@@ -365,33 +374,40 @@ export function validateSlideHtml(html, layoutType) {
|
|
|
365
374
|
/Display value \(e\.g\./i,
|
|
366
375
|
/1-2 sentence key insight/i,
|
|
367
376
|
/Category name/i,
|
|
368
|
-
/Another detail/i,
|
|
369
|
-
/Third point/i,
|
|
370
|
-
/Fourth point/i,
|
|
371
|
-
/Brief context/i,
|
|
372
377
|
/Segment name/i,
|
|
378
|
+
/Lorem ipsum/i,
|
|
379
|
+
/\[placeholder\]/i,
|
|
380
|
+
/\[내용\]/i,
|
|
381
|
+
/\[.{2,20}을\s*입력/i,
|
|
382
|
+
/\[YYYY/i,
|
|
383
|
+
/\[이메일/i,
|
|
384
|
+
/\[작성자/i,
|
|
385
|
+
/\[직급/i,
|
|
386
|
+
/\[NNNN\]/i,
|
|
387
|
+
/\[MM월/i,
|
|
388
|
+
/\[담당자\s*명\]/i,
|
|
373
389
|
];
|
|
374
390
|
for (const pattern of placeholderPatterns) {
|
|
375
391
|
if (pattern.test(html)) {
|
|
376
|
-
return { pass: false, feedback: `Placeholder text detected: "${pattern.source}". Generate REAL content
|
|
392
|
+
return { pass: false, feedback: `Placeholder text detected: "${pattern.source}". Generate REAL content.` };
|
|
377
393
|
}
|
|
378
394
|
}
|
|
379
395
|
const textElements = (html.match(/<(p|li|td|th|span|div|h[1-6])[^>]*>[^<]{2,}/gi) || []).length;
|
|
380
396
|
if (textElements < 5) {
|
|
381
|
-
return { pass: false, feedback: `Low content density: only ${textElements} text elements
|
|
397
|
+
return { pass: false, feedback: `Low content density: only ${textElements} text elements. Need at least 5.` };
|
|
382
398
|
}
|
|
383
399
|
const visibleText = html.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
384
400
|
.replace(/<[^>]+>/g, ' ')
|
|
385
401
|
.replace(/\s+/g, ' ')
|
|
386
402
|
.trim();
|
|
387
|
-
if (visibleText.length >
|
|
388
|
-
return { pass: false, feedback: `Content overflow risk: ${visibleText.length} chars of visible text. Reduce to under
|
|
403
|
+
if (visibleText.length > 1200) {
|
|
404
|
+
return { pass: false, feedback: `Content overflow risk: ${visibleText.length} chars of visible text. Reduce to under 1200 chars. Remove 1-2 sections or shorten text to prevent bottom clipping.` };
|
|
389
405
|
}
|
|
390
406
|
const smallFonts = html.match(/font-size\s*:\s*(\d+)px/gi) || [];
|
|
391
407
|
for (const match of smallFonts) {
|
|
392
408
|
const size = parseInt(match.replace(/[^0-9]/g, ''), 10);
|
|
393
|
-
if (size > 0 && size <
|
|
394
|
-
return { pass: false, feedback: `Font too small: found font-size:${size}px. Minimum allowed is
|
|
409
|
+
if (size > 0 && size < 24 && size > 13) {
|
|
410
|
+
return { pass: false, feedback: `Font too small: found font-size:${size}px. Minimum allowed is 24px (except 12-13px for page numbers). Increase font size or reduce content.` };
|
|
395
411
|
}
|
|
396
412
|
}
|
|
397
413
|
return { pass: true, feedback: '' };
|
|
@@ -481,7 +497,7 @@ MANDATORY STRUCTURE (body has EXACTLY 2 direct children):
|
|
|
481
497
|
${getLayoutSpecificCss(layoutType, design)}
|
|
482
498
|
|
|
483
499
|
═══ DESIGN RULES ═══
|
|
484
|
-
1. Title:
|
|
500
|
+
1. Title: 42-48px bold. Body: 26-32px. MINIMUM any visible text: 24px. NEVER use font-size below 24px. If content doesn't fit at 24px, REDUCE ITEM COUNT instead of shrinking text. Card descriptions: 26-30px. Card titles: 32-40px. Labels/captions: 24px minimum. body { font-size: 26px; } is MANDATORY. MAX visible text ~1000 characters.
|
|
485
501
|
2. Use gradients, box-shadow (0 4px 20px rgba(0,0,0,0.06)), border-radius (12-20px)
|
|
486
502
|
3. Follow the REQUIRED LAYOUT specified above exactly. Do NOT substitute a different layout type.
|
|
487
503
|
4. LAYOUT LIMITS: MAX 4 cards (2×2 grid), MAX 2 cards per row. Tables: 5-8 rows for rich data. Timeline: 4-5 steps.
|
|
@@ -532,64 +548,64 @@ export function buildContentFillJsonPrompt(slideTitle, contentDirection, layoutT
|
|
|
532
548
|
const schemas = {
|
|
533
549
|
cards: `{
|
|
534
550
|
"cards": [
|
|
535
|
-
{ "icon": "single emoji", "title": "
|
|
551
|
+
{ "icon": "single emoji", "title": "Short card title (2-5 words)", "bullets": ["Detail with data", "Another detail", "Third point"], "stat": "Key metric with number" }
|
|
536
552
|
]
|
|
537
553
|
}
|
|
538
554
|
RULES: 3-4 cards. Each: icon + title + 3 bullets (MAX 3, keep each under 25 chars) + stat.`,
|
|
539
555
|
bar_chart: `{
|
|
540
556
|
"bars": [
|
|
541
|
-
{ "label": "Category name", "value": "
|
|
557
|
+
{ "label": "Category name (under 15 chars)", "value": "Number with unit", "height": 85 }
|
|
542
558
|
],
|
|
543
559
|
"insight": "1-2 sentence key insight about the data"
|
|
544
560
|
}
|
|
545
|
-
RULES: 4-5 bars (MAX 5). height: 10-90 (tallest=85, others proportional). Values
|
|
561
|
+
RULES: 4-5 bars (MAX 5). height: 10-90 (tallest=85, others proportional). Values must include units.`,
|
|
546
562
|
donut_chart: `{
|
|
547
563
|
"segments": [
|
|
548
|
-
{ "label": "Segment name", "value": "
|
|
564
|
+
{ "label": "Segment name", "value": "Number with unit", "percent": 45 }
|
|
549
565
|
],
|
|
550
|
-
"centerText": "
|
|
566
|
+
"centerText": "Total or summary label",
|
|
551
567
|
"summary": "1-2 sentence summary"
|
|
552
568
|
}
|
|
553
569
|
RULES: 3-5 segments. Percents MUST sum to exactly 100.`,
|
|
554
570
|
table: `{
|
|
555
|
-
"headers": ["
|
|
556
|
-
"rows": [["data", "data", "data", "data"]],
|
|
571
|
+
"headers": ["Descriptive column name", "Another column", "Third column", "Fourth column"],
|
|
572
|
+
"rows": [["real data", "real data", "real data", "real data"]],
|
|
557
573
|
"highlightRow": 0,
|
|
558
574
|
"summary": "1-2 sentence summary"
|
|
559
575
|
}
|
|
560
|
-
RULES: 3-4 columns. 4-5 rows. ALL cells real data. highlightRow: 0-indexed or null.
|
|
561
|
-
⚠ headers MUST be meaningful column names
|
|
576
|
+
RULES: 3-4 columns. 4-5 rows. ALL cells must contain real data. highlightRow: 0-indexed or null.
|
|
577
|
+
⚠ headers MUST be meaningful column names — NEVER use generic "Column 1", "Column 2".`,
|
|
562
578
|
process_flow: `{
|
|
563
579
|
"steps": [
|
|
564
|
-
{ "title": "Step name (2-4 words)", "desc": "2-3 sentence description with details", "detail": "Duration or metric" }
|
|
580
|
+
{ "title": "Step name (2-4 words)", "desc": "2-3 sentence description with specific details", "detail": "Duration or key metric" }
|
|
565
581
|
]
|
|
566
582
|
}
|
|
567
583
|
RULES: 3-4 steps (MAX 4). Each: title + detailed description + time/metric.`,
|
|
568
584
|
big_numbers: `{
|
|
569
585
|
"metrics": [
|
|
570
|
-
{ "value": "
|
|
586
|
+
{ "value": "Number only", "unit": "Unit text", "label": "Metric name", "desc": "1-2 sentence context", "trend": "▲ or ▼ + percentage", "positive": true }
|
|
571
587
|
]
|
|
572
588
|
}
|
|
573
|
-
RULES: 2-3 metrics. value=number only, unit=separate. trend: ▲/▼ + percentage.`,
|
|
589
|
+
RULES: 2-3 metrics. value=number only, unit=separate field. trend: ▲/▼ + percentage.`,
|
|
574
590
|
timeline: `{
|
|
575
591
|
"milestones": [
|
|
576
|
-
{ "date": "
|
|
592
|
+
{ "date": "Date or period", "title": "Milestone name", "desc": "2-3 sentence description", "kpi": "Target metric" }
|
|
577
593
|
]
|
|
578
594
|
}
|
|
579
595
|
RULES: 3 milestones (MAX 3). Each with date, description, and KPI target.`,
|
|
580
596
|
progress_bars: `{
|
|
581
597
|
"bars": [
|
|
582
|
-
{ "label": "Category name", "value": "Display
|
|
598
|
+
{ "label": "Category name", "value": "Display value with unit", "percent": 75, "detail": "Brief context" }
|
|
583
599
|
]
|
|
584
600
|
}
|
|
585
601
|
RULES: 4-6 bars. percent: 5-100. Include context detail for each.`,
|
|
586
602
|
hero_stat: `{
|
|
587
|
-
"number": "
|
|
588
|
-
"unit": "
|
|
589
|
-
"label": "
|
|
603
|
+
"number": "The big number",
|
|
604
|
+
"unit": "Unit or symbol",
|
|
605
|
+
"label": "What this number measures",
|
|
590
606
|
"context": "2-3 sentence context explaining significance",
|
|
591
607
|
"supporting": [
|
|
592
|
-
{ "value": "
|
|
608
|
+
{ "value": "Number with unit", "label": "Supporting metric name" }
|
|
593
609
|
]
|
|
594
610
|
}
|
|
595
611
|
RULES: 1 hero number + context + 2-3 supporting metrics.`,
|
|
@@ -601,7 +617,7 @@ RULES: 1 hero number + context + 2-3 supporting metrics.`,
|
|
|
601
617
|
"rightTitle": "Right column heading",
|
|
602
618
|
"rightBullets": ["Detailed bullet point with data"]
|
|
603
619
|
}
|
|
604
|
-
RULES: Left: 3-4 key-value items. Right: 3-4 detailed bullets. Keep each bullet under 30 chars.`,
|
|
620
|
+
RULES: Left: 3-4 key-value items with REAL data. Right: 3-4 detailed bullets with REAL data. Keep each bullet under 30 chars.`,
|
|
605
621
|
};
|
|
606
622
|
return `Extract content from the direction below and output ONLY valid JSON.
|
|
607
623
|
Do NOT output markdown fences, explanations, or anything besides the JSON object.
|
|
@@ -656,6 +672,10 @@ export function parseContentFillJson(raw, layoutType) {
|
|
|
656
672
|
return null;
|
|
657
673
|
}
|
|
658
674
|
}
|
|
675
|
+
const stringLiteralMatches = jsonStr.match(/"string"/g) || [];
|
|
676
|
+
if (stringLiteralMatches.length >= 2) {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
659
679
|
switch (layoutType) {
|
|
660
680
|
case 'cards':
|
|
661
681
|
if (!Array.isArray(parsed['cards']) || parsed['cards'].length === 0)
|
|
@@ -696,8 +716,15 @@ export function parseContentFillJson(raw, layoutType) {
|
|
|
696
716
|
case 'two_col_split':
|
|
697
717
|
if (!parsed['leftTitle'] && !parsed['rightTitle'])
|
|
698
718
|
return null;
|
|
719
|
+
if (!Array.isArray(parsed['leftItems']) || parsed['leftItems'].length === 0)
|
|
720
|
+
return null;
|
|
721
|
+
if (!Array.isArray(parsed['rightBullets']) || parsed['rightBullets'].length === 0)
|
|
722
|
+
return null;
|
|
699
723
|
break;
|
|
700
724
|
}
|
|
725
|
+
const allText = JSON.stringify(parsed).replace(/[{}\[\]",:]/g, '').trim();
|
|
726
|
+
if (allText.length < 50)
|
|
727
|
+
return null;
|
|
701
728
|
return parsed;
|
|
702
729
|
}
|
|
703
730
|
function getCardTextColor(design) {
|
|
@@ -748,17 +775,17 @@ function buildCardsContent(design, data) {
|
|
|
748
775
|
const cols = n <= 3 ? `repeat(${n},1fr)` : '1fr 1fr';
|
|
749
776
|
const is2x2 = n === 4;
|
|
750
777
|
const gridExtra = is2x2 ? 'grid-template-rows:1fr 1fr' : 'grid-template-rows:1fr';
|
|
751
|
-
const cardPad = is2x2 ? '28px 24px' : '36px
|
|
752
|
-
const cardGap = is2x2 ? '10px' : '
|
|
753
|
-
const h2Size = is2x2 ? '32px' : '
|
|
754
|
-
const liSize = is2x2 ? '26px' : '
|
|
755
|
-
const liMargin = is2x2 ? '8px' : '
|
|
778
|
+
const cardPad = is2x2 ? '28px 24px' : '48px 36px';
|
|
779
|
+
const cardGap = is2x2 ? '10px' : '16px';
|
|
780
|
+
const h2Size = is2x2 ? '32px' : '40px';
|
|
781
|
+
const liSize = is2x2 ? '26px' : '30px';
|
|
782
|
+
const liMargin = is2x2 ? '8px' : '14px';
|
|
756
783
|
const cardText = getCardTextColor(design);
|
|
757
784
|
const css = `.content{flex:1;display:grid;grid-template-columns:${cols};${gridExtra};gap:24px}
|
|
758
785
|
.card{background:#fff;border-radius:16px;padding:${cardPad};box-shadow:0 4px 20px rgba(0,0,0,0.06);display:flex;flex-direction:column;gap:${cardGap};overflow:hidden;color:${cardText}}
|
|
759
786
|
.card-icon{font-size:36px;line-height:1}
|
|
760
787
|
.card h2{font-size:${h2Size};font-weight:700;color:${design.primary_color}}
|
|
761
|
-
.card ul{list-style:none;flex:1}
|
|
788
|
+
.card ul{list-style:none;flex:1;display:flex;flex-direction:column;justify-content:center}
|
|
762
789
|
.card li{margin-bottom:${liMargin};padding-left:22px;position:relative;font-size:${liSize};line-height:1.4}
|
|
763
790
|
.card li::before{content:"•";color:${design.accent_color};position:absolute;left:0;font-weight:bold}
|
|
764
791
|
.card-stat{margin-top:auto;padding-top:12px;border-top:2px solid ${design.accent_light};font-size:24px;font-weight:600;color:${design.accent_color}}`;
|
|
@@ -812,16 +839,16 @@ function buildDonutChartContent(design, data) {
|
|
|
812
839
|
cumPct += pct;
|
|
813
840
|
return `${segColors[i % segColors.length]} ${start.toFixed(1)}% ${cumPct.toFixed(1)}%`;
|
|
814
841
|
}).join(',');
|
|
815
|
-
const css = `.content{flex:1;display:grid;grid-template-columns:1.2fr 1fr;gap:40px;align-items:center;align-content:center}
|
|
842
|
+
const css = `.content{flex:1;display:grid;grid-template-columns:1.2fr 1fr;gap:40px;align-items:center;align-content:center;padding:0}
|
|
816
843
|
.donut-wrap{display:flex;justify-content:center;align-items:center}
|
|
817
|
-
.donut{width:
|
|
818
|
-
.donut-hole{width:
|
|
819
|
-
.donut-center{font-size:
|
|
820
|
-
.legend{display:flex;flex-direction:column;gap:
|
|
821
|
-
.legend-item{display:flex;align-items:center;gap:16px;font-size:28px;padding:
|
|
844
|
+
.donut{width:600px;height:600px;border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 8px 32px rgba(0,0,0,0.08)}
|
|
845
|
+
.donut-hole{width:290px;height:290px;border-radius:50%;background:${design.background_color};display:flex;align-items:center;justify-content:center;flex-direction:column;gap:4px}
|
|
846
|
+
.donut-center{font-size:34px;font-weight:800;color:${design.primary_color};text-align:center;line-height:1.3}
|
|
847
|
+
.legend{display:flex;flex-direction:column;gap:24px}
|
|
848
|
+
.legend-item{display:flex;align-items:center;gap:16px;font-size:28px;padding:14px 18px;background:#fff;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,0.04);color:${getCardTextColor(design)}}
|
|
822
849
|
.legend-dot{width:24px;height:24px;border-radius:50%;flex-shrink:0}
|
|
823
850
|
.legend-value{font-weight:700;color:${design.primary_color};margin-left:auto;white-space:nowrap}
|
|
824
|
-
.chart-summary{
|
|
851
|
+
.chart-summary{padding:16px 20px;background:${design.accent_light};border-radius:12px;font-size:24px;color:${getCardTextColor(design)};line-height:1.5;margin-top:8px}`;
|
|
825
852
|
const legendHtml = segs.map((s, i) => `
|
|
826
853
|
<div class="legend-item">
|
|
827
854
|
<div class="legend-dot" style="background:${segColors[i % segColors.length]}"></div>
|
|
@@ -836,26 +863,27 @@ function buildDonutChartContent(design, data) {
|
|
|
836
863
|
<div class="donut-hole"><div class="donut-center">${escapeHtmlTemplate(data.centerText || '')}</div></div>
|
|
837
864
|
</div>
|
|
838
865
|
</div>
|
|
839
|
-
<div class="legend">${legendHtml}
|
|
840
|
-
|
|
866
|
+
<div class="legend">${legendHtml}
|
|
867
|
+
${data.summary ? `<div class="chart-summary">${escapeHtmlTemplate(data.summary)}</div>` : ''}
|
|
868
|
+
</div>
|
|
841
869
|
</div>`,
|
|
842
870
|
};
|
|
843
871
|
}
|
|
844
872
|
function buildTableContent(design, data) {
|
|
845
873
|
const cardText = getCardTextColor(design);
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
.data-table
|
|
849
|
-
.data-table
|
|
874
|
+
const maxCols = Math.min((data.headers || []).length, 4);
|
|
875
|
+
const css = `.content{flex:1;display:flex;flex-direction:column;justify-content:center}
|
|
876
|
+
.data-table{width:100%;border-collapse:collapse;background:#fff;box-shadow:0 4px 12px rgba(0,0,0,0.05);border-radius:12px;overflow:hidden;table-layout:fixed}
|
|
877
|
+
.data-table th{background:${design.primary_color};color:#fff;font-size:28px;text-align:left;padding:22px 28px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
878
|
+
.data-table td{font-size:28px;color:${cardText};padding:22px 28px;border-bottom:1px solid ${design.accent_light};overflow:hidden;word-break:break-word}
|
|
850
879
|
.data-table tr:last-child td{border-bottom:none}
|
|
851
880
|
.data-table tr:nth-child(even){background:${design.accent_light}20}
|
|
852
881
|
.data-table tr.highlight td{background:${design.accent_color}15;font-weight:600}
|
|
853
|
-
.table-summary{margin-top:
|
|
854
|
-
const headers = (data.headers || []).slice(0,
|
|
855
|
-
const maxCols = Math.min((data.headers || []).length, 4);
|
|
882
|
+
.table-summary{margin-top:20px;padding:16px 24px;background:${design.accent_light};border-radius:12px;font-size:26px;color:${cardText};line-height:1.5}`;
|
|
883
|
+
const headers = (data.headers || []).slice(0, maxCols).map(h => `<th>${escapeHtmlTemplate(String(h || '').slice(0, 25))}</th>`).join('');
|
|
856
884
|
const rows = (data.rows || []).slice(0, 5).map((row, ri) => {
|
|
857
885
|
const cls = ri === data.highlightRow ? ' class="highlight"' : '';
|
|
858
|
-
const cells = (row || []).slice(0, maxCols).map(c => `<td>${escapeHtmlTemplate(String(c || ''))}</td>`).join('');
|
|
886
|
+
const cells = (row || []).slice(0, maxCols).map(c => `<td>${escapeHtmlTemplate(String(c || '').slice(0, 50))}</td>`).join('');
|
|
859
887
|
return `<tr${cls}>${cells}</tr>`;
|
|
860
888
|
}).join('\n');
|
|
861
889
|
return {
|
|
@@ -873,19 +901,21 @@ function buildProcessFlowContent(design, data) {
|
|
|
873
901
|
const descSz = n <= 3 ? '28px' : '26px';
|
|
874
902
|
const detailSz = n <= 3 ? '28px' : '26px';
|
|
875
903
|
const css = `.content{flex:1;display:flex;align-items:stretch;gap:0}
|
|
876
|
-
.step{flex:1;display:flex;flex-direction:column;align-items:center;padding:36px 24px;background:#fff;border-radius:16px;text-align:center;gap:16px;box-shadow:0 4px 20px rgba(0,0,0,0.06)}
|
|
904
|
+
.step{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:36px 24px;background:#fff;border-radius:16px;text-align:center;gap:16px;box-shadow:0 4px 20px rgba(0,0,0,0.06)}
|
|
877
905
|
.step-num{width:52px;height:52px;border-radius:50%;background:linear-gradient(135deg,${design.primary_color},${design.gradient_end});color:#fff;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:700;flex-shrink:0}
|
|
878
906
|
.step-title{font-size:${titleSz};font-weight:700;color:${design.primary_color}}
|
|
879
|
-
.step-desc{font-size:${descSz};color:${cardText};line-height:1.5
|
|
880
|
-
.step-detail{font-size:${detailSz};color:${design.accent_color};font-weight:600;padding-top:14px;border-top:2px solid ${design.accent_color}40
|
|
907
|
+
.step-desc{font-size:${descSz};color:${cardText};line-height:1.5}
|
|
908
|
+
.step-detail{font-size:${detailSz};color:${design.accent_color};font-weight:600;padding-top:14px;border-top:2px solid ${design.accent_color}40}
|
|
881
909
|
.arrow{width:48px;display:flex;align-items:center;justify-content:center;font-size:40px;color:${design.accent_color};flex-shrink:0}`;
|
|
882
910
|
const steps = (data.steps || []).slice(0, 4);
|
|
911
|
+
const maxDescLen = n <= 3 ? 120 : 80;
|
|
883
912
|
const stepsHtml = steps.map((s, i) => {
|
|
913
|
+
const desc = (s.desc || '').slice(0, maxDescLen);
|
|
884
914
|
const stepDiv = `
|
|
885
915
|
<div class="step">
|
|
886
916
|
<div class="step-num">${i + 1}</div>
|
|
887
917
|
<div class="step-title">${escapeHtmlTemplate(s.title || '')}</div>
|
|
888
|
-
<div class="step-desc">${escapeHtmlTemplate(
|
|
918
|
+
<div class="step-desc">${escapeHtmlTemplate(desc)}</div>
|
|
889
919
|
${s.detail ? `<div class="step-detail">${escapeHtmlTemplate(s.detail)}</div>` : ''}
|
|
890
920
|
</div>`;
|
|
891
921
|
return i < steps.length - 1 ? stepDiv + '\n <div class="arrow">→</div>' : stepDiv;
|
|
@@ -894,13 +924,13 @@ function buildProcessFlowContent(design, data) {
|
|
|
894
924
|
}
|
|
895
925
|
function buildBigNumbersContent(design, data) {
|
|
896
926
|
const cardText = getCardTextColor(design);
|
|
897
|
-
const css = `.content{flex:1;display:flex;gap:40px;align-items:
|
|
898
|
-
.metric-card{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;padding:
|
|
899
|
-
.metric-value{font-size:
|
|
900
|
-
.metric-unit{font-size:
|
|
901
|
-
.metric-label{font-size:
|
|
902
|
-
.metric-desc{font-size:26px;color:${cardText}aa;line-height:1.5}
|
|
903
|
-
.metric-trend{font-size:28px;font-weight:600
|
|
927
|
+
const css = `.content{flex:1;display:flex;gap:40px;align-items:center}
|
|
928
|
+
.metric-card{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;padding:56px 36px;border-radius:20px;background:#fff;box-shadow:0 6px 28px rgba(0,0,0,0.08);text-align:center;gap:20px;border-top:4px solid ${design.accent_color}}
|
|
929
|
+
.metric-value{font-size:100px;font-weight:800;color:${design.primary_color};line-height:1}
|
|
930
|
+
.metric-unit{font-size:36px;font-weight:600;color:${design.accent_color}}
|
|
931
|
+
.metric-label{font-size:32px;font-weight:600;color:${cardText}}
|
|
932
|
+
.metric-desc{font-size:26px;color:${cardText}aa;line-height:1.5;max-width:90%}
|
|
933
|
+
.metric-trend{font-size:28px;font-weight:600}`;
|
|
904
934
|
const metrics = (data.metrics || []).slice(0, 3).map(m => `
|
|
905
935
|
<div class="metric-card">
|
|
906
936
|
<div class="metric-value">${escapeHtmlTemplate(m.value || '')}</div>
|
|
@@ -912,16 +942,16 @@ function buildBigNumbersContent(design, data) {
|
|
|
912
942
|
return { css, html: `<div class="content">${metrics}\n</div>` };
|
|
913
943
|
}
|
|
914
944
|
function buildTimelineContent(design, data) {
|
|
915
|
-
const titleSz = '
|
|
916
|
-
const descSz = '
|
|
917
|
-
const kpiSz = '
|
|
918
|
-
const pad = '40px
|
|
919
|
-
const css = `.content{flex:1;display:flex;align-items:stretch;gap:
|
|
920
|
-
.milestone{flex:1;display:flex;flex-direction:column;padding:${pad};border-radius:16px;background:#fff;box-shadow:0 4px 20px rgba(0,0,0,0.06);gap:
|
|
921
|
-
.ms-date{display:inline-block;padding:8px
|
|
945
|
+
const titleSz = '40px';
|
|
946
|
+
const descSz = '32px';
|
|
947
|
+
const kpiSz = '30px';
|
|
948
|
+
const pad = '44px 40px';
|
|
949
|
+
const css = `.content{flex:1;display:flex;align-items:stretch;gap:24px}
|
|
950
|
+
.milestone{flex:1;display:flex;flex-direction:column;justify-content:center;padding:${pad};border-radius:16px;background:#fff;box-shadow:0 4px 20px rgba(0,0,0,0.06);gap:18px}
|
|
951
|
+
.ms-date{display:inline-block;padding:8px 18px;border-radius:8px;background:${design.primary_color};color:#fff;font-size:26px;font-weight:700;align-self:flex-start}
|
|
922
952
|
.ms-title{font-size:${titleSz};font-weight:700;color:${design.primary_color}}
|
|
923
|
-
.ms-desc{font-size:${descSz};color:${getCardTextColor(design)};line-height:1.55
|
|
924
|
-
.ms-kpi{font-size:${kpiSz};font-weight:600;color:${design.accent_color};padding-top:16px;border-top:2px solid ${design.accent_light}
|
|
953
|
+
.ms-desc{font-size:${descSz};color:${getCardTextColor(design)};line-height:1.55}
|
|
954
|
+
.ms-kpi{font-size:${kpiSz};font-weight:600;color:${design.accent_color};padding-top:16px;border-top:2px solid ${design.accent_light}}`;
|
|
925
955
|
const milestones = (data.milestones || []).slice(0, 3).map((m) => `
|
|
926
956
|
<div class="milestone">
|
|
927
957
|
<div class="ms-date">${escapeHtmlTemplate(m.date || '')}</div>
|
|
@@ -932,14 +962,18 @@ function buildTimelineContent(design, data) {
|
|
|
932
962
|
return { css, html: `<div class="content">${milestones}\n</div>` };
|
|
933
963
|
}
|
|
934
964
|
function buildProgressBarsContent(design, data) {
|
|
935
|
-
const
|
|
965
|
+
const barCount = Math.min((data.bars || []).length, 6);
|
|
966
|
+
const barGap = barCount >= 5 ? '28px' : '44px';
|
|
967
|
+
const barHeight = barCount >= 5 ? '44px' : '56px';
|
|
968
|
+
const barRadius = barCount >= 5 ? '22px' : '28px';
|
|
969
|
+
const css = `.content{flex:1;display:flex;flex-direction:column;justify-content:center;gap:${barGap};padding:20px 0}
|
|
936
970
|
.bar-item{display:flex;flex-direction:column;gap:10px}
|
|
937
971
|
.bar-header{display:flex;justify-content:space-between;align-items:baseline}
|
|
938
|
-
.bar-label{font-size:
|
|
939
|
-
.bar-val{font-size:
|
|
940
|
-
.bar-track{width:100%;height
|
|
941
|
-
.bar-fill{height:100%;border-radius
|
|
942
|
-
.bar-detail{font-size:
|
|
972
|
+
.bar-label{font-size:32px;font-weight:600;color:${design.text_color}}
|
|
973
|
+
.bar-val{font-size:32px;font-weight:700;color:${design.primary_color}}
|
|
974
|
+
.bar-track{width:100%;height:${barHeight};background:${design.accent_light};border-radius:${barRadius};overflow:hidden}
|
|
975
|
+
.bar-fill{height:100%;border-radius:${barRadius};background:linear-gradient(90deg,${design.primary_color},${design.accent_color})}
|
|
976
|
+
.bar-detail{font-size:26px;color:${design.text_color}88;margin-top:-2px}`;
|
|
943
977
|
const bars = (data.bars || []).slice(0, 6).map(b => `
|
|
944
978
|
<div class="bar-item">
|
|
945
979
|
<div class="bar-header"><span class="bar-label">${escapeHtmlTemplate(b.label || '')}</span><span class="bar-val">${escapeHtmlTemplate(b.value || '')}</span></div>
|
|
@@ -978,11 +1012,11 @@ function buildTwoColSplitContent(design, data) {
|
|
|
978
1012
|
const css = `.content{flex:1;display:grid;grid-template-columns:1fr 1fr;gap:40px;align-items:stretch}
|
|
979
1013
|
.col{display:flex;flex-direction:column;gap:14px;justify-content:space-evenly}
|
|
980
1014
|
.col h2{font-size:36px;font-weight:700;color:${design.primary_color};margin-bottom:12px;padding-bottom:12px;border-bottom:3px solid ${design.accent_color}}
|
|
981
|
-
.kv-item{display:flex;justify-content:space-between;align-items:center;padding:
|
|
1015
|
+
.kv-item{display:flex;justify-content:space-between;align-items:center;padding:28px 28px;background:#fff;border-radius:14px;box-shadow:0 2px 12px rgba(0,0,0,0.04);font-size:30px;color:${getCardTextColor(design)}}
|
|
982
1016
|
.kv-label{color:${getCardTextColor(design)};font-weight:500}
|
|
983
|
-
.kv-value{color:${design.primary_color};font-weight:700;font-size:
|
|
984
|
-
.col ul{list-style:none;display:flex;flex-direction:column;gap:
|
|
985
|
-
.col li{padding:
|
|
1017
|
+
.kv-value{color:${design.primary_color};font-weight:700;font-size:30px}
|
|
1018
|
+
.col ul{list-style:none;display:flex;flex-direction:column;gap:12px}
|
|
1019
|
+
.col li{padding:20px 20px 20px 34px;position:relative;font-size:28px;line-height:1.5;background:#fff;border-radius:12px;box-shadow:0 1px 8px rgba(0,0,0,0.03);color:${getCardTextColor(design)}}
|
|
986
1020
|
.col li::before{content:"•";color:${design.accent_color};position:absolute;left:12px;font-weight:bold}`;
|
|
987
1021
|
const leftItems = (data.leftItems || []).slice(0, 4).map(item => `
|
|
988
1022
|
<div class="kv-item"><span class="kv-label">${escapeHtmlTemplate(item.label || '')}</span><span class="kv-value">${escapeHtmlTemplate(item.value || '')}</span></div>`).join('');
|