Skip to content

Commit f00923e

Browse files
authored
Merge pull request #32 from hobbytp/feature/auto-ai-cover-generation
feat: Add automatic AI cover generation for new blog articles
2 parents 5e2f555 + e5d88f7 commit f00923e

File tree

1,786 files changed

+1901
-59
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,786 files changed

+1901
-59
lines changed

.github/workflows/generate-blog-images.yml

Lines changed: 191 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,41 @@
11
name: Generate Blog Images
22

33
on:
4-
workflow_dispatch: # 只支持手动触发
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'content/**/*.md'
9+
workflow_dispatch:
10+
inputs:
11+
image_service:
12+
description: 'AI service to use'
13+
required: false
14+
default: 'modelscope'
15+
type: choice
16+
options:
17+
- modelscope
18+
- gemini
19+
- both
20+
target_type:
21+
description: 'Generation target'
22+
required: false
23+
default: 'both'
24+
type: choice
25+
options:
26+
- covers
27+
- articles
28+
- both
29+
force_regenerate:
30+
description: 'Force regenerate existing images'
31+
required: false
32+
default: false
33+
type: boolean
34+
auto_generate:
35+
description: 'Auto-generate for new/updated articles only'
36+
required: false
37+
default: true
38+
type: boolean
539

640
permissions:
741
contents: write
@@ -26,27 +60,57 @@ jobs:
2660
run: |
2761
cd scripts
2862
pip install -r requirements.txt
63+
pip install requests pillow python-dotenv
2964
3065
- name: Detect changed blog files
3166
id: detect-changes
3267
run: |
3368
# 获取本次提交修改的.md文件
3469
if [ "${{ github.event_name }}" = "push" ]; then
3570
if [ -z "${{ github.event.before }}" ]; then
36-
# No previous commit to diff against (e.g., first commit), so no changed files
37-
CHANGED_FILES=""
71+
# First commit, process all content files
72+
CHANGED_FILES=$(find content -name "*.md" -not -path "*/daily_ai/*" | tr '\n' ' ')
73+
echo "First commit detected, processing all files"
3874
else
39-
CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep '\.md$' | grep '^content/' || true)
75+
# Get changed .md files, excluding daily_ai
76+
CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep '\.md$' | grep '^content/' | grep -v 'daily_ai' || true)
77+
echo "Push detected, analyzing changed files"
4078
fi
4179
else
4280
# 手动触发时处理所有没有图片的博客
4381
CHANGED_FILES=""
82+
echo "Manual workflow triggered"
83+
fi
84+
85+
# Filter for files that need AI covers (have description but no ai_cover)
86+
NEED_AI_COVER=""
87+
if [ -n "$CHANGED_FILES" ]; then
88+
for file in $CHANGED_FILES; do
89+
if [ -f "$file" ]; then
90+
# Check if file has description
91+
if grep -q "^description:" "$file"; then
92+
# Check if file doesn't have ai_cover or ai_generated: true
93+
if ! grep -q "ai_cover:" "$file" || ! grep -q "ai_generated: true" "$file"; then
94+
NEED_AI_COVER="$NEED_AI_COVER $file"
95+
echo "Need AI cover: $file"
96+
else
97+
echo "Already has AI cover: $file"
98+
fi
99+
else
100+
echo "No description, skipping: $file"
101+
fi
102+
fi
103+
done
44104
fi
45105
46106
echo "changed_files<<EOF" >> $GITHUB_OUTPUT
47107
echo "$CHANGED_FILES" >> $GITHUB_OUTPUT
48108
echo "EOF" >> $GITHUB_OUTPUT
49109
110+
echo "need_ai_cover<<EOF" >> $GITHUB_OUTPUT
111+
echo "$NEED_AI_COVER" >> $GITHUB_OUTPUT
112+
echo "EOF" >> $GITHUB_OUTPUT
113+
50114
- name: Generate images for new blogs
51115
if: steps.detect-changes.outputs.changed_files != ''
52116
env:
@@ -72,42 +136,152 @@ jobs:
72136
cd scripts
73137
python generate_image.py --backfill
74138
139+
- name: Auto-generate ModelScope AI Covers for new blogs
140+
if: github.event_name == 'push' && steps.detect-changes.outputs.need_ai_cover != ''
141+
env:
142+
MODELSCOPE_API_KEY: ${{ secrets.MODELSCOPE_API_KEY }}
143+
TEXT2IMAGE_PROVIDER: modelscope
144+
WORKFLOW_MODE: true
145+
FORCE_REGENERATE: false
146+
run: |
147+
cd scripts
148+
echo "🤖 Auto-generating ModelScope AI covers for new blogs..."
149+
echo "Files needing AI covers: ${{ steps.detect-changes.outputs.need_ai_cover }}"
150+
151+
# Process files that need AI covers (limit to avoid API overload)
152+
NEED_COVER_FILES="${{ steps.detect-changes.outputs.need_ai_cover }}"
153+
file_count=0
154+
max_files=5 # Limit per push to avoid API overload
155+
156+
for file in $NEED_COVER_FILES; do
157+
if [ -f "$file" ] && [ $file_count -lt $max_files ]; then
158+
echo "Processing: $file"
159+
python ai_cover_generator.py --workflow-mode --target=covers --specific-file="$file"
160+
file_count=$((file_count + 1))
161+
elif [ $file_count -ge $max_files ]; then
162+
echo "Reached limit of $max_files files, skipping remaining files"
163+
break
164+
fi
165+
done
166+
167+
echo "✅ Auto-generation completed: processed $file_count files"
168+
169+
- name: Generate ModelScope AI Covers (manual trigger)
170+
if: github.event_name == 'workflow_dispatch' && (contains(github.event.inputs.image_service, 'modelscope') || github.event.inputs.image_service == 'both')
171+
env:
172+
MODELSCOPE_API_KEY: ${{ secrets.MODELSCOPE_API_KEY }}
173+
TEXT2IMAGE_PROVIDER: modelscope
174+
WORKFLOW_MODE: true
175+
FORCE_REGENERATE: ${{ github.event.inputs.force_regenerate }}
176+
run: |
177+
cd scripts
178+
echo "🤖 Generating ModelScope AI covers..."
179+
echo "Target: ${{ github.event.inputs.target_type }}"
180+
echo "Force regenerate: $FORCE_REGENERATE"
181+
182+
# Generate covers if target includes covers
183+
if [[ "${{ github.event.inputs.target_type }}" == "covers" || "${{ github.event.inputs.target_type }}" == "both" ]]; then
184+
python ai_cover_generator.py --workflow-mode --target=covers --force=${FORCE_REGENERATE}
185+
else
186+
echo "Skipping cover generation (target: articles only)"
187+
fi
188+
75189
- name: Create images directory
76190
run: |
77191
mkdir -p static/images/articles
78-
echo "Created static/images/articles directory"
192+
mkdir -p static/images/generated-covers
193+
echo "Created image directories"
79194
80195
- name: Commit generated images
81196
if: always()
82197
env:
83198
GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}
84199
run: |
85-
# 检查是否有新的图片文件
200+
git config --local user.email "action@github.com"
201+
git config --local user.name "GitHub Action"
202+
git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git
203+
204+
has_changes=false
205+
206+
# Check for article images
86207
if [ -d "static/images/articles" ] && [ "$(ls -A static/images/articles 2>/dev/null)" ]; then
87-
echo "Found new images to commit"
88-
git config --local user.email "action@github.com"
89-
git config --local user.name "GitHub Action"
90-
git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git
208+
echo "Found new article images to commit"
91209
git add static/images/articles/
92-
git commit -m "Auto-generate blog images [skip ci]" || echo "No changes to commit"
210+
has_changes=true
211+
fi
212+
213+
# Check for generated covers
214+
if [ -d "static/images/generated-covers" ] && [ "$(ls -A static/images/generated-covers 2>/dev/null)" ]; then
215+
echo "Found new AI-generated covers to commit"
216+
git add static/images/generated-covers/
217+
has_changes=true
218+
fi
219+
220+
# Commit if there are changes
221+
if [ "$has_changes" = true ]; then
222+
git commit -m "🤖 Auto-generate blog images and AI covers [skip ci]" || echo "No new changes to commit"
93223
git push
224+
echo "✅ Images committed successfully"
94225
else
95-
echo "No new images to commit"
226+
echo "ℹ️ No new images to commit"
96227
fi
97228
98229
- name: Create summary
99230
if: always()
100231
run: |
101232
echo "## Blog Image Generation Summary" >> $GITHUB_STEP_SUMMARY
102233
echo "" >> $GITHUB_STEP_SUMMARY
234+
echo "### 📋 Configuration" >> $GITHUB_STEP_SUMMARY
103235
echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
236+
echo "**Image Service:** ${{ github.event.inputs.image_service || 'N/A' }}" >> $GITHUB_STEP_SUMMARY
237+
echo "**Target Type:** ${{ github.event.inputs.target_type || 'N/A' }}" >> $GITHUB_STEP_SUMMARY
238+
echo "**Force Regenerate:** ${{ github.event.inputs.force_regenerate || 'false' }}" >> $GITHUB_STEP_SUMMARY
104239
echo "**Changed files:** ${{ steps.detect-changes.outputs.changed_files }}" >> $GITHUB_STEP_SUMMARY
105240
echo "" >> $GITHUB_STEP_SUMMARY
106-
echo "### Generated Images:" >> $GITHUB_STEP_SUMMARY
107-
if [ -d "static/images/articles" ]; then
108-
ls -la static/images/articles/ | tail -n +2 | while read line; do
109-
echo "- $line" >> $GITHUB_STEP_SUMMARY
241+
242+
echo "### 🖼️ Generated Article Images" >> $GITHUB_STEP_SUMMARY
243+
if [ -d "static/images/articles" ] && [ "$(ls -A static/images/articles 2>/dev/null)" ]; then
244+
echo "| Filename | Size | Type |" >> $GITHUB_STEP_SUMMARY
245+
echo "|----------|------|------|" >> $GITHUB_STEP_SUMMARY
246+
cd static/images/articles
247+
ls -lh *.jpg *.png *.webp 2>/dev/null | while read -r line; do
248+
filename=$(echo $line | awk '{print $9}')
249+
size=$(echo $line | awk '{print $5}')
250+
echo "| $filename | $size | Image |" >> $GITHUB_STEP_SUMMARY
251+
done
252+
else
253+
echo "No article images generated" >> $GITHUB_STEP_SUMMARY
254+
fi
255+
echo "" >> $GITHUB_STEP_SUMMARY
256+
257+
echo "### 🤖 Generated AI Covers" >> $GITHUB_STEP_SUMMARY
258+
if [ -d "static/images/generated-covers" ] && [ "$(ls -A static/images/generated-covers 2>/dev/null)" ]; then
259+
echo "| Filename | Size | AI Service |" >> $GITHUB_STEP_SUMMARY
260+
echo "|----------|------|-----------|" >> $GITHUB_STEP_SUMMARY
261+
cd static/images/generated-covers
262+
ls -lh *.jpg *.png *.webp 2>/dev/null | while read -r line; do
263+
filename=$(echo $line | awk '{print $9}')
264+
size=$(echo $line | awk '{print $5}')
265+
echo "| $filename | $size | ModelScope |" >> $GITHUB_STEP_SUMMARY
110266
done
111267
else
112-
echo "No images directory found" >> $GITHUB_STEP_SUMMARY
268+
echo "No AI covers generated" >> $GITHUB_STEP_SUMMARY
269+
fi
270+
echo "" >> $GITHUB_STEP_SUMMARY
271+
272+
# Add generation statistics
273+
article_count=$(find static/images/articles -name "*.jpg" -o -name "*.png" -o -name "*.webp" 2>/dev/null | wc -l)
274+
cover_count=$(find static/images/generated-covers -name "*.jpg" -o -name "*.png" -o -name "*.webp" 2>/dev/null | wc -l)
275+
276+
echo "### 📊 Statistics" >> $GITHUB_STEP_SUMMARY
277+
echo "**Article Images:** $article_count files" >> $GITHUB_STEP_SUMMARY
278+
echo "**AI Covers:** $cover_count files" >> $GITHUB_STEP_SUMMARY
279+
echo "**Total Images:** $((article_count + cover_count)) files" >> $GITHUB_STEP_SUMMARY
280+
echo "" >> $GITHUB_STEP_SUMMARY
281+
282+
echo "### ✅ Status" >> $GITHUB_STEP_SUMMARY
283+
if [ $((article_count + cover_count)) -gt 0 ]; then
284+
echo "🎉 **Generation Successful**: Images were generated and committed" >> $GITHUB_STEP_SUMMARY
285+
else
286+
echo "ℹ️ **No Generation**: No new images were generated" >> $GITHUB_STEP_SUMMARY
113287
fi

