Skip to content

Commit 8e875c2

Browse files
Maynor996claude
andcommitted
feat: 添加自动项目提交流程(Issue → AI 解析 → PR)
- 新增 Issue 模板:用户提交项目介绍,自动打 add-project label - 新增 GitHub Action:监听 add-project label,调用 AI 解析自由文本 - AI 自动提取项目名、描述、分类,创建 projects/ 文件夹并更新 README - 每个 Issue 独立创建一个 PR Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f2edd26 commit 8e875c2

3 files changed

Lines changed: 317 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: 提交新项目
2+
description: 向 awesome-openclaw-tutorial 提交你的 OpenClaw 项目/案例/配置
3+
title: "[项目提交] "
4+
labels: ["add-project"]
5+
body:
6+
- type: textarea
7+
id: project-info
8+
attributes:
9+
label: 项目介绍
10+
description: |
11+
请描述你的项目,尽量包含以下信息(自由格式即可,AI 会自动整理):
12+
- 项目名称
13+
- 项目简介(做了什么、解决什么问题)
14+
- 项目链接(GitHub / 网页 / 其他)
15+
- 使用的 OpenClaw 功能或版本
16+
- 适合哪类用户
17+
- 其他你觉得重要的信息
18+
placeholder: |
19+
我做了一个 xxx 项目,主要是用 OpenClaw 实现了 xxx 功能...
20+
项目地址:https://github.com/xxx/xxx
21+
...
22+
validations:
23+
required: true
24+
- type: textarea
25+
id: extra-info
26+
attributes:
27+
label: 补充信息(可选)
28+
description: 任何你想补充的内容,比如截图、使用说明等
29+
placeholder: 截图链接、使用技巧等...
30+
validations:
31+
required: false

