Compare commits

..

14 Commits

Author SHA1 Message Date
fujie
40d77121bd docs: update plugin development standards for readme requirements 2026-01-03 12:37:05 +08:00
fujie
3795976a79 feat: optimize export to excel filename generation and update docs 2026-01-03 12:37:05 +08:00
fujie
f5e5e5caa4 feat: 添加版本一致性检查脚本以确保插件和文档版本同步 2026-01-03 12:25:04 +08:00
fujie
0c893ce61f docs: 新增插件开发工作流程文档并更新了多个插件的版本信息 2026-01-03 12:22:14 +08:00
fujie
8f4ce8f084 docs: 更新异步上下文压缩的图标。 2026-01-03 12:00:18 +08:00
fujie
ac2cf00807 feat: 更新了异步上下文压缩插件版本,并优化了发布工作流以直接上传插件文件,同时排除了新的插件模板文件。 2026-01-03 11:54:17 +08:00
fujie
b9d8100cdb docs: add consistency maintenance section to copilot instructions 2026-01-03 11:18:39 +08:00
fujie
bb1cc0d966 docs: update copilot instructions for consistency and sync READMEs 2026-01-03 11:17:54 +08:00
fujie
2e238c5b5d 移除多模型上下文合并器插件的描述。 2026-01-03 11:17:48 +08:00
fujie
b56e7cb41e docs: update documentation versions, fix rendering issues, and update READMEs 2026-01-03 11:16:24 +08:00
fujie
236ae43c0c fix: 提取插件版本时跳过 gemini_manifold.pygemini_manifold_companion.py 文件 2026-01-03 11:00:14 +08:00
fujie
a4e8cc52f9 docs: 更新 Copilot 指令。 2026-01-03 10:57:21 +08:00
fujie
c8e8434bc6 feat: 引入选项以在发布说明中忽略已移除的插件。 2026-01-03 10:55:05 +08:00
fujie
3ee00bb083 docs(copilot-instructions): add release workflow guidelines
- Add release workflow section with automatic release process
- Document version numbering rules (semver)
- Add three release methods: direct push, PR, manual trigger
- Include commit message convention (Conventional Commits)
- Add release checklist
2026-01-01 04:23:22 +08:00
30 changed files with 985 additions and 84 deletions

View File

@@ -0,0 +1,93 @@
---
description: OpenWebUI Plugin Development & Release Workflow
---
# OpenWebUI Plugin Development Workflow
This workflow outlines the standard process for developing, documenting, and releasing plugins for OpenWebUI, ensuring compliance with project standards and CI/CD requirements.
## 1. Development Standards
Reference: `.github/copilot-instructions.md`
### Bilingual Requirement
Every plugin **MUST** have bilingual versions for both code and documentation:
- **Code**:
- English: `plugins/{type}/{name}/{name}.py`
- Chinese: `plugins/{type}/{name}/{name_cn}.py` (or `中文名.py`)
- **README**:
- English: `plugins/{type}/{name}/README.md`
- Chinese: `plugins/{type}/{name}/README_CN.md`
### Code Structure
- **Docstring**: Must include `title`, `author`, `version`, `description`, etc.
- **Valves**: Use `pydantic` for configuration.
- **Database**: Re-use `open_webui.internal.db` shared connection.
- **User Context**: Use `_get_user_context` helper method.
### Commit Messages
- **Language**: **English ONLY**. Do not use Chinese in commit messages.
- **Format**: Conventional Commits (e.g., `feat:`, `fix:`, `docs:`).
## 2. Documentation Updates
When adding or updating a plugin, you **MUST** update the following documentation files to maintain consistency:
### Plugin Directory
- `README.md`: Update version, description, and usage. **Explicitly describe new features.**
- `README_CN.md`: Update version, description, and usage. **Explicitly describe new features.**
### Global Documentation (`docs/`)
- **Index Pages**:
- `docs/plugins/{type}/index.md`: Add/Update list item with **correct version**.
- `docs/plugins/{type}/index.zh.md`: Add/Update list item with **correct version**.
- **Detail Pages**:
- `docs/plugins/{type}/{name}.md`: Ensure content matches README.
- `docs/plugins/{type}/{name}.zh.md`: Ensure content matches README_CN.
### Root README
- `README.md`: Add to "Featured Plugins" if applicable.
- `README_CN.md`: Add to "Featured Plugins" if applicable.
## 3. Version Control & Release
Reference: `.github/workflows/release.yml`
### Version Bumping
- **Rule**: Any change to plugin logic **MUST** be accompanied by a version bump in the docstring.
- **Format**: Semantic Versioning (e.g., `1.0.0` -> `1.0.1`).
- **Consistency**: Update version in **ALL** locations:
1. English Code (`.py`)
2. Chinese Code (`.py`)
3. English README (`README.md`)
4. Chinese README (`README_CN.md`)
5. Docs Index (`docs/.../index.md`)
6. Docs Index CN (`docs/.../index.zh.md`)
7. Docs Detail (`docs/.../{name}.md`)
8. Docs Detail CN (`docs/.../{name}.zh.md`)
### Automated Release Process
1. **Trigger**: Push to `main` branch with changes in `plugins/**/*.py`.
2. **Detection**: `scripts/extract_plugin_versions.py` detects changed plugins and compares versions.
3. **Release**:
- Generates release notes based on changes.
- Creates a GitHub Release tag (e.g., `v2024.01.01-1`).
- Uploads individual `.py` files of **changed plugins only** as assets.
### Pull Request Check
- Workflow: `.github/workflows/plugin-version-check.yml`
- Checks if plugin files are modified.
- **Fails** if version number is not updated.
- **Fails** if PR description is too short (< 20 chars).
## 4. Verification Checklist
Before committing:
- [ ] Code is bilingual and functional?
- [ ] Docstrings have updated version?
- [ ] READMEs are updated and bilingual?
- [ ] `docs/` index and detail pages are updated?
- [ ] Root `README.md` is updated?
- [ ] All version numbers match exactly?

View File

