diff --git a/docs/plugins/actions/summary.zh.md b/docs/plugins/actions/summary.zh.md index 811bfb3..f89d405 100644 --- a/docs/plugins/actions/summary.zh.md +++ b/docs/plugins/actions/summary.zh.md @@ -1,7 +1,7 @@ # Summary(摘要) Action -v1.0.0 +v0.1.0 为长文本生成简洁摘要,并提取关键要点。 diff --git a/docs/plugins/filters/index.md b/docs/plugins/filters/index.md index 5b5c98c..669cdbf 100644 --- a/docs/plugins/filters/index.md +++ b/docs/plugins/filters/index.md @@ -42,7 +42,7 @@ Filters act as middleware in the message pipeline: Companion filter for the Gemini Manifold pipe plugin. - **Version:** 0.3.2 + **Version:** 1.7.0 [:octicons-arrow-right-24: Documentation](gemini-manifold-companion.md) diff --git a/docs/plugins/filters/index.zh.md b/docs/plugins/filters/index.zh.md index c68db29..59f7f11 100644 --- a/docs/plugins/filters/index.zh.md +++ b/docs/plugins/filters/index.zh.md @@ -42,7 +42,7 @@ Filter 充当消息管线中的中间件: Gemini Manifold Pipe 插件的伴随过滤器。 - **版本:** 0.3.2 + **版本:** 1.7.0 [:octicons-arrow-right-24: 查看文档](gemini-manifold-companion.md) diff --git a/plugins/actions/summary/精读.py b/plugins/actions/summary/精读.py index 07aa8c3..d72198d 100644 --- a/plugins/actions/summary/精读.py +++ b/plugins/actions/summary/精读.py @@ -1,7 +1,7 @@ """ title: 精读 (Deep Reading) icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIxIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjNDI4NWY0Ii8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjMWU4OGU1Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTYgMmg4bDYgNnYxMmEyIDIgMCAwIDEtMiAySDZhMiAyIDAgMCAxLTItMlY0YTIgMiAwIDAgMSAyLTJ6IiBmaWxsPSJ1cmwoI2cpIi8+PHBhdGggZD0iTTE0IDJsNiA2aC02eiIgZmlsbD0iIzFlODhlNSIgb3BhY2l0eT0iMC42Ii8+PGxpbmUgeDE9IjgiIHkxPSIxMyIgeDI9IjE2IiB5Mj0iMTMiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48bGluZSB4MT0iOCIgeTE9IjE3IiB4Mj0iMTQiIHkyPSIxNyIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMTgiIHI9IjMiIGZpbGw9IiNmZmQ3MDAiLz48cGF0aCBkPSJNMTYgMTZsMS41IDEuNSIgc3Ryb2tlPSIjNDI4NWY0IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg== -version: 2.0.0 +version: 0.1.0 description: 深度分析长篇文本,提炼详细摘要、关键信息点和可执行的行动建议,适合工作和学习场景。 requirements: jinja2, markdown """ diff --git a/plugins/filters/async-context-compression/README.md b/plugins/filters/async-context-compression/README.md index e3e3491..abee133 100644 --- a/plugins/filters/async-context-compression/README.md +++ b/plugins/filters/async-context-compression/README.md @@ -1,6 +1,6 @@ # Async Context Compression Filter -**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.2.0 | **License:** MIT +**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.1.0 | **License:** MIT This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent. diff --git a/plugins/pipes/gemini_mainfold/README.md b/plugins/pipes/gemini_mainfold/README.md index 0b70480..3e97e5d 100644 --- a/plugins/pipes/gemini_mainfold/README.md +++ b/plugins/pipes/gemini_mainfold/README.md @@ -1,6 +1,6 @@ # Example Pipe Plugin -**Author:** OpenWebUI Community | **Version:** 1.0.0 | **License:** MIT +**Author:** OpenWebUI Community | **Version:** 1.26.0 | **License:** MIT This is a template/example for creating Pipe plugins in OpenWebUI. diff --git a/scripts/check_version_consistency.py b/scripts/check_version_consistency.py new file mode 100644 index 0000000..bbf868c --- /dev/null +++ b/scripts/check_version_consistency.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +""" +Script to check and enforce version consistency across OpenWebUI plugins and documentation. +用于检查并强制 OpenWebUI 插件和文档之间版本一致性的脚本。 + +Usage: + python scripts/check_version_consistency.py # Check only + python scripts/check_version_consistency.py --fix # Check and fix +""" + +import argparse +import os +import re +import sys +from pathlib import Path +from typing import Optional, List, Dict, Tuple + +# ANSI colors +GREEN = "\033[92m" +RED = "\033[91m" +YELLOW = "\033[93m" +BLUE = "\033[94m" +RESET = "\033[0m" + + +def log_info(msg): + print(f"{BLUE}[INFO]{RESET} {msg}") + + +def log_success(msg): + print(f"{GREEN}[OK]{RESET} {msg}") + + +def log_warning(msg): + print(f"{YELLOW}[WARN]{RESET} {msg}") + + +def log_error(msg): + print(f"{RED}[ERR]{RESET} {msg}") + + +class VersionChecker: + def __init__(self, root_dir: str, fix: bool = False): + self.root_dir = Path(root_dir) + self.plugins_dir = self.root_dir / "plugins" + self.docs_dir = self.root_dir / "docs" / "plugins" + self.fix = fix + self.issues_found = 0 + self.fixed_count = 0 + + def extract_version_from_py(self, file_path: Path) -> Optional[str]: + """Extract version from Python docstring.""" + try: + content = file_path.read_text(encoding="utf-8") + match = re.search(r"version:\s*([\d\.]+)", content) + if match: + return match.group(1) + except Exception as e: + log_error(f"Failed to read {file_path}: {e}") + return None + + def update_file_content( + self, file_path: Path, pattern: str, replacement: str, version: str + ) -> bool: + """Update file content with new version.""" + try: + content = file_path.read_text(encoding="utf-8") + new_content = re.sub(pattern, replacement, content) + + if content != new_content: + if self.fix: + file_path.write_text(new_content, encoding="utf-8") + log_success( + f"Fixed {file_path.relative_to(self.root_dir)}: -> {version}" + ) + self.fixed_count += 1 + return True + else: + log_error( + f"Mismatch in {file_path.relative_to(self.root_dir)}: Expected {version}" + ) + self.issues_found += 1 + return False + return True + except Exception as e: + log_error(f"Failed to update {file_path}: {e}") + return False + + def check_plugin(self, plugin_type: str, plugin_dir: Path): + """Check consistency for a single plugin.""" + plugin_name = plugin_dir.name + + # 1. Identify Source of Truth (English .py file) + py_file = plugin_dir / f"{plugin_name}.py" + if not py_file.exists(): + # Try finding any .py file that matches the directory name pattern or is the main file + py_files = list(plugin_dir.glob("*.py")) + # Filter out _cn.py, templates, etc. + candidates = [ + f + for f in py_files + if not f.name.endswith("_cn.py") and "TEMPLATE" not in f.name + ] + if candidates: + py_file = candidates[0] + else: + return # Not a valid plugin dir + + true_version = self.extract_version_from_py(py_file) + if not true_version: + log_warning(f"Skipping {plugin_name}: No version found in {py_file.name}") + return + + log_info(f"Checking {plugin_name} (v{true_version})...") + + # 2. Check Chinese .py file + cn_py_files = list(plugin_dir.glob("*_cn.py")) + list( + plugin_dir.glob("*中文*.py") + ) + # Also check for files that are not the main file but might be the CN version + for f in plugin_dir.glob("*.py"): + if f != py_file and "TEMPLATE" not in f.name and f not in cn_py_files: + # Heuristic: if it has Chinese characters or ends in _cn + if re.search(r"[\u4e00-\u9fff]", f.name) or f.name.endswith("_cn.py"): + cn_py_files.append(f) + + for cn_py in set(cn_py_files): + self.update_file_content( + cn_py, r"(version:\s*)([\d\.]+)", rf"\g<1>{true_version}", true_version + ) + + # 3. Check README.md (English) + readme = plugin_dir / "README.md" + if readme.exists(): + # Pattern 1: **Version:** 1.0.0 + self.update_file_content( + readme, + r"(\*\*Version:?\*\*\s*)([\d\.]+)", + rf"\g<1>{true_version}", + true_version, + ) + # Pattern 2: | **Version:** 1.0.0 | + self.update_file_content( + readme, + r"(\|\s*\*\*Version:\*\*\s*)([\d\.]+)", + rf"\g<1>{true_version}", + true_version, + ) + + # 4. Check README_CN.md (Chinese) + readme_cn = plugin_dir / "README_CN.md" + if readme_cn.exists(): + # Pattern: **版本:** 1.0.0 + self.update_file_content( + readme_cn, + r"(\*\*版本:?\*\*\s*)([\d\.]+)", + rf"\g<1>{true_version}", + true_version, + ) + + # 5. Check Global Docs Index (docs/plugins/{type}/index.md) + index_md = self.docs_dir / plugin_type / "index.md" + if index_md.exists(): + # Need to find the specific block for this plugin. + # This is harder with regex on the whole file. + # We assume the format: **Version:** X.Y.Z + # But we need to make sure we are updating the RIGHT plugin's version. + # Strategy: Look for the plugin title or link, then the version nearby. + + # Extract title from py file to help search + title = self.extract_title(py_file) + if title: + self.update_version_in_index(index_md, title, true_version) + + # 6. Check Global Docs Index CN (docs/plugins/{type}/index.zh.md) + index_zh = self.docs_dir / plugin_type / "index.zh.md" + if index_zh.exists(): + # Try to find Chinese title? Or just use English title if listed? + # Often Chinese index uses English title or Chinese title. + # Let's try to extract Chinese title from cn_py if available + cn_title = None + if cn_py_files: + cn_title = self.extract_title(cn_py_files[0]) + + target_title = cn_title if cn_title else title + if target_title: + self.update_version_in_index( + index_zh, target_title, true_version, is_zh=True + ) + + # 7. Check Global Detail Page (docs/plugins/{type}/{name}.md) + # The doc filename usually matches the plugin directory name + detail_md = self.docs_dir / plugin_type / f"{plugin_name}.md" + if detail_md.exists(): + self.update_file_content( + detail_md, + r'(v)([\d\.]+)()', + rf"\g<1>{true_version}\g<3>", + true_version, + ) + + # 8. Check Global Detail Page CN (docs/plugins/{type}/{name}.zh.md) + detail_zh = self.docs_dir / plugin_type / f"{plugin_name}.zh.md" + if detail_zh.exists(): + self.update_file_content( + detail_zh, + r'(v)([\d\.]+)()', + rf"\g<1>{true_version}\g<3>", + true_version, + ) + + def extract_title(self, file_path: Path) -> Optional[str]: + try: + content = file_path.read_text(encoding="utf-8") + match = re.search(r"title:\s*(.+)", content) + if match: + return match.group(1).strip() + except: + pass + return None + + def update_version_in_index( + self, file_path: Path, title: str, version: str, is_zh: bool = False + ): + """ + Update version in index file. + Look for: + - ... **Title** ... + - ... + - **Version:** X.Y.Z + """ + try: + content = file_path.read_text(encoding="utf-8") + + # Escape title for regex + safe_title = re.escape(title) + + # Regex to find the plugin block and its version + # We look for the title, then non-greedy match until we find Version line + if is_zh: + ver_label = r"\*\*版本:\*\*" + else: + ver_label = r"\*\*Version:\*\*" + + # Pattern: (Title ...)(Version: )(\d+\.\d+\.\d+) + # We allow some lines between title and version + pattern = rf"(\*\*{safe_title}\*\*[\s\S]*?{ver_label}\s*)([\d\.]+)" + + match = re.search(pattern, content) + if match: + current_ver = match.group(2) + if current_ver != version: + if self.fix: + new_content = content.replace( + match.group(0), f"{match.group(1)}{version}" + ) + file_path.write_text(new_content, encoding="utf-8") + log_success( + f"Fixed index for {title}: {current_ver} -> {version}" + ) + self.fixed_count += 1 + else: + log_error( + f"Mismatch in index for {title}: Found {current_ver}, Expected {version}" + ) + self.issues_found += 1 + else: + # log_warning(f"Could not find entry for '{title}' in {file_path.name}") + pass + + except Exception as e: + log_error(f"Failed to check index {file_path}: {e}") + + def run(self): + if not self.plugins_dir.exists(): + log_error(f"Plugins directory not found: {self.plugins_dir}") + return + + # Scan actions, filters, pipes + for type_dir in self.plugins_dir.iterdir(): + if type_dir.is_dir() and type_dir.name in ["actions", "filters", "pipes"]: + for plugin_dir in type_dir.iterdir(): + if plugin_dir.is_dir(): + self.check_plugin(type_dir.name, plugin_dir) + + print("-" * 40) + if self.issues_found > 0: + if self.fix: + print(f"Fixed {self.fixed_count} issues.") + else: + print(f"Found {self.issues_found} version inconsistencies.") + print(f"Run with --fix to automatically resolve them.") + sys.exit(1) + else: + print("All versions are consistent! ✨") + + +def main(): + parser = argparse.ArgumentParser(description="Check version consistency.") + parser.add_argument("--fix", action="store_true", help="Fix inconsistencies") + args = parser.parse_args() + + # Assume script is run from root or scripts dir + root = Path.cwd() + if (root / "scripts").exists(): + pass + elif root.name == "scripts": + root = root.parent + + checker = VersionChecker(str(root), fix=args.fix) + checker.run() + + +if __name__ == "__main__": + main()