.github/workflows/image-optimization.yml

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ on:
99
- "!static/images/optimized/**"
1010
- "!static/images/backup/**"
1111
pull_request:
12+
branches:
13+
- main
1214
paths:
1315
- "static/images/**"
1416
- "!static/images/optimized/**"
@@ -45,8 +47,12 @@ jobs:
4547
restore-keys: |
4648
${{ runner.os }}-pip-image-optimization-
4749
48-
- name: Install Python dependencies
50+
- name: Install dependencies
4951
run: |
52+
# Install jq for JSON parsing
53+
sudo apt-get update && sudo apt-get install -y jq
54+
55+
# Install Python dependencies
5056
python -m pip install --upgrade pip
5157
pip install -r tools/image-optimization/requirements.txt
5258
@@ -88,5 +94,26 @@ jobs:
8894
GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}
8995
run: |
9096
git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git
91-
git push origin main
97+
98+
# 智能推送逻辑
99+
if [[ "${{ github.ref_name }}" == "main" ]]; then
100+
echo "Pushing to main branch"
101+
git push origin main
102+
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
103+
echo "Pull request detected, pushing to source branch instead"
104+
# 获取PR的源分支名
105+
SOURCE_BRANCH=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
106+
https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.number }} | \
107+
jq -r '.head.ref')
108+
echo "Source branch: $SOURCE_BRANCH"
109+
if [[ "$SOURCE_BRANCH" != "null" && "$SOURCE_BRANCH" != "" ]]; then
110+
git push origin HEAD:$SOURCE_BRANCH
111+
else
112+
echo "Could not determine source branch, skipping push"
113+
fi
114+
else
115+
echo "Pushing to current branch"
116+
git push origin HEAD:${{ github.ref_name }}
117+
fi
118+
92119
echo "推送优化后的图片到仓库"