.github/scripts/process_project.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#!/usr/bin/env python3
2+
"""
3+
从 Issue 中提取项目信息,用 AI 格式化,创建项目文件夹并更新 README。
4+
"""
5+
6+
import json
7+
import os
8+
import re
9+
import subprocess
10+
import sys
11+
from pathlib import Path
12+
13+
from openai import OpenAI
14+
15+
# ── 读取环境变量 ──────────────────────────────────────────────
16+
issue_title = os.environ["ISSUE_TITLE"]
17+
issue_body = os.environ["ISSUE_BODY"]
18+
issue_number = os.environ["ISSUE_NUMBER"]
19+
issue_author = os.environ["ISSUE_AUTHOR"]
20+
api_key = os.environ["AI_API_KEY"]
21+
base_url = os.environ["AI_BASE_URL"] or "https://api.openai.com/v1"
22+
model = os.environ["AI_MODEL"] or "gpt-4o"
23+
24+
repo_root = Path(os.getcwd())
25+
projects_dir = repo_root / "projects"
26+
readme_path = repo_root / "README.md"
27+
28+
# ── Step 1: AI 解析 Issue 内容 ────────────────────────────────
29+
client = OpenAI(api_key=api_key, base_url=base_url)
30+
31+
system_prompt = """你是一个项目信息提取助手。用户会提交一段自由格式的项目介绍,
32+
你需要从中提取结构化信息并返回 JSON。
33+
34+
请提取以下字段(如果用户没写某项,用合理的默认值):
35+
- name: 项目名称(简短,用于文件夹名,只允许英文/数字/连字符/下划线)
36+
- display_name: 项目展示名称(中文也可以)
37+
- description: 一句话项目描述(50字以内)
38+
- long_description: 详细项目介绍(保留原始内容的精华)
39+
- link: 项目链接(GitHub 仓库地址或网页地址)
40+
- category: 项目分类,必须是以下之一:
41+
- "配置示例" (配置文件、部署模板)
42+
- "技能扩展" (Skills/插件/扩展)
43+
- "实战案例" (使用场景、效率工具、自动化工作流)
44+
- "教程资源" (教程、指南、学习资料)
45+
- "工具集成" (API集成、第三方工具对接)
46+
- author: 提交者
47+
- install_command: 安装命令(如果能从内容推断出来)
48+
- usage_tips: 使用技巧/示例对话
49+
50+
只返回 JSON,不要返回其他任何内容。"""
51+
52+
user_prompt = f"""Issue 标题: {issue_title}
53+
54+
Issue 内容:
55+
{issue_body}
56+
57+
提交者: {issue_author}"""
58+
59+
print("🔍 正在用 AI 解析 Issue 内容...")
60+
response = client.chat.completions.create(
61+
model=model,
62+
messages=[
63+
{"role": "system", "content": system_prompt},
64+
{"role": "user", "content": user_prompt},
65+
],
66+
temperature=0.1,
67+
)
68+
69+
content = response.choices[0].message.content.strip()
70+
# 去掉可能的 markdown 代码块包裹
71+
if content.startswith("```"):
72+
content = re.sub(r"^```\w*\n?", "", content)
73+
content = re.sub(r"\n?```$", "", content)
74+
75+
data = json.loads(content)
76+
print(f"✅ 解析完成: {data['name']} ({data['category']})")
77+
78+
# 保存供后续步骤使用
79+
with open("/tmp/project_data.json", "w") as f:
80+
json.dump(data, f, ensure_ascii=False, indent=2)
81+
82+
# ── Step 2: 创建项目文件夹 ────────────────────────────────────
83+
projects_dir.mkdir(exist_ok=True)
84+
project_dir = projects_dir / data["name"]
85+
project_dir.mkdir(exist_ok=True)
86+
87+
# 生成 README.md
88+
readme_content = f"""# {data['display_name']}
89+
90+
> {data['description']}
91+
92+
**分类**: {data['category']}
93+
**提交者**: @{data['author']}
94+
**来源**: [Issue #{issue_number}](https://github.com/xianyu110/awesome-openclaw-tutorial/issues/{issue_number})
95+
96+
---
97+
98+
## 项目介绍
99+
100+
{data['long_description']}
101+
102+
"""
103+
104+
if data.get("link"):
105+
readme_content += f"""## 项目链接
106+
107+
- **主页**: {data['link']}
108+
109+
"""
110+
111+
if data.get("install_command"):
112+
readme_content += f"""## 快速安装
113+
114+
```bash
115+
{data['install_command']}
116+
```
117+
118+
"""
119+
120+
if data.get("usage_tips"):
121+
readme_content += f"""## 使用示例
122+
123+
{data['usage_tips']}
124+
125+
"""
126+
127+
readme_content += """---
128+
129+
*此项目由 AI 自动从 Issue 提取生成,如需修改请提交 PR。*
130+
"""
131+
132+
(project_dir / "README.md").write_text(readme_content, encoding="utf-8")
133+
print(f"📁 已创建项目文件夹: {project_dir}")
134+
135+
# ── Step 3: 更新 README.md ────────────────────────────────────
136+
readme_text = readme_path.read_text(encoding="utf-8")
137+
138+
# 构造新增条目
139+
new_entry = f"- [{data['display_name']}]({project_dir.relative_to(repo_root)}/README.md) - {data['description']}"
140+
141+
# 根据分类决定插入位置
142+
category_sections = {
143+
"配置示例": ("### 📦 配置示例(开箱即用)", "### 🎬 实战场景"),
144+
"技能扩展": ("### 🔌 Skills 与插件", "### 🎬 实战场景"),
145+
"实战案例": ("### 🎬 实战场景", "---"),
146+
"教程资源": ("### 📚 社区教程与资源", None),
147+
"工具集成": ("### 🔗 工具与集成", "### 🎬 实战场景"),
148+
}
149+
150+
category, (section_start, section_end) = data["category"], category_sections.get(
151+
data["category"], ("### 🎬 实战场景", "---")
152+
)
153+
154+
# 检查分类区块是否存在
155+
if section_start not in readme_text:
156+
# 需要创建新的分类区块
157+
insert_pos = readme_text.find("---\n\n## 🤝 贡献指南")
158+
if insert_pos == -1:
159+
insert_pos = readme_text.find("---\n\n## 📮 联系方式")
160+
if insert_pos == -1:
161+
print("❌ 无法找到合适的插入位置")
162+
sys.exit(1)
163+
164+
new_section = f"""{section_start}
165+
166+
{new_entry}
167+
168+
"""
169+
readme_text = readme_text[:insert_pos] + new_section + "---\n\n" + readme_text[insert_pos:]
170+
print(f"📝 已在 README 中创建新分类区块: {category}")
171+
else:
172+
# 在已有区块中追加
173+
start_idx = readme_text.find(section_start)
174+
if section_end:
175+
end_idx = readme_text.find(section_end, start_idx)
176+
else:
177+
end_idx = readme_text.find("---\n\n## 🤝 贡献指南", start_idx)
178+
if end_idx == -1:
179+
end_idx = readme_text.find("---\n\n## 📮 联系方式", start_idx)
180+
181+
if end_idx == -1 or start_idx == -1:
182+
print(f"❌ 无法定位分类区块: {category}")
183+
sys.exit(1)
184+
185+
# 在该区块的最后一条目后插入
186+
section_text = readme_text[start_idx:end_idx]
187+
last_entry_match = re.search(r"(^- .+$)", section_text, re.MULTILINE)
188+
if last_entry_match:
189+
insert_idx = start_idx + last_entry_match.end()
190+
else:
191+
insert_idx = start_idx + len(section_start) + 1
192+
193+
readme_text = readme_text[:insert_idx] + "\n" + new_entry + readme_text[insert_idx:]
194+
print(f"📝 已在 README 区块「{category}」中添加条目")
195+
196+
readme_path.write_text(readme_text, encoding="utf-8")
197+
198+
# ── Step 4: 创建分支并提交 ────────────────────────────────────
199+
branch_name = f"add-project-{issue_number}"
200+
subprocess.run(["git", "checkout", "-b", branch_name], check=True)
201+
subprocess.run(["git", "add", str(project_dir), str(readme_path)], check=True)
202+
subprocess.run(
203+
["git", "commit", "-m", f"feat: 添加项目 {data['display_name']} (Issue #{issue_number})"],
204+
check=True,
205+
)
206+
subprocess.run(["git", "push", "origin", branch_name], check=True)
207+
208+
print(f"🚀 已推送分支: {branch_name}")
209+
print("✅ 完成!等待 PR 创建步骤...")

