deckbuilder 1.0.0b1__py3-none-any.whl
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.
- deckbuilder/__init__.py +22 -0
- deckbuilder/cli.py +544 -0
- deckbuilder/cli_tools.py +739 -0
- deckbuilder/engine.py +1546 -0
- deckbuilder/image_handler.py +291 -0
- deckbuilder/layout_intelligence.json +288 -0
- deckbuilder/layout_intelligence.py +398 -0
- deckbuilder/naming_conventions.py +541 -0
- deckbuilder/placeholder_types.py +101 -0
- deckbuilder/placekitten_integration.py +280 -0
- deckbuilder/structured_frontmatter.py +862 -0
- deckbuilder/table_styles.py +37 -0
- deckbuilder-1.0.0b1.dist-info/METADATA +378 -0
- deckbuilder-1.0.0b1.dist-info/RECORD +37 -0
- deckbuilder-1.0.0b1.dist-info/WHEEL +5 -0
- deckbuilder-1.0.0b1.dist-info/entry_points.txt +3 -0
- deckbuilder-1.0.0b1.dist-info/licenses/LICENSE +201 -0
- deckbuilder-1.0.0b1.dist-info/top_level.txt +4 -0
- mcp_server/__init__.py +9 -0
- mcp_server/content_analysis.py +436 -0
- mcp_server/content_optimization.py +822 -0
- mcp_server/layout_recommendations.py +595 -0
- mcp_server/main.py +550 -0
- mcp_server/tools.py +492 -0
- placekitten/README.md +561 -0
- placekitten/__init__.py +44 -0
- placekitten/core.py +184 -0
- placekitten/filters.py +183 -0
- placekitten/images/ACuteKitten-1.png +0 -0
- placekitten/images/ACuteKitten-2.png +0 -0
- placekitten/images/ACuteKitten-3.png +0 -0
- placekitten/images/TwoKitttens Playing-1.png +0 -0
- placekitten/images/TwoKitttens Playing-2.png +0 -0
- placekitten/images/TwoKitttensSleeping-1.png +0 -0
- placekitten/processor.py +262 -0
- placekitten/smart_crop.py +314 -0
- shared/__init__.py +9 -0
@@ -0,0 +1,595 @@
|
|
1
|
+
"""
|
2
|
+
Layout Recommendation Engine for Content-First Presentation Intelligence
|
3
|
+
|
4
|
+
This module implements Tool #2: recommend_slide_approach() which analyzes specific
|
5
|
+
content pieces and recommends optimal PowerPoint layouts based on communication intent.
|
6
|
+
|
7
|
+
Design Philosophy: Match content structure and message intent to most effective layout,
|
8
|
+
not just "what layouts exist?"
|
9
|
+
"""
|
10
|
+
|
11
|
+
import re
|
12
|
+
from dataclasses import dataclass
|
13
|
+
from typing import Any, Dict, List, Optional
|
14
|
+
|
15
|
+
|
16
|
+
@dataclass
|
17
|
+
class LayoutRecommendation:
|
18
|
+
"""Single layout recommendation with confidence scoring"""
|
19
|
+
|
20
|
+
layout: str
|
21
|
+
confidence: float
|
22
|
+
reason: str
|
23
|
+
best_for: str
|
24
|
+
|
25
|
+
|
26
|
+
class LayoutRecommendationEngine:
|
27
|
+
"""
|
28
|
+
Analyzes content pieces and recommends optimal slide layouts.
|
29
|
+
|
30
|
+
Focuses on content structure, message intent, and communication effectiveness
|
31
|
+
rather than just available template options.
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(self):
|
35
|
+
"""Initialize with layout intelligence mapping"""
|
36
|
+
self.layout_intelligence = self._build_layout_intelligence()
|
37
|
+
self.available_layouts = self._get_available_layouts()
|
38
|
+
|
39
|
+
def recommend_slide_approach(
|
40
|
+
self, content_piece: str, message_intent: str, presentation_context: Optional[Dict] = None
|
41
|
+
) -> Dict[str, Any]:
|
42
|
+
"""
|
43
|
+
Analyze content and recommend optimal slide layouts.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
content_piece: Specific content to present
|
47
|
+
message_intent: What they want this content to communicate
|
48
|
+
presentation_context: Optional context from analyze_presentation_needs()
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Dictionary with layout recommendations, content analysis, and suggestions
|
52
|
+
"""
|
53
|
+
# Analyze the content structure and type
|
54
|
+
content_analysis = self._analyze_content_structure(content_piece)
|
55
|
+
|
56
|
+
# Determine communication intent
|
57
|
+
intent_analysis = self._analyze_message_intent(message_intent, content_piece)
|
58
|
+
|
59
|
+
# Generate layout recommendations with confidence scoring
|
60
|
+
layout_recommendations = self._generate_layout_recommendations(
|
61
|
+
content_analysis, intent_analysis, presentation_context
|
62
|
+
)
|
63
|
+
|
64
|
+
# Provide content structuring suggestions
|
65
|
+
content_suggestions = self._generate_content_suggestions(
|
66
|
+
content_analysis, intent_analysis, layout_recommendations
|
67
|
+
)
|
68
|
+
|
69
|
+
# Generate structured frontmatter preview
|
70
|
+
structured_frontmatter = self._generate_structured_frontmatter_preview(
|
71
|
+
layout_recommendations, content_piece, message_intent
|
72
|
+
)
|
73
|
+
|
74
|
+
return {
|
75
|
+
"recommended_layouts": [
|
76
|
+
{
|
77
|
+
"layout": rec.layout,
|
78
|
+
"confidence": rec.confidence,
|
79
|
+
"reason": rec.reason,
|
80
|
+
"best_for": rec.best_for,
|
81
|
+
}
|
82
|
+
for rec in layout_recommendations
|
83
|
+
],
|
84
|
+
"content_analysis": content_analysis,
|
85
|
+
"content_suggestions": content_suggestions,
|
86
|
+
"structured_frontmatter": structured_frontmatter,
|
87
|
+
}
|
88
|
+
|
89
|
+
def _analyze_content_structure(self, content_piece: str) -> Dict[str, Any]:
|
90
|
+
"""Analyze the structure and characteristics of the content."""
|
91
|
+
content_lower = content_piece.lower()
|
92
|
+
|
93
|
+
# Count various content elements
|
94
|
+
numbers_count = len(re.findall(r"\d+%|\$[\d,]+|\d+", content_piece))
|
95
|
+
bullet_indicators = len(re.findall(r"[-*•]|\d+\.|\w\)", content_piece))
|
96
|
+
|
97
|
+
# Detect content patterns
|
98
|
+
has_comparison = any(
|
99
|
+
word in content_lower for word in ["vs", "versus", "compared to", "versus", "against"]
|
100
|
+
)
|
101
|
+
has_list_structure = (
|
102
|
+
bullet_indicators > 2
|
103
|
+
or content_piece.count("\n") > 2
|
104
|
+
or content_piece.count(",") >= 3
|
105
|
+
or content_piece.count(":") >= 2
|
106
|
+
)
|
107
|
+
has_metrics = numbers_count >= 2
|
108
|
+
has_process = any(
|
109
|
+
word in content_lower for word in ["first", "then", "next", "finally", "step"]
|
110
|
+
)
|
111
|
+
|
112
|
+
# Determine content type
|
113
|
+
if has_comparison:
|
114
|
+
content_type = "comparison"
|
115
|
+
elif has_metrics:
|
116
|
+
content_type = "metrics_data"
|
117
|
+
elif has_process:
|
118
|
+
content_type = "process_timeline"
|
119
|
+
elif has_list_structure:
|
120
|
+
content_type = "structured_list"
|
121
|
+
else:
|
122
|
+
content_type = "narrative_text"
|
123
|
+
|
124
|
+
# Count distinct elements for layout sizing
|
125
|
+
distinct_elements = self._count_distinct_elements(content_piece)
|
126
|
+
|
127
|
+
return {
|
128
|
+
"content_type": content_type,
|
129
|
+
"data_elements": numbers_count,
|
130
|
+
"distinct_elements": distinct_elements,
|
131
|
+
"has_comparison": has_comparison,
|
132
|
+
"has_metrics": has_metrics,
|
133
|
+
"has_list_structure": has_list_structure,
|
134
|
+
"has_process": has_process,
|
135
|
+
"structure_pattern": self._determine_structure_pattern(content_piece),
|
136
|
+
"visual_emphasis": self._determine_visual_emphasis(content_piece, content_type),
|
137
|
+
}
|
138
|
+
|
139
|
+
def _analyze_message_intent(self, message_intent: str, content_piece: str) -> Dict[str, Any]:
|
140
|
+
"""Analyze what the user wants to communicate with this content."""
|
141
|
+
intent_lower = message_intent.lower()
|
142
|
+
|
143
|
+
# Categorize communication intent
|
144
|
+
if any(word in intent_lower for word in ["compare", "contrast", "versus", "vs"]):
|
145
|
+
intent_category = "comparison"
|
146
|
+
elif any(
|
147
|
+
word in intent_lower for word in ["highlight", "emphasize", "showcase", "feature"]
|
148
|
+
):
|
149
|
+
intent_category = "emphasis"
|
150
|
+
elif any(
|
151
|
+
word in intent_lower
|
152
|
+
for word in ["explain", "show process", "demonstrate", "walk through"]
|
153
|
+
):
|
154
|
+
intent_category = "explanation"
|
155
|
+
elif any(word in intent_lower for word in ["data", "metrics", "numbers", "performance"]):
|
156
|
+
intent_category = "data_presentation"
|
157
|
+
elif any(word in intent_lower for word in ["overview", "summary", "introduction"]):
|
158
|
+
intent_category = "overview"
|
159
|
+
else:
|
160
|
+
intent_category = "general_information"
|
161
|
+
|
162
|
+
return {
|
163
|
+
"intent_category": intent_category,
|
164
|
+
"emphasis_level": self._determine_emphasis_level(message_intent),
|
165
|
+
"audience_focus": self._determine_audience_focus(message_intent),
|
166
|
+
"communication_goal": self._determine_communication_goal(message_intent),
|
167
|
+
}
|
168
|
+
|
169
|
+
def _generate_layout_recommendations(
|
170
|
+
self,
|
171
|
+
content_analysis: Dict[str, Any],
|
172
|
+
intent_analysis: Dict[str, Any],
|
173
|
+
presentation_context: Optional[Dict] = None,
|
174
|
+
) -> List[LayoutRecommendation]:
|
175
|
+
"""Generate ranked layout recommendations based on content and intent analysis."""
|
176
|
+
recommendations = []
|
177
|
+
|
178
|
+
content_type = content_analysis["content_type"]
|
179
|
+
intent_category = intent_analysis["intent_category"]
|
180
|
+
distinct_elements = content_analysis["distinct_elements"]
|
181
|
+
|
182
|
+
# Apply layout intelligence rules
|
183
|
+
if content_type == "comparison" or intent_category == "comparison":
|
184
|
+
if distinct_elements == 2:
|
185
|
+
recommendations.append(
|
186
|
+
LayoutRecommendation(
|
187
|
+
"Comparison",
|
188
|
+
0.95,
|
189
|
+
"Two distinct elements perfect for side-by-side comparison",
|
190
|
+
"head-to-head comparisons and before/after scenarios",
|
191
|
+
)
|
192
|
+
)
|
193
|
+
recommendations.append(
|
194
|
+
LayoutRecommendation(
|
195
|
+
"Two Content",
|
196
|
+
0.80,
|
197
|
+
"Alternative two-column layout for comparison content",
|
198
|
+
"detailed side-by-side content with more text",
|
199
|
+
)
|
200
|
+
)
|
201
|
+
elif 3 <= distinct_elements <= 4:
|
202
|
+
recommendations.append(
|
203
|
+
LayoutRecommendation(
|
204
|
+
"Four Columns",
|
205
|
+
0.90,
|
206
|
+
f"{distinct_elements} elements ideal for structured comparison grid",
|
207
|
+
"feature matrices and multi-factor comparisons",
|
208
|
+
)
|
209
|
+
)
|
210
|
+
recommendations.append(
|
211
|
+
LayoutRecommendation(
|
212
|
+
"Comparison",
|
213
|
+
0.70,
|
214
|
+
"Simplified two-way comparison focusing on main contrasts",
|
215
|
+
"high-level comparison without detailed breakdown",
|
216
|
+
)
|
217
|
+
)
|
218
|
+
|
219
|
+
elif content_type == "metrics_data" or intent_category == "data_presentation":
|
220
|
+
if content_analysis["data_elements"] >= 3:
|
221
|
+
recommendations.append(
|
222
|
+
LayoutRecommendation(
|
223
|
+
"Four Columns",
|
224
|
+
0.85,
|
225
|
+
"Multiple metrics benefit from structured grid presentation",
|
226
|
+
"KPI dashboards and performance metrics",
|
227
|
+
)
|
228
|
+
)
|
229
|
+
recommendations.append(
|
230
|
+
LayoutRecommendation(
|
231
|
+
"Title and Content",
|
232
|
+
0.75,
|
233
|
+
"Traditional layout works well for data with explanatory text",
|
234
|
+
"metrics with context and analysis",
|
235
|
+
)
|
236
|
+
)
|
237
|
+
|
238
|
+
elif content_type == "structured_list":
|
239
|
+
if 3 <= distinct_elements <= 4:
|
240
|
+
recommendations.append(
|
241
|
+
LayoutRecommendation(
|
242
|
+
"Four Columns",
|
243
|
+
0.88,
|
244
|
+
"List structure maps well to column-based presentation",
|
245
|
+
"feature lists and categorized information",
|
246
|
+
)
|
247
|
+
)
|
248
|
+
recommendations.append(
|
249
|
+
LayoutRecommendation(
|
250
|
+
"Title and Content",
|
251
|
+
0.85,
|
252
|
+
"Classic bulleted list presentation",
|
253
|
+
"traditional content delivery with clear hierarchy",
|
254
|
+
)
|
255
|
+
)
|
256
|
+
if distinct_elements == 2:
|
257
|
+
recommendations.append(
|
258
|
+
LayoutRecommendation(
|
259
|
+
"Two Content",
|
260
|
+
0.80,
|
261
|
+
"Two main topics work well in side-by-side layout",
|
262
|
+
"dual-topic presentations",
|
263
|
+
)
|
264
|
+
)
|
265
|
+
|
266
|
+
elif content_type == "process_timeline":
|
267
|
+
recommendations.append(
|
268
|
+
LayoutRecommendation(
|
269
|
+
"Four Columns",
|
270
|
+
0.80,
|
271
|
+
"Process steps map well to sequential columns",
|
272
|
+
"step-by-step processes and timelines",
|
273
|
+
)
|
274
|
+
)
|
275
|
+
recommendations.append(
|
276
|
+
LayoutRecommendation(
|
277
|
+
"Title and Content",
|
278
|
+
0.75,
|
279
|
+
"Sequential list format for process explanation",
|
280
|
+
"detailed process documentation",
|
281
|
+
)
|
282
|
+
)
|
283
|
+
|
284
|
+
else: # narrative_text or general
|
285
|
+
recommendations.append(
|
286
|
+
LayoutRecommendation(
|
287
|
+
"Title and Content",
|
288
|
+
0.85,
|
289
|
+
"Standard layout ideal for narrative and explanatory content",
|
290
|
+
"general information and detailed explanations",
|
291
|
+
)
|
292
|
+
)
|
293
|
+
if intent_category == "overview":
|
294
|
+
recommendations.append(
|
295
|
+
LayoutRecommendation(
|
296
|
+
"Section Header",
|
297
|
+
0.70,
|
298
|
+
"High-level overview content works as section divider",
|
299
|
+
"topic introductions and agenda items",
|
300
|
+
)
|
301
|
+
)
|
302
|
+
|
303
|
+
# Add fallback recommendations if none were generated
|
304
|
+
if not recommendations:
|
305
|
+
recommendations.append(
|
306
|
+
LayoutRecommendation(
|
307
|
+
"Title and Content",
|
308
|
+
0.70,
|
309
|
+
"Default layout suitable for most content types",
|
310
|
+
"general content presentation and information delivery",
|
311
|
+
)
|
312
|
+
)
|
313
|
+
if content_analysis["has_comparison"]:
|
314
|
+
recommendations.append(
|
315
|
+
LayoutRecommendation(
|
316
|
+
"Comparison",
|
317
|
+
0.65,
|
318
|
+
"Content contains comparison elements",
|
319
|
+
"side-by-side comparisons and contrasts",
|
320
|
+
)
|
321
|
+
)
|
322
|
+
|
323
|
+
# Sort by confidence score
|
324
|
+
recommendations.sort(key=lambda x: x.confidence, reverse=True)
|
325
|
+
|
326
|
+
# Return top 3 recommendations
|
327
|
+
return recommendations[:3]
|
328
|
+
|
329
|
+
def _generate_content_suggestions(
|
330
|
+
self,
|
331
|
+
content_analysis: Dict[str, Any],
|
332
|
+
intent_analysis: Dict[str, Any],
|
333
|
+
layout_recommendations: List[LayoutRecommendation],
|
334
|
+
) -> Dict[str, Any]:
|
335
|
+
"""Generate content structuring and presentation suggestions."""
|
336
|
+
primary_layout = layout_recommendations[0] if layout_recommendations else None
|
337
|
+
|
338
|
+
if not primary_layout:
|
339
|
+
return {}
|
340
|
+
|
341
|
+
# Generate suggestions based on recommended layout
|
342
|
+
if primary_layout.layout == "Four Columns":
|
343
|
+
return {
|
344
|
+
"primary_message": "Structured comparison or categorized information",
|
345
|
+
"supporting_details": [
|
346
|
+
"distinct categories",
|
347
|
+
"balanced content distribution",
|
348
|
+
"clear labels",
|
349
|
+
],
|
350
|
+
"recommended_structure": "title + categorized columns with headers",
|
351
|
+
"visual_approach": "balanced grid with consistent formatting",
|
352
|
+
}
|
353
|
+
elif primary_layout.layout == "Comparison":
|
354
|
+
return {
|
355
|
+
"primary_message": "Side-by-side comparison highlighting key differences",
|
356
|
+
"supporting_details": [
|
357
|
+
"contrasting elements",
|
358
|
+
"clear distinctions",
|
359
|
+
"decision factors",
|
360
|
+
],
|
361
|
+
"recommended_structure": "title + left vs right comparison",
|
362
|
+
"visual_approach": "clear visual separation with balanced content",
|
363
|
+
}
|
364
|
+
elif primary_layout.layout == "Two Content":
|
365
|
+
return {
|
366
|
+
"primary_message": "Dual-topic presentation with equal emphasis",
|
367
|
+
"supporting_details": [
|
368
|
+
"complementary topics",
|
369
|
+
"balanced detail level",
|
370
|
+
"related themes",
|
371
|
+
],
|
372
|
+
"recommended_structure": "title + two main content areas",
|
373
|
+
"visual_approach": "side-by-side layout with clear separation",
|
374
|
+
}
|
375
|
+
else: # Title and Content or others
|
376
|
+
return {
|
377
|
+
"primary_message": "Structured information with clear hierarchy",
|
378
|
+
"supporting_details": ["main points", "supporting details", "logical flow"],
|
379
|
+
"recommended_structure": "title + bulleted or paragraph content",
|
380
|
+
"visual_approach": "traditional layout with clear visual hierarchy",
|
381
|
+
}
|
382
|
+
|
383
|
+
def _generate_structured_frontmatter_preview(
|
384
|
+
self,
|
385
|
+
layout_recommendations: List[LayoutRecommendation],
|
386
|
+
content_piece: str,
|
387
|
+
message_intent: str,
|
388
|
+
) -> Dict[str, Any]:
|
389
|
+
"""Generate structured frontmatter preview for the recommended layout."""
|
390
|
+
if not layout_recommendations:
|
391
|
+
return {}
|
392
|
+
|
393
|
+
primary_layout = layout_recommendations[0].layout
|
394
|
+
|
395
|
+
# Generate title from content or intent
|
396
|
+
title = self._extract_title_from_content(content_piece, message_intent)
|
397
|
+
|
398
|
+
if primary_layout == "Four Columns":
|
399
|
+
return {
|
400
|
+
"preferred_format": "Four Columns",
|
401
|
+
"yaml_preview": f"""layout: Four Columns
|
402
|
+
title: {title}
|
403
|
+
columns:
|
404
|
+
- title: Category 1
|
405
|
+
content: "Key information for first category"
|
406
|
+
- title: Category 2
|
407
|
+
content: "Key information for second category"
|
408
|
+
- title: Category 3
|
409
|
+
content: "Key information for third category"
|
410
|
+
- title: Category 4
|
411
|
+
content: "Key information for fourth category" """,
|
412
|
+
"placeholder_mapping": {
|
413
|
+
"Title 1": title,
|
414
|
+
"Col 1 Title": "Category 1",
|
415
|
+
"Col 2 Title": "Category 2",
|
416
|
+
"Col 3 Title": "Category 3",
|
417
|
+
"Col 4 Title": "Category 4",
|
418
|
+
},
|
419
|
+
}
|
420
|
+
elif primary_layout == "Comparison":
|
421
|
+
return {
|
422
|
+
"preferred_format": "Comparison",
|
423
|
+
"yaml_preview": f"""layout: Comparison
|
424
|
+
title: {title}
|
425
|
+
comparison:
|
426
|
+
left:
|
427
|
+
title: Option A
|
428
|
+
content: "Benefits and characteristics of first option"
|
429
|
+
right:
|
430
|
+
title: Option B
|
431
|
+
content: "Benefits and characteristics of second option" """,
|
432
|
+
"placeholder_mapping": {
|
433
|
+
"Title 1": title,
|
434
|
+
"Text Placeholder 2": "Option A",
|
435
|
+
"Text Placeholder 4": "Option B",
|
436
|
+
},
|
437
|
+
}
|
438
|
+
else:
|
439
|
+
return {
|
440
|
+
"preferred_format": primary_layout,
|
441
|
+
"yaml_preview": f"""layout: {primary_layout}
|
442
|
+
title: {title}
|
443
|
+
content: |
|
444
|
+
- Key point from your content
|
445
|
+
- Supporting information
|
446
|
+
- Additional details""",
|
447
|
+
"placeholder_mapping": {
|
448
|
+
"Title 1": title,
|
449
|
+
"Content Placeholder 2": "Your content here",
|
450
|
+
},
|
451
|
+
}
|
452
|
+
|
453
|
+
def _count_distinct_elements(self, content: str) -> int:
|
454
|
+
"""Count distinct content elements that could map to separate layout areas."""
|
455
|
+
# Look for bullet points, numbered lists, or natural separators
|
456
|
+
bullet_count = len(re.findall(r"[-*•]\s+", content))
|
457
|
+
number_list_count = len(re.findall(r"\d+\.\s+", content))
|
458
|
+
paragraph_count = len([p for p in content.split("\n\n") if p.strip()])
|
459
|
+
|
460
|
+
# Special handling for comparison content
|
461
|
+
if any(word in content.lower() for word in ["vs", "versus", "compared to", "against"]):
|
462
|
+
# For comparisons, assume at least 2 elements being compared
|
463
|
+
return max(2, bullet_count, number_list_count, min(paragraph_count, 4))
|
464
|
+
|
465
|
+
# For lists, count actual items
|
466
|
+
if bullet_count > 0:
|
467
|
+
return bullet_count
|
468
|
+
elif number_list_count > 0:
|
469
|
+
return number_list_count
|
470
|
+
|
471
|
+
# Check for comma-separated lists (like "feature1, feature2, feature3, and feature4")
|
472
|
+
comma_separated = [item.strip() for item in content.split(",") if item.strip()]
|
473
|
+
if len(comma_separated) >= 3:
|
474
|
+
return min(len(comma_separated), 4)
|
475
|
+
|
476
|
+
# Check for colon-separated lists (like "feature1: description, feature2: description")
|
477
|
+
colon_separated = len(re.findall(r"[^:]+:", content))
|
478
|
+
if colon_separated >= 2:
|
479
|
+
return min(colon_separated, 4)
|
480
|
+
|
481
|
+
# For paragraph content, minimum of 1, maximum of 4
|
482
|
+
return max(1, min(paragraph_count, 4))
|
483
|
+
|
484
|
+
def _determine_structure_pattern(self, content: str) -> str:
|
485
|
+
"""Determine the structural pattern of the content."""
|
486
|
+
if re.search(r"[-*•]\s+.*[-*•]\s+", content):
|
487
|
+
return "bulleted_list"
|
488
|
+
elif re.search(r"\d+\.\s+.*\d+\.\s+", content):
|
489
|
+
return "numbered_list"
|
490
|
+
elif content.count("\n\n") >= 2:
|
491
|
+
return "paragraph_blocks"
|
492
|
+
else:
|
493
|
+
return "continuous_text"
|
494
|
+
|
495
|
+
def _determine_visual_emphasis(self, content: str, content_type: str) -> str:
|
496
|
+
"""Determine what visual emphasis would work best."""
|
497
|
+
if content_type == "comparison":
|
498
|
+
return "balanced_comparison"
|
499
|
+
elif content_type == "metrics_data":
|
500
|
+
return "data_focused"
|
501
|
+
elif content_type == "process_timeline":
|
502
|
+
return "sequential_flow"
|
503
|
+
else:
|
504
|
+
return "content_hierarchy"
|
505
|
+
|
506
|
+
def _determine_emphasis_level(self, message_intent: str) -> str:
|
507
|
+
"""Determine how much visual emphasis is needed."""
|
508
|
+
intent_lower = message_intent.lower()
|
509
|
+
if any(word in intent_lower for word in ["critical", "important", "key", "highlight"]):
|
510
|
+
return "high"
|
511
|
+
elif any(word in intent_lower for word in ["show", "present", "explain"]):
|
512
|
+
return "medium"
|
513
|
+
else:
|
514
|
+
return "standard"
|
515
|
+
|
516
|
+
def _determine_audience_focus(self, message_intent: str) -> str:
|
517
|
+
"""Determine audience consideration from intent."""
|
518
|
+
intent_lower = message_intent.lower()
|
519
|
+
if any(word in intent_lower for word in ["executive", "board", "leadership"]):
|
520
|
+
return "executive"
|
521
|
+
elif any(word in intent_lower for word in ["technical", "detail", "implementation"]):
|
522
|
+
return "technical"
|
523
|
+
else:
|
524
|
+
return "general"
|
525
|
+
|
526
|
+
def _determine_communication_goal(self, message_intent: str) -> str:
|
527
|
+
"""Determine the primary communication goal."""
|
528
|
+
intent_lower = message_intent.lower()
|
529
|
+
if any(word in intent_lower for word in ["decide", "choose", "recommend"]):
|
530
|
+
return "decision_support"
|
531
|
+
elif any(word in intent_lower for word in ["understand", "explain", "clarify"]):
|
532
|
+
return "comprehension"
|
533
|
+
elif any(word in intent_lower for word in ["convince", "persuade", "sell"]):
|
534
|
+
return "persuasion"
|
535
|
+
else:
|
536
|
+
return "information_sharing"
|
537
|
+
|
538
|
+
def _extract_title_from_content(self, content_piece: str, message_intent: str) -> str:
|
539
|
+
"""Extract or generate an appropriate title from content and intent."""
|
540
|
+
# Try to extract from first line if it looks like a title
|
541
|
+
first_line = content_piece.split("\n")[0].strip()
|
542
|
+
if len(first_line) < 60 and not first_line.endswith("."):
|
543
|
+
return first_line
|
544
|
+
|
545
|
+
# Generate from message intent
|
546
|
+
intent_words = message_intent.split()[:3]
|
547
|
+
if intent_words:
|
548
|
+
return " ".join(word.capitalize() for word in intent_words)
|
549
|
+
|
550
|
+
# Fallback
|
551
|
+
return "Content Overview"
|
552
|
+
|
553
|
+
def _build_layout_intelligence(self) -> Dict[str, Any]:
|
554
|
+
"""Build the layout intelligence mapping for content-to-layout recommendations."""
|
555
|
+
return {
|
556
|
+
"comparison_layouts": ["Comparison", "Two Content", "Four Columns"],
|
557
|
+
"data_layouts": ["Four Columns", "Title and Content"],
|
558
|
+
"list_layouts": ["Title and Content", "Four Columns", "Two Content"],
|
559
|
+
"process_layouts": ["Four Columns", "Title and Content", "Two Content"],
|
560
|
+
"narrative_layouts": ["Title and Content", "Section Header"],
|
561
|
+
}
|
562
|
+
|
563
|
+
def _get_available_layouts(self) -> List[str]:
|
564
|
+
"""Get list of currently available PowerPoint layouts."""
|
565
|
+
return [
|
566
|
+
"Title Slide",
|
567
|
+
"Title and Content",
|
568
|
+
"Section Header",
|
569
|
+
"Two Content",
|
570
|
+
"Comparison",
|
571
|
+
"Title Only",
|
572
|
+
"Blank",
|
573
|
+
"Content with Caption",
|
574
|
+
"Picture with Caption",
|
575
|
+
"Four Columns",
|
576
|
+
]
|
577
|
+
|
578
|
+
|
579
|
+
# Helper function for easy import
|
580
|
+
def recommend_slide_approach(
|
581
|
+
content_piece: str, message_intent: str, presentation_context: Optional[Dict] = None
|
582
|
+
) -> Dict[str, Any]:
|
583
|
+
"""
|
584
|
+
Convenience function for getting slide layout recommendations.
|
585
|
+
|
586
|
+
Args:
|
587
|
+
content_piece: Specific content to present
|
588
|
+
message_intent: What they want this content to communicate
|
589
|
+
presentation_context: Optional context from analyze_presentation_needs()
|
590
|
+
|
591
|
+
Returns:
|
592
|
+
Dictionary with layout recommendations and content suggestions
|
593
|
+
"""
|
594
|
+
engine = LayoutRecommendationEngine()
|
595
|
+
return engine.recommend_slide_approach(content_piece, message_intent, presentation_context)
|