CLAUDE.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,42 @@ make full-build
3333
# AI-enhanced build pipeline
3434
make full-build-ai
3535

36+
# Generate AI covers for articles
37+
make generate-ai-covers
38+
3639
# Clean build artifacts
3740
make clean
3841
```
3942

43+
### AI Cover Generation
44+
45+
The blog features automated AI cover generation using ModelScope Qwen-image:
46+
47+
**🆕 Automatic Generation (Recommended):**
48+
```bash
49+
# Simply commit new articles, AI covers are generated automatically
50+
git add content/zh/category/new-article.md
51+
git commit -m "Add new article"
52+
git push origin main
53+
```
54+
55+
**Manual Generation:**
56+
```bash
57+
# Generate AI covers for articles without covers
58+
make generate-ai-covers
59+
60+
# Limited batch generation (recommended for API limits)
61+
conda run -n news_collector python scripts/ai_cover_generator.py --workflow-mode --limit=10
62+
63+
# Force regenerate existing covers
64+
conda run -n news_collector python scripts/ai_cover_generator.py --workflow-mode --force
65+
```
66+
67+
**GitHub Actions:**
68+
- Automatic: Triggered on push to main branch
69+
- Manual: Use "Generate Blog Images" workflow in Actions tab
70+
- Supports: ModelScope (default), Gemini, or both services
71+
4072
### Content Management
4173

4274
```bash

content/papers/example.md

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)