jekyll-theme-zer0 0.15.2 → 0.16.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/_includes/components/preview-image.html +20 -3
- data/_includes/content/intro.html +11 -2
- data/_includes/content/seo.html +12 -1
- data/_plugins/preview_image_generator.rb +29 -8
- data/assets/images/previews/site-personalization-configuration.png +0 -0
- data/scripts/README.md +8 -1
- data/scripts/lib/preview_generator.py +164 -8
- data/scripts/update-preview-paths.sh +145 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 82d722f8c9249249da9634e5e774dddda47d6ae3888e60e8f87269d697fc2752
|
|
4
|
+
data.tar.gz: 54c07e714da9f2e718f1c057354745adb0b1490562052d3ffec0a4fcf125430d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a85d20e56b617f547beeee6e9d12571f110e606bf85e68c5773830d9ecc7f9b24c7d632f326db902792a7bf507ef3bb5eead8faff40a659112b481414293c13e
|
|
7
|
+
data.tar.gz: de2aa109131a6067d6ae045992ebca0fb0eb31cf048b336516aa426a3ab46be58d7d51b979923431bb4a8f0f42e73b110b5a60192ea094ec82ebbdf6afa14441
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.16.0] - 2025-12-20
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Configurable Assets Prefix**: New `assets_prefix` and `auto_prefix` configuration options for preview images
|
|
7
|
+
- Allows shorter frontmatter paths like `/images/previews/image.png` instead of `/assets/images/previews/image.png`
|
|
8
|
+
- Automatic path normalization in Liquid templates detects external URLs vs local paths
|
|
9
|
+
- Configured via `_config.yml` under `preview_images.assets_prefix` and `preview_images.auto_prefix`
|
|
10
|
+
- **xAI Grok Image Provider**: Added xAI as a new AI provider for preview image generation
|
|
11
|
+
- Uses `grok-2-image` model at `https://api.x.ai/v1/images/generations`
|
|
12
|
+
- Set `XAI_API_KEY` environment variable and use `--provider xai` flag
|
|
13
|
+
- **Preview Path Migration Script**: New `scripts/update-preview-paths.sh` for migrating existing frontmatter
|
|
14
|
+
- Supports dry-run and apply modes
|
|
15
|
+
- Removes `/assets/` prefix from preview paths in markdown files
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Updated `preview-image.html` include with path normalization logic
|
|
19
|
+
- Updated `intro.html` include with assets prefix support for hero backgrounds
|
|
20
|
+
- Updated `seo.html` include with assets prefix normalization for og:image meta tags
|
|
21
|
+
- Enhanced `preview_image_generator.rb` Ruby plugin with `normalize_preview_path` method
|
|
22
|
+
- Enhanced `preview_generator.py` with xAI provider, `--assets-prefix`, and `--no-auto-prefix` CLI options
|
|
23
|
+
- Migrated 24 markdown files to use shorter preview paths (without `/assets/` prefix)
|
|
24
|
+
- Updated scripts/README.md documentation with new provider and prefix options
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Critical: Liquid syntax error in `seo.html`** - Fixed invalid nested curly braces on line 27
|
|
28
|
+
|
|
29
|
+
## [0.15.5] - 2025-12-20
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Version bump: patch release
|
|
33
|
+
|
|
34
|
+
### Commits in this release
|
|
35
|
+
- d46cdb1 Merge branch 'main' of https://github.com/bamr87/zer0-mistakes
|
|
36
|
+
- 36767cd fix: update last modified timestamp in quick start guide
|
|
37
|
+
- f606096 fix: update quick start guide for clarity and consistency
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## [0.15.4] - 2025-12-20
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
- Version bump: patch release
|
|
44
|
+
|
|
45
|
+
### Commits in this release
|
|
46
|
+
- bafb5ea fix: resolve CI quality check failures for preview images
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## [0.15.3] - 2025-12-20
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
- Version bump: patch release
|
|
53
|
+
|
|
54
|
+
### Commits in this release
|
|
55
|
+
- 2f8b580 chore: merge branch and version bump to 0.15.2
|
|
56
|
+
- a239892 chore: bump version to 0.15.2
|
|
57
|
+
|
|
58
|
+
|
|
3
59
|
## [0.15.2] - 2025-12-19
|
|
4
60
|
|
|
5
61
|
### Changed
|
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
style="height: 180px; object-fit: cover;"
|
|
10
10
|
-%}
|
|
11
11
|
|
|
12
|
-
Convention:
|
|
13
|
-
|
|
12
|
+
Convention: Preview paths can omit the /assets/ prefix.
|
|
13
|
+
The component will auto-prepend the assets_prefix from _config.yml.
|
|
14
|
+
Examples:
|
|
15
|
+
- /images/previews/my-image.png → /assets/images/previews/my-image.png
|
|
16
|
+
- /assets/images/previews/my-image.png → unchanged
|
|
17
|
+
- https://example.com/image.png → unchanged (external URL)
|
|
14
18
|
{% endcomment %}
|
|
15
19
|
{% assign img = include.src | default: site.teaser %}
|
|
16
20
|
{% assign alt = include.alt | default: "Preview image" %}
|
|
@@ -18,9 +22,22 @@
|
|
|
18
22
|
{% assign style = include.style %}
|
|
19
23
|
{% assign loading = include.loading | default: "lazy" %}
|
|
20
24
|
|
|
21
|
-
{% comment %}
|
|
25
|
+
{% comment %} Get assets prefix configuration {% endcomment %}
|
|
26
|
+
{% assign assets_prefix = site.preview_images.assets_prefix | default: '/assets' %}
|
|
27
|
+
{% assign auto_prefix = site.preview_images.auto_prefix | default: true %}
|
|
28
|
+
|
|
29
|
+
{% comment %} Determine final image source {% endcomment %}
|
|
22
30
|
{% if img contains '://' %}
|
|
31
|
+
{% comment %} External URL - use as-is {% endcomment %}
|
|
23
32
|
{% assign final_src = img %}
|
|
33
|
+
{% elsif auto_prefix and img != blank %}
|
|
34
|
+
{% comment %} Check if path already has the assets prefix {% endcomment %}
|
|
35
|
+
{% if img contains assets_prefix %}
|
|
36
|
+
{% assign final_src = img | relative_url %}
|
|
37
|
+
{% else %}
|
|
38
|
+
{% comment %} Prepend assets prefix to the path {% endcomment %}
|
|
39
|
+
{% assign final_src = assets_prefix | append: img | relative_url %}
|
|
40
|
+
{% endif %}
|
|
24
41
|
{% else %}
|
|
25
42
|
{% assign final_src = img | relative_url %}
|
|
26
43
|
{% endif %}
|
|
@@ -16,12 +16,21 @@
|
|
|
16
16
|
<!-- Intro Section -->
|
|
17
17
|
|
|
18
18
|
{% comment %}
|
|
19
|
-
Determine the preview image path.
|
|
20
|
-
|
|
19
|
+
Determine the preview image path. Preview paths can omit the /assets/ prefix.
|
|
20
|
+
The component will auto-prepend the assets_prefix from _config.yml.
|
|
21
21
|
{% endcomment %}
|
|
22
22
|
{% assign preview_image = page.preview | default: site.info_banner %}
|
|
23
|
+
{% assign assets_prefix = site.preview_images.assets_prefix | default: '/assets' %}
|
|
24
|
+
{% assign auto_prefix = site.preview_images.auto_prefix | default: true %}
|
|
25
|
+
|
|
23
26
|
{% if preview_image contains '://' %}
|
|
24
27
|
{% assign preview_path = preview_image %}
|
|
28
|
+
{% elsif auto_prefix and preview_image != blank %}
|
|
29
|
+
{% if preview_image contains assets_prefix %}
|
|
30
|
+
{% assign preview_path = preview_image | relative_url %}
|
|
31
|
+
{% else %}
|
|
32
|
+
{% assign preview_path = assets_prefix | append: preview_image | relative_url %}
|
|
33
|
+
{% endif %}
|
|
25
34
|
{% else %}
|
|
26
35
|
{% assign preview_path = preview_image | relative_url %}
|
|
27
36
|
{% endif %}
|
data/_includes/content/seo.html
CHANGED
|
@@ -23,8 +23,19 @@
|
|
|
23
23
|
{%- assign page_large_image = page.header.og_image | default: page.header.overlay_image | default: page.header.image | absolute_url -%}
|
|
24
24
|
{%- assign page_large_image = page_large_image | escape -%}
|
|
25
25
|
|
|
26
|
+
{%- comment -%} Handle preview image with configurable assets prefix {%- endcomment -%}
|
|
26
27
|
{%- assign page_teaser_image = page.preview | default: site.og_image -%}
|
|
27
|
-
{%- assign
|
|
28
|
+
{%- assign assets_prefix = site.preview_images.assets_prefix | default: '/assets' -%}
|
|
29
|
+
{%- assign auto_prefix = site.preview_images.auto_prefix | default: true -%}
|
|
30
|
+
|
|
31
|
+
{%- if page_teaser_image contains '://' -%}
|
|
32
|
+
{%- comment -%} External URL - use as-is {%- endcomment -%}
|
|
33
|
+
{%- elsif auto_prefix and page_teaser_image != blank -%}
|
|
34
|
+
{%- unless page_teaser_image contains assets_prefix -%}
|
|
35
|
+
{%- assign page_teaser_image = assets_prefix | append: page_teaser_image -%}
|
|
36
|
+
{%- endunless -%}
|
|
37
|
+
{%- endif -%}
|
|
38
|
+
{%- assign page_teaser_image = page_teaser_image | absolute_url | escape -%}
|
|
28
39
|
|
|
29
40
|
{%- assign site_og_image = site.og_image | absolute_url -%}
|
|
30
41
|
{%- assign site_og_image = site_og_image | escape -%}
|
|
@@ -33,6 +33,8 @@ module Jekyll
|
|
|
33
33
|
'style' => 'retro pixel art, 8-bit video game aesthetic, vibrant colors, nostalgic, clean pixel graphics',
|
|
34
34
|
'style_modifiers' => 'pixelated, retro gaming style, CRT screen glow effect, limited color palette',
|
|
35
35
|
'output_dir' => 'assets/images/previews',
|
|
36
|
+
'assets_prefix' => '/assets',
|
|
37
|
+
'auto_prefix' => true,
|
|
36
38
|
'auto_generate' => false,
|
|
37
39
|
'collections' => ['posts', 'docs', 'quickstart']
|
|
38
40
|
}.freeze
|
|
@@ -52,18 +54,37 @@ module Jekyll
|
|
|
52
54
|
site = doc.site
|
|
53
55
|
config = self.config(site)
|
|
54
56
|
|
|
57
|
+
# Normalize the preview path using assets_prefix
|
|
58
|
+
normalized_preview = normalize_preview_path(preview, config)
|
|
59
|
+
|
|
55
60
|
# Build the full path
|
|
56
|
-
preview_path = if
|
|
57
|
-
File.join(site.source,
|
|
58
|
-
elsif
|
|
61
|
+
preview_path = if normalized_preview.start_with?('/')
|
|
62
|
+
File.join(site.source, normalized_preview.sub(/^\//, ''))
|
|
63
|
+
elsif normalized_preview.start_with?('http')
|
|
59
64
|
return true # External URL, assume it exists
|
|
60
65
|
else
|
|
61
|
-
File.join(site.source, config['output_dir'],
|
|
66
|
+
File.join(site.source, config['output_dir'], normalized_preview)
|
|
62
67
|
end
|
|
63
68
|
|
|
64
69
|
File.exist?(preview_path)
|
|
65
70
|
end
|
|
66
71
|
|
|
72
|
+
# Normalize a preview path by adding assets_prefix if needed
|
|
73
|
+
def self.normalize_preview_path(preview, config)
|
|
74
|
+
return preview if preview.nil? || preview.to_s.strip.empty?
|
|
75
|
+
return preview if preview.start_with?('http')
|
|
76
|
+
|
|
77
|
+
assets_prefix = config['assets_prefix'] || '/assets'
|
|
78
|
+
auto_prefix = config['auto_prefix'] != false # Default to true
|
|
79
|
+
|
|
80
|
+
# If auto_prefix is enabled and path doesn't already contain assets_prefix
|
|
81
|
+
if auto_prefix && !preview.include?(assets_prefix)
|
|
82
|
+
"#{assets_prefix}#{preview}"
|
|
83
|
+
else
|
|
84
|
+
preview
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
67
88
|
# Get the preview image path for a document
|
|
68
89
|
def self.preview_path(doc)
|
|
69
90
|
preview = doc.data['preview']
|
|
@@ -72,11 +93,11 @@ module Jekyll
|
|
|
72
93
|
site = doc.site
|
|
73
94
|
config = self.config(site)
|
|
74
95
|
|
|
75
|
-
# If it's
|
|
76
|
-
return preview if preview.start_with?('
|
|
96
|
+
# If it's an external URL, return as-is
|
|
97
|
+
return preview if preview.start_with?('http')
|
|
77
98
|
|
|
78
|
-
#
|
|
79
|
-
|
|
99
|
+
# Normalize path with assets_prefix
|
|
100
|
+
normalize_preview_path(preview, config)
|
|
80
101
|
end
|
|
81
102
|
|
|
82
103
|
# Get list of documents missing preview images
|
|
Binary file
|
data/scripts/README.md
CHANGED
|
@@ -91,7 +91,14 @@ Options:
|
|
|
91
91
|
--dry-run Preview without changes
|
|
92
92
|
--collection TYPE Generate for specific collection (posts, docs, etc.)
|
|
93
93
|
-f, --file PATH Process specific file
|
|
94
|
-
--provider PROVIDER Use specific AI provider (openai)
|
|
94
|
+
--provider PROVIDER Use specific AI provider (openai, stability, xai)
|
|
95
|
+
--assets-prefix Custom assets path prefix (default: /assets)
|
|
96
|
+
--no-auto-prefix Disable automatic path prefixing
|
|
97
|
+
|
|
98
|
+
AI Providers:
|
|
99
|
+
openai - OpenAI DALL-E (requires OPENAI_API_KEY)
|
|
100
|
+
stability - Stability AI (requires STABILITY_API_KEY)
|
|
101
|
+
xai - xAI Grok image generation (requires XAI_API_KEY)
|
|
95
102
|
```
|
|
96
103
|
|
|
97
104
|
#### `install-preview-generator`
|
|
@@ -101,6 +101,8 @@ class PreviewGenerator:
|
|
|
101
101
|
output_dir: str = "assets/images/previews",
|
|
102
102
|
image_style: str = "digital art, professional blog illustration",
|
|
103
103
|
image_size: str = "1024x1024",
|
|
104
|
+
assets_prefix: str = "/assets",
|
|
105
|
+
auto_prefix: bool = True,
|
|
104
106
|
dry_run: bool = False,
|
|
105
107
|
verbose: bool = False,
|
|
106
108
|
force: bool = False,
|
|
@@ -110,6 +112,8 @@ class PreviewGenerator:
|
|
|
110
112
|
self.output_dir = project_root / output_dir
|
|
111
113
|
self.image_style = image_style
|
|
112
114
|
self.image_size = image_size
|
|
115
|
+
self.assets_prefix = assets_prefix
|
|
116
|
+
self.auto_prefix = auto_prefix
|
|
113
117
|
self.dry_run = dry_run
|
|
114
118
|
self.verbose = verbose
|
|
115
119
|
self.force = force
|
|
@@ -129,6 +133,27 @@ class PreviewGenerator:
|
|
|
129
133
|
if self.verbose:
|
|
130
134
|
log(msg, "debug")
|
|
131
135
|
|
|
136
|
+
def normalize_preview_path(self, preview_path: Optional[str]) -> Optional[str]:
|
|
137
|
+
"""Normalize a preview path by adding assets_prefix if needed.
|
|
138
|
+
|
|
139
|
+
This allows users to omit the /assets/ prefix in frontmatter:
|
|
140
|
+
- /images/previews/my-image.png -> /assets/images/previews/my-image.png
|
|
141
|
+
- /assets/images/previews/my-image.png -> unchanged
|
|
142
|
+
- https://example.com/image.png -> unchanged (external URL)
|
|
143
|
+
"""
|
|
144
|
+
if not preview_path:
|
|
145
|
+
return preview_path
|
|
146
|
+
|
|
147
|
+
# External URLs pass through unchanged
|
|
148
|
+
if preview_path.startswith('http://') or preview_path.startswith('https://'):
|
|
149
|
+
return preview_path
|
|
150
|
+
|
|
151
|
+
# If auto_prefix is enabled and path doesn't contain assets_prefix
|
|
152
|
+
if self.auto_prefix and self.assets_prefix not in preview_path:
|
|
153
|
+
return f"{self.assets_prefix}{preview_path}"
|
|
154
|
+
|
|
155
|
+
return preview_path
|
|
156
|
+
|
|
132
157
|
def parse_front_matter(self, file_path: Path) -> Optional[ContentFile]:
|
|
133
158
|
"""Parse front matter and content from a markdown file."""
|
|
134
159
|
try:
|
|
@@ -178,19 +203,19 @@ class PreviewGenerator:
|
|
|
178
203
|
if not preview_path:
|
|
179
204
|
return False
|
|
180
205
|
|
|
206
|
+
# Normalize the path first (adds assets_prefix if needed)
|
|
207
|
+
normalized_path = self.normalize_preview_path(preview_path)
|
|
208
|
+
if not normalized_path:
|
|
209
|
+
return False
|
|
210
|
+
|
|
181
211
|
# Handle absolute and relative paths
|
|
182
|
-
clean_path =
|
|
212
|
+
clean_path = normalized_path.lstrip('/')
|
|
183
213
|
|
|
184
214
|
# Check direct path
|
|
185
215
|
full_path = self.project_root / clean_path
|
|
186
216
|
if full_path.exists():
|
|
187
217
|
return True
|
|
188
218
|
|
|
189
|
-
# Check in assets directory
|
|
190
|
-
assets_path = self.project_root / 'assets' / clean_path
|
|
191
|
-
if assets_path.exists():
|
|
192
|
-
return True
|
|
193
|
-
|
|
194
219
|
return False
|
|
195
220
|
|
|
196
221
|
def generate_prompt(self, content: ContentFile) -> str:
|
|
@@ -395,12 +420,131 @@ class PreviewGenerator:
|
|
|
395
420
|
prompt_used=prompt,
|
|
396
421
|
)
|
|
397
422
|
|
|
423
|
+
def generate_image_xai(self, prompt: str, output_path: Path) -> GenerationResult:
|
|
424
|
+
"""Generate image using xAI Grok API."""
|
|
425
|
+
if not HAS_REQUESTS:
|
|
426
|
+
return GenerationResult(
|
|
427
|
+
success=False,
|
|
428
|
+
image_path=None,
|
|
429
|
+
preview_url=None,
|
|
430
|
+
error="requests package not installed. Run: pip install requests",
|
|
431
|
+
prompt_used=prompt,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
api_key = os.environ.get('XAI_API_KEY')
|
|
435
|
+
if not api_key:
|
|
436
|
+
return GenerationResult(
|
|
437
|
+
success=False,
|
|
438
|
+
image_path=None,
|
|
439
|
+
preview_url=None,
|
|
440
|
+
error="XAI_API_KEY environment variable not set",
|
|
441
|
+
prompt_used=prompt,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
try:
|
|
445
|
+
# xAI has a max prompt length of 1024 characters
|
|
446
|
+
truncated_prompt = prompt[:1000] if len(prompt) > 1000 else prompt
|
|
447
|
+
self.debug(f"Generating with xAI Grok, prompt: {truncated_prompt[:200]}...")
|
|
448
|
+
|
|
449
|
+
# xAI uses OpenAI-compatible API format
|
|
450
|
+
response = requests.post(
|
|
451
|
+
"https://api.x.ai/v1/images/generations",
|
|
452
|
+
headers={
|
|
453
|
+
"Authorization": f"Bearer {api_key}",
|
|
454
|
+
"Content-Type": "application/json",
|
|
455
|
+
},
|
|
456
|
+
json={
|
|
457
|
+
"model": "grok-2-image",
|
|
458
|
+
"prompt": truncated_prompt,
|
|
459
|
+
"n": 1,
|
|
460
|
+
},
|
|
461
|
+
timeout=120, # 2 minute timeout for image generation
|
|
462
|
+
)
|
|
463
|
+
response.raise_for_status()
|
|
464
|
+
|
|
465
|
+
data = response.json()
|
|
466
|
+
|
|
467
|
+
# xAI returns base64-encoded images
|
|
468
|
+
if 'data' not in data or not data['data']:
|
|
469
|
+
return GenerationResult(
|
|
470
|
+
success=False,
|
|
471
|
+
image_path=None,
|
|
472
|
+
preview_url=None,
|
|
473
|
+
error="No image data in response",
|
|
474
|
+
prompt_used=prompt,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
image_data = data['data'][0]
|
|
478
|
+
|
|
479
|
+
# Check if it's a URL or base64
|
|
480
|
+
if 'url' in image_data:
|
|
481
|
+
# Download from URL
|
|
482
|
+
img_response = requests.get(image_data['url'], timeout=60)
|
|
483
|
+
img_response.raise_for_status()
|
|
484
|
+
output_path.write_bytes(img_response.content)
|
|
485
|
+
elif 'b64_json' in image_data:
|
|
486
|
+
# Decode base64
|
|
487
|
+
import base64
|
|
488
|
+
image_bytes = base64.b64decode(image_data['b64_json'])
|
|
489
|
+
output_path.write_bytes(image_bytes)
|
|
490
|
+
else:
|
|
491
|
+
return GenerationResult(
|
|
492
|
+
success=False,
|
|
493
|
+
image_path=None,
|
|
494
|
+
preview_url=None,
|
|
495
|
+
error="Unexpected response format from xAI",
|
|
496
|
+
prompt_used=prompt,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
return GenerationResult(
|
|
500
|
+
success=True,
|
|
501
|
+
image_path=str(output_path),
|
|
502
|
+
preview_url=str(output_path.relative_to(self.project_root)),
|
|
503
|
+
error=None,
|
|
504
|
+
prompt_used=prompt,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
except requests.exceptions.HTTPError as e:
|
|
508
|
+
error_msg = str(e)
|
|
509
|
+
try:
|
|
510
|
+
error_data = e.response.json()
|
|
511
|
+
self.debug(f"xAI error response: {error_data}")
|
|
512
|
+
if 'error' in error_data:
|
|
513
|
+
error_msg = error_data['error'].get('message', str(error_data['error']))
|
|
514
|
+
elif 'detail' in error_data:
|
|
515
|
+
error_msg = str(error_data['detail'])
|
|
516
|
+
else:
|
|
517
|
+
error_msg = str(error_data)
|
|
518
|
+
except:
|
|
519
|
+
# Try to get raw text
|
|
520
|
+
try:
|
|
521
|
+
error_msg = e.response.text[:500]
|
|
522
|
+
except:
|
|
523
|
+
pass
|
|
524
|
+
return GenerationResult(
|
|
525
|
+
success=False,
|
|
526
|
+
image_path=None,
|
|
527
|
+
preview_url=None,
|
|
528
|
+
error=f"xAI API error: {error_msg}",
|
|
529
|
+
prompt_used=prompt,
|
|
530
|
+
)
|
|
531
|
+
except Exception as e:
|
|
532
|
+
return GenerationResult(
|
|
533
|
+
success=False,
|
|
534
|
+
image_path=None,
|
|
535
|
+
preview_url=None,
|
|
536
|
+
error=str(e),
|
|
537
|
+
prompt_used=prompt,
|
|
538
|
+
)
|
|
539
|
+
|
|
398
540
|
def generate_image(self, prompt: str, output_path: Path) -> GenerationResult:
|
|
399
541
|
"""Generate image using configured provider."""
|
|
400
542
|
if self.provider == "openai":
|
|
401
543
|
return self.generate_image_openai(prompt, output_path)
|
|
402
544
|
elif self.provider == "stability":
|
|
403
545
|
return self.generate_image_stability(prompt, output_path)
|
|
546
|
+
elif self.provider == "xai":
|
|
547
|
+
return self.generate_image_xai(prompt, output_path)
|
|
404
548
|
else:
|
|
405
549
|
return GenerationResult(
|
|
406
550
|
success=False,
|
|
@@ -559,9 +703,9 @@ def main():
|
|
|
559
703
|
)
|
|
560
704
|
parser.add_argument(
|
|
561
705
|
'-p', '--provider',
|
|
562
|
-
choices=['openai', 'stability'],
|
|
706
|
+
choices=['openai', 'stability', 'xai'],
|
|
563
707
|
default='openai',
|
|
564
|
-
help="AI provider for image generation"
|
|
708
|
+
help="AI provider for image generation (openai, stability, xai)"
|
|
565
709
|
)
|
|
566
710
|
parser.add_argument(
|
|
567
711
|
'-d', '--dry-run',
|
|
@@ -593,6 +737,16 @@ def main():
|
|
|
593
737
|
default='digital art, professional blog illustration, clean design',
|
|
594
738
|
help="Image style prompt"
|
|
595
739
|
)
|
|
740
|
+
parser.add_argument(
|
|
741
|
+
'--assets-prefix',
|
|
742
|
+
default='/assets',
|
|
743
|
+
help="Prefix to prepend to relative preview paths (default: /assets)"
|
|
744
|
+
)
|
|
745
|
+
parser.add_argument(
|
|
746
|
+
'--no-auto-prefix',
|
|
747
|
+
action='store_true',
|
|
748
|
+
help="Disable automatic assets prefix prepending"
|
|
749
|
+
)
|
|
596
750
|
|
|
597
751
|
args = parser.parse_args()
|
|
598
752
|
|
|
@@ -606,6 +760,8 @@ def main():
|
|
|
606
760
|
provider=args.provider,
|
|
607
761
|
output_dir=args.output_dir,
|
|
608
762
|
image_style=args.style,
|
|
763
|
+
assets_prefix=args.assets_prefix,
|
|
764
|
+
auto_prefix=not args.no_auto_prefix,
|
|
609
765
|
dry_run=args.dry_run,
|
|
610
766
|
verbose=args.verbose,
|
|
611
767
|
force=args.force,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# update-preview-paths.sh
|
|
4
|
+
#
|
|
5
|
+
# Updates all preview paths in markdown frontmatter to remove the /assets/ prefix.
|
|
6
|
+
# This aligns with the new auto_prefix feature that automatically prepends /assets/.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ./scripts/update-preview-paths.sh # Dry run (preview changes)
|
|
10
|
+
# ./scripts/update-preview-paths.sh --apply # Apply changes
|
|
11
|
+
#
|
|
12
|
+
# Example transformation:
|
|
13
|
+
# preview: /assets/images/previews/my-image.png
|
|
14
|
+
# becomes:
|
|
15
|
+
# preview: /images/previews/my-image.png
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
# Colors for output
|
|
21
|
+
RED='\033[0;31m'
|
|
22
|
+
GREEN='\033[0;32m'
|
|
23
|
+
YELLOW='\033[1;33m'
|
|
24
|
+
BLUE='\033[0;34m'
|
|
25
|
+
CYAN='\033[0;36m'
|
|
26
|
+
NC='\033[0m' # No Color
|
|
27
|
+
|
|
28
|
+
# Script directory
|
|
29
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
30
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
31
|
+
|
|
32
|
+
# Counters
|
|
33
|
+
TOTAL_FILES=0
|
|
34
|
+
MODIFIED_FILES=0
|
|
35
|
+
SKIPPED_FILES=0
|
|
36
|
+
|
|
37
|
+
# Mode
|
|
38
|
+
DRY_RUN=true
|
|
39
|
+
|
|
40
|
+
# Parse arguments
|
|
41
|
+
for arg in "$@"; do
|
|
42
|
+
case $arg in
|
|
43
|
+
--apply)
|
|
44
|
+
DRY_RUN=false
|
|
45
|
+
shift
|
|
46
|
+
;;
|
|
47
|
+
--help|-h)
|
|
48
|
+
echo "Usage: $0 [--apply]"
|
|
49
|
+
echo ""
|
|
50
|
+
echo "Updates preview paths in markdown frontmatter to remove /assets/ prefix."
|
|
51
|
+
echo ""
|
|
52
|
+
echo "Options:"
|
|
53
|
+
echo " --apply Apply changes (default is dry run)"
|
|
54
|
+
echo " --help Show this help message"
|
|
55
|
+
exit 0
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
log_info() {
|
|
61
|
+
echo -e "${BLUE}[INFO]${NC} $1"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
log_success() {
|
|
65
|
+
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
log_warning() {
|
|
69
|
+
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
log_change() {
|
|
73
|
+
echo -e "${CYAN}[CHANGE]${NC} $1"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
echo ""
|
|
77
|
+
echo -e "${BLUE}========================================${NC}"
|
|
78
|
+
echo -e "${BLUE}🔄 Preview Path Updater${NC}"
|
|
79
|
+
echo -e "${BLUE}========================================${NC}"
|
|
80
|
+
echo ""
|
|
81
|
+
|
|
82
|
+
if [ "$DRY_RUN" = true ]; then
|
|
83
|
+
log_warning "DRY RUN MODE - No files will be modified"
|
|
84
|
+
log_info "Run with --apply to make changes"
|
|
85
|
+
else
|
|
86
|
+
log_warning "APPLY MODE - Files will be modified"
|
|
87
|
+
fi
|
|
88
|
+
echo ""
|
|
89
|
+
|
|
90
|
+
# Find all markdown files in pages directory
|
|
91
|
+
find "$PROJECT_ROOT/pages" -name "*.md" -type f | while read -r file; do
|
|
92
|
+
TOTAL_FILES=$((TOTAL_FILES + 1))
|
|
93
|
+
|
|
94
|
+
# Check if file has a preview field with /assets/ prefix
|
|
95
|
+
if grep -q "^preview: /assets/" "$file" 2>/dev/null; then
|
|
96
|
+
# Extract current preview path
|
|
97
|
+
current_path=$(grep "^preview:" "$file" | head -1 | sed 's/^preview: //')
|
|
98
|
+
|
|
99
|
+
# Remove /assets/ prefix
|
|
100
|
+
new_path=$(echo "$current_path" | sed 's|^/assets/|/|')
|
|
101
|
+
|
|
102
|
+
log_change "$file"
|
|
103
|
+
echo " Old: $current_path"
|
|
104
|
+
echo " New: $new_path"
|
|
105
|
+
|
|
106
|
+
if [ "$DRY_RUN" = false ]; then
|
|
107
|
+
# Use sed to replace the preview line
|
|
108
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
109
|
+
# macOS sed requires empty string for -i
|
|
110
|
+
sed -i '' "s|^preview: /assets/|preview: /|" "$file"
|
|
111
|
+
else
|
|
112
|
+
# Linux sed
|
|
113
|
+
sed -i "s|^preview: /assets/|preview: /|" "$file"
|
|
114
|
+
fi
|
|
115
|
+
log_success "Updated: $file"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
MODIFIED_FILES=$((MODIFIED_FILES + 1))
|
|
119
|
+
else
|
|
120
|
+
SKIPPED_FILES=$((SKIPPED_FILES + 1))
|
|
121
|
+
fi
|
|
122
|
+
done
|
|
123
|
+
|
|
124
|
+
echo ""
|
|
125
|
+
echo -e "${CYAN}========================================${NC}"
|
|
126
|
+
echo -e "${CYAN}📊 Summary${NC}"
|
|
127
|
+
echo -e "${CYAN}========================================${NC}"
|
|
128
|
+
|
|
129
|
+
# Re-count since subshell doesn't persist variables
|
|
130
|
+
MODIFIED_COUNT=$(find "$PROJECT_ROOT/pages" -name "*.md" -type f -exec grep -l "^preview: /assets/" {} \; 2>/dev/null | wc -l | tr -d ' ')
|
|
131
|
+
TOTAL_COUNT=$(find "$PROJECT_ROOT/pages" -name "*.md" -type f | wc -l | tr -d ' ')
|
|
132
|
+
|
|
133
|
+
if [ "$DRY_RUN" = true ]; then
|
|
134
|
+
echo " Files to update: $MODIFIED_COUNT"
|
|
135
|
+
echo " Total markdown files: $TOTAL_COUNT"
|
|
136
|
+
echo ""
|
|
137
|
+
log_info "Run with --apply to make these changes"
|
|
138
|
+
else
|
|
139
|
+
# After applying, count should be 0
|
|
140
|
+
REMAINING=$(find "$PROJECT_ROOT/pages" -name "*.md" -type f -exec grep -l "^preview: /assets/" {} \; 2>/dev/null | wc -l | tr -d ' ')
|
|
141
|
+
echo " Files updated: $MODIFIED_COUNT"
|
|
142
|
+
echo " Files remaining: $REMAINING"
|
|
143
|
+
echo " Total markdown files: $TOTAL_COUNT"
|
|
144
|
+
fi
|
|
145
|
+
echo ""
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-theme-zer0
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amr Abdel
|
|
@@ -183,6 +183,7 @@ files:
|
|
|
183
183
|
- assets/images/previews/published-documentation-library.png
|
|
184
184
|
- assets/images/previews/quantum-computing-explained-from-qubits-to-quantum.png
|
|
185
185
|
- assets/images/previews/science.png
|
|
186
|
+
- assets/images/previews/site-personalization-configuration.png
|
|
186
187
|
- assets/images/previews/technology.png
|
|
187
188
|
- assets/images/previews/the-complete-guide-to-startup-funding-in-2025.png
|
|
188
189
|
- assets/images/previews/the-remote-work-revolution-how-global-teams-are-re.png
|
|
@@ -244,6 +245,7 @@ files:
|
|
|
244
245
|
- scripts/test/lib/test_validation.sh
|
|
245
246
|
- scripts/test/lib/test_version.sh
|
|
246
247
|
- scripts/test/theme/validate
|
|
248
|
+
- scripts/update-preview-paths.sh
|
|
247
249
|
- scripts/utils/analyze-commits
|
|
248
250
|
- scripts/utils/fix-markdown
|
|
249
251
|
- scripts/utils/setup
|