From 53f04debaf2bbbd1a509b2d6b659e03c9e3785ce Mon Sep 17 00:00:00 2001 From: fujie Date: Tue, 6 Jan 2026 01:32:38 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=B7=BB=E5=8A=A0=20OpenWebUI=20?= =?UTF-8?q?=E7=A4=BE=E5=8C=BA=E7=BB=9F=E8=AE=A1=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增统计脚本 scripts/openwebui_stats.py - 新增 GitHub Actions 每日自动更新统计 - README 中英文版添加统计徽章和热门插件 Top 5 - 统计数据输出到 docs/community-stats.md 和 JSON --- .github/workflows/community-stats.yml | 54 ++++ README.md | 23 ++ README_CN.md | 23 ++ docs/community-stats.json | 168 +++++++++++ docs/community-stats.md | 34 +++ scripts/openwebui_stats.py | 404 ++++++++++++++++++++++++++ 6 files changed, 706 insertions(+) create mode 100644 .github/workflows/community-stats.yml create mode 100644 docs/community-stats.json create mode 100644 docs/community-stats.md create mode 100644 scripts/openwebui_stats.py diff --git a/.github/workflows/community-stats.yml b/.github/workflows/community-stats.yml new file mode 100644 index 0000000..4a7e17d --- /dev/null +++ b/.github/workflows/community-stats.yml @@ -0,0 +1,54 @@ +# OpenWebUI 社区统计报告自动生成 +# 每天自动获取并更新社区统计数据 + +name: Community Stats + +on: + # 每天 UTC 8:00 (北京时间 16:00) 自动运行 + schedule: + - cron: '0 8 * * *' + # 手动触发 + workflow_dispatch: + +permissions: + contents: write + +jobs: + update-stats: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + pip install requests python-dotenv + + - name: Generate stats report + env: + OPENWEBUI_API_KEY: ${{ secrets.OPENWEBUI_API_KEY }} + OPENWEBUI_USER_ID: ${{ secrets.OPENWEBUI_USER_ID }} + run: | + python scripts/openwebui_stats.py + + - name: Check for changes + id: check_changes + run: | + git diff --quiet docs/community-stats.md README.md README_CN.md || echo "changed=true" >> $GITHUB_OUTPUT + + - name: Commit and push changes + if: steps.check_changes.outputs.changed == 'true' + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add docs/community-stats.md docs/community-stats.json README.md README_CN.md + git commit -m "📊 更新社区统计数据 $(date +'%Y-%m-%d')" + git push diff --git a/README.md b/README.md index 21f0ce7..4342151 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,28 @@ # OpenWebUI Extras + + +## 📊 社区统计 + +> 🕐 自动更新于 2026-01-06 + +| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 | +|:---:|:---:|:---:|:---:|:---:| +| **11** | **729** | **7627** | **54** | **43** | + +### 🔥 热门插件 Top 5 + +| 排名 | 插件 | 下载 | 浏览 | +|:---:|------|:---:|:---:| +| 🥇 | [Turn Any Text into Beautiful Mind Maps](https://openwebui.com/f/turn_any_text_into_beautiful_mind_maps_3094c59a) | 212 | 1892 | +| 🥈 | [Export to Excel](https://openwebui.com/f/export_mulit_table_to_excel_244b8f9d) | 167 | 433 | +| 🥉 | [Async Context Compression](https://openwebui.com/f/async_context_compression_b1655bc8) | 110 | 1185 | +| 4️⃣ | [Flash Card ](https://openwebui.com/f/flash_card_65a2ea8f) | 70 | 1332 | +| 5️⃣ | [Smart Infographic](https://openwebui.com/f/smart_infographic_ad6f0c7f) | 62 | 850 | + +*完整统计请查看 [社区统计报告](./docs/community-stats.md)* + + English | [中文](./README_CN.md) A collection of enhancements, plugins, and prompts for [OpenWebUI](https://github.com/open-webui/open-webui), developed and curated for personal use to extend functionality and improve experience. diff --git a/README_CN.md b/README_CN.md index 3e7e6fc..de52ef9 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,5 +1,28 @@ # OpenWebUI Extras + + +## 📊 社区统计 + +> 🕐 自动更新于 2026-01-06 + +| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 | +|:---:|:---:|:---:|:---:|:---:| +| **11** | **729** | **7627** | **54** | **43** | + +### 🔥 热门插件 Top 5 + +| 排名 | 插件 | 下载 | 浏览 | +|:---:|------|:---:|:---:| +| 🥇 | [Turn Any Text into Beautiful Mind Maps](https://openwebui.com/f/turn_any_text_into_beautiful_mind_maps_3094c59a) | 212 | 1892 | +| 🥈 | [Export to Excel](https://openwebui.com/f/export_mulit_table_to_excel_244b8f9d) | 167 | 433 | +| 🥉 | [Async Context Compression](https://openwebui.com/f/async_context_compression_b1655bc8) | 110 | 1185 | +| 4️⃣ | [Flash Card ](https://openwebui.com/f/flash_card_65a2ea8f) | 70 | 1332 | +| 5️⃣ | [Smart Infographic](https://openwebui.com/f/smart_infographic_ad6f0c7f) | 62 | 850 | + +*完整统计请查看 [社区统计报告](./docs/community-stats.md)* + + [English](./README.md) | 中文 OpenWebUI 增强功能集合。包含个人开发与收集的### 🧩 插件 (Plugins) diff --git a/docs/community-stats.json b/docs/community-stats.json new file mode 100644 index 0000000..4cc6013 --- /dev/null +++ b/docs/community-stats.json @@ -0,0 +1,168 @@ +{ + "total_posts": 11, + "total_downloads": 729, + "total_views": 7627, + "total_upvotes": 54, + "total_downvotes": 0, + "total_saves": 43, + "total_comments": 13, + "by_type": { + "unknown": 11 + }, + "posts": [ + { + "title": "Turn Any Text into Beautiful Mind Maps", + "slug": "turn_any_text_into_beautiful_mind_maps_3094c59a", + "type": "unknown", + "version": "", + "downloads": 212, + "views": 1892, + "upvotes": 10, + "saves": 15, + "comments": 8, + "created_at": "2025-12-31", + "updated_at": "2026-01-04", + "url": "https://openwebui.com/f/turn_any_text_into_beautiful_mind_maps_3094c59a" + }, + { + "title": "Export to Excel", + "slug": "export_mulit_table_to_excel_244b8f9d", + "type": "unknown", + "version": "", + "downloads": 167, + "views": 433, + "upvotes": 3, + "saves": 2, + "comments": 0, + "created_at": "2025-05-30", + "updated_at": "2026-01-03", + "url": "https://openwebui.com/f/export_mulit_table_to_excel_244b8f9d" + }, + { + "title": "Async Context Compression", + "slug": "async_context_compression_b1655bc8", + "type": "unknown", + "version": "", + "downloads": 110, + "views": 1185, + "upvotes": 5, + "saves": 9, + "comments": 0, + "created_at": "2025-11-08", + "updated_at": "2025-12-31", + "url": "https://openwebui.com/f/async_context_compression_b1655bc8" + }, + { + "title": "Flash Card ", + "slug": "flash_card_65a2ea8f", + "type": "unknown", + "version": "", + "downloads": 70, + "views": 1332, + "upvotes": 8, + "saves": 5, + "comments": 2, + "created_at": "2025-12-30", + "updated_at": "2026-01-04", + "url": "https://openwebui.com/f/flash_card_65a2ea8f" + }, + { + "title": "Smart Infographic", + "slug": "smart_infographic_ad6f0c7f", + "type": "unknown", + "version": "", + "downloads": 62, + "views": 850, + "upvotes": 6, + "saves": 7, + "comments": 2, + "created_at": "2025-12-28", + "updated_at": "2026-01-04", + "url": "https://openwebui.com/f/smart_infographic_ad6f0c7f" + }, + { + "title": "Export to Word (Enhanced Formatting)", + "slug": "export_to_word_enhanced_formatting_fca6a315", + "type": "unknown", + "version": "", + "downloads": 37, + "views": 419, + "upvotes": 5, + "saves": 2, + "comments": 0, + "created_at": "2026-01-03", + "updated_at": "2026-01-05", + "url": "https://openwebui.com/f/export_to_word_enhanced_formatting_fca6a315" + }, + { + "title": "智能信息图", + "slug": "智能信息图_e04a48ff", + "type": "unknown", + "version": "", + "downloads": 31, + "views": 372, + "upvotes": 3, + "saves": 0, + "comments": 0, + "created_at": "2025-12-28", + "updated_at": "2025-12-29", + "url": "https://openwebui.com/f/智能信息图_e04a48ff" + }, + { + "title": "导出为 Word-支持公式、流程图、表格和代码块", + "slug": "导出为_word_支持公式流程图表格和代码块_8a6306c0", + "type": "unknown", + "version": "", + "downloads": 13, + "views": 502, + "upvotes": 7, + "saves": 1, + "comments": 1, + "created_at": "2026-01-04", + "updated_at": "2026-01-05", + "url": "https://openwebui.com/f/导出为_word_支持公式流程图表格和代码块_8a6306c0" + }, + { + "title": "闪记卡生成插件", + "slug": "闪记卡生成插件_4a31eac3", + "type": "unknown", + "version": "", + "downloads": 12, + "views": 305, + "upvotes": 3, + "saves": 1, + "comments": 0, + "created_at": "2025-12-30", + "updated_at": "2026-01-01", + "url": "https://openwebui.com/f/闪记卡生成插件_4a31eac3" + }, + { + "title": "智能生成交互式思维导图,帮助用户可视化知识", + "slug": "智能生成交互式思维导图帮助用户可视化知识_8d4b097b", + "type": "unknown", + "version": "", + "downloads": 10, + "views": 233, + "upvotes": 2, + "saves": 0, + "comments": 0, + "created_at": "2025-12-31", + "updated_at": "2025-12-31", + "url": "https://openwebui.com/f/智能生成交互式思维导图帮助用户可视化知识_8d4b097b" + }, + { + "title": "异步上下文压缩", + "slug": "异步上下文压缩_5c0617cb", + "type": "unknown", + "version": "", + "downloads": 5, + "views": 104, + "upvotes": 2, + "saves": 1, + "comments": 0, + "created_at": "2025-11-08", + "updated_at": "2025-12-31", + "url": "https://openwebui.com/f/异步上下文压缩_5c0617cb" + } + ] +} \ No newline at end of file diff --git a/docs/community-stats.md b/docs/community-stats.md new file mode 100644 index 0000000..5223871 --- /dev/null +++ b/docs/community-stats.md @@ -0,0 +1,34 @@ +# 📊 OpenWebUI 社区统计报告 + +> 📅 更新时间: 2026-01-06 01:31:12 + +## 📈 总览 + +| 指标 | 数值 | +|------|------| +| 📝 发布数量 | 11 | +| ⬇️ 总下载量 | 729 | +| 👁️ 总浏览量 | 7627 | +| 👍 总点赞数 | 54 | +| 💾 总收藏数 | 43 | +| 💬 总评论数 | 13 | + +## 📂 按类型分类 + +- **unknown**: 11 + +## 📋 发布列表 + +| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 | +|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| 1 | [Turn Any Text into Beautiful Mind Maps](https://openwebui.com/f/turn_any_text_into_beautiful_mind_maps_3094c59a) | unknown | | 212 | 1892 | 10 | 15 | 2026-01-04 | +| 2 | [Export to Excel](https://openwebui.com/f/export_mulit_table_to_excel_244b8f9d) | unknown | | 167 | 433 | 3 | 2 | 2026-01-03 | +| 3 | [Async Context Compression](https://openwebui.com/f/async_context_compression_b1655bc8) | unknown | | 110 | 1185 | 5 | 9 | 2025-12-31 | +| 4 | [Flash Card ](https://openwebui.com/f/flash_card_65a2ea8f) | unknown | | 70 | 1332 | 8 | 5 | 2026-01-04 | +| 5 | [Smart Infographic](https://openwebui.com/f/smart_infographic_ad6f0c7f) | unknown | | 62 | 850 | 6 | 7 | 2026-01-04 | +| 6 | [Export to Word (Enhanced Formatting)](https://openwebui.com/f/export_to_word_enhanced_formatting_fca6a315) | unknown | | 37 | 419 | 5 | 2 | 2026-01-05 | +| 7 | [智能信息图](https://openwebui.com/f/智能信息图_e04a48ff) | unknown | | 31 | 372 | 3 | 0 | 2025-12-29 | +| 8 | [导出为 Word-支持公式、流程图、表格和代码块](https://openwebui.com/f/导出为_word_支持公式流程图表格和代码块_8a6306c0) | unknown | | 13 | 502 | 7 | 1 | 2026-01-05 | +| 9 | [闪记卡生成插件](https://openwebui.com/f/闪记卡生成插件_4a31eac3) | unknown | | 12 | 305 | 3 | 1 | 2026-01-01 | +| 10 | [智能生成交互式思维导图,帮助用户可视化知识](https://openwebui.com/f/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | unknown | | 10 | 233 | 2 | 0 | 2025-12-31 | +| 11 | [异步上下文压缩](https://openwebui.com/f/异步上下文压缩_5c0617cb) | unknown | | 5 | 104 | 2 | 1 | 2025-12-31 | diff --git a/scripts/openwebui_stats.py b/scripts/openwebui_stats.py new file mode 100644 index 0000000..41f9977 --- /dev/null +++ b/scripts/openwebui_stats.py @@ -0,0 +1,404 @@ +#!/usr/bin/env python3 +""" +OpenWebUI 社区统计工具 + +获取并统计你在 openwebui.com 上发布的插件/帖子数据。 + +使用方法: + 1. 设置环境变量: + - OPENWEBUI_API_KEY: 你的 API Key + - OPENWEBUI_USER_ID: 你的用户 ID + 2. 运行: python scripts/openwebui_stats.py + +获取 API Key: + 访问 https://openwebui.com/settings/api 创建 API Key (sk-开头) + +获取 User ID: + 从个人主页的 API 请求中获取,格式如: b15d1348-4347-42b4-b815-e053342d6cb0 +""" + +import os +import json +import requests +from datetime import datetime +from typing import Optional +from pathlib import Path + +# 尝试加载 .env 文件 +try: + from dotenv import load_dotenv + + load_dotenv() +except ImportError: + pass + + +class OpenWebUIStats: + """OpenWebUI 社区统计工具""" + + BASE_URL = "https://api.openwebui.com/api/v1" + + def __init__(self, api_key: str, user_id: Optional[str] = None): + """ + 初始化统计工具 + + Args: + api_key: OpenWebUI API Key (JWT Token) + user_id: 用户 ID,如果为 None 则从 token 中解析 + """ + self.api_key = api_key + self.user_id = user_id or self._parse_user_id_from_token(api_key) + self.session = requests.Session() + self.session.headers.update( + { + "Authorization": f"Bearer {api_key}", + "Accept": "application/json", + "Content-Type": "application/json", + } + ) + + def _parse_user_id_from_token(self, token: str) -> str: + """从 JWT Token 中解析用户 ID""" + import base64 + + try: + # JWT 格式: header.payload.signature + payload = token.split(".")[1] + # 添加 padding + padding = 4 - len(payload) % 4 + if padding != 4: + payload += "=" * padding + decoded = base64.urlsafe_b64decode(payload) + data = json.loads(decoded) + return data.get("id", "") + except Exception as e: + print(f"⚠️ 无法从 Token 解析用户 ID: {e}") + return "" + + def get_user_posts(self, sort: str = "new", page: int = 1) -> list: + """ + 获取用户发布的帖子列表 + + Args: + sort: 排序方式 (new/top/hot) + page: 页码 + + Returns: + 帖子列表 + """ + url = f"{self.BASE_URL}/posts/users/{self.user_id}" + params = {"sort": sort, "page": page} + + response = self.session.get(url, params=params) + response.raise_for_status() + return response.json() + + def get_all_posts(self, sort: str = "new") -> list: + """获取所有帖子(自动分页)""" + all_posts = [] + page = 1 + + while True: + posts = self.get_user_posts(sort=sort, page=page) + if not posts: + break + all_posts.extend(posts) + page += 1 + + return all_posts + + def generate_stats(self, posts: list) -> dict: + """生成统计数据""" + stats = { + "total_posts": len(posts), + "total_downloads": 0, + "total_views": 0, + "total_upvotes": 0, + "total_downvotes": 0, + "total_saves": 0, + "total_comments": 0, + "by_type": {}, + "posts": [], + } + + for post in posts: + # 累计统计 + stats["total_downloads"] += post.get("downloads", 0) + stats["total_views"] += post.get("views", 0) + stats["total_upvotes"] += post.get("upvotes", 0) + stats["total_downvotes"] += post.get("downvotes", 0) + stats["total_saves"] += post.get("saveCount", 0) + stats["total_comments"] += post.get("commentCount", 0) + + # 按类型分类 + post_type = post.get("data", {}).get("meta", {}).get("type", "unknown") + if post_type not in stats["by_type"]: + stats["by_type"][post_type] = 0 + stats["by_type"][post_type] += 1 + + # 单个帖子信息 + manifest = post.get("data", {}).get("meta", {}).get("manifest", {}) + created_at = datetime.fromtimestamp(post.get("createdAt", 0)) + updated_at = datetime.fromtimestamp(post.get("updatedAt", 0)) + + stats["posts"].append( + { + "title": post.get("title", ""), + "slug": post.get("slug", ""), + "type": post_type, + "version": manifest.get("version", ""), + "downloads": post.get("downloads", 0), + "views": post.get("views", 0), + "upvotes": post.get("upvotes", 0), + "saves": post.get("saveCount", 0), + "comments": post.get("commentCount", 0), + "created_at": created_at.strftime("%Y-%m-%d"), + "updated_at": updated_at.strftime("%Y-%m-%d"), + "url": f"https://openwebui.com/f/{post.get('slug', '')}", + } + ) + + # 按下载量排序 + stats["posts"].sort(key=lambda x: x["downloads"], reverse=True) + + return stats + + def print_stats(self, stats: dict): + """打印统计报告到终端""" + print("\n" + "=" * 60) + print("📊 OpenWebUI 社区统计报告") + print("=" * 60) + print(f"📅 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print() + + # 总览 + print("📈 总览") + print("-" * 40) + print(f" 📝 发布数量: {stats['total_posts']}") + print(f" ⬇️ 总下载量: {stats['total_downloads']}") + print(f" 👁️ 总浏览量: {stats['total_views']}") + print(f" 👍 总点赞数: {stats['total_upvotes']}") + print(f" 💾 总收藏数: {stats['total_saves']}") + print(f" 💬 总评论数: {stats['total_comments']}") + print() + + # 按类型分类 + print("📂 按类型分类") + print("-" * 40) + for post_type, count in stats["by_type"].items(): + print(f" • {post_type}: {count}") + print() + + # 详细列表 + print("📋 发布列表 (按下载量排序)") + print("-" * 60) + + # 表头 + print(f"{'排名':<4} {'标题':<30} {'下载':<8} {'浏览':<8} {'点赞':<6}") + print("-" * 60) + + for i, post in enumerate(stats["posts"], 1): + title = ( + post["title"][:28] + ".." if len(post["title"]) > 30 else post["title"] + ) + print( + f"{i:<4} {title:<30} {post['downloads']:<8} {post['views']:<8} {post['upvotes']:<6}" + ) + + print("=" * 60) + + def generate_markdown(self, stats: dict) -> str: + """生成 Markdown 格式报告""" + md = [] + md.append("# 📊 OpenWebUI 社区统计报告") + md.append("") + md.append(f"> 📅 更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + md.append("") + + # 总览 + md.append("## 📈 总览") + md.append("") + md.append("| 指标 | 数值 |") + md.append("|------|------|") + md.append(f"| 📝 发布数量 | {stats['total_posts']} |") + md.append(f"| ⬇️ 总下载量 | {stats['total_downloads']} |") + md.append(f"| 👁️ 总浏览量 | {stats['total_views']} |") + md.append(f"| 👍 总点赞数 | {stats['total_upvotes']} |") + md.append(f"| 💾 总收藏数 | {stats['total_saves']} |") + md.append(f"| 💬 总评论数 | {stats['total_comments']} |") + md.append("") + + # 按类型分类 + md.append("## 📂 按类型分类") + md.append("") + for post_type, count in stats["by_type"].items(): + md.append(f"- **{post_type}**: {count}") + md.append("") + + # 详细列表 + md.append("## 📋 发布列表") + md.append("") + md.append( + "| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |" + ) + md.append("|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|") + + for i, post in enumerate(stats["posts"], 1): + title_link = f"[{post['title']}]({post['url']})" + md.append( + f"| {i} | {title_link} | {post['type']} | {post['version']} | " + f"{post['downloads']} | {post['views']} | {post['upvotes']} | " + f"{post['saves']} | {post['updated_at']} |" + ) + + md.append("") + return "\n".join(md) + + def save_json(self, stats: dict, filepath: str): + """保存 JSON 格式数据""" + with open(filepath, "w", encoding="utf-8") as f: + json.dump(stats, f, ensure_ascii=False, indent=2) + print(f"✅ JSON 数据已保存到: {filepath}") + + def generate_readme_stats(self, stats: dict) -> str: + """生成 README 统计徽章区域""" + # 获取 Top 5 插件 + top_plugins = stats["posts"][:5] + + lines = [] + lines.append("") + lines.append("## 📊 社区统计") + lines.append("") + lines.append(f"> 🕐 自动更新于 {datetime.now().strftime('%Y-%m-%d')}") + lines.append("") + + # 统计徽章 - 使用 shields.io 风格的表格 + lines.append("| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |") + lines.append("|:---:|:---:|:---:|:---:|:---:|") + lines.append( + f"| **{stats['total_posts']}** | **{stats['total_downloads']}** | " + f"**{stats['total_views']}** | **{stats['total_upvotes']}** | **{stats['total_saves']}** |" + ) + lines.append("") + + # Top 5 热门插件 + lines.append("### 🔥 热门插件 Top 5") + lines.append("") + lines.append("| 排名 | 插件 | 下载 | 浏览 |") + lines.append("|:---:|------|:---:|:---:|") + + medals = ["🥇", "🥈", "🥉", "4️⃣", "5️⃣"] + for i, post in enumerate(top_plugins): + medal = medals[i] if i < len(medals) else str(i + 1) + lines.append( + f"| {medal} | [{post['title']}]({post['url']}) | {post['downloads']} | {post['views']} |" + ) + + lines.append("") + lines.append("*完整统计请查看 [社区统计报告](./docs/community-stats.md)*") + lines.append("") + + return "\n".join(lines) + + def update_readme(self, stats: dict, readme_path: str): + """更新 README 文件中的统计区域""" + import re + + # 读取现有内容 + with open(readme_path, "r", encoding="utf-8") as f: + content = f.read() + + # 生成新的统计区域 + new_stats = self.generate_readme_stats(stats) + + # 检查是否已有统计区域 + pattern = r".*?" + if re.search(pattern, content, re.DOTALL): + # 替换现有区域 + new_content = re.sub(pattern, new_stats, content, flags=re.DOTALL) + else: + # 在文件开头(标题之后)插入统计区域 + # 找到第一个 ## 标题或在第一个空行后插入 + lines = content.split("\n") + insert_pos = 0 + + for i, line in enumerate(lines): + if line.startswith("# "): + # 找到主标题后继续 + continue + if line.startswith("[") or line.strip() == "": + insert_pos = i + 1 + if line.strip() == "": + break + + # 在适当位置插入 + lines.insert(insert_pos, "") + lines.insert(insert_pos + 1, new_stats) + lines.insert(insert_pos + 2, "") + new_content = "\n".join(lines) + + # 写回文件 + with open(readme_path, "w", encoding="utf-8") as f: + f.write(new_content) + + print(f"✅ README 已更新: {readme_path}") + + +def main(): + """主函数""" + # 获取配置 + api_key = os.getenv("OPENWEBUI_API_KEY") + user_id = os.getenv("OPENWEBUI_USER_ID") + + if not api_key: + print("❌ 错误: 未设置 OPENWEBUI_API_KEY 环境变量") + print("请设置环境变量:") + print(" export OPENWEBUI_API_KEY='your_api_key_here'") + return 1 + + if not user_id: + print("❌ 错误: 未设置 OPENWEBUI_USER_ID 环境变量") + print("请设置环境变量:") + print(" export OPENWEBUI_USER_ID='your_user_id_here'") + print("\n提示: 用户 ID 可以从之前的 curl 请求中获取") + print(" 例如: b15d1348-4347-42b4-b815-e053342d6cb0") + return 1 + + # 初始化 + stats_client = OpenWebUIStats(api_key, user_id) + print(f"🔍 用户 ID: {stats_client.user_id}") + + # 获取所有帖子 + print("📥 正在获取帖子数据...") + posts = stats_client.get_all_posts() + print(f"✅ 获取到 {len(posts)} 个帖子") + + # 生成统计 + stats = stats_client.generate_stats(posts) + + # 打印到终端 + stats_client.print_stats(stats) + + # 保存 Markdown 报告 + script_dir = Path(__file__).parent.parent + md_path = script_dir / "docs" / "community-stats.md" + md_content = stats_client.generate_markdown(stats) + with open(md_path, "w", encoding="utf-8") as f: + f.write(md_content) + print(f"\n✅ Markdown 报告已保存到: {md_path}") + + # 保存 JSON 数据 + json_path = script_dir / "docs" / "community-stats.json" + stats_client.save_json(stats, str(json_path)) + + # 更新 README 文件 + readme_path = script_dir / "README.md" + readme_cn_path = script_dir / "README_CN.md" + stats_client.update_readme(stats, str(readme_path)) + stats_client.update_readme(stats, str(readme_cn_path)) + + return 0 + + +if __name__ == "__main__": + exit(main())