feat(stats): enhance plugin contribution tracking and update statistics display
This commit is contained in:
@@ -89,12 +89,15 @@ class OpenWebUIStats:
|
||||
"action",
|
||||
"filter",
|
||||
"pipe",
|
||||
"pipeline",
|
||||
"tool",
|
||||
"function",
|
||||
"prompt",
|
||||
"model",
|
||||
]
|
||||
|
||||
NON_PLUGIN_TYPES = {"post", "review", "comment"}
|
||||
|
||||
TYPE_ALIASES = {
|
||||
"tools": "tool",
|
||||
}
|
||||
@@ -104,6 +107,11 @@ class OpenWebUIStats:
|
||||
normalized = str(post_type or "").strip().lower()
|
||||
return self.TYPE_ALIASES.get(normalized, normalized)
|
||||
|
||||
def _is_published_plugin(self, post_type: str, downloads: int) -> bool:
|
||||
"""Treat marketplace items with downloads > 0 as published plugins."""
|
||||
normalized = self._normalize_post_type(post_type)
|
||||
return downloads > 0 and normalized not in self.NON_PLUGIN_TYPES
|
||||
|
||||
def load_history(self) -> list:
|
||||
"""Load history records (merge Gist + local file, keep the one with more records)"""
|
||||
gist_history = []
|
||||
@@ -199,7 +207,10 @@ class OpenWebUIStats:
|
||||
"total_saves": stats["total_saves"],
|
||||
"followers": stats.get("user", {}).get("followers", 0),
|
||||
"points": stats.get("user", {}).get("total_points", 0),
|
||||
"contributions": stats.get("user", {}).get("contributions", 0),
|
||||
"contributions": stats.get(
|
||||
"plugin_contributions",
|
||||
stats.get("user", {}).get("contributions", 0),
|
||||
),
|
||||
"posts": {p["slug"]: p["downloads"] for p in stats.get("posts", [])},
|
||||
}
|
||||
|
||||
@@ -268,8 +279,11 @@ class OpenWebUIStats:
|
||||
- prev.get("followers", 0),
|
||||
"points": stats.get("user", {}).get("total_points", 0)
|
||||
- prev.get("points", 0),
|
||||
"contributions": stats.get("user", {}).get("contributions", 0)
|
||||
- prev.get("contributions", 0),
|
||||
"contributions": stats.get(
|
||||
"plugin_contributions",
|
||||
stats.get("user", {}).get("contributions", 0),
|
||||
)
|
||||
- prev.get("contributions", prev.get("plugin_contributions", 0)),
|
||||
"posts": {
|
||||
p["slug"]: p["downloads"]
|
||||
- prev.get("posts", {}).get(p["slug"], p["downloads"])
|
||||
@@ -601,6 +615,7 @@ class OpenWebUIStats:
|
||||
"total_downvotes": 0,
|
||||
"total_saves": 0,
|
||||
"total_comments": 0,
|
||||
"plugin_contributions": 0,
|
||||
"by_type": {},
|
||||
"posts": [],
|
||||
"user": {}, # User info
|
||||
@@ -629,19 +644,21 @@ class OpenWebUIStats:
|
||||
meta = plugin_obj.get("meta", {}) or {}
|
||||
manifest = meta.get("manifest", {}) or {}
|
||||
|
||||
# Accumulate statistics
|
||||
post_downloads = post.get("downloads", 0)
|
||||
post_views = post.get("views", 0)
|
||||
post_saves = post.get("saveCount", 0)
|
||||
is_published_plugin = self._is_published_plugin(post_type, post_downloads)
|
||||
|
||||
stats["total_downloads"] += post_downloads
|
||||
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)
|
||||
|
||||
# Key: only count views for posts with actual downloads (exclude post/review types)
|
||||
if post_type not in ("post", "review") and post_downloads > 0:
|
||||
# Plugin-only marketplace totals: published items with downloads > 0.
|
||||
if is_published_plugin:
|
||||
stats["total_downloads"] += post_downloads
|
||||
stats["total_views"] += post_views
|
||||
stats["total_saves"] += post_saves
|
||||
stats["plugin_contributions"] += 1
|
||||
if post_type not in stats["by_type"]:
|
||||
stats["by_type"][post_type] = 0
|
||||
stats["by_type"][post_type] += 1
|
||||
@@ -661,8 +678,9 @@ class OpenWebUIStats:
|
||||
"downloads": post.get("downloads", 0),
|
||||
"views": post.get("views", 0),
|
||||
"upvotes": post.get("upvotes", 0),
|
||||
"saves": post.get("saveCount", 0),
|
||||
"saves": post_saves,
|
||||
"comments": post.get("commentCount", 0),
|
||||
"is_published_plugin": is_published_plugin,
|
||||
"created_at": created_at.strftime("%Y-%m-%d"),
|
||||
"updated_at": updated_at.strftime("%Y-%m-%d"),
|
||||
"url": f"https://openwebui.com/posts/{post.get('slug', '')}",
|
||||
@@ -688,10 +706,11 @@ class OpenWebUIStats:
|
||||
print("📈 Overview")
|
||||
print("-" * 40)
|
||||
print(f" 📝 Posts: {stats['total_posts']}")
|
||||
print(f" ⬇️ Total Downloads: {stats['total_downloads']}")
|
||||
print(f" 👁️ Total Views: {stats['total_views']}")
|
||||
print(f" 🧩 Published Plugins: {stats['plugin_contributions']}")
|
||||
print(f" ⬇️ Plugin Downloads: {stats['total_downloads']}")
|
||||
print(f" 👁️ Plugin Views: {stats['total_views']}")
|
||||
print(f" 👍 Total Upvotes: {stats['total_upvotes']}")
|
||||
print(f" 💾 Total Saves: {stats['total_saves']}")
|
||||
print(f" 💾 Plugin Saves: {stats['total_saves']}")
|
||||
print(f" 💬 Total Comments: {stats['total_comments']}")
|
||||
print()
|
||||
|
||||
@@ -745,13 +764,14 @@ class OpenWebUIStats:
|
||||
"overview_title": "## 📈 总览",
|
||||
"overview_header": "| 指标 | 数值 |",
|
||||
"posts": "📝 发布数量",
|
||||
"downloads": "⬇️ 总下载量",
|
||||
"views": "👁️ 总浏览量",
|
||||
"downloads": "⬇️ 插件总下载量",
|
||||
"views": "👁️ 插件总浏览量",
|
||||
"upvotes": "👍 总点赞数",
|
||||
"saves": "💾 总收藏数",
|
||||
"saves": "💾 插件总收藏数",
|
||||
"comments": "💬 总评论数",
|
||||
"author_points": "⭐ 作者总积分",
|
||||
"author_followers": "👥 粉丝数量",
|
||||
"author_contributions": "🧩 已发布插件数",
|
||||
"type_title": "## 📂 按类型分类",
|
||||
"list_title": "## 📋 发布列表",
|
||||
"list_header": "| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |",
|
||||
@@ -762,13 +782,14 @@ class OpenWebUIStats:
|
||||
"overview_title": "## 📈 Overview",
|
||||
"overview_header": "| Metric | Value |",
|
||||
"posts": "📝 Total Posts",
|
||||
"downloads": "⬇️ Total Downloads",
|
||||
"views": "👁️ Total Views",
|
||||
"downloads": "⬇️ Total Plugin Downloads",
|
||||
"views": "👁️ Total Plugin Views",
|
||||
"upvotes": "👍 Total Upvotes",
|
||||
"saves": "💾 Total Saves",
|
||||
"saves": "💾 Total Plugin Saves",
|
||||
"comments": "💬 Total Comments",
|
||||
"author_points": "⭐ Author Points",
|
||||
"author_followers": "👥 Followers",
|
||||
"author_contributions": "🧩 Published Plugins",
|
||||
"type_title": "## 📂 By Type",
|
||||
"list_title": "## 📋 Posts List",
|
||||
"list_header": "| Rank | Title | Type | Version | Downloads | Views | Upvotes | Saves | Updated |",
|
||||
@@ -815,6 +836,9 @@ class OpenWebUIStats:
|
||||
md.append(
|
||||
f"| {t['author_followers']} | {self.get_badge('followers', stats, user, delta)} |"
|
||||
)
|
||||
md.append(
|
||||
f"| {t['author_contributions']} | {self.get_badge('contributions', stats, user, delta)} |"
|
||||
)
|
||||
|
||||
md.append("")
|
||||
|
||||
@@ -914,6 +938,12 @@ class OpenWebUIStats:
|
||||
"color": "blue",
|
||||
"namedLogo": "openwebui",
|
||||
},
|
||||
"views": {
|
||||
"schemaVersion": 1,
|
||||
"label": "views",
|
||||
"message": format_number(stats["total_views"]),
|
||||
"color": "blueviolet",
|
||||
},
|
||||
"plugins": {
|
||||
"schemaVersion": 1,
|
||||
"label": "plugins",
|
||||
@@ -932,6 +962,18 @@ class OpenWebUIStats:
|
||||
"message": format_number(stats.get("user", {}).get("total_points", 0)),
|
||||
"color": "orange",
|
||||
},
|
||||
"saves": {
|
||||
"schemaVersion": 1,
|
||||
"label": "saves",
|
||||
"message": format_number(stats["total_saves"]),
|
||||
"color": "lightgrey",
|
||||
},
|
||||
"contributions": {
|
||||
"schemaVersion": 1,
|
||||
"label": "contributions",
|
||||
"message": str(stats.get("plugin_contributions", 0)),
|
||||
"color": "green",
|
||||
},
|
||||
"upvotes": {
|
||||
"schemaVersion": 1,
|
||||
"label": "upvotes",
|
||||
@@ -960,8 +1002,6 @@ class OpenWebUIStats:
|
||||
if not (self.gist_token and self.gist_id):
|
||||
return
|
||||
|
||||
delta = self.get_stat_delta(stats)
|
||||
|
||||
# Define badge config {key: (label, value, color)}
|
||||
badges_config = {
|
||||
"downloads": ("Downloads", stats["total_downloads"], "brightgreen"),
|
||||
@@ -980,7 +1020,7 @@ class OpenWebUIStats:
|
||||
),
|
||||
"contributions": (
|
||||
"Contributions",
|
||||
stats.get("user", {}).get("contributions", 0),
|
||||
stats.get("plugin_contributions", 0),
|
||||
"green",
|
||||
),
|
||||
"posts": ("Posts", stats["total_posts"], "informational"),
|
||||
@@ -988,22 +1028,12 @@ class OpenWebUIStats:
|
||||
|
||||
files_payload = {}
|
||||
for key, (label, val, color) in badges_config.items():
|
||||
diff = delta.get(key, 0)
|
||||
if isinstance(diff, dict):
|
||||
diff = 0 # Avoid dict vs int comparison error with 'posts' key
|
||||
|
||||
message = f"{val}"
|
||||
if diff > 0:
|
||||
message += f" (+{diff}🚀)"
|
||||
elif diff < 0:
|
||||
message += f" ({diff})"
|
||||
|
||||
# Build Shields.io endpoint JSON
|
||||
# 参考: https://shields.io/badges/endpoint-badge
|
||||
badge_data = {
|
||||
"schemaVersion": 1,
|
||||
"label": label,
|
||||
"message": message,
|
||||
"message": f"{val}",
|
||||
"color": color,
|
||||
}
|
||||
|
||||
@@ -1013,22 +1043,18 @@ class OpenWebUIStats:
|
||||
}
|
||||
|
||||
# Generate top 6 plugins badges (based on slots p1, p2...)
|
||||
post_deltas = delta.get("posts", {})
|
||||
for i, post in enumerate(stats.get("posts", [])[:6]):
|
||||
top_plugin_posts = [
|
||||
post for post in stats.get("posts", []) if post.get("is_published_plugin")
|
||||
][:6]
|
||||
for i, post in enumerate(top_plugin_posts):
|
||||
idx = i + 1
|
||||
diff = post_deltas.get(post["slug"], 0)
|
||||
|
||||
# Downloads badge
|
||||
dl_msg = f"{post['downloads']}"
|
||||
if diff > 0:
|
||||
dl_msg += f" (+{diff}🚀)"
|
||||
|
||||
files_payload[f"badge_p{idx}_dl.json"] = {
|
||||
"content": json.dumps(
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "Downloads",
|
||||
"message": dl_msg,
|
||||
"message": f"{post['downloads']}",
|
||||
"color": "brightgreen",
|
||||
}
|
||||
)
|
||||
@@ -1062,19 +1088,13 @@ class OpenWebUIStats:
|
||||
# 生成所有帖子的个体徽章 (用于详细报表)
|
||||
for post in stats.get("posts", []):
|
||||
slug_hash = self._safe_key(post["slug"])
|
||||
diff = post_deltas.get(post["slug"], 0)
|
||||
|
||||
# 1. Downloads
|
||||
dl_msg = f"{post['downloads']}"
|
||||
if diff > 0:
|
||||
dl_msg += f" (+{diff}🚀)"
|
||||
|
||||
files_payload[f"badge_post_{slug_hash}_dl.json"] = {
|
||||
"content": json.dumps(
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "Downloads",
|
||||
"message": dl_msg,
|
||||
"message": f"{post['downloads']}",
|
||||
"color": "brightgreen",
|
||||
}
|
||||
)
|
||||
@@ -1163,19 +1183,11 @@ class OpenWebUIStats:
|
||||
is_post: bool = False,
|
||||
style: str = "flat",
|
||||
) -> str:
|
||||
"""获取 Shields.io 徽章 URL (包含增量显示)"""
|
||||
"""获取 Shields.io 徽章 URL。"""
|
||||
import urllib.parse
|
||||
|
||||
gist_user = "Fu-Jie"
|
||||
|
||||
def _fmt_delta(k: str) -> str:
|
||||
val = delta.get(k, 0)
|
||||
if isinstance(val, dict):
|
||||
return ""
|
||||
if val > 0:
|
||||
return f" <br><sub>(+{val}🚀)</sub>"
|
||||
return ""
|
||||
|
||||
if not self.gist_id:
|
||||
if is_post:
|
||||
return "**-**"
|
||||
@@ -1185,14 +1197,14 @@ class OpenWebUIStats:
|
||||
if key == "points":
|
||||
val = user.get("total_points", 0)
|
||||
if key == "contributions":
|
||||
val = user.get("contributions", 0)
|
||||
val = stats.get("plugin_contributions", user.get("contributions", 0))
|
||||
if key == "posts":
|
||||
val = stats.get("total_posts", 0)
|
||||
if key == "saves":
|
||||
val = stats.get("total_saves", 0)
|
||||
if key.startswith("updated"):
|
||||
return f"🕐 {get_beijing_time().strftime('%Y-%m-%d %H:%M')}"
|
||||
return f"**{val}**{_fmt_delta(key)}"
|
||||
return f"**{val}**"
|
||||
|
||||
raw_url = f"https://gist.githubusercontent.com/{gist_user}/{self.gist_id}/raw/badge_{key}.json"
|
||||
encoded_url = urllib.parse.quote(raw_url, safe="")
|
||||
@@ -1208,30 +1220,26 @@ class OpenWebUIStats:
|
||||
stats: 统计数据
|
||||
lang: 语言 ("zh" 中文, "en" 英文)
|
||||
"""
|
||||
# 获取 Top 6 插件
|
||||
top_plugins = stats["posts"][:6]
|
||||
# 获取 Top 6 已发布插件
|
||||
top_plugins = [
|
||||
post for post in stats["posts"] if post.get("is_published_plugin")
|
||||
][:6]
|
||||
delta = self.get_stat_delta(stats)
|
||||
|
||||
def fmt_delta(key: str) -> str:
|
||||
val = delta.get(key, 0)
|
||||
if val > 0:
|
||||
return f" <br><sub>(+{val}🚀)</sub>"
|
||||
return ""
|
||||
|
||||
# 中英文文本
|
||||
texts = {
|
||||
"zh": {
|
||||
"title": "## 📊 社区统计",
|
||||
"author_header": "| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |",
|
||||
"header": "| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |",
|
||||
"author_header": "| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🧩 插件贡献 |",
|
||||
"header": "| 📝 发布 | ⬇️ 插件下载 | 👁️ 插件浏览 | 👍 点赞 | 💾 插件收藏 |",
|
||||
"top6_title": "### 🔥 热门插件 Top 6",
|
||||
"top6_header": "| 排名 | 插件 | 版本 | 下载 | 浏览 | 📅 更新 |",
|
||||
"full_stats": "*完整统计与趋势图请查看 [社区统计报告](./docs/community-stats.zh.md)*",
|
||||
},
|
||||
"en": {
|
||||
"title": "## 📊 Community Stats",
|
||||
"author_header": "| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |",
|
||||
"header": "| 📝 Posts | ⬇️ Downloads | 👁️ Views | 👍 Upvotes | 💾 Saves |",
|
||||
"author_header": "| 👤 Author | 👥 Followers | ⭐ Points | 🧩 Plugin Contributions |",
|
||||
"header": "| 📝 Posts | ⬇️ Plugin Downloads | 👁️ Plugin Views | 👍 Upvotes | 💾 Plugin Saves |",
|
||||
"top6_title": "### 🔥 Top 6 Popular Plugins",
|
||||
"top6_header": "| Rank | Plugin | Version | Downloads | Views | 📅 Updated |",
|
||||
"full_stats": "*See full stats and charts in [Community Stats Report](./docs/community-stats.md)*",
|
||||
|
||||
Reference in New Issue
Block a user