.github/workflows/add-project.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Auto Add Project
2+
3+
on:
4+
issues:
5+
types: [labeled]
6+
7+
permissions:
8+
contents: write
9+
pull-requests: write
10+
11+
jobs:
12+
process-project:
13+
# 只处理带有 add-project 标签的 Issue
14+
if: contains(github.event.label.name, 'add-project')
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout repo
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
22+
- name: Setup Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: "3.12"
26+
27+
- name: Install dependencies
28+
run: pip install openai
29+
30+
- name: Process issue with AI
31+
env:
32+
ISSUE_TITLE: ${{ github.event.issue.title }}
33+
ISSUE_BODY: ${{ github.event.issue.body }}
34+
ISSUE_NUMBER: ${{ github.event.issue.number }}
35+
ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
36+
AI_API_KEY: ${{ secrets.MAYNOR_API_KEY }}
37+
AI_BASE_URL: ${{ secrets.MAYNOR_API_BASE_URL }}
38+
AI_MODEL: ${{ secrets.MAYNOR_API_MODEL }}
39+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40+
run: python .github/scripts/process_project.py
41+
42+
- name: Create PR
43+
env:
44+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45+
run: |
46+
BRANCH_NAME="add-project-${{ github.event.issue.number }}"
47+
48+
# 从 Python 脚本生成的 JSON 文件读取数据(安全的,不直接注入 issue 内容)
49+
PR_TITLE=$(python3 -c "
50+
import json
51+
with open('/tmp/project_data.json') as f:
52+
data = json.load(f)
53+
print(f'[项目提交] {data[\"display_name\"]}')
54+
")
55+
56+
PR_BODY=$(python3 -c "
57+
import json
58+
with open('/tmp/project_data.json') as f:
59+
data = json.load(f)
60+
body = (
61+
f'## 自动提交的项目\n\n'
62+
f'- **项目名称**: {data[\"display_name\"]}\n'
63+
f'- **项目分类**: {data[\"category\"]}\n'
64+
f'- **项目链接**: {data.get(\"link\", \"无\")}\n'
65+
f'- **提交者**: @${{ github.event.issue.user.login }}\n'
66+
f'- **文件夹**: \`projects/{data[\"name\"]}/\`\n\n'
67+
f'---\n\n'
68+
f'Closes #${{ github.event.issue.number }}\n'
69+
)
70+
print(body)
71+
")
72+
73+
gh pr create \
74+
--title "$PR_TITLE" \
75+
--body "$PR_BODY" \
76+
--base main \
77+
--head "$BRANCH_NAME"

0 commit comments

Comments
 (0)