@@ -37,7 +37,9 @@ README 文件应包含以下内容:
- 安装和设置说明 / Installation and setup instructions - 安装和设置说明 / Installation and setup instructions
- 使用示例 / Usage examples - 使用示例 / Usage examples
- 故障排除指南 / Troubleshooting guide - 故障排除指南 / Troubleshooting guide
- 故障排除指南 / Troubleshooting guide
- 版本和作者信息 / Version and author information - 版本和作者信息 / Version and author information
- **新增功能 / New Features**: 如果是更新现有插件,必须明确列出并描述新增功能(发布到官方市场的重要要求)。/ If updating an existing plugin, explicitly list and describe new features (Critical for official market release).
### 官方文档 (Official Documentation) ### 官方文档 (Official Documentation)
@@ -795,10 +797,147 @@ For iframe plugins to access parent document theme information, users need to co
- [ ] 实现 Valves 配置 - [ ] 实现 Valves 配置
- [ ] 使用 logging 而非 print - [ ] 使用 logging 而非 print
- [ ] 测试双语界面 - [ ] 测试双语界面
- [ ] **一致性检查 (Consistency Check)**:
- [ ] 更新 `README.md` 插件列表
- [ ] 更新 `README_CN.md` 插件列表
- [ ] 更新/创建 `docs/` 下的对应文档
- [ ] 确保文档版本号与代码一致
--- ---
## 📚 参考资源 (Reference Resources) ## 🔄 一致性维护 (Consistency Maintenance)
任何插件的**新增、修改或移除**,必须同时更新以下三个位置,保持完全一致:
1. **插件代码 (Plugin Code)**: 更新 `version` 和功能实现。
2. **项目文档 (Docs)**: 更新 `docs/` 下对应的文档文件(版本号、功能描述)。
3. **自述文件 (README)**: 更新根目录下的 `README.md``README_CN.md` 中的插件列表。
> [!IMPORTANT]
> 提交 PR 前,请务必检查这三处是否同步。例如:如果删除了一个插件,必须同时从 README 列表中移除,并删除对应的 docs 文档。
---
## <20> 发布工作流 (Release Workflow)
### 自动发布 (Automatic Release)
当插件更新推送到 `main` 分支时,会**自动触发**发布流程:
1. 🔍 检测版本变化(与上次 release 对比)
2. 📝 生成发布说明(包含更新内容和提交记录)
3. 📦 创建 GitHub Release包含可下载的插件文件
4. 🏷️ 自动生成版本号(格式:`vYYYY.MM.DD-运行号`
**注意**:仅**移除插件**(删除文件)**不会触发**自动发布。只有新增或修改插件(且更新了版本号)才会触发发布。移除的插件将不会出现在发布日志中。
### 发布前必须完成 (Pre-release Requirements)
1.**更新版本号** - 修改插件文档字符串中的 `version` 字段
2.**中英文版本同步** - 确保两个版本的版本号一致
```python
"""
title: My Plugin
version: 0.2.0 # <- 必须更新这里!
...
"""
```
### 版本编号规则 (Versioning)
遵循[语义化版本](https://semver.org/lang/zh-CN/)
| 变更类型 | 版本变化 | 示例 |
|---------|---------|------|
| Bug 修复 | PATCH +1 | 0.1.0 → 0.1.1 |
| 新功能 | MINOR +1 | 0.1.1 → 0.2.0 |
| 不兼容变更 | MAJOR +1 | 0.2.0 → 1.0.0 |
### 发布方式 (Release Methods)
**方式 A直接推送到 main推荐**
```bash
# 1. 暂存更改
git add plugins/actions/my-plugin/
# 2. 提交(使用规范的 commit message
git commit -m "feat(my-plugin): add new feature X
- Add feature X for better user experience
- Fix bug Y
- Update version to 0.2.0"
# 3. 推送到 main
git push origin main
# GitHub Actions 会自动创建 Release
```
**方式 B创建 PR团队协作**
```bash
# 1. 创建功能分支
git checkout -b feature/my-plugin-v0.2.0
# 2. 提交更改
git commit -m "feat(my-plugin): add new feature X"
# 3. 推送并创建 PR
git push origin feature/my-plugin-v0.2.0
# 4. PR 合并后自动触发发布
```
**方式 C手动触发发布**
1. 前往 GitHub Actions → "Plugin Release / 插件发布"
2. 点击 "Run workflow"
3. 填写版本号和发布说明
### Commit Message 规范 (Commit Convention)
使用 [Conventional Commits](https://www.conventionalcommits.org/) 格式:
```
<type>(<scope>): <description>
[optional body]
[optional footer]
```
常用类型:
- `feat`: 新功能
- `fix`: Bug 修复
- `docs`: 文档更新
- `refactor`: 代码重构
- `style`: 代码格式调整
- `perf`: 性能优化
示例:
```
feat(flash-card): add _get_user_context for safer user info retrieval
- Add _get_user_context method to handle various __user__ types
- Prevent AttributeError when __user__ is not a dict
- Update version to 0.2.2 for both English and Chinese versions
```
### 发布检查清单 (Release Checklist)
发布前确保完成以下检查:
- [ ] 更新插件版本号(英文版 + 中文版)
- [ ] 测试插件功能正常
- [ ] 确保代码通过格式检查
- [ ] 编写清晰的 commit message
- [ ] 推送到 main 分支或合并 PR
---
## <20>📚 参考资源 (Reference Resources)
- [Action 插件模板 (英文)](plugins/actions/ACTION_PLUGIN_TEMPLATE.py) - [Action 插件模板 (英文)](plugins/actions/ACTION_PLUGIN_TEMPLATE.py)
- [Action 插件模板 (中文)](plugins/actions/ACTION_PLUGIN_TEMPLATE_CN.py) - [Action 插件模板 (中文)](plugins/actions/ACTION_PLUGIN_TEMPLATE_CN.py)
@@ -816,3 +955,32 @@ GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
## License ## License
MIT License MIT License
---
## 📝 Commit Message Guidelines
**Commit messages MUST be in English.** Do not use Chinese.
### Format
Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation only changes
- `style`: Changes that do not affect the meaning of the code (white-space, formatting, etc)
- `refactor`: A code change that neither fixes a bug nor adds a feature
- `perf`: A code change that improves performance
- `test`: Adding missing tests or correcting existing tests
- `chore`: Changes to the build process or auxiliary tools and libraries such as documentation generation
### Examples
**Good:**
- `feat: add new export to pdf plugin`
- `fix: resolve icon rendering issue in documentation`
- `docs: update README with installation steps`
**Bad:**
- `新增导出PDF插件` (Chinese is not allowed)
- `update code` (Too vague)

View File

@@ -101,7 +101,7 @@ jobs:
fi fi
# Compare versions and generate release notes # Compare versions and generate release notes
python scripts/extract_plugin_versions.py --compare old_versions.json --output changes.md python scripts/extract_plugin_versions.py --compare old_versions.json --ignore-removed --output changes.md
python scripts/extract_plugin_versions.py --compare old_versions.json --json --output changes.json python scripts/extract_plugin_versions.py --compare old_versions.json --json --output changes.json
echo "=== Version Changes ===" echo "=== Version Changes ==="
@@ -175,10 +175,7 @@ jobs:
id: plugins id: plugins
run: | run: |
python scripts/extract_plugin_versions.py --json --output plugin_versions.json python scripts/extract_plugin_versions.py --json --output plugin_versions.json
python scripts/extract_plugin_versions.py --markdown --output plugin_table.md python scripts/extract_plugin_versions.py --json --output plugin_versions.json
echo "=== Plugin Versions ==="
cat plugin_table.md
- name: Collect plugin files for release - name: Collect plugin files for release
id: collect_files id: collect_files
@@ -198,28 +195,12 @@ jobs:
fi fi
done done
else else
echo "Collecting all plugin files..." echo "No changed plugins detected. Skipping file collection."
find plugins -name "*.py" -type f ! -name "__*" | while read -r file; do
dir=$(dirname "$file")
mkdir -p "release_plugins/$dir"
cp "$file" "release_plugins/$file"
done
fi fi
# Create a zip file with error handling # Create a zip file with error handling
cd release_plugins # cd release_plugins
if [ -n "$(ls -A . 2>/dev/null)" ]; then # Zip step removed as per user request
if zip -r ../plugins_release.zip .; then
echo "Successfully created plugins_release.zip"
else
echo "Warning: Failed to create zip file, creating empty placeholder"
touch ../plugins_release.zip
fi
else
echo "No plugin files to zip, creating empty placeholder"
touch ../plugins_release.zip
fi
cd ..
echo "=== Collected Files ===" echo "=== Collected Files ==="
find release_plugins -name "*.py" -type f | head -20 find release_plugins -name "*.py" -type f | head -20
@@ -280,16 +261,13 @@ jobs:
echo "" >> release_notes.md echo "" >> release_notes.md
fi fi
echo "## All Plugin Versions / 所有插件版本" >> release_notes.md
echo "" >> release_notes.md
cat plugin_table.md >> release_notes.md
echo "" >> release_notes.md
cat >> release_notes.md << 'EOF' cat >> release_notes.md << 'EOF'
## Download / 下载 ## Download / 下载
📦 **plugins_release.zip** - 包含本次更新的所有插件文件 / Contains all updated plugin files 📦 **Download the updated plugin files below** / 请在下方下载更新的插件文件
### Installation / 安装 ### Installation / 安装
@@ -323,7 +301,7 @@ jobs:
prerelease: ${{ github.event.inputs.prerelease || false }} prerelease: ${{ github.event.inputs.prerelease || false }}
files: | files: |
plugin_versions.json plugin_versions.json
plugins_release.zip release_plugins/**/*.py
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -336,5 +314,4 @@ jobs:
echo "### Updated Plugins" >> $GITHUB_STEP_SUMMARY echo "### Updated Plugins" >> $GITHUB_STEP_SUMMARY
echo "${{ needs.check-changes.outputs.release_notes }}" >> $GITHUB_STEP_SUMMARY echo "${{ needs.check-changes.outputs.release_notes }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "### All Plugin Versions" >> $GITHUB_STEP_SUMMARY
cat plugin_table.md >> $GITHUB_STEP_SUMMARY

View File

@@ -14,15 +14,17 @@ Located in the `plugins/` directory, containing Python-based enhancements:
#### Actions #### Actions
- **Smart Mind Map** (`smart-mind-map`): Generates interactive mind maps from text. - **Smart Mind Map** (`smart-mind-map`): Generates interactive mind maps from text.
- **Smart Infographic** (`infographic`): Transforms text into professional infographics using AntV.
- **Knowledge Card** (`knowledge-card`): Creates beautiful flashcards for learning. - **Knowledge Card** (`knowledge-card`): Creates beautiful flashcards for learning.
- **Export to Excel** (`export_to_excel`): Exports chat history to Excel files. - **Export to Excel** (`export_to_excel`): Exports chat history to Excel files.
- **Export to Word** (`export_to_docx`): Exports chat history to Word documents.
- **Summary** (`summary`): Text summarization tool. - **Summary** (`summary`): Text summarization tool.
#### Filters #### Filters
- **Async Context Compression** (`async-context-compression`): Optimizes token usage via context compression. - **Async Context Compression** (`async-context-compression`): Optimizes token usage via context compression.
- **Context Enhancement** (`context_enhancement_filter`): Enhances chat context. - **Context Enhancement** (`context_enhancement_filter`): Enhances chat context.
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Companion filter for Gemini Manifold. - **Gemini Manifold Companion** (`gemini_manifold_companion`): Companion filter for Gemini Manifold.
- **Multi-Model Context Merger** (`multi_model_context_merger`): Merges context from multiple models.
#### Pipes #### Pipes
- **Gemini Manifold** (`gemini_mainfold`): Pipeline for Gemini model integration. - **Gemini Manifold** (`gemini_mainfold`): Pipeline for Gemini model integration.

View File

@@ -8,15 +8,17 @@ OpenWebUI 增强功能集合。包含个人开发与收集的### 🧩 插件 (Pl
#### Actions (交互增强) #### Actions (交互增强)
- **Smart Mind Map** (`smart-mind-map`): 智能分析文本并生成交互式思维导图。 - **Smart Mind Map** (`smart-mind-map`): 智能分析文本并生成交互式思维导图。
- **Smart Infographic** (`infographic`): 基于 AntV 的智能信息图生成工具。
- **Knowledge Card** (`knowledge-card`): 快速生成精美的学习记忆卡片。 - **Knowledge Card** (`knowledge-card`): 快速生成精美的学习记忆卡片。
- **Export to Excel** (`export_to_excel`): 将对话内容导出为 Excel 文件。 - **Export to Excel** (`export_to_excel`): 将对话内容导出为 Excel 文件。
- **Export to Word** (`export_to_docx`): 将对话内容导出为 Word 文档。
- **Summary** (`summary`): 文本摘要生成工具。 - **Summary** (`summary`): 文本摘要生成工具。
#### Filters (消息处理) #### Filters (消息处理)
- **Async Context Compression** (`async-context-compression`): 异步上下文压缩,优化 Token 使用。 - **Async Context Compression** (`async-context-compression`): 异步上下文压缩,优化 Token 使用。
- **Context Enhancement** (`context_enhancement_filter`): 上下文增强过滤器。 - **Context Enhancement** (`context_enhancement_filter`): 上下文增强过滤器。
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Gemini Manifold 配套增强。 - **Gemini Manifold Companion** (`gemini_manifold_companion`): Gemini Manifold 配套增强。
- **Multi-Model Context Merger** (`multi_model_context_merger`): 多模型上下文合并。
#### Pipes (模型管道) #### Pipes (模型管道)
- **Gemini Manifold** (`gemini_mainfold`): 集成 Gemini 模型的管道。 - **Gemini Manifold** (`gemini_mainfold`): 集成 Gemini 模型的管道。

View File

@@ -1,12 +1,19 @@
# Export to Excel # Export to Excel
<span class="category-badge action">Action</span> <span class="category-badge action">Action</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v0.3.4</span>
Export chat conversations to Excel spreadsheet format for analysis, archiving, and sharing. Export chat conversations to Excel spreadsheet format for analysis, archiving, and sharing.
## What's New in v0.3.4
- **Smart Filename Generation**: Now supports generating filenames based on Chat Title, AI Summary, or Markdown Headers.
- **Configuration Options**: Added `TITLE_SOURCE` setting to control filename generation strategy.
--- ---
## Overview ## Overview
The Export to Excel plugin allows you to download your chat conversations as Excel files. This is useful for: The Export to Excel plugin allows you to download your chat conversations as Excel files. This is useful for:
@@ -23,6 +30,13 @@ The Export to Excel plugin allows you to download your chat conversations as Exc
- :material-download: **One-Click Download**: Instant file generation - :material-download: **One-Click Download**: Instant file generation
- :material-history: **Full History**: Exports complete conversation - :material-history: **Full History**: Exports complete conversation
## Configuration
- **Title Source**: Choose how the filename is generated:
- `chat_title`: Use the chat title (default).
- `ai_generated`: Use AI to generate a concise title from the content.
- `markdown_title`: Extract the first H1/H2 header from the markdown content.
--- ---
## Installation ## Installation

View File

@@ -1,12 +1,19 @@
# Export to Excel导出到 Excel # Export to Excel导出到 Excel
<span class="category-badge action">Action</span> <span class="category-badge action">Action</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v0.3.4</span>
将聊天记录导出为 Excel 表格,便于分析、归档和分享。 将聊天记录导出为 Excel 表格,便于分析、归档和分享。
## v0.3.4 更新内容
- **智能文件名生成**支持根据对话标题、AI 总结或 Markdown 标题生成文件名。
- **配置选项**:新增 `TITLE_SOURCE` 设置,用于控制文件名生成策略。
--- ---
## 概览 ## 概览
Export to Excel 插件可以把你的聊天记录下载为 Excel 文件,适用于: Export to Excel 插件可以把你的聊天记录下载为 Excel 文件,适用于:
@@ -23,6 +30,13 @@ Export to Excel 插件可以把你的聊天记录下载为 Excel 文件,适用
- :material-download: **一键下载**:即时生成文件 - :material-download: **一键下载**:即时生成文件
- :material-history: **完整历史**:导出完整会话内容 - :material-history: **完整历史**:导出完整会话内容
## 配置
- **标题来源 (Title Source)**:选择文件名的生成方式:
- `chat_title`:使用对话标题(默认)。
- `ai_generated`:使用 AI 根据内容生成简洁标题。
- `markdown_title`:提取 Markdown 内容中的第一个 H1/H2 标题。
--- ---
## 安装 ## 安装

View File

@@ -33,7 +33,7 @@ Actions are interactive plugins that:
Transform text into professional infographics using AntV visualization engine with various templates. Transform text into professional infographics using AntV visualization engine with various templates.
**Version:** 1.0.0 **Version:** 1.3.0
[:octicons-arrow-right-24: Documentation](smart-infographic.md) [:octicons-arrow-right-24: Documentation](smart-infographic.md)
@@ -43,7 +43,7 @@ Actions are interactive plugins that:
Quickly generates beautiful learning memory cards, perfect for studying and memorization. Quickly generates beautiful learning memory cards, perfect for studying and memorization.
**Version:** 0.2.0 **Version:** 0.2.2
[:octicons-arrow-right-24: Documentation](knowledge-card.md) [:octicons-arrow-right-24: Documentation](knowledge-card.md)
@@ -53,7 +53,7 @@ Actions are interactive plugins that:
Export chat conversations to Excel spreadsheet format for analysis and archiving. Export chat conversations to Excel spreadsheet format for analysis and archiving.
**Version:** 1.0.0 **Version:** 0.3.4
[:octicons-arrow-right-24: Documentation](export-to-excel.md) [:octicons-arrow-right-24: Documentation](export-to-excel.md)
@@ -73,7 +73,7 @@ Actions are interactive plugins that:
Generate concise summaries of long text content with key points extraction. Generate concise summaries of long text content with key points extraction.
**Version:** 1.0.0 **Version:** 0.1.0
[:octicons-arrow-right-24: Documentation](summary.md) [:octicons-arrow-right-24: Documentation](summary.md)

View File

@@ -33,7 +33,7 @@ Actions 是交互式插件,能够:
使用 AntV 可视化引擎,将文本转成专业的信息图。 使用 AntV 可视化引擎,将文本转成专业的信息图。
**版本:** 1.0.0 **版本:** 1.3.0
[:octicons-arrow-right-24: 查看文档](smart-infographic.md) [:octicons-arrow-right-24: 查看文档](smart-infographic.md)
@@ -43,7 +43,7 @@ Actions 是交互式插件,能够:
快速生成精美的学习记忆卡片,适合学习与记忆。 快速生成精美的学习记忆卡片,适合学习与记忆。
**版本:** 0.2.0 **版本:** 0.2.2
[:octicons-arrow-right-24: 查看文档](knowledge-card.md) [:octicons-arrow-right-24: 查看文档](knowledge-card.md)
@@ -53,7 +53,7 @@ Actions 是交互式插件,能够:
将聊天记录导出为 Excel 电子表格,方便分析或归档。 将聊天记录导出为 Excel 电子表格,方便分析或归档。
**版本:** 1.0.0 **版本:** 0.3.4
[:octicons-arrow-right-24: 查看文档](export-to-excel.md) [:octicons-arrow-right-24: 查看文档](export-to-excel.md)
@@ -73,7 +73,7 @@ Actions 是交互式插件,能够:
对长文本进行精简总结,提取要点。 对长文本进行精简总结,提取要点。
**版本:** 1.0.0 **版本:** 0.1.0
[:octicons-arrow-right-24: 查看文档](summary.md) [:octicons-arrow-right-24: 查看文档](summary.md)

View File

@@ -1,7 +1,7 @@
# Knowledge Card # Knowledge Card
<span class="category-badge action">Action</span> <span class="category-badge action">Action</span>
<span class="version-badge">v0.2.0</span> <span class="version-badge">v0.2.2</span>
Quickly generates beautiful learning memory cards, perfect for studying and quick memorization. Quickly generates beautiful learning memory cards, perfect for studying and quick memorization.

View File

@@ -1,7 +1,7 @@
# Smart Infographic # Smart Infographic
<span class="category-badge action">Action</span> <span class="category-badge action">Action</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v1.3.0</span>
An AntV Infographic engine powered plugin that transforms long text into professional, beautiful infographics with a single click. An AntV Infographic engine powered plugin that transforms long text into professional, beautiful infographics with a single click.

View File

@@ -1,7 +1,7 @@
# Summary # Summary
<span class="category-badge action">Action</span> <span class="category-badge action">Action</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v0.1.0</span>
Generate concise summaries of long text content with key points extraction. Generate concise summaries of long text content with key points extraction.

View File

@@ -1,7 +1,7 @@
# Summary摘要 # Summary摘要
<span class="category-badge action">Action</span> <span class="category-badge action">Action</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v0.1.0</span>
为长文本生成简洁摘要,并提取关键要点。 为长文本生成简洁摘要,并提取关键要点。

View File

@@ -1,7 +1,7 @@
# Async Context Compression # Async Context Compression
<span class="category-badge filter">Filter</span> <span class="category-badge filter">Filter</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v1.1.0</span>
Reduces token consumption in long conversations through intelligent summarization while maintaining conversational coherence. Reduces token consumption in long conversations through intelligent summarization while maintaining conversational coherence.

View File

@@ -1,7 +1,7 @@
# Async Context Compression异步上下文压缩 # Async Context Compression异步上下文压缩
<span class="category-badge filter">Filter</span> <span class="category-badge filter">Filter</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v1.1.0</span>
通过智能摘要减少长对话的 token 消耗,同时保持对话连贯。 通过智能摘要减少长对话的 token 消耗,同时保持对话连贯。

View File

@@ -1,7 +1,7 @@
# Context Enhancement # Context Enhancement
<span class="category-badge filter">Filter</span> <span class="category-badge filter">Filter</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v0.2</span>
Enhances chat context with additional information for improved LLM responses. Enhances chat context with additional information for improved LLM responses.

View File

@@ -1,7 +1,7 @@
# Context Enhancement上下文增强 # Context Enhancement上下文增强
<span class="category-badge filter">Filter</span> <span class="category-badge filter">Filter</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v0.2</span>
为聊天自动补充上下文信息,让 LLM 回复更相关、更准确。 为聊天自动补充上下文信息,让 LLM 回复更相关、更准确。

View File

@@ -1,7 +1,7 @@
# Gemini Manifold Companion # Gemini Manifold Companion
<span class="category-badge filter">Filter</span> <span class="category-badge filter">Filter</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v0.3.2</span>
Companion filter for the Gemini Manifold pipe plugin, providing enhanced functionality. Companion filter for the Gemini Manifold pipe plugin, providing enhanced functionality.

View File

@@ -1,7 +1,7 @@
# Gemini Manifold Companion # Gemini Manifold Companion
<span class="category-badge filter">Filter</span> <span class="category-badge filter">Filter</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v0.3.2</span>
Gemini Manifold Pipe 的伴随过滤器,用于增强 Gemini 集成的处理效果。 Gemini Manifold Pipe 的伴随过滤器,用于增强 Gemini 集成的处理效果。

View File

@@ -16,13 +16,13 @@ Filters act as middleware in the message pipeline:
<div class="grid cards" markdown> <div class="grid cards" markdown>
- :material-compress:{ .lg .middle } **Async Context Compression** - :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
--- ---
Reduces token consumption in long conversations through intelligent summarization while maintaining coherence. Reduces token consumption in long conversations through intelligent summarization while maintaining coherence.
**Version:** 1.0.0 **Version:** 1.1.0
[:octicons-arrow-right-24: Documentation](async-context-compression.md) [:octicons-arrow-right-24: Documentation](async-context-compression.md)
@@ -32,7 +32,7 @@ Filters act as middleware in the message pipeline:
Enhances chat context with additional information for better responses. Enhances chat context with additional information for better responses.
**Version:** 1.0.0 **Version:** 0.2
[:octicons-arrow-right-24: Documentation](context-enhancement.md) [:octicons-arrow-right-24: Documentation](context-enhancement.md)
@@ -42,7 +42,7 @@ Filters act as middleware in the message pipeline:
Companion filter for the Gemini Manifold pipe plugin. Companion filter for the Gemini Manifold pipe plugin.
**Version:** 1.0.0 **Version:** 1.7.0
[:octicons-arrow-right-24: Documentation](gemini-manifold-companion.md) [:octicons-arrow-right-24: Documentation](gemini-manifold-companion.md)

View File

@@ -16,13 +16,13 @@ Filter 充当消息管线中的中间件:
<div class="grid cards" markdown> <div class="grid cards" markdown>
- :material-compress:{ .lg .middle } **Async Context Compression** - :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
--- ---
通过智能总结减少长对话的 token 消耗,同时保持连贯性。 通过智能总结减少长对话的 token 消耗,同时保持连贯性。
**版本:** 1.0.0 **版本:** 1.1.0
[:octicons-arrow-right-24: 查看文档](async-context-compression.md) [:octicons-arrow-right-24: 查看文档](async-context-compression.md)
@@ -32,7 +32,7 @@ Filter 充当消息管线中的中间件:
为聊天增加额外信息,提升回复质量。 为聊天增加额外信息,提升回复质量。
**版本:** 1.0.0 **版本:** 0.2
[:octicons-arrow-right-24: 查看文档](context-enhancement.md) [:octicons-arrow-right-24: 查看文档](context-enhancement.md)
@@ -42,7 +42,7 @@ Filter 充当消息管线中的中间件:
Gemini Manifold Pipe 插件的伴随过滤器。 Gemini Manifold Pipe 插件的伴随过滤器。
**版本:** 1.0.0 **版本:** 1.7.0
[:octicons-arrow-right-24: 查看文档](gemini-manifold-companion.md) [:octicons-arrow-right-24: 查看文档](gemini-manifold-companion.md)

View File

@@ -2,12 +2,24 @@
This plugin allows you to export your chat history to an Excel (.xlsx) file directly from the chat interface. This plugin allows you to export your chat history to an Excel (.xlsx) file directly from the chat interface.
## What's New in v0.3.4
- **Smart Filename Generation**: Now supports generating filenames based on Chat Title, AI Summary, or Markdown Headers.
- **Configuration Options**: Added `TITLE_SOURCE` setting to control filename generation strategy.
## Features ## Features
- **One-Click Export**: Adds an "Export to Excel" button to the chat. - **One-Click Export**: Adds an "Export to Excel" button to the chat.
- **Automatic Header Extraction**: Intelligently identifies table headers from the chat content. - **Automatic Header Extraction**: Intelligently identifies table headers from the chat content.
- **Multi-Table Support**: Handles multiple tables within a single chat session. - **Multi-Table Support**: Handles multiple tables within a single chat session.
## Configuration
- **Title Source**: Choose how the filename is generated:
- `chat_title`: Use the chat title (default).
- `ai_generated`: Use AI to generate a concise title from the content.
- `markdown_title`: Extract the first H1/H2 header from the markdown content.
## Usage ## Usage
1. Install the plugin. 1. Install the plugin.

View File

@@ -2,12 +2,24 @@
此插件允许你直接从聊天界面将对话历史导出为 Excel (.xlsx) 文件。 此插件允许你直接从聊天界面将对话历史导出为 Excel (.xlsx) 文件。
## v0.3.4 更新内容
- **智能文件名生成**支持根据对话标题、AI 总结或 Markdown 标题生成文件名。
- **配置选项**:新增 `TITLE_SOURCE` 设置,用于控制文件名生成策略。
## 功能特点 ## 功能特点
- **一键导出**:在聊天界面添加“导出为 Excel”按钮。 - **一键导出**:在聊天界面添加“导出为 Excel”按钮。
- **自动表头提取**:智能识别聊天内容中的表格标题。 - **自动表头提取**:智能识别聊天内容中的表格标题。
- **多表支持**:支持处理单次对话中的多个表格。 - **多表支持**:支持处理单次对话中的多个表格。
## 配置
- **标题来源 (Title Source)**:选择文件名的生成方式:
- `chat_title`:使用对话标题(默认)。
- `ai_generated`:使用 AI 根据内容生成简洁标题。
- `markdown_title`:提取 Markdown 内容中的第一个 H1/H2 标题。
## 使用方法 ## 使用方法
1. 安装插件。 1. 安装插件。

View File

@@ -3,7 +3,7 @@ title: Export to Excel
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 0.3.3 version: 0.3.4
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg== icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
description: Exports the current chat history to an Excel (.xlsx) file, with automatic header extraction. description: Exports the current chat history to an Excel (.xlsx) file, with automatic header extraction.
""" """
@@ -15,14 +15,24 @@ import base64
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from typing import Optional, Callable, Awaitable, Any, List, Dict from typing import Optional, Callable, Awaitable, Any, List, Dict
import datetime import datetime
import asyncio
from open_webui.models.chats import Chats
from open_webui.models.users import Users
from open_webui.utils.chat import generate_chat_completion
from pydantic import BaseModel, Field
app = FastAPI() app = FastAPI()
class Action: class Action:
class Valves(BaseModel):
TITLE_SOURCE: str = Field(
default="chat_title",
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
)
def __init__(self): def __init__(self):
pass self.valves = self.Valves()
async def _send_notification(self, emitter: Callable, type: str, content: str): async def _send_notification(self, emitter: Callable, type: str, content: str):
await emitter( await emitter(
@@ -35,6 +45,7 @@ class Action:
__user__=None, __user__=None,
__event_emitter__=None, __event_emitter__=None,
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None, __event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
__request__: Optional[Any] = None,
): ):
print(f"action:{__name__}") print(f"action:{__name__}")
if isinstance(__user__, (list, tuple)): if isinstance(__user__, (list, tuple)):
@@ -69,18 +80,64 @@ class Action:
if not tables: if not tables:
raise HTTPException(status_code=400, detail="No tables found.") raise HTTPException(status_code=400, detail="No tables found.")
# Generate filename
title = ""
chat_id = self.extract_chat_id(
body, None
) # metadata not available in action signature yet, but usually in body
# Fetch chat_title directly via chat_id as it's usually missing in body
chat_title = ""
if chat_id:
chat_title = await self.fetch_chat_title(chat_id, user_id)
if (
self.valves.TITLE_SOURCE == "chat_title"
or not self.valves.TITLE_SOURCE
):
title = chat_title
elif self.valves.TITLE_SOURCE == "markdown_title":
title = self.extract_title(message_content)
elif self.valves.TITLE_SOURCE == "ai_generated":
# We need request object for AI generation, but it's not passed in standard action signature in this version
# However, we can try to use the one from global context if available or skip
# For now, let's assume we might not have it and fallback or use what we have
# Wait, export_to_word uses __request__. Let's check if we can add it to signature.
pass
# Get dynamic filename and sheet names # Get dynamic filename and sheet names
workbook_name, sheet_names = self.generate_names_from_content( workbook_name_from_content, sheet_names = (
message_content, tables self.generate_names_from_content(message_content, tables)
) )
# If AI generation is selected but we need request, we need to update signature.
# Let's update signature in next chunk.
# Fallback logic for title
if not title:
if self.valves.TITLE_SOURCE == "ai_generated":
# AI generation needs request, handled later
pass
elif self.valves.TITLE_SOURCE == "markdown_title":
pass # Already tried
# If still no title, try workbook_name_from_content (which uses headers)
if not title and workbook_name_from_content:
title = workbook_name_from_content
# If still no title, use chat_title if available
if not title and chat_title:
title = chat_title
# Use optimized filename generation logic # Use optimized filename generation logic
current_datetime = datetime.datetime.now() current_datetime = datetime.datetime.now()
formatted_date = current_datetime.strftime("%Y%m%d") formatted_date = current_datetime.strftime("%Y%m%d")
# If no title found, use user_yyyymmdd format # If no title found, use user_yyyymmdd format
if not workbook_name: if not title:
workbook_name = f"{user_name}_{formatted_date}" workbook_name = f"{user_name}_{formatted_date}"
else:
workbook_name = self.clean_filename(title)
filename = f"{workbook_name}.xlsx" filename = f"{workbook_name}.xlsx"
excel_file_path = os.path.join( excel_file_path = os.path.join(
@@ -172,6 +229,88 @@ class Action:
__event_emitter__, "error", "No tables found to export!" __event_emitter__, "error", "No tables found to export!"
) )
async def generate_title_using_ai(
self, body: dict, content: str, user_id: str, request: Any
) -> str:
if not request:
return ""
try:
user_obj = Users.get_user_by_id(user_id)
model = body.get("model")
payload = {
"model": model,
"messages": [
{
"role": "system",
"content": "You are a helpful assistant. Generate a short, concise title (max 10 words) for the following text. Do not use quotes. Only output the title.",
},
{"role": "user", "content": content[:2000]}, # Limit content length
],
"stream": False,
}
response = await generate_chat_completion(request, payload, user_obj)
if response and "choices" in response:
return response["choices"][0]["message"]["content"].strip()
except Exception as e:
print(f"Error generating title: {e}")
return ""
def extract_title(self, content: str) -> str:
"""Extract title from Markdown h1/h2 only"""
lines = content.split("\n")
for line in lines:
# Match h1-h2 headings only
match = re.match(r"^#{1,2}\s+(.+)$", line.strip())
if match:
return match.group(1).strip()
return ""
def extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
"""Extract chat_id from body or metadata"""
if isinstance(body, dict):
chat_id = body.get("chat_id") or body.get("id")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
for key in ("chat", "conversation"):
nested = body.get(key)
if isinstance(nested, dict):
nested_id = nested.get("id") or nested.get("chat_id")
if isinstance(nested_id, str) and nested_id.strip():
return nested_id.strip()
if isinstance(metadata, dict):
chat_id = metadata.get("chat_id")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
return ""
async def fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
"""Fetch chat title from database by chat_id"""
if not chat_id:
return ""
def _load_chat():
if user_id:
return Chats.get_chat_by_id_and_user_id(id=chat_id, user_id=user_id)
return Chats.get_chat_by_id(chat_id)
try:
chat = await asyncio.to_thread(_load_chat)
except Exception as exc:
print(f"Failed to load chat {chat_id}: {exc}")
return ""
if not chat:
return ""
data = getattr(chat, "chat", {}) or {}
title = data.get("title") or getattr(chat, "title", "")
return title.strip() if isinstance(title, str) else ""
def extract_tables_from_message(self, message: str) -> List[Dict]: def extract_tables_from_message(self, message: str) -> List[Dict]:
""" """
Extract Markdown tables and their positions from message text Extract Markdown tables and their positions from message text

View File

@@ -3,7 +3,7 @@ title: 导出为 Excel
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 0.3.3 version: 0.3.4
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg== icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
description: 将当前对话历史导出为 Excel (.xlsx) 文件,支持自动提取表头。 description: 将当前对话历史导出为 Excel (.xlsx) 文件,支持自动提取表头。
""" """
@@ -15,14 +15,24 @@ import base64
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from typing import Optional, Callable, Awaitable, Any, List, Dict from typing import Optional, Callable, Awaitable, Any, List, Dict
import datetime import datetime
import asyncio
from open_webui.models.chats import Chats
from open_webui.models.users import Users
from open_webui.utils.chat import generate_chat_completion
from pydantic import BaseModel, Field
app = FastAPI() app = FastAPI()
class Action: class Action:
class Valves(BaseModel):
TITLE_SOURCE: str = Field(
default="chat_title",
description="标题来源:'chat_title' (对话标题), 'ai_generated' (AI生成), 'markdown_title' (Markdown标题)",
)
def __init__(self): def __init__(self):
pass self.valves = self.Valves()
async def _send_notification(self, emitter: Callable, type: str, content: str): async def _send_notification(self, emitter: Callable, type: str, content: str):
await emitter( await emitter(
@@ -35,6 +45,7 @@ class Action:
__user__=None, __user__=None,
__event_emitter__=None, __event_emitter__=None,
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None, __event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
__request__: Optional[Any] = None,
): ):
print(f"action:{__name__}") print(f"action:{__name__}")
if isinstance(__user__, (list, tuple)): if isinstance(__user__, (list, tuple)):
@@ -69,18 +80,58 @@ class Action:
if not tables: if not tables:
raise HTTPException(status_code=400, detail="未找到任何表格。") raise HTTPException(status_code=400, detail="未找到任何表格。")
# 生成文件名
title = ""
chat_id = self.extract_chat_id(
body, None
) # metadata 在此版本 action 签名中不可用,但通常在 body 中
# 直接通过 chat_id 获取对话标题,因为 body 中通常缺少该信息
chat_title = ""
if chat_id:
chat_title = await self.fetch_chat_title(chat_id, user_id)
if (
self.valves.TITLE_SOURCE == "chat_title"
or not self.valves.TITLE_SOURCE
):
title = chat_title
elif self.valves.TITLE_SOURCE == "markdown_title":
title = self.extract_title(message_content)
elif self.valves.TITLE_SOURCE == "ai_generated":
# AI 生成需要 request 对象,稍后处理
pass
# 获取动态文件名和sheet名称 # 获取动态文件名和sheet名称
workbook_name, sheet_names = self.generate_names_from_content( workbook_name_from_content, sheet_names = (
message_content, tables self.generate_names_from_content(message_content, tables)
) )
# 标题回退逻辑
if not title:
if self.valves.TITLE_SOURCE == "ai_generated":
# AI 生成需要 request稍后处理
pass
elif self.valves.TITLE_SOURCE == "markdown_title":
pass # 已尝试
# 如果仍无标题,尝试使用 workbook_name_from_content (基于表头)
if not title and workbook_name_from_content:
title = workbook_name_from_content
# 如果仍无标题,尝试使用 chat_title
if not title and chat_title:
title = chat_title
# 使用优化后的文件名生成逻辑 # 使用优化后的文件名生成逻辑
current_datetime = datetime.datetime.now() current_datetime = datetime.datetime.now()
formatted_date = current_datetime.strftime("%Y%m%d") formatted_date = current_datetime.strftime("%Y%m%d")
# 如果没找到标题则使用 user_yyyymmdd 格式 # 如果没找到标题则使用 user_yyyymmdd 格式
if not workbook_name: if not title:
workbook_name = f"{user_name}_{formatted_date}" workbook_name = f"{user_name}_{formatted_date}"
else:
workbook_name = self.clean_filename(title)
filename = f"{workbook_name}.xlsx" filename = f"{workbook_name}.xlsx"
excel_file_path = os.path.join( excel_file_path = os.path.join(
@@ -172,6 +223,88 @@ class Action:
__event_emitter__, "error", "没有找到可以导出的表格!" __event_emitter__, "error", "没有找到可以导出的表格!"
) )
async def generate_title_using_ai(
self, body: dict, content: str, user_id: str, request: Any
) -> str:
if not request:
return ""
try:
user_obj = Users.get_user_by_id(user_id)
model = body.get("model")
payload = {
"model": model,
"messages": [
{
"role": "system",
"content": "你是一个乐于助人的助手。请为以下文本生成一个简短、简洁的标题最多10个字。不要使用引号。只输出标题。",
},
{"role": "user", "content": content[:2000]}, # 限制内容长度
],
"stream": False,
}
response = await generate_chat_completion(request, payload, user_obj)
if response and "choices" in response:
return response["choices"][0]["message"]["content"].strip()
except Exception as e:
print(f"生成标题时出错: {e}")
return ""
def extract_title(self, content: str) -> str:
"""从 Markdown h1/h2 中提取标题"""
lines = content.split("\n")
for line in lines:
# 仅匹配 h1-h2 标题
match = re.match(r"^#{1,2}\s+(.+)$", line.strip())
if match:
return match.group(1).strip()
return ""
def extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
"""从 body 或 metadata 中提取 chat_id"""
if isinstance(body, dict):
chat_id = body.get("chat_id") or body.get("id")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
for key in ("chat", "conversation"):
nested = body.get(key)
if isinstance(nested, dict):
nested_id = nested.get("id") or nested.get("chat_id")
if isinstance(nested_id, str) and nested_id.strip():
return nested_id.strip()
if isinstance(metadata, dict):
chat_id = metadata.get("chat_id")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
return ""
async def fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
"""通过 chat_id 从数据库获取对话标题"""
if not chat_id:
return ""
def _load_chat():
if user_id:
return Chats.get_chat_by_id_and_user_id(id=chat_id, user_id=user_id)
return Chats.get_chat_by_id(chat_id)
try:
chat = await asyncio.to_thread(_load_chat)
except Exception as exc:
print(f"加载对话 {chat_id} 失败: {exc}")
return ""
if not chat:
return ""
data = getattr(chat, "chat", {}) or {}
title = data.get("title") or getattr(chat, "title", "")
return title.strip() if isinstance(title, str) else ""
def extract_tables_from_message(self, message: str) -> List[Dict]: def extract_tables_from_message(self, message: str) -> List[Dict]:
""" """
从消息文本中提取Markdown表格及位置信息 从消息文本中提取Markdown表格及位置信息

View File

@@ -1,7 +1,7 @@
""" """
title: 精读 (Deep Reading) title: 精读 (Deep Reading)
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIxIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjNDI4NWY0Ii8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjMWU4OGU1Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTYgMmg4bDYgNnYxMmEyIDIgMCAwIDEtMiAySDZhMiAyIDAgMCAxLTItMlY0YTIgMiAwIDAgMSAyLTJ6IiBmaWxsPSJ1cmwoI2cpIi8+PHBhdGggZD0iTTE0IDJsNiA2aC02eiIgZmlsbD0iIzFlODhlNSIgb3BhY2l0eT0iMC42Ii8+PGxpbmUgeDE9IjgiIHkxPSIxMyIgeDI9IjE2IiB5Mj0iMTMiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48bGluZSB4MT0iOCIgeTE9IjE3IiB4Mj0iMTQiIHkyPSIxNyIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMTgiIHI9IjMiIGZpbGw9IiNmZmQ3MDAiLz48cGF0aCBkPSJNMTYgMTZsMS41IDEuNSIgc3Ryb2tlPSIjNDI4NWY0IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg== icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIxIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjNDI4NWY0Ii8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjMWU4OGU1Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTYgMmg4bDYgNnYxMmEyIDIgMCAwIDEtMiAySDZhMiAyIDAgMCAxLTItMlY0YTIgMiAwIDAgMSAyLTJ6IiBmaWxsPSJ1cmwoI2cpIi8+PHBhdGggZD0iTTE0IDJsNiA2aC02eiIgZmlsbD0iIzFlODhlNSIgb3BhY2l0eT0iMC42Ii8+PGxpbmUgeDE9IjgiIHkxPSIxMyIgeDI9IjE2IiB5Mj0iMTMiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48bGluZSB4MT0iOCIgeTE9IjE3IiB4Mj0iMTQiIHkyPSIxNyIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMTgiIHI9IjMiIGZpbGw9IiNmZmQ3MDAiLz48cGF0aCBkPSJNMTYgMTZsMS41IDEuNSIgc3Ryb2tlPSIjNDI4NWY0IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg==
version: 2.0.0 version: 0.1.0
description: 深度分析长篇文本,提炼详细摘要、关键信息点和可执行的行动建议,适合工作和学习场景。 description: 深度分析长篇文本,提炼详细摘要、关键信息点和可执行的行动建议,适合工作和学习场景。
requirements: jinja2, markdown requirements: jinja2, markdown
""" """

View File

@@ -1,6 +1,6 @@
# Async Context Compression Filter # 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. This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent.

View File

@@ -1,6 +1,6 @@
# Example Pipe Plugin # 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. This is a template/example for creating Pipe plugins in OpenWebUI.

View File

@@ -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'(<span class="version-badge">v)([\d\.]+)(</span>)',
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'(<span class="version-badge">v)([\d\.]+)(</span>)',
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()

View File

@@ -96,6 +96,15 @@ def scan_plugins_directory(plugins_dir: str) -> list[dict[str, Any]]:
for root, _dirs, files in os.walk(plugins_path): for root, _dirs, files in os.walk(plugins_path):
for file in files: for file in files:
if file.endswith(".py") and not file.startswith("__"): if file.endswith(".py") and not file.startswith("__"):
# Skip specific files that should not trigger release
if file in [
"gemini_manifold.py",
"gemini_manifold_companion.py",
"ACTION_PLUGIN_TEMPLATE.py",
"ACTION_PLUGIN_TEMPLATE_CN.py",
]:
continue
file_path = os.path.join(root, file) file_path = os.path.join(root, file)
metadata = extract_plugin_metadata(file_path) metadata = extract_plugin_metadata(file_path)
if metadata: if metadata:
@@ -109,9 +118,7 @@ def scan_plugins_directory(plugins_dir: str) -> list[dict[str, Any]]:
return plugins return plugins
def compare_versions( def compare_versions(current: list[dict], previous_file: str) -> dict[str, list[dict]]:
current: list[dict], previous_file: str
) -> dict[str, list[dict]]:
""" """
Compare current plugin versions with a previous version file. Compare current plugin versions with a previous version file.
比较当前插件版本与之前的版本文件。 比较当前插件版本与之前的版本文件。
@@ -168,7 +175,9 @@ def format_markdown_table(plugins: list[dict]) -> str:
"|---------------|----------------|-------------|---------------------|", "|---------------|----------------|-------------|---------------------|",
] ]
for plugin in sorted(plugins, key=lambda x: (x.get("type", ""), x.get("title", ""))): for plugin in sorted(
plugins, key=lambda x: (x.get("type", ""), x.get("title", ""))
):
title = plugin.get("title", "Unknown") title = plugin.get("title", "Unknown")
version = plugin.get("version", "Unknown") version = plugin.get("version", "Unknown")
plugin_type = plugin.get("type", "Unknown").capitalize() plugin_type = plugin.get("type", "Unknown").capitalize()
@@ -181,7 +190,9 @@ def format_markdown_table(plugins: list[dict]) -> str:
return "\n".join(lines) return "\n".join(lines)
def format_release_notes(comparison: dict[str, list]) -> str: def format_release_notes(
comparison: dict[str, list], ignore_removed: bool = False
) -> str:
""" """
Format version comparison as release notes. Format version comparison as release notes.
将版本比较格式化为发布说明。 将版本比较格式化为发布说明。
@@ -206,7 +217,7 @@ def format_release_notes(comparison: dict[str, list]) -> str:
) )
lines.append("") lines.append("")
if comparison["removed"]: if comparison["removed"] and not ignore_removed:
lines.append("### 移除插件 / Removed Plugins") lines.append("### 移除插件 / Removed Plugins")
for plugin in comparison["removed"]: for plugin in comparison["removed"]:
lines.append(f"- **{plugin['title']}** v{plugin['version']}") lines.append(f"- **{plugin['title']}** v{plugin['version']}")
@@ -239,6 +250,11 @@ def main():
metavar="FILE", metavar="FILE",
help="Compare with previous version JSON file", help="Compare with previous version JSON file",
) )
parser.add_argument(
"--ignore-removed",
action="store_true",
help="Ignore removed plugins in output",
)
parser.add_argument( parser.add_argument(
"--output", "--output",
"-o", "-o",
@@ -257,7 +273,9 @@ def main():
if args.json: if args.json:
output = json.dumps(comparison, indent=2, ensure_ascii=False) output = json.dumps(comparison, indent=2, ensure_ascii=False)
else: else:
output = format_release_notes(comparison) output = format_release_notes(
comparison, ignore_removed=args.ignore_removed
)
if not output.strip(): if not output.strip():
output = "No changes detected. / 未检测到更改。" output = "No changes detected. / 未检测到更改。"
elif args.json: elif args.json:
@@ -268,7 +286,9 @@ def main():
# Default: simple list # Default: simple list
lines = [] lines = []
for plugin in sorted(plugins, key=lambda x: x.get("title", "")): for plugin in sorted(plugins, key=lambda x: x.get("title", "")):
lines.append(f"{plugin.get('title', 'Unknown')}: v{plugin.get('version', '?')}") lines.append(
f"{plugin.get('title', 'Unknown')}: v{plugin.get('version', '?')}"
)
output = "\n".join(lines) output = "\n".join(lines)
# Write output # Write output