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,398 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Layout Intelligence Engine for Content-First Presentation Generation
|
4
|
+
|
5
|
+
Provides intelligent layout recommendations based on content analysis using
|
6
|
+
semantic metadata and convention-based placeholder names.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import json
|
10
|
+
import re
|
11
|
+
from dataclasses import dataclass
|
12
|
+
from pathlib import Path
|
13
|
+
from typing import Dict, List, Optional, Tuple
|
14
|
+
|
15
|
+
|
16
|
+
@dataclass
|
17
|
+
class LayoutRecommendation:
|
18
|
+
"""Layout recommendation with confidence score and reasoning"""
|
19
|
+
|
20
|
+
layout_name: str
|
21
|
+
confidence: float
|
22
|
+
reasoning: List[str]
|
23
|
+
placeholder_mapping: Dict[str, str]
|
24
|
+
optimization_hints: List[str]
|
25
|
+
|
26
|
+
|
27
|
+
@dataclass
|
28
|
+
class ContentAnalysis:
|
29
|
+
"""Content analysis results"""
|
30
|
+
|
31
|
+
intent: str
|
32
|
+
content_type: str
|
33
|
+
structure_indicators: List[str]
|
34
|
+
keywords_found: List[str]
|
35
|
+
content_blocks: int
|
36
|
+
has_images: bool
|
37
|
+
has_numbers: bool
|
38
|
+
|
39
|
+
|
40
|
+
class LayoutIntelligence:
|
41
|
+
"""
|
42
|
+
Content-first layout intelligence engine that analyzes content and
|
43
|
+
recommends optimal layouts with confidence scoring.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def __init__(self, intelligence_file: Optional[str] = None):
|
47
|
+
"""
|
48
|
+
Initialize layout intelligence engine.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
intelligence_file: Path to layout_intelligence.json file
|
52
|
+
"""
|
53
|
+
if intelligence_file is None:
|
54
|
+
# Default to src/layout_intelligence.json relative to this file
|
55
|
+
current_dir = Path(__file__).parent.parent
|
56
|
+
intelligence_file = str(current_dir / "layout_intelligence.json")
|
57
|
+
|
58
|
+
self.intelligence_file = intelligence_file
|
59
|
+
self.intelligence_data = self._load_intelligence_data()
|
60
|
+
|
61
|
+
def _load_intelligence_data(self) -> Dict:
|
62
|
+
"""Load layout intelligence metadata"""
|
63
|
+
try:
|
64
|
+
with open(self.intelligence_file, "r", encoding="utf-8") as f:
|
65
|
+
return json.load(f)
|
66
|
+
except FileNotFoundError:
|
67
|
+
raise FileNotFoundError(f"Layout intelligence file not found: {self.intelligence_file}")
|
68
|
+
except json.JSONDecodeError as e:
|
69
|
+
raise ValueError(f"Invalid JSON in intelligence file: {e}")
|
70
|
+
|
71
|
+
def analyze_content(self, content: str) -> ContentAnalysis:
|
72
|
+
"""
|
73
|
+
Analyze content to extract semantic information for layout recommendations.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
content: Raw markdown content to analyze
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
ContentAnalysis with detected patterns and characteristics
|
80
|
+
"""
|
81
|
+
content_lower = content.lower()
|
82
|
+
|
83
|
+
# Detect intent patterns
|
84
|
+
intent = self._detect_intent(content_lower)
|
85
|
+
|
86
|
+
# Detect content type
|
87
|
+
content_type = self._detect_content_type(content_lower)
|
88
|
+
|
89
|
+
# Analyze structure
|
90
|
+
structure_indicators = self._analyze_structure(content)
|
91
|
+
|
92
|
+
# Find relevant keywords
|
93
|
+
keywords_found = self._find_keywords(content_lower)
|
94
|
+
|
95
|
+
# Count content characteristics
|
96
|
+
content_blocks = self._count_content_blocks(content)
|
97
|
+
has_images = self._has_image_content(content_lower)
|
98
|
+
has_numbers = self._has_numeric_content(content_lower)
|
99
|
+
|
100
|
+
return ContentAnalysis(
|
101
|
+
intent=intent,
|
102
|
+
content_type=content_type,
|
103
|
+
structure_indicators=structure_indicators,
|
104
|
+
keywords_found=keywords_found,
|
105
|
+
content_blocks=content_blocks,
|
106
|
+
has_images=has_images,
|
107
|
+
has_numbers=has_numbers,
|
108
|
+
)
|
109
|
+
|
110
|
+
def recommend_layouts(
|
111
|
+
self, content: str, max_recommendations: int = 3
|
112
|
+
) -> List[LayoutRecommendation]:
|
113
|
+
"""
|
114
|
+
Recommend optimal layouts based on content analysis.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
content: Content to analyze
|
118
|
+
max_recommendations: Maximum number of recommendations to return
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
List of LayoutRecommendation objects sorted by confidence
|
122
|
+
"""
|
123
|
+
analysis = self.analyze_content(content)
|
124
|
+
recommendations = []
|
125
|
+
|
126
|
+
# Get layout compatibility data
|
127
|
+
layout_compatibility = self.intelligence_data.get("layout_compatibility", {})
|
128
|
+
content_patterns = self.intelligence_data.get("content_patterns", {})
|
129
|
+
scoring_weights = self.intelligence_data.get("recommendation_engine", {}).get(
|
130
|
+
"scoring_weights", {}
|
131
|
+
)
|
132
|
+
|
133
|
+
# Score each layout
|
134
|
+
for layout_name, layout_info in layout_compatibility.items():
|
135
|
+
confidence, reasoning = self._score_layout(
|
136
|
+
analysis, layout_name, layout_info, content_patterns, scoring_weights
|
137
|
+
)
|
138
|
+
|
139
|
+
if confidence >= self.intelligence_data.get("recommendation_engine", {}).get(
|
140
|
+
"minimum_confidence", 0.6
|
141
|
+
):
|
142
|
+
placeholder_mapping = self._generate_placeholder_mapping(analysis, layout_info)
|
143
|
+
optimization_hints = self._get_optimization_hints(layout_name, analysis)
|
144
|
+
|
145
|
+
recommendations.append(
|
146
|
+
LayoutRecommendation(
|
147
|
+
layout_name=layout_name,
|
148
|
+
confidence=confidence,
|
149
|
+
reasoning=reasoning,
|
150
|
+
placeholder_mapping=placeholder_mapping,
|
151
|
+
optimization_hints=optimization_hints,
|
152
|
+
)
|
153
|
+
)
|
154
|
+
|
155
|
+
# Sort by confidence and return top recommendations
|
156
|
+
recommendations.sort(key=lambda x: x.confidence, reverse=True)
|
157
|
+
return recommendations[:max_recommendations]
|
158
|
+
|
159
|
+
def _detect_intent(self, content_lower: str) -> str:
|
160
|
+
"""Detect primary intent from content"""
|
161
|
+
intent_patterns = self.intelligence_data.get("content_patterns", {}).get(
|
162
|
+
"intent_recognition", {}
|
163
|
+
)
|
164
|
+
|
165
|
+
best_intent = "overview"
|
166
|
+
best_score = 0
|
167
|
+
|
168
|
+
for intent, pattern_info in intent_patterns.items():
|
169
|
+
score = 0
|
170
|
+
keywords = pattern_info.get("keywords", [])
|
171
|
+
|
172
|
+
for keyword in keywords:
|
173
|
+
if keyword in content_lower:
|
174
|
+
score += 1
|
175
|
+
|
176
|
+
# Normalize score by number of keywords
|
177
|
+
if keywords:
|
178
|
+
score = score / len(keywords)
|
179
|
+
|
180
|
+
if score > best_score:
|
181
|
+
best_score = score
|
182
|
+
best_intent = intent
|
183
|
+
|
184
|
+
return best_intent
|
185
|
+
|
186
|
+
def _detect_content_type(self, content_lower: str) -> str:
|
187
|
+
"""Detect primary content type"""
|
188
|
+
# Simple heuristics for content type detection
|
189
|
+
if any(word in content_lower for word in ["image", "picture", "photo", "diagram"]):
|
190
|
+
return "image_content"
|
191
|
+
elif any(word in content_lower for word in ["vs", "versus", "compare", "option"]):
|
192
|
+
return "comparison_content"
|
193
|
+
elif re.search(r"\d+[%]|\d+\.\d+", content_lower):
|
194
|
+
return "statistics"
|
195
|
+
elif any(word in content_lower for word in ["step", "agenda", "process"]):
|
196
|
+
return "agenda_content"
|
197
|
+
elif len(re.findall(r"^#{1,3}\s", content_lower, re.MULTILINE)) >= 3:
|
198
|
+
return "column_content"
|
199
|
+
else:
|
200
|
+
return "body_content"
|
201
|
+
|
202
|
+
def _analyze_structure(self, content: str) -> List[str]:
|
203
|
+
"""Analyze content structure indicators"""
|
204
|
+
indicators = []
|
205
|
+
|
206
|
+
# Count headings
|
207
|
+
heading_count = len(re.findall(r"^#{1,6}\s", content, re.MULTILINE))
|
208
|
+
if heading_count >= 4:
|
209
|
+
indicators.append("multiple_columns")
|
210
|
+
elif heading_count == 2:
|
211
|
+
indicators.append("paired_content")
|
212
|
+
|
213
|
+
# Check for lists
|
214
|
+
if re.search(r"^\s*[-*+]\s", content, re.MULTILINE):
|
215
|
+
indicators.append("bullet_list")
|
216
|
+
if re.search(r"^\s*\d+\.\s", content, re.MULTILINE):
|
217
|
+
indicators.append("numbered_list")
|
218
|
+
|
219
|
+
# Check for structured frontmatter
|
220
|
+
if content.strip().startswith("---"):
|
221
|
+
indicators.append("structured_frontmatter")
|
222
|
+
|
223
|
+
# Check for tables
|
224
|
+
if "|" in content and re.search(r"\|.*\|.*\|", content):
|
225
|
+
indicators.append("table_content")
|
226
|
+
|
227
|
+
return indicators
|
228
|
+
|
229
|
+
def _find_keywords(self, content_lower: str) -> List[str]:
|
230
|
+
"""Find relevant keywords in content"""
|
231
|
+
found_keywords = []
|
232
|
+
intent_patterns = self.intelligence_data.get("content_patterns", {}).get(
|
233
|
+
"intent_recognition", {}
|
234
|
+
)
|
235
|
+
|
236
|
+
for intent, pattern_info in intent_patterns.items():
|
237
|
+
keywords = pattern_info.get("keywords", [])
|
238
|
+
for keyword in keywords:
|
239
|
+
if keyword in content_lower:
|
240
|
+
found_keywords.append(keyword)
|
241
|
+
|
242
|
+
return list(set(found_keywords)) # Remove duplicates
|
243
|
+
|
244
|
+
def _count_content_blocks(self, content: str) -> int:
|
245
|
+
"""Count distinct content blocks"""
|
246
|
+
# Count major sections (headings + paragraphs)
|
247
|
+
headings = len(re.findall(r"^#{1,6}\s", content, re.MULTILINE))
|
248
|
+
paragraphs = len(
|
249
|
+
[p for p in content.split("\n\n") if p.strip() and not p.strip().startswith("#")]
|
250
|
+
)
|
251
|
+
return max(headings, paragraphs // 2) # Estimate content blocks
|
252
|
+
|
253
|
+
def _has_image_content(self, content_lower: str) -> bool:
|
254
|
+
"""Check if content references images"""
|
255
|
+
image_indicators = ["image", "picture", "photo", "diagram", "visual", "media"]
|
256
|
+
return any(indicator in content_lower for indicator in image_indicators)
|
257
|
+
|
258
|
+
def _has_numeric_content(self, content_lower: str) -> bool:
|
259
|
+
"""Check if content has significant numeric data"""
|
260
|
+
return bool(re.search(r"\d+[%]|\$\d+|\d+\.\d+|^\d+$", content_lower, re.MULTILINE))
|
261
|
+
|
262
|
+
def _score_layout(
|
263
|
+
self,
|
264
|
+
analysis: ContentAnalysis,
|
265
|
+
layout_name: str,
|
266
|
+
layout_info: Dict,
|
267
|
+
content_patterns: Dict,
|
268
|
+
scoring_weights: Dict,
|
269
|
+
) -> Tuple[float, List[str]]:
|
270
|
+
"""Score a layout's compatibility with analyzed content"""
|
271
|
+
score = 0.0
|
272
|
+
reasoning = []
|
273
|
+
|
274
|
+
# Content structure scoring
|
275
|
+
optimal_for = layout_info.get("optimal_for", [])
|
276
|
+
structure_match = any(
|
277
|
+
indicator in analysis.structure_indicators for indicator in optimal_for
|
278
|
+
)
|
279
|
+
if structure_match:
|
280
|
+
score += scoring_weights.get("content_structure", 0.4)
|
281
|
+
reasoning.append(f"Content structure matches {layout_name} purpose")
|
282
|
+
|
283
|
+
# Keyword matching scoring
|
284
|
+
confidence_factors = layout_info.get("confidence_factors", {})
|
285
|
+
for factor, weight in confidence_factors.items():
|
286
|
+
if factor in analysis.keywords_found or factor in analysis.structure_indicators:
|
287
|
+
score += scoring_weights.get("keyword_matching", 0.3) * weight
|
288
|
+
reasoning.append(f"Found {factor} indicator")
|
289
|
+
|
290
|
+
# Intent recognition scoring
|
291
|
+
intent_patterns = content_patterns.get("intent_recognition", {})
|
292
|
+
if analysis.intent in intent_patterns:
|
293
|
+
intent_layouts = intent_patterns[analysis.intent].get("layouts", [])
|
294
|
+
if layout_name in intent_layouts:
|
295
|
+
score += scoring_weights.get("intent_recognition", 0.2)
|
296
|
+
reasoning.append(f"Layout matches {analysis.intent} intent")
|
297
|
+
|
298
|
+
# Layout compatibility scoring
|
299
|
+
if analysis.content_blocks == 4 and "Four Columns" in layout_name:
|
300
|
+
score += scoring_weights.get("layout_compatibility", 0.1)
|
301
|
+
reasoning.append("Content blocks match four-column structure")
|
302
|
+
elif analysis.content_blocks == 3 and "Three Columns" in layout_name:
|
303
|
+
score += scoring_weights.get("layout_compatibility", 0.1)
|
304
|
+
reasoning.append("Content blocks match three-column structure")
|
305
|
+
elif analysis.has_images and "Picture" in layout_name:
|
306
|
+
score += scoring_weights.get("layout_compatibility", 0.1)
|
307
|
+
reasoning.append("Image content matches picture layout")
|
308
|
+
|
309
|
+
return min(score, 1.0), reasoning
|
310
|
+
|
311
|
+
def _generate_placeholder_mapping(
|
312
|
+
self, analysis: ContentAnalysis, layout_info: Dict
|
313
|
+
) -> Dict[str, str]:
|
314
|
+
"""Generate placeholder mapping suggestions"""
|
315
|
+
mapping = {}
|
316
|
+
placeholders = layout_info.get("placeholders", {})
|
317
|
+
content_hints = layout_info.get("content_hints", {})
|
318
|
+
|
319
|
+
for placeholder_type in ["required", "optional"]:
|
320
|
+
if placeholder_type in placeholders:
|
321
|
+
for placeholder in placeholders[placeholder_type]:
|
322
|
+
if placeholder in content_hints:
|
323
|
+
mapping[placeholder] = content_hints[placeholder]
|
324
|
+
else:
|
325
|
+
mapping[placeholder] = f"Content for {placeholder}"
|
326
|
+
|
327
|
+
return mapping
|
328
|
+
|
329
|
+
def _get_optimization_hints(self, layout_name: str, analysis: ContentAnalysis) -> List[str]:
|
330
|
+
"""Get optimization hints for the layout"""
|
331
|
+
hints = []
|
332
|
+
optimization_data = self.intelligence_data.get("optimization_hints", {})
|
333
|
+
|
334
|
+
# General content length hints
|
335
|
+
content_length = optimization_data.get("content_length", {})
|
336
|
+
for placeholder, hint in content_length.items():
|
337
|
+
hints.append(hint)
|
338
|
+
|
339
|
+
# Layout-specific hints
|
340
|
+
layout_specific = optimization_data.get("layout_specific", {})
|
341
|
+
if layout_name in layout_specific:
|
342
|
+
hints.append(layout_specific[layout_name])
|
343
|
+
|
344
|
+
# Analysis-based hints
|
345
|
+
if analysis.has_images:
|
346
|
+
hints.append("Consider using high-quality images with proper aspect ratios")
|
347
|
+
|
348
|
+
if analysis.has_numbers:
|
349
|
+
hints.append("Use consistent number formatting and consider highlighting key metrics")
|
350
|
+
|
351
|
+
return hints[:3] # Limit to top 3 hints
|
352
|
+
|
353
|
+
|
354
|
+
def test_layout_intelligence():
|
355
|
+
"""Test the layout intelligence system"""
|
356
|
+
engine = LayoutIntelligence()
|
357
|
+
|
358
|
+
test_content = """
|
359
|
+
# Feature Comparison: Our Platform vs Competition
|
360
|
+
|
361
|
+
## Performance
|
362
|
+
Our platform delivers **fast processing** with optimized algorithms
|
363
|
+
|
364
|
+
## Security
|
365
|
+
***Enterprise-grade*** encryption with SOC2 compliance
|
366
|
+
|
367
|
+
## Usability
|
368
|
+
*Intuitive* interface with minimal learning curve
|
369
|
+
## Cost
|
370
|
+
Transparent pricing with flexible plans
|
371
|
+
"""
|
372
|
+
|
373
|
+
print("Testing Layout Intelligence Engine")
|
374
|
+
print("=" * 50)
|
375
|
+
|
376
|
+
# Analyze content
|
377
|
+
analysis = engine.analyze_content(test_content)
|
378
|
+
print("Content Analysis:")
|
379
|
+
print(f" Intent: {analysis.intent}")
|
380
|
+
print(f" Content Type: {analysis.content_type}")
|
381
|
+
print(f" Structure: {analysis.structure_indicators}")
|
382
|
+
print(f" Keywords: {analysis.keywords_found}")
|
383
|
+
print(f" Content Blocks: {analysis.content_blocks}")
|
384
|
+
|
385
|
+
# Get recommendations
|
386
|
+
recommendations = engine.recommend_layouts(test_content)
|
387
|
+
print("\nLayout Recommendations:")
|
388
|
+
|
389
|
+
for i, rec in enumerate(recommendations, 1):
|
390
|
+
print(f"\n{i}. {rec.layout_name} (Confidence: {rec.confidence:.2f})")
|
391
|
+
print(f" Reasoning: {'; '.join(rec.reasoning)}")
|
392
|
+
print(f" Key Placeholders: {list(rec.placeholder_mapping.keys())[:3]}")
|
393
|
+
if rec.optimization_hints:
|
394
|
+
print(f" Hint: {rec.optimization_hints[0]}")
|
395
|
+
|
396
|
+
|
397
|
+
if __name__ == "__main__":
|
398
|
+
test_layout_intelligence()
|