Compare commits

...

80 Commits

Author SHA1 Message Date
fujie
f650c64ffe feat: update markdown-normalizer to v1.1.0 (fix mermaid syntax & html safeguard) 2026-01-12 23:44:27 +08:00
github-actions[bot]
6000c880de chore: update community stats 2026-01-12 2026-01-12 15:10:48 +00:00
github-actions[bot]
048fbb26d7 chore: update community stats 2026-01-12 2026-01-12 14:10:53 +00:00
github-actions[bot]
a88eda62cc chore: update community stats 2026-01-12 2026-01-12 13:21:31 +00:00
github-actions[bot]
957fb2dfb7 chore: update community stats 2026-01-12 2026-01-12 12:14:57 +00:00
github-actions[bot]
d2be5109ad chore: update community stats 2026-01-12 2026-01-12 11:08:33 +00:00
github-actions[bot]
80fdc52598 chore: update community stats 2026-01-12 2026-01-12 10:10:32 +00:00
github-actions[bot]
2b90ead3cf chore: update community stats 2026-01-12 2026-01-12 09:14:36 +00:00
github-actions[bot]
2aa5d77586 chore: update community stats 2026-01-12 2026-01-12 08:12:52 +00:00
github-actions[bot]
2b1b1ef939 chore: update community stats 2026-01-12 2026-01-12 07:14:15 +00:00
github-actions[bot]
4e21e06617 chore: update community stats 2026-01-12 2026-01-12 06:14:40 +00:00
github-actions[bot]
82ce1cef29 chore: update community stats 2026-01-12 2026-01-12 05:15:42 +00:00
github-actions[bot]
533eace74e chore: update community stats 2026-01-12 2026-01-12 04:28:03 +00:00
github-actions[bot]
83b3dcda65 chore: update community stats 2026-01-12 2026-01-12 03:39:51 +00:00
github-actions[bot]
e89373e0ed chore: update community stats 2026-01-12 2026-01-12 02:52:20 +00:00
github-actions[bot]
4b66a2bb1c chore: update community stats 2026-01-12 2026-01-12 01:38:07 +00:00
github-actions[bot]
59ba23da63 chore: update community stats 2026-01-12 2026-01-12 00:37:16 +00:00
github-actions[bot]
f8a89e222c chore: update community stats 2026-01-11 2026-01-11 23:07:51 +00:00
github-actions[bot]
096568f3e6 chore: update community stats 2026-01-11 2026-01-11 22:07:55 +00:00
github-actions[bot]
e10e12ebc9 chore: update community stats 2026-01-11 2026-01-11 21:07:16 +00:00
github-actions[bot]
c4df5eba47 chore: update community stats 2026-01-11 2026-01-11 20:08:19 +00:00
Jeff
0da3d3d881 Merge pull request #25 from Fu-Jie/all-contributors/add-i-iooi-i
docs: add i-iooi-i as a contributor for bug, and ideas
2026-01-12 03:59:47 +08:00
allcontributors[bot]
6f4a62d1bc docs: update .all-contributorsrc [skip ci] 2026-01-11 19:59:25 +00:00
allcontributors[bot]
5d71c2a4d3 docs: update README.md [skip ci] 2026-01-11 19:59:24 +00:00
github-actions[bot]
097707c168 chore: update community stats 2026-01-11 2026-01-11 19:06:29 +00:00
fujie
8f4cfceb50 docs: add multiple contributions handling to copilot instructions 2026-01-12 02:22:57 +08:00
Jeff
4ab5fab7d0 Update README.md 2026-01-12 02:21:19 +08:00
fujie
0e293be8bc docs: add contributor recognition standards to copilot instructions 2026-01-12 02:20:18 +08:00
Jeff
182c12f81a Merge pull request #23 from Fu-Jie/all-contributors/add-rbb-dev
docs: add rbb-dev as a contributor for ideas, and code
2026-01-12 02:16:00 +08:00
Jeff
1337a90911 Merge branch 'main' into all-contributors/add-rbb-dev 2026-01-12 02:15:51 +08:00
Jeff
2f0a347ab3 Merge pull request #24 from Fu-Jie/all-contributors/add-dhaern
docs: add dhaern as a contributor for bug, and ideas
2026-01-12 02:12:48 +08:00
allcontributors[bot]
4eda286512 docs: update .all-contributorsrc [skip ci] 2026-01-11 18:11:12 +00:00
allcontributors[bot]
0fead8158d docs: update README.md [skip ci] 2026-01-11 18:11:11 +00:00
github-actions[bot]
031bef563a chore: update community stats 2026-01-11 2026-01-11 18:10:54 +00:00
allcontributors[bot]
04c3fd2bf9 docs: update .all-contributorsrc [skip ci] 2026-01-11 18:10:12 +00:00
allcontributors[bot]
cbbf6118b5 docs: update README.md [skip ci] 2026-01-11 18:10:11 +00:00
Jeff
4c529369ce Merge pull request #22 from Fu-Jie/all-contributors/add-rbb-dev
docs: add rbb-dev as a contributor for ideas
2026-01-12 02:07:35 +08:00
allcontributors[bot]
797dea0d77 docs: create .all-contributorsrc [skip ci] 2026-01-11 18:06:46 +00:00
allcontributors[bot]
a91aee31de docs: update README.md [skip ci] 2026-01-11 18:06:45 +00:00
fujie
8511b7df80 feat: add version column to top plugins stats and optimize workflow 2026-01-12 01:57:15 +08:00
fujie
afd1e7a444 docs: update copilot instructions with filter plugin best practices 2026-01-12 01:49:46 +08:00
fujie
34b2c3d6cf fix(async-context-compression): resolve race condition, update role to assistant, bump to v1.1.3 2026-01-12 01:45:58 +08:00
github-actions[bot]
d5c099dd15 chore: update community stats 2026-01-11 2026-01-11 17:06:52 +00:00
fujie
8810223693 docs: add Multi-Model Context Merger to plugin lists in READMEs 2026-01-12 00:30:55 +08:00
fujie
84974a2fb9 docs: add Gemini Multimodal Filter to plugin lists in READMEs 2026-01-12 00:30:09 +08:00
fujie
af847293af docs: correct plugin lists in READMEs (rename Knowledge Card, remove Summary, add Deep Dive) 2026-01-12 00:28:41 +08:00
fujie
a44e80ce5b fix: resolve syntax error in community client and refine error logging 2026-01-12 00:22:22 +08:00
fujie
c2815e13e9 chore: cleanup debug logs in community client 2026-01-12 00:22:05 +08:00
fujie
56bfa3a3ef fix: provide function id in update payload to resolve 400 error 2026-01-12 00:18:58 +08:00
fujie
a13c915f27 fix: revert _find_images to _find_image to ensure API compatibility 2026-01-12 00:17:03 +08:00
fujie
fb2d35237e fix: revert to single image support as API does not support multiple images 2026-01-12 00:16:47 +08:00
fujie
3f19ecfd20 feat: support multiple images and improve error logging for plugin updates 2026-01-12 00:13:32 +08:00
fujie
2fd96f07aa fix: robust payload cleaning for plugin updates to resolve 422 error 2026-01-12 00:12:56 +08:00
fujie
a1c1ed9840 fix: resolve 422 error in plugin update by cleaning payload and fixing media format 2026-01-12 00:07:10 +08:00
fujie
c63701d05f docs: update infographic plugin documentation to v1.4.9 2026-01-12 00:01:28 +08:00
fujie
863805dc68 feat: release markdown_normalizer v1.0.1 with enhanced mermaid support and debug logging 2026-01-11 23:58:23 +08:00
github-actions[bot]
98f7dff458 chore: update community stats 2026-01-11 2026-01-11 11:06:46 +00:00
fujie
08c0dd984c docs: add 'Updated' column for each plugin in Top 6 table 2026-01-11 18:05:01 +08:00
fujie
e870ad8823 docs: add last updated time to Top 6 plugins section and update stats script 2026-01-11 17:59:08 +08:00
fujie
d687fffdb5 docs: further simplify contributing guides to focus on plugin files 2026-01-11 17:56:37 +08:00
fujie
d534d8b319 docs: split contributing guide into English and Chinese and remove docs requirement 2026-01-11 17:55:47 +08:00
fujie
d5c5158726 docs: simplify and bilingualize contributing guide 2026-01-11 17:54:49 +08:00
fujie
888026876f ci: auto-trigger plugin publishing on main branch push 2026-01-11 17:51:34 +08:00
Jeff
06e8d30900 Merge pull request #20 from Fu-Jie/copilot/fix-session-alias-errors
Harden async context compression against new DB session alias and runtime edge cases
2026-01-11 17:26:03 +08:00
fujie
cbf2ff7f93 chore: release async-context-compression v1.1.2
- Enhanced error reporting via status bar and console
- Robust model ID handling
- Open WebUI v0.7.x compatibility (dynamic DB session)
- Updated documentation and version bumps
2026-01-11 17:25:07 +08:00
copilot-swe-agent[bot]
abbe3fb248 chore: centralize chat_id extraction helper
Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com>
2026-01-11 08:36:13 +00:00
copilot-swe-agent[bot]
7e44dde979 chore: add discovery docstrings
Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com>
2026-01-11 08:31:10 +00:00
copilot-swe-agent[bot]
3649d75539 chore: add discovery debug logs
Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com>
2026-01-11 08:30:02 +00:00
copilot-swe-agent[bot]
d3b4219a9a chore: refine db session discovery messaging
Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com>
2026-01-11 08:28:52 +00:00
copilot-swe-agent[bot]
9e98d55e11 fix: make async compression db session discovery robust
Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com>
2026-01-11 08:27:36 +00:00
copilot-swe-agent[bot]
4b8515f682 fix: ensure empty summary model skips compression
Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com>
2026-01-11 08:25:33 +00:00
copilot-swe-agent[bot]
d2f35ce396 fix: harden async compression compatibility
Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com>
2026-01-11 08:24:56 +00:00
copilot-swe-agent[bot]
f479f23b38 Initial plan 2026-01-11 08:19:33 +00:00
github-actions[bot]
51048f9e5d chore: update community stats 2026-01-10 2026-01-10 18:11:03 +00:00
github-actions[bot]
1118ae34c4 chore: update community stats 2026-01-10 2026-01-10 14:07:43 +00:00
github-actions[bot]
7a5e1a4e12 chore: update community stats 2026-01-10 2026-01-10 12:12:24 +00:00
fujie
8e377e1794 Update Copilot instructions: Limit What's New section to latest 3 updates 2026-01-10 19:09:09 +08:00
fujie
d66360b02d Update READMEs with v1.1.1 release notes 2026-01-10 19:07:49 +08:00
fujie
1ece648006 Update Async Context Compression docs to v1.1.1 and improve plugin update logic to detect README changes 2026-01-10 19:07:49 +08:00
github-actions[bot]
a262a716a3 chore: update community stats 2026-01-10 2026-01-10 11:06:57 +00:00
38 changed files with 1583 additions and 682 deletions

47
.all-contributorsrc Normal file
View File

@@ -0,0 +1,47 @@
{
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"commitType": "docs",
"commitConvention": "angular",
"contributors": [
{
"login": "rbb-dev",
"name": "rbb-dev",
"avatar_url": "https://avatars.githubusercontent.com/u/37469229?v=4",
"profile": "https://github.com/rbb-dev",
"contributions": [
"ideas",
"code"
]
},
{
"login": "dhaern",
"name": "Raxxoor",
"avatar_url": "https://avatars.githubusercontent.com/u/7317522?v=4",
"profile": "https://trade.xyz/?ref=BZ1RJRXWO",
"contributions": [
"bug",
"ideas"
]
},
{
"login": "i-iooi-i",
"name": "ZOLO",
"avatar_url": "https://avatars.githubusercontent.com/u/1827701?v=4",
"profile": "https://github.com/i-iooi-i",
"contributions": [
"bug",
"ideas"
]
}
],
"contributorsPerLine": 7,
"skipCi": true,
"repoType": "github",
"repoHost": "https://github.com",
"projectName": "awesome-openwebui",
"projectOwner": "Fu-Jie"
}

View File

@@ -40,7 +40,7 @@ plugins/actions/export_to_docx/
- 格式: `**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** x.x.x | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)`
- **注意**: Author 和 Project 为固定值,仅需更新 Version 版本号
3. **描述 (Description)**: 一句话功能介绍
4. **最新更新 (What's New)**: **必须**放在描述之后,显著展示最新版本的变更点
4. **最新更新 (What's New)**: **必须**放在描述之后,显著展示最新版本的变更点 (仅展示最近 3 次更新)
5. **核心特性 (Key Features)**: 使用 Emoji + 粗体标题 + 描述格式
6. **使用方法 (How to Use)**: 按步骤说明
7. **配置参数 (Configuration/Valves)**: 使用表格格式,包含参数名、默认值、描述
@@ -96,7 +96,7 @@ example code or syntax here
### 文档内容要求 (Content Requirements)
- **新增功能**: 必须在 "What's New" 章节中明确列出,使用 Emoji + 粗体标题格式。
- **新增功能**: 必须在 "What's New" 章节中明确列出,使用 Emoji + 粗体标题格式 (仅保留最近 3 个版本的更新记录)
- **双语**: 必须提供 `README.md` (英文) 和 `README_CN.md` (中文)。
- **表格对齐**: 配置参数表格使用左对齐 `:---`
- **Emoji 规范**: 标题使用合适的 Emoji 增强可读性。
@@ -320,6 +320,43 @@ logger.error(f"Processing failed: {e}", exc_info=True)
---
## 🛡️ Filter 插件开发规范 (Filter Plugin Standards)
### 1. 状态管理 (State Management) - **关键 (Critical)**
Filter 实例在 OpenWebUI 生命周期中是**单例 (Singleton)**。这意味着同一个 Filter 实例会处理所有并发请求。
- **❌ 禁止 (Prohibited)**: 使用 `self` 存储请求级别的临时状态(如 `self.temp_state`)。这会导致严重的**竞态条件 (Race Conditions)**,即一个请求的数据被另一个请求覆盖。
- **✅ 推荐 (Recommended)**:
- **无状态设计**: `inlet``outlet` 应该尽可能独立。
- **重新计算**: 在 `outlet` 中根据 `body['messages']` 重新计算所需的状态,而不是依赖 `inlet` 传递。
- **元数据传递**: 如果必须传递状态,尝试使用 `body` 中的临时字段(需谨慎处理清理)或 `__metadata__`(如果可写)。
### 2. 摘要注入角色 (Summary Injection Role)
当注入历史摘要或上下文时:
- **❌ 避免 (Avoid)**: 使用 `system` 角色(部分模型对 system prompt 位置敏感或不支持中间插入)。
- **❌ 避免 (Avoid)**: 使用 `user` 角色(容易混淆用户真实意图)。
- **✅ 推荐 (Recommended)**: 使用 **`assistant`** 角色。这通常被模型视为上下文历史的一部分,兼容性最好。
### 3. 模型默认值 (Model Defaults)
- **❌ 禁止 (Prohibited)**: 硬编码特定模型 ID`gpt-3.5-turbo`)作为默认值。这会导致非 OpenAI 用户出错。
- **✅ 推荐 (Recommended)**:
- 默认值设为 `None` 或空字符串。
- 优先使用当前对话的模型 (`body.get("model")`)。
- 如果必须指定,通过 `Valves` 让用户配置。
### 4. 异步处理 (Async Processing)
对于耗时的后台任务(如摘要生成、日志记录):
- **✅ 推荐 (Recommended)**: 在 `outlet` 中使用 `asyncio.create_task()` 启动后台任务,确保不阻塞用户响应。
- **✅ 推荐 (Recommended)**: 在后台任务中捕获所有异常,防止崩溃影响主进程。
---
## 🎨 HTML 注入规范 (HTML Injection)
使用统一的标记和结构:
@@ -1550,6 +1587,48 @@ feat(flash-card): add _get_user_context for safer user info retrieval
---
## 🤝 贡献者认可规范 (Contributor Recognition Standards)
本项目使用 [All Contributors](https://allcontributors.org/) 规范来认可所有形式的贡献。
### 1. 如何添加贡献者 (How to Add)
在 GitHub 的 **Issue** 或 **Pull Request** 评论区发送以下指令Bot 会自动创建 PR 更新 README
```text
@all-contributors please add @username for <contribution-type>
```
### 2. 常用贡献类型 (Common Contribution Types)
| 类型 (Type) | 含义 (Meaning) | 图标 (Icon) |
| :--- | :--- | :---: |
| **`ideas`** | 提供想法、功能建议或改进思路 | 🤔 |
| **`code`** | 编写并提交代码实现 | 💻 |
| **`bug`** | 报告 Bug 或发现逻辑缺陷 | 🐛 |
| **`doc`** | 改进文档、README 或注释 | 📖 |
| **`translation`** | 提供多语言翻译支持 | 🌍 |
| **`review`** | 进行代码审查 (Code Review) | 👀 |
| **`design`** | 提供 UI/UX 设计或图标 | 🎨 |
| **`question`** | 在讨论区回答用户问题 | 💬 |
| **`tutorial`** | 编写教程或使用指南 | ✅ |
### 3. 核心区别:`ideas` vs `code`
- **`ideas`**: 贡献者提供了核心思路、逻辑优化方案或功能需求,但未直接编写代码。
- **`code`**: 贡献者直接编写并提交了 Pull Request。
- **组合使用**: 如果贡献者既提出了方案又完成了实现,应同时添加:`for ideas, code`。
### 4. 多次贡献处理 (Multiple Contributions)
All Contributors 支持勋章累加,无需担心重复添加:
- **累加勋章**: 如果贡献者已在列表中,再次发送指令指定新类型(如 `@all-contributors please add @user for doc`Bot 会自动将新勋章追加到该用户头像下方。
- **一次性添加**: 支持在单条指令中列出所有类型:`for code, doc, ideas`。
- **手动修正**: 若需删除或修正勋章,需手动编辑 `.all-contributorsrc` 文件中的 `contributions` 数组。
---
## <20>📚 参考资源 (Reference Resources)
- [Action 插件模板 (英文)](plugins/actions/ACTION_PLUGIN_TEMPLATE.py)

View File

@@ -32,16 +32,7 @@ jobs:
run: |
pip install requests python-dotenv
- name: Get previous stats
id: prev_stats
run: |
# 获取当前的 points 用于比较
if [ -f docs/community-stats.json ]; then
OLD_POINTS=$(jq -r '.user.total_points' docs/community-stats.json 2>/dev/null || echo "0")
echo "old_points=$OLD_POINTS" >> $GITHUB_OUTPUT
else
echo "old_points=0" >> $GITHUB_OUTPUT
fi
- name: Generate stats report
env:
@@ -50,26 +41,7 @@ jobs:
run: |
python scripts/openwebui_stats.py
- name: Check for significant changes
id: check_changes
run: |
# 获取新的 points
NEW_POINTS=$(jq -r '.user.total_points' docs/community-stats.json 2>/dev/null || echo "0")
echo "📊 Previous points: ${{ steps.prev_stats.outputs.old_points }}"
echo "📊 Current points: $NEW_POINTS"
# 只在 points 变化时才 commit
if [ "$NEW_POINTS" != "${{ steps.prev_stats.outputs.old_points }}" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
echo "✅ Points changed (${{ steps.prev_stats.outputs.old_points }} → $NEW_POINTS), will commit"
else
echo "changed=false" >> $GITHUB_OUTPUT
echo "⏭️ Points unchanged, skipping commit"
fi
- 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]"

View File

@@ -1,6 +1,11 @@
name: Publish Plugins to OpenWebUI Market
on:
push:
branches:
- main
paths:
- 'plugins/**/*.py'
release:
types: [published]
workflow_dispatch:

View File

@@ -1,87 +1,16 @@
# 贡献指南 (Contributing Guide)
# Contributing Guide
感谢你对 **OpenWebUI Extras** 感兴趣!我们非常欢迎社区贡献更多的插件、提示词和创意。
Thank you for your interest in **OpenWebUI Extras**!
## 🤝 如何贡献
## 🚀 How to Contribute
### 1. 分享提示词 (Prompts)
1. **Fork** this repository.
2. **Add/Modify** the plugin file in the `plugins/` directory.
3. **Submit PR**: We will review and merge it.
如果你有一个好用的提示词:
1.`prompts/` 目录下找到合适的分类(如 `coding/`, `writing/`)。如果没有合适的,可以新建一个文件夹。
2. 创建一个新的 `.md``.json` 文件。
3. 提交 Pull Request (PR)。
## 💡 Important
### 2. 开发插件 (Plugins)
- Ensure your plugin includes complete metadata (title, author, version, description).
- If updating an existing plugin, please **increment the version number** (e.g., `0.1.0` -> `0.1.1`) to trigger the auto-update.
如果你开发了一个新的 OpenWebUI 插件 (Function/Tool)
1. 确保你的插件代码包含完整的元数据Frontmatter
```python
"""
title: 插件名称
author: 你的名字
version: 0.1.0
description: 简短描述插件的功能
"""
```
2. 将插件文件放入 `plugins/` 目录下的合适位置:
- `plugins/actions/`: 用于添加按钮或修改消息的 Action 插件。
- `plugins/filters/`: 用于拦截请求或响应的 Filter 插件。
- `plugins/pipes/`: 用于自定义模型或 API 的 Pipe 插件。
- `plugins/tools/`: 用于 LLM 调用的 Tool 插件。
3. 建议在 `docs/` 下添加一个简单的使用说明。
### 3. 改进文档
如果你发现文档有错误或可以改进的地方,直接提交 PR 即可。
## 🛠️ 开发规范
- **代码风格**Python 代码请遵循 PEP 8 规范。
- **注释**:关键逻辑请添加注释,方便他人理解。
- **测试**:提交前请在本地 OpenWebUI 环境中测试通过。
## 📝 提交 PR
1. Fork 本仓库。
2. 创建一个新的分支 (`git checkout -b feature/AmazingFeature`)。
3. 提交你的修改 (`git commit -m 'Add some AmazingFeature'`)。
4. 推送到分支 (`git push origin feature/AmazingFeature`)。
5. 开启一个 Pull Request。
## 📦 版本更新与发布
当你更新插件时,请遵循以下流程:
### 1. 更新版本号
在插件文件的 docstring 中更新版本号(遵循[语义化版本](https://semver.org/lang/zh-CN/)
```python
"""
title: 我的插件
version: 0.2.0 # 更新此处
...
"""
```
### 2. 更新更新日志
在 `CHANGELOG.md` 的 `[Unreleased]` 部分添加你的更改:
```markdown
### Added / 新增
- 新功能描述
### Fixed / 修复
- Bug 修复描述
```
### 3. 发布流程
维护者会通过以下方式发布新版本:
- 手动触发 GitHub Actions 中的 "Plugin Release" 工作流
- 或创建版本标签 (`v*`)
详细说明请参阅 [发布工作流文档](docs/release-workflow.zh.md)。
再次感谢你的贡献!🚀
Thank you! 🚀

16
CONTRIBUTING_CN.md Normal file
View File

@@ -0,0 +1,16 @@
# 贡献指南
感谢你对 **OpenWebUI Extras** 感兴趣!
## 🚀 贡献流程
1. **Fork** 本仓库。
2. **修改/添加** `plugins/` 目录下的插件文件。
3. **提交 PR**: 我们会尽快审核并合并。
## 💡 注意事项
- 请确保插件包含完整的元数据title, author, version, description
- 如果是更新已有插件,请记得**增加版本号**(如 `0.1.0` -> `0.1.1`),这样系统会自动同步更新。
再次感谢你的贡献!🚀

View File

@@ -1,4 +1,7 @@
# OpenWebUI Extras
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
English | [中文](./README_CN.md)
@@ -7,26 +10,28 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
<!-- STATS_START -->
## 📊 Community Stats
> 🕐 Auto-updated: 2026-01-10 17:08
> 🕐 Auto-updated: 2026-01-12 23:10
| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |
|:---:|:---:|:---:|:---:|
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **71** | **72** | **21** |
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **88** | **91** | **22** |
| 📝 Posts | ⬇️ Downloads | 👁️ Views | 👍 Upvotes | 💾 Saves |
|:---:|:---:|:---:|:---:|:---:|
| **14** | **1066** | **11486** | **64** | **66** |
| **14** | **1230** | **13650** | **80** | **79** |
### 🔥 Top 6 Popular Plugins
| Rank | Plugin | Downloads | Views |
|:---:|------|:---:|:---:|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 341 | 3080 |
| 🥈 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 181 | 551 |
| 🥉 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 125 | 1390 |
| 4 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 116 | 1355 |
| 5 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 92 | 1735 |
| 6️⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 87 | 800 |
> 🕐 Auto-updated: 2026-01-12 23:10
| Rank | Plugin | Version | Downloads | Views | Updated |
|:---:|------|:---:|:---:|:---:|:---:|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 392 | 3535 | 2026-01-07 |
| 🥈 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 187 | 604 | 2026-01-07 |
| 🥉 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 145 | 1594 | 2026-01-11 |
| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.1.3 | 144 | 1596 | 2026-01-11 |
| 5⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 104 | 938 | 2026-01-07 |
| 6⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 101 | 1881 | 2026-01-07 |
*See full stats in [Community Stats Report](./docs/community-stats.md)*
<!-- STATS_END -->
@@ -40,15 +45,18 @@ Located in the `plugins/` directory, containing Python-based enhancements:
#### Actions
- **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.
- **Flash Card** (`flash-card`): Quickly generates beautiful flashcards for learning.
- **Deep Dive** (`deep-dive`): A comprehensive thinking lens that dives deep into any content.
- **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.
#### Filters
- **Async Context Compression** (`async-context-compression`): Optimizes token usage via context compression.
- **Context Enhancement** (`context_enhancement_filter`): Enhances chat context.
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Companion filter for Gemini Manifold.
- **Gemini Multimodal Filter** (`web_gemini_multimodel_filter`): Provides multimodal capabilities (PDF, Office, Video) for any model via Gemini.
- **Markdown Normalizer** (`markdown_normalizer`): Fixes common Markdown formatting issues in LLM outputs.
- **Multi-Model Context Merger** (`multi_model_context_merger`): Automatically merges and injects context from multiple model responses.
#### Pipes
@@ -104,3 +112,27 @@ If you have great prompts or plugins to share:
3. Submit a Pull Request.
[Contributing](./CONTRIBUTING.md)
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rbb-dev"><img src="https://avatars.githubusercontent.com/u/37469229?v=4?s=100" width="100px;" alt="rbb-dev"/><br /><sub><b>rbb-dev</b></sub></a><br /><a href="#ideas-rbb-dev" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Fu-Jie/awesome-openwebui/commits?author=rbb-dev" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://trade.xyz/?ref=BZ1RJRXWO"><img src="https://avatars.githubusercontent.com/u/7317522?v=4?s=100" width="100px;" alt="Raxxoor"/><br /><sub><b>Raxxoor</b></sub></a><br /><a href="https://github.com/Fu-Jie/awesome-openwebui/issues?q=author%3Adhaern" title="Bug reports">🐛</a> <a href="#ideas-dhaern" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/i-iooi-i"><img src="https://avatars.githubusercontent.com/u/1827701?v=4?s=100" width="100px;" alt="ZOLO"/><br /><sub><b>ZOLO</b></sub></a><br /><a href="https://github.com/Fu-Jie/awesome-openwebui/issues?q=author%3Ai-iooi-i" title="Bug reports">🐛</a> <a href="#ideas-i-iooi-i" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -7,26 +7,28 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
<!-- STATS_START -->
## 📊 社区统计
> 🕐 自动更新于 2026-01-10 17:08
> 🕐 自动更新于 2026-01-12 23:10
| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |
|:---:|:---:|:---:|:---:|
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **71** | **72** | **21** |
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **88** | **91** | **22** |
| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |
|:---:|:---:|:---:|:---:|:---:|
| **14** | **1066** | **11486** | **64** | **66** |
| **14** | **1230** | **13650** | **80** | **79** |
### 🔥 热门插件 Top 6
| 排名 | 插件 | 下载 | 浏览 |
|:---:|------|:---:|:---:|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 341 | 3080 |
| 🥈 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 181 | 551 |
| 🥉 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 125 | 1390 |
| 4 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 116 | 1355 |
| 5 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 92 | 1735 |
| 6️⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 87 | 800 |
> 🕐 自动更新于 2026-01-12 23:10
| 排名 | 插件 | 版本 | 下载 | 浏览 | 更新日期 |
|:---:|------|:---:|:---:|:---:|:---:|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 392 | 3535 | 2026-01-07 |
| 🥈 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 187 | 604 | 2026-01-07 |
| 🥉 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 145 | 1594 | 2026-01-11 |
| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.1.3 | 144 | 1596 | 2026-01-11 |
| 5⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 104 | 938 | 2026-01-07 |
| 6⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 101 | 1881 | 2026-01-07 |
*完整统计请查看 [社区统计报告](./docs/community-stats.zh.md)*
<!-- STATS_END -->
@@ -40,15 +42,18 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
#### Actions (交互增强)
- **Smart Mind Map** (`smart-mind-map`): 智能分析文本并生成交互式思维导图。
- **Smart Infographic** (`infographic`): 基于 AntV 的智能信息图生成工具。
- **Knowledge Card** (`knowledge-card`): 快速生成精美的学习记忆卡片。
- **Flash Card** (`flash-card`): 快速生成精美的学习记忆卡片。
- **Deep Dive** (`deep-dive`): 深度思考透镜,从背景、逻辑、洞察到行动路径的全方位分析。
- **Export to Excel** (`export_to_excel`): 将对话内容导出为 Excel 文件。
- **Export to Word** (`export_to_docx`): 将对话内容导出为 Word 文档。
- **Summary** (`summary`): 文本摘要生成工具。
#### Filters (消息处理)
- **Async Context Compression** (`async-context-compression`): 异步上下文压缩,优化 Token 使用。
- **Context Enhancement** (`context_enhancement_filter`): 上下文增强过滤器。
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Gemini Manifold 配套增强。
- **Gemini Multimodal Filter** (`web_gemini_multimodel_filter`): 为任意模型提供多模态能力PDF、Office、视频等支持智能路由和字幕精修。
- **Markdown Normalizer** (`markdown_normalizer`): 修复 LLM 输出中常见的 Markdown 格式问题。
- **Multi-Model Context Merger** (`multi_model_context_merger`): 自动合并并注入多模型回答的上下文。
#### Pipes (模型管道)
- **Gemini Manifold** (`gemini_mainfold`): 集成 Gemini 模型的管道。
@@ -105,4 +110,4 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
2. 将你的文件添加到对应的 `prompts/``plugins/` 目录。
3. 提交 Pull Request。
[贡献指南](./CONTRIBUTING.md) | [更新日志](./CHANGELOG.md)
[贡献指南](./CONTRIBUTING_CN.md) | [更新日志](./CHANGELOG.md)

View File

@@ -1,15 +1,14 @@
{
"total_posts": 14,
"total_downloads": 1066,
"total_views": 11486,
"total_upvotes": 64,
"total_downloads": 1230,
"total_views": 13650,
"total_upvotes": 80,
"total_downvotes": 2,
"total_saves": 66,
"total_comments": 15,
"total_saves": 79,
"total_comments": 18,
"by_type": {
"unknown": 1,
"action": 11,
"filter": 2
"action": 13,
"unknown": 1
},
"posts": [
{
@@ -19,11 +18,11 @@
"version": "0.9.1",
"author": "Fu-Jie",
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
"downloads": 341,
"views": 3080,
"upvotes": 10,
"saves": 21,
"comments": 10,
"downloads": 392,
"views": 3535,
"upvotes": 11,
"saves": 22,
"comments": 11,
"created_at": "2025-12-30",
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a"
@@ -35,8 +34,8 @@
"version": "0.3.7",
"author": "Fu-Jie",
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
"downloads": 181,
"views": 551,
"downloads": 187,
"views": 604,
"upvotes": 3,
"saves": 4,
"comments": 0,
@@ -44,53 +43,37 @@
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d"
},
{
"title": "Async Context Compression",
"slug": "async_context_compression_b1655bc8",
"type": "filter",
"version": "1.1.0",
"author": "Fu-Jie",
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
"downloads": 125,
"views": 1390,
"upvotes": 5,
"saves": 9,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
},
{
"title": "📊 Smart Infographic (AntV)",
"slug": "smart_infographic_ad6f0c7f",
"type": "action",
"version": "1.4.1",
"author": "jeff",
"version": "1.4.9",
"author": "Fu-Jie",
"description": "AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.",
"downloads": 116,
"views": 1355,
"upvotes": 7,
"saves": 9,
"downloads": 145,
"views": 1594,
"upvotes": 8,
"saves": 10,
"comments": 2,
"created_at": "2025-12-28",
"updated_at": "2026-01-07",
"updated_at": "2026-01-11",
"url": "https://openwebui.com/posts/smart_infographic_ad6f0c7f"
},
{
"title": "Flash Card",
"slug": "flash_card_65a2ea8f",
"title": "Async Context Compression",
"slug": "async_context_compression_b1655bc8",
"type": "action",
"version": "0.2.4",
"version": "1.1.3",
"author": "Fu-Jie",
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
"downloads": 92,
"views": 1735,
"upvotes": 8,
"saves": 6,
"comments": 2,
"created_at": "2025-12-30",
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/flash_card_65a2ea8f"
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
"downloads": 144,
"views": 1596,
"upvotes": 7,
"saves": 11,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-11",
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
},
{
"title": "Export to Word (Enhanced)",
@@ -99,30 +82,30 @@
"version": "0.4.3",
"author": "Fu-Jie",
"description": "Export current conversation from Markdown to Word (.docx) with Mermaid diagrams rendered client-side (Mermaid.js, SVG+PNG), LaTeX math, real hyperlinks, improved tables, syntax highlighting, and blockquote support.",
"downloads": 87,
"views": 800,
"upvotes": 5,
"saves": 8,
"downloads": 104,
"views": 938,
"upvotes": 6,
"saves": 10,
"comments": 0,
"created_at": "2026-01-03",
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315"
},
{
"title": "📊 智能信息图 (AntV Infographic)",
"slug": "智能信息图_e04a48ff",
"title": "Flash Card",
"slug": "flash_card_65a2ea8f",
"type": "action",
"version": "1.4.1",
"author": "jeff",
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
"downloads": 35,
"views": 480,
"upvotes": 3,
"saves": 0,
"comments": 0,
"created_at": "2025-12-28",
"version": "0.2.4",
"author": "Fu-Jie",
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
"downloads": 101,
"views": 1881,
"upvotes": 8,
"saves": 6,
"comments": 2,
"created_at": "2025-12-30",
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
"url": "https://openwebui.com/posts/flash_card_65a2ea8f"
},
{
"title": "导出为 Word (增强版)",
@@ -131,15 +114,31 @@
"version": "0.4.3",
"author": "Fu-Jie",
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
"downloads": 31,
"views": 929,
"upvotes": 8,
"downloads": 40,
"views": 1038,
"upvotes": 9,
"saves": 2,
"comments": 1,
"created_at": "2026-01-04",
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0"
},
{
"title": "📊 智能信息图 (AntV Infographic)",
"slug": "智能信息图_e04a48ff",
"type": "action",
"version": "1.4.9",
"author": "Fu-Jie",
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
"downloads": 35,
"views": 525,
"upvotes": 3,
"saves": 0,
"comments": 0,
"created_at": "2025-12-28",
"updated_at": "2026-01-11",
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
},
{
"title": "Deep Dive",
"slug": "deep_dive_c0b846e4",
@@ -147,10 +146,10 @@
"version": "1.0.0",
"author": "Fu-Jie",
"description": "A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.",
"downloads": 22,
"views": 259,
"downloads": 35,
"views": 351,
"upvotes": 3,
"saves": 3,
"saves": 4,
"comments": 0,
"created_at": "2026-01-08",
"updated_at": "2026-01-08",
@@ -163,8 +162,8 @@
"version": "0.9.1",
"author": "Fu-Jie",
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
"downloads": 17,
"views": 304,
"downloads": 19,
"views": 339,
"upvotes": 2,
"saves": 1,
"comments": 0,
@@ -172,6 +171,22 @@
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b"
},
{
"title": "异步上下文压缩",
"slug": "异步上下文压缩_5c0617cb",
"type": "action",
"version": "1.1.3",
"author": "Fu-Jie",
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
"downloads": 12,
"views": 254,
"upvotes": 4,
"saves": 1,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-11",
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
},
{
"title": "闪记卡 (Flash Card)",
"slug": "闪记卡生成插件_4a31eac3",
@@ -180,7 +195,7 @@
"author": "Fu-Jie",
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
"downloads": 12,
"views": 345,
"views": 376,
"upvotes": 4,
"saves": 1,
"comments": 0,
@@ -188,22 +203,6 @@
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/闪记卡生成插件_4a31eac3"
},
{
"title": "异步上下文压缩",
"slug": "异步上下文压缩_5c0617cb",
"type": "filter",
"version": "1.1.0",
"author": "Fu-Jie",
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
"downloads": 6,
"views": 153,
"upvotes": 2,
"saves": 1,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-07",
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
},
{
"title": "精读",
"slug": "精读_99830b0f",
@@ -211,8 +210,8 @@
"version": "1.0.0",
"author": "Fu-Jie",
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
"downloads": 1,
"views": 86,
"downloads": 4,
"views": 131,
"upvotes": 2,
"saves": 1,
"comments": 0,
@@ -228,10 +227,10 @@
"author": "",
"description": "",
"downloads": 0,
"views": 19,
"upvotes": 2,
"saves": 0,
"comments": 0,
"views": 488,
"upvotes": 10,
"saves": 6,
"comments": 2,
"created_at": "2026-01-10",
"updated_at": "2026-01-10",
"url": "https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960"
@@ -242,11 +241,11 @@
"name": "Fu-Jie",
"profile_url": "https://openwebui.com/u/Fu-Jie",
"profile_image": "https://community.s3.openwebui.com/uploads/users/b15d1348-4347-42b4-b815-e053342d6cb0/profile_d9510745-4bd4-4f8f-a997-4a21847d9300.webp",
"followers": 71,
"followers": 88,
"following": 2,
"total_points": 72,
"post_points": 62,
"comment_points": 10,
"contributions": 21
"total_points": 91,
"post_points": 78,
"comment_points": 13,
"contributions": 22
}
}

View File

@@ -1,39 +1,38 @@
# 📊 OpenWebUI Community Stats Report
> 📅 Updated: 2026-01-10 17:08
> 📅 Updated: 2026-01-12 23:10
## 📈 Overview
| Metric | Value |
|------|------|
| 📝 Total Posts | 14 |
| ⬇️ Total Downloads | 1066 |
| 👁️ Total Views | 11486 |
| 👍 Total Upvotes | 64 |
| 💾 Total Saves | 66 |
| 💬 Total Comments | 15 |
| ⬇️ Total Downloads | 1230 |
| 👁️ Total Views | 13650 |
| 👍 Total Upvotes | 80 |
| 💾 Total Saves | 79 |
| 💬 Total Comments | 18 |
## 📂 By Type
- **action**: 13
- **unknown**: 1
- **action**: 11
- **filter**: 2
## 📋 Posts List
| Rank | Title | Type | Version | Downloads | Views | Upvotes | Saves | Updated |
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 341 | 3080 | 10 | 21 | 2026-01-07 |
| 2 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 181 | 551 | 3 | 4 | 2026-01-07 |
| 3 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | 1.1.0 | 125 | 1390 | 5 | 9 | 2026-01-07 |
| 4 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.1 | 116 | 1355 | 7 | 9 | 2026-01-07 |
| 5 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 92 | 1735 | 8 | 6 | 2026-01-07 |
| 6 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 87 | 800 | 5 | 8 | 2026-01-07 |
| 7 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.1 | 35 | 480 | 3 | 0 | 2026-01-07 |
| 8 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 31 | 929 | 8 | 2 | 2026-01-07 |
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 22 | 259 | 3 | 3 | 2026-01-08 |
| 10 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 17 | 304 | 2 | 1 | 2026-01-07 |
| 11 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 12 | 345 | 4 | 1 | 2026-01-07 |
| 12 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | filter | 1.1.0 | 6 | 153 | 2 | 1 | 2026-01-07 |
| 13 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 1 | 86 | 2 | 1 | 2026-01-08 |
| 14 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 19 | 2 | 0 | 2026-01-10 |
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 392 | 3535 | 11 | 22 | 2026-01-07 |
| 2 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 187 | 604 | 3 | 4 | 2026-01-07 |
| 3 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 145 | 1594 | 8 | 10 | 2026-01-11 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.1.3 | 144 | 1596 | 7 | 11 | 2026-01-11 |
| 5 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 104 | 938 | 6 | 10 | 2026-01-07 |
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 101 | 1881 | 8 | 6 | 2026-01-07 |
| 7 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 40 | 1038 | 9 | 2 | 2026-01-07 |
| 8 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 35 | 525 | 3 | 0 | 2026-01-11 |
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 35 | 351 | 3 | 4 | 2026-01-08 |
| 10 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 19 | 339 | 2 | 1 | 2026-01-07 |
| 11 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.1.3 | 12 | 254 | 4 | 1 | 2026-01-11 |
| 12 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 12 | 376 | 4 | 1 | 2026-01-07 |
| 13 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 4 | 131 | 2 | 1 | 2026-01-08 |
| 14 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 488 | 10 | 6 | 2026-01-10 |

View File

@@ -1,39 +1,38 @@
# 📊 OpenWebUI 社区统计报告
> 📅 更新时间: 2026-01-10 17:08
> 📅 更新时间: 2026-01-12 23:10
## 📈 总览
| 指标 | 数值 |
|------|------|
| 📝 发布数量 | 14 |
| ⬇️ 总下载量 | 1066 |
| 👁️ 总浏览量 | 11486 |
| 👍 总点赞数 | 64 |
| 💾 总收藏数 | 66 |
| 💬 总评论数 | 15 |
| ⬇️ 总下载量 | 1230 |
| 👁️ 总浏览量 | 13650 |
| 👍 总点赞数 | 80 |
| 💾 总收藏数 | 79 |
| 💬 总评论数 | 18 |
## 📂 按类型分类
- **action**: 13
- **unknown**: 1
- **action**: 11
- **filter**: 2
## 📋 发布列表
| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 341 | 3080 | 10 | 21 | 2026-01-07 |
| 2 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 181 | 551 | 3 | 4 | 2026-01-07 |
| 3 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | 1.1.0 | 125 | 1390 | 5 | 9 | 2026-01-07 |
| 4 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.1 | 116 | 1355 | 7 | 9 | 2026-01-07 |
| 5 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 92 | 1735 | 8 | 6 | 2026-01-07 |
| 6 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 87 | 800 | 5 | 8 | 2026-01-07 |
| 7 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.1 | 35 | 480 | 3 | 0 | 2026-01-07 |
| 8 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 31 | 929 | 8 | 2 | 2026-01-07 |
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 22 | 259 | 3 | 3 | 2026-01-08 |
| 10 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 17 | 304 | 2 | 1 | 2026-01-07 |
| 11 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 12 | 345 | 4 | 1 | 2026-01-07 |
| 12 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | filter | 1.1.0 | 6 | 153 | 2 | 1 | 2026-01-07 |
| 13 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 1 | 86 | 2 | 1 | 2026-01-08 |
| 14 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 19 | 2 | 0 | 2026-01-10 |
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 392 | 3535 | 11 | 22 | 2026-01-07 |
| 2 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 187 | 604 | 3 | 4 | 2026-01-07 |
| 3 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 145 | 1594 | 8 | 10 | 2026-01-11 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.1.3 | 144 | 1596 | 7 | 11 | 2026-01-11 |
| 5 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 104 | 938 | 6 | 10 | 2026-01-07 |
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 101 | 1881 | 8 | 6 | 2026-01-07 |
| 7 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 40 | 1038 | 9 | 2 | 2026-01-07 |
| 8 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 35 | 525 | 3 | 0 | 2026-01-11 |
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 35 | 351 | 3 | 4 | 2026-01-08 |
| 10 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 19 | 339 | 2 | 1 | 2026-01-07 |
| 11 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.1.3 | 12 | 254 | 4 | 1 | 2026-01-11 |
| 12 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 12 | 376 | 4 | 1 | 2026-01-07 |
| 13 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 4 | 131 | 2 | 1 | 2026-01-08 |
| 14 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 488 | 10 | 6 | 2026-01-10 |

View File

@@ -33,7 +33,7 @@ Actions are interactive plugins that:
Transform text into professional infographics using AntV visualization engine with various templates.
**Version:** 1.4.1
**Version:** 1.4.9
[:octicons-arrow-right-24: Documentation](smart-infographic.md)

View File

@@ -33,7 +33,7 @@ Actions 是交互式插件,能够:
使用 AntV 可视化引擎,将文本转成专业的信息图。
**版本:** 1.4.1
**版本:** 1.4.9
[:octicons-arrow-right-24: 查看文档](smart-infographic.md)

View File

@@ -1,7 +1,7 @@
# Smart Infographic
<span class="category-badge action">Action</span>
<span class="version-badge">v1.4.0</span>
<span class="version-badge">v1.4.9</span>
An AntV Infographic engine powered plugin that transforms long text into professional, beautiful infographics with a single click.
@@ -14,8 +14,8 @@ The Smart Infographic plugin uses AI to analyze text content and generate profes
## Features
- :material-robot: **AI-Powered Transformation**: Automatically analyzes text logic, extracts key points, and generates structured charts
- :material-palette: **Professional Templates**: Includes various AntV official templates: Lists, Trees, Mindmaps, Comparison Tables, Flowcharts, and Statistical Charts
- :material-magnify: **Auto-Icon Matching**: Built-in logic to search and match the most relevant Material Design Icons based on content
- :material-palette: **70+ Professional Templates**: Includes various AntV official templates: Lists, Trees, Roadmaps, Timelines, Comparison Tables, SWOT, Quadrants, and Statistical Charts
- :material-magnify: **Auto-Icon Matching**: Built-in logic to search and match the most relevant icons (Iconify) and illustrations (unDraw)
- :material-download: **Multi-Format Export**: Download your infographics as **SVG**, **PNG**, or **Standalone HTML** file
- :material-theme-light-dark: **Theme Support**: Supports Dark/Light modes, auto-adapts theme colors
- :material-cellphone-link: **Responsive Design**: Generated charts look great on both desktop and mobile devices
@@ -37,10 +37,11 @@ The Smart Infographic plugin uses AI to analyze text content and generate profes
| Category | Template Name | Use Case |
|:---------|:--------------|:---------|
| **Lists & Hierarchy** | `list-grid`, `tree-vertical`, `mindmap` | Features, Org Charts, Brainstorming |
| **Sequence & Relation** | `sequence-roadmap`, `relation-circle` | Roadmaps, Circular Flows, Steps |
| **Comparison & Analysis** | `compare-binary`, `compare-swot`, `quadrant-quarter` | Pros/Cons, SWOT, Quadrants |
| **Charts & Data** | `chart-bar`, `chart-line`, `chart-pie` | Trends, Distributions, Metrics |
| **Sequence** | `sequence-timeline-simple`, `sequence-roadmap-vertical-simple`, `sequence-snake-steps-compact-card` | Timelines, Roadmaps, Processes |
| **Lists** | `list-grid-candy-card-lite`, `list-row-horizontal-icon-arrow`, `list-column-simple-vertical-arrow` | Features, Bullet Points, Lists |
| **Comparison** | `compare-binary-horizontal-underline-text-vs`, `compare-swot`, `quadrant-quarter-simple-card` | Pros/Cons, SWOT, Quadrants |
| **Hierarchy** | `hierarchy-tree-tech-style-capsule-item`, `hierarchy-structure` | Org Charts, Structures |
| **Charts** | `chart-column-simple`, `chart-bar-plain-text`, `chart-line-plain-text`, `chart-wordcloud` | Trends, Distributions, Metrics |
---

View File

@@ -1,7 +1,7 @@
# Smart Infographic智能信息图
<span class="category-badge action">Action</span>
<span class="version-badge">v1.4.0</span>
<span class="version-badge">v1.4.9</span>
基于 AntV 信息图引擎,将长文本一键转成专业、美观的信息图。
@@ -14,8 +14,8 @@ Smart Infographic 使用 AI 分析文本,并基于 AntV 可视化引擎生成
## 功能特性
- :material-robot: **AI 转换**:自动分析文本逻辑,提取要点并生成结构化图表
- :material-palette: **专业模板**:内置 AntV 官方模板列表、树、思维导图、对比表、流程图、统计图等
- :material-magnify: **自动匹配图标**根据内容自动选择最合适的 Material Design Icons
- :material-palette: **70+ 专业模板**:内置多种 AntV 官方模板,包括列表、树图、路线图、时间线、对比图、SWOT、象限图及统计图
- :material-magnify: **自动匹配图标**内置图标搜索逻辑,支持 Iconify 图标和 unDraw 插图自动匹配
- :material-download: **多格式导出**:支持下载 **SVG**、**PNG**、**独立 HTML**
- :material-theme-light-dark: **主题支持**:适配深色/浅色模式
- :material-cellphone-link: **响应式**:桌面与移动端都能良好展示
@@ -37,10 +37,11 @@ Smart Infographic 使用 AI 分析文本,并基于 AntV 可视化引擎生成
| 分类 | 模板名称 | 典型场景 |
|:---------|:--------------|:---------|
| **列表与层级** | `list-grid`, `tree-vertical`, `mindmap` | 特性列表、组织结构、头脑风暴 |
| **序列与关系** | `sequence-roadmap`, `relation-circle` | 路线图、循环流程、步骤拆解 |
| **对比与分析** | `compare-binary`, `compare-swot`, `quadrant-quarter` | 优劣势、SWOT、象限分析 |
| **图表与数据** | `chart-bar`, `chart-line`, `chart-pie` | 趋势、分布、指标对比 |
| **时序与流程** | `sequence-timeline-simple`, `sequence-roadmap-vertical-simple`, `sequence-snake-steps-compact-card` | 时间线、路线图、步骤说明 |
| **列表与网格** | `list-grid-candy-card-lite`, `list-row-horizontal-icon-arrow`, `list-column-simple-vertical-arrow` | 功能亮点、要点列举、清单 |
| **对比与分析** | `compare-binary-horizontal-underline-text-vs`, `compare-swot`, `quadrant-quarter-simple-card` | 优劣势对比、SWOT 分析、象限 |
| **层级与结构** | `hierarchy-tree-tech-style-capsule-item`, `hierarchy-structure` | 组织架构、层级关系 |
| **图表与数据** | `chart-column-simple`, `chart-bar-plain-text`, `chart-line-plain-text`, `chart-wordcloud` | 数据趋势、比例分布、数值对比 |
---

View File

@@ -1,7 +1,7 @@
# Async Context Compression
<span class="category-badge filter">Filter</span>
<span class="version-badge">v1.1.0</span>
<span class="version-badge">v1.1.3</span>
Reduces token consumption in long conversations through intelligent summarization while maintaining conversational coherence.
@@ -29,6 +29,11 @@ This is especially useful for:
- :material-clock-fast: **Async Processing**: Non-blocking background compression
- :material-memory: **Context Preservation**: Keeps important information
- :material-currency-usd-off: **Cost Reduction**: Minimize token usage
- :material-console: **Frontend Debugging**: Debug logs in browser console
- :material-alert-circle-check: **Enhanced Error Reporting**: Clear error status notifications
- :material-check-all: **Open WebUI v0.7.x Compatibility**: Dynamic DB session handling
- :material-account-convert: **Improved Compatibility**: Summary role changed to `assistant`
- :material-shield-check: **Enhanced Stability**: Resolved race conditions in state management
---

View File

@@ -1,7 +1,7 @@
# Async Context Compression异步上下文压缩
<span class="category-badge filter">Filter</span>
<span class="version-badge">v1.1.0</span>
<span class="version-badge">v1.1.3</span>
通过智能摘要减少长对话的 token 消耗,同时保持对话连贯。
@@ -29,6 +29,11 @@ Async Context Compression 过滤器通过以下方式帮助管理长对话的 to
- :material-clock-fast: **异步处理**:后台非阻塞压缩
- :material-memory: **保留上下文**:尽量保留重要信息
- :material-currency-usd-off: **降低成本**:减少 token 使用
- :material-console: **前端调试**:支持浏览器控制台日志
- :material-alert-circle-check: **增强错误报告**:清晰的错误状态通知
- :material-check-all: **Open WebUI v0.7.x 兼容性**:动态数据库会话处理
- :material-account-convert: **兼容性提升**:摘要角色改为 `assistant`
- :material-shield-check: **稳定性增强**:解决状态管理竞态条件
---

View File

@@ -22,7 +22,7 @@ Filters act as middleware in the message pipeline:
Reduces token consumption in long conversations through intelligent summarization while maintaining coherence.
**Version:** 1.1.0
**Version:** 1.1.3
[:octicons-arrow-right-24: Documentation](async-context-compression.md)
@@ -46,6 +46,16 @@ Filters act as middleware in the message pipeline:
[:octicons-arrow-right-24: Documentation](gemini-manifold-companion.md)
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
---
Fixes common Markdown formatting issues in LLM outputs, including Mermaid syntax, code blocks, and LaTeX formulas.
**Version:** 1.0.1
[:octicons-arrow-right-24: Documentation](markdown_normalizer.md)
</div>
---

View File

@@ -22,7 +22,7 @@ Filter 充当消息管线中的中间件:
通过智能总结减少长对话的 token 消耗,同时保持连贯性。
**版本:** 1.1.0
**版本:** 1.1.3
[:octicons-arrow-right-24: 查看文档](async-context-compression.md)
@@ -46,6 +46,16 @@ Filter 充当消息管线中的中间件:
[:octicons-arrow-right-24: 查看文档](gemini-manifold-companion.md)
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
---
修复 LLM 输出中常见的 Markdown 格式问题,包括 Mermaid 语法、代码块和 LaTeX 公式。
**版本:** 1.0.1
[:octicons-arrow-right-24: 查看文档](markdown_normalizer.zh.md)
</div>
---

View File

@@ -0,0 +1,46 @@
# Markdown Normalizer Filter
A production-grade content normalizer filter for Open WebUI that fixes common Markdown formatting issues in LLM outputs. It ensures that code blocks, LaTeX formulas, Mermaid diagrams, and other Markdown elements are rendered correctly.
## Features
* **Mermaid Syntax Fix**: Automatically fixes common Mermaid syntax errors, such as unquoted node labels (including multi-line labels and citations) and unclosed subgraphs, ensuring diagrams render correctly.
* **Frontend Console Debugging**: Supports printing structured debug logs directly to the browser console (F12) for easier troubleshooting.
* **Code Block Formatting**: Fixes broken code block prefixes, suffixes, and indentation.
* **LaTeX Normalization**: Standardizes LaTeX formula delimiters (`\[` -> `$$`, `\(` -> `$`).
* **Thought Tag Normalization**: Unifies thought tags (`<think>`, `<thinking>` -> `<thought>`).
* **Escape Character Fix**: Cleans up excessive escape characters (`\\n`, `\\t`).
* **List Formatting**: Ensures proper newlines in list items.
* **Heading Fix**: Adds missing spaces in headings (`#Heading` -> `# Heading`).
* **Table Fix**: Adds missing closing pipes in tables.
* **XML Cleanup**: Removes leftover XML artifacts.
## Usage
1. Install the plugin in Open WebUI.
2. Enable the filter globally or for specific models.
3. Configure the enabled fixes in the **Valves** settings.
4. (Optional) **Show Debug Log** is enabled by default in Valves. This prints structured logs to the browser console (F12).
> [!WARNING]
> As this is an initial version, some "negative fixes" might occur (e.g., breaking valid Markdown). If you encounter issues, please check the console logs, copy the "Original" vs "Normalized" content, and submit an issue.
## Configuration (Valves)
* `priority`: Filter priority (default: 50).
* `enable_escape_fix`: Fix excessive escape characters.
* `enable_thought_tag_fix`: Normalize thought tags.
* `enable_code_block_fix`: Fix code block formatting.
* `enable_latex_fix`: Normalize LaTeX formulas.
* `enable_list_fix`: Fix list item newlines (Experimental).
* `enable_unclosed_block_fix`: Auto-close unclosed code blocks.
* `enable_fullwidth_symbol_fix`: Fix full-width symbols in code blocks.
* `enable_mermaid_fix`: Fix Mermaid syntax errors.
* `enable_heading_fix`: Fix missing space in headings.
* `enable_table_fix`: Fix missing closing pipe in tables.
* `enable_xml_tag_cleanup`: Cleanup leftover XML tags.
* `show_status`: Show status notification when fixes are applied.
* `show_debug_log`: Print debug logs to browser console.
## License
MIT

View File

@@ -0,0 +1,46 @@
# Markdown 格式化过滤器 (Markdown Normalizer)
这是一个用于 Open WebUI 的生产级内容格式化过滤器,旨在修复 LLM 输出中常见的 Markdown 格式问题。它能确保代码块、LaTeX 公式、Mermaid 图表和其他 Markdown 元素被正确渲染。
## 功能特性
* **Mermaid 语法修复**: 自动修复常见的 Mermaid 语法错误,如未加引号的节点标签(支持多行标签和引用标记)和未闭合的子图 (Subgraph),确保图表能正确渲染。
* **前端控制台调试**: 支持将结构化的调试日志直接打印到浏览器控制台 (F12),方便排查问题。
* **代码块格式化**: 修复破损的代码块前缀、后缀和缩进问题。
* **LaTeX 规范化**: 标准化 LaTeX 公式定界符 (`\[` -> `$$`, `\(` -> `$`)。
* **思维标签规范化**: 统一思维链标签 (`<think>`, `<thinking>` -> `<thought>`)。
* **转义字符修复**: 清理过度的转义字符 (`\\n`, `\\t`)。
* **列表格式化**: 确保列表项有正确的换行。
* **标题修复**: 修复标题中缺失的空格 (`#标题` -> `# 标题`)。
* **表格修复**: 修复表格中缺失的闭合管道符。
* **XML 清理**: 移除残留的 XML 标签。
## 使用方法
1. 在 Open WebUI 中安装此插件。
2. 全局启用或为特定模型启用此过滤器。
3.**Valves** 设置中配置需要启用的修复项。
4. (可选) **显示调试日志 (Show Debug Log)** 在 Valves 中默认开启。这会将结构化的日志打印到浏览器控制台 (F12)。
> [!WARNING]
> 由于这是初版,可能会出现“负向修复”的情况(例如破坏了原本正确的格式)。如果您遇到问题,请务必查看控制台日志,复制“原始 (Original)”与“规范化 (Normalized)”的内容对比,并提交 Issue 反馈。
## 配置项 (Valves)
* `priority`: 过滤器优先级 (默认: 50)。
* `enable_escape_fix`: 修复过度的转义字符。
* `enable_thought_tag_fix`: 规范化思维标签。
* `enable_code_block_fix`: 修复代码块格式。
* `enable_latex_fix`: 规范化 LaTeX 公式。
* `enable_list_fix`: 修复列表项换行 (实验性)。
* `enable_unclosed_block_fix`: 自动闭合未闭合的代码块。
* `enable_fullwidth_symbol_fix`: 修复代码块中的全角符号。
* `enable_mermaid_fix`: 修复 Mermaid 语法错误。
* `enable_heading_fix`: 修复标题中缺失的空格。
* `enable_table_fix`: 修复表格中缺失的闭合管道符。
* `enable_xml_tag_cleanup`: 清理残留的 XML 标签。
* `show_status`: 应用修复时显示状态通知。
* `show_debug_log`: 在浏览器控制台打印调试日志。
## 许可证
MIT

View File

@@ -1,11 +1,14 @@
# 📊 Smart Infographic (AntV)
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.4.1 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.4.9 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
An Open WebUI plugin powered by the AntV Infographic engine. It transforms long text into professional, beautiful infographics with a single click.
## 🔥 What's New in v1.4.1
## 🔥 What's New in v1.4.9
- 🎨 **70+ Official Templates**: Integrated comprehensive AntV infographic template library.
- 🖼️ **Iconify & unDraw Support**: Richer visuals with official icons and illustrations.
- 📏 **Visual Optimization**: Improved text wrapping, adaptive sizing, and layout refinement.
-**PNG Upload**: Infographics now upload as PNG format for better Word export compatibility.
- 🔧 **Canvas Conversion**: Uses browser canvas for high-quality SVG to PNG conversion (2x scale).
@@ -17,8 +20,8 @@ An Open WebUI plugin powered by the AntV Infographic engine. It transforms long
## ✨ Key Features
- 🚀 **AI-Powered Transformation**: Automatically analyzes text logic, extracts key points, and generates structured charts.
- 🎨 **Professional Templates**: Includes various AntV official templates: Lists, Trees, Mindmaps, Comparison Tables, Flowcharts, and Statistical Charts.
- 🔍 **Auto-Icon Matching**: Built-in logic to search and match the most relevant Material Design Icons based on content.
- 🎨 **70+ Professional Templates**: Includes various AntV official templates: Lists, Trees, Roadmaps, Timelines, Comparison Tables, SWOT, Quadrants, and Statistical Charts.
- 🔍 **Auto-Icon Matching**: Built-in logic to search and match the most relevant icons (Iconify) and illustrations (unDraw).
- 📥 **Multi-Format Export**: Download your infographics as **SVG**, **PNG**, or a **Standalone HTML** file.
- 🌈 **Highly Customizable**: Supports Dark/Light modes, auto-adapts theme colors, with bold titles and refined card layouts.
- 📱 **Responsive Design**: Generated charts look great on both desktop and mobile devices.
@@ -47,10 +50,11 @@ You can adjust the following parameters in the plugin settings to optimize the g
| Category | Template Name | Use Case |
| :--- | :--- | :--- |
| **Lists & Hierarchy** | `list-grid`, `tree-vertical`, `mindmap` | Features, Org Charts, Brainstorming |
| **Sequence & Relation** | `sequence-roadmap`, `relation-circle` | Roadmaps, Circular Flows, Steps |
| **Comparison & Analysis** | `compare-binary`, `compare-swot`, `quadrant-quarter` | Pros/Cons, SWOT, Quadrants |
| **Charts & Data** | `chart-bar`, `chart-line`, `chart-pie` | Trends, Distributions, Metrics |
| **Sequence** | `sequence-timeline-simple`, `sequence-roadmap-vertical-simple`, `sequence-snake-steps-compact-card` | Timelines, Roadmaps, Processes |
| **Lists** | `list-grid-candy-card-lite`, `list-row-horizontal-icon-arrow`, `list-column-simple-vertical-arrow` | Features, Bullet Points, Lists |
| **Comparison** | `compare-binary-horizontal-underline-text-vs`, `compare-swot`, `quadrant-quarter-simple-card` | Pros/Cons, SWOT, Quadrants |
| **Hierarchy** | `hierarchy-tree-tech-style-capsule-item`, `hierarchy-structure` | Org Charts, Structures |
| **Charts** | `chart-column-simple`, `chart-bar-plain-text`, `chart-line-plain-text`, `chart-wordcloud` | Trends, Distributions, Metrics |
## 📝 Syntax Example (For Advanced Users)

View File

@@ -1,11 +1,14 @@
# 📊 智能信息图 (AntV Infographic)
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.4.1 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.4.9 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
基于 AntV Infographic 引擎的 Open WebUI 插件,能够将长文本内容一键转换为专业、美观的信息图表。
## 🔥 v1.4.1 更新日志
## 🔥 v1.4.9 更新日志
- 🎨 **70+ 官方模板**:全面集成 AntV 官方信息图模板库。
- 🖼️ **图标与插图支持**:支持 Iconify 图标库与 unDraw 插图库,视觉效果更丰富。
- 📏 **视觉优化**:改进文本换行逻辑,优化自适应尺寸,提升卡片布局精细度。
-**PNG 上传**:信息图现在以 PNG 格式上传,与 Word 导出完美兼容。
- 🔧 **Canvas 转换**:使用浏览器 Canvas 高质量转换 SVG 为 PNG2倍缩放
@@ -17,8 +20,8 @@
## ✨ 核心特性
- 🚀 **智能转换**:自动分析文本核心逻辑,提取关键点并生成结构化图表。
- 🎨 **专业模板**:内置多种 AntV 官方模板,包括列表、树图、思维导图、对比图、流程图及统计图表等。
- 🔍 **自动图标匹配**:内置图标搜索逻辑,根据内容自动匹配最相关的 Material Design Icons
- 🎨 **70+ 专业模板**:内置多种 AntV 官方模板,包括列表、树图、路线图、时间线、对比图、SWOT、象限图及统计图表等。
- 🔍 **自动图标匹配**:内置图标搜索逻辑,支持 Iconify 图标和 unDraw 插图自动匹配
- 📥 **多格式导出**:支持一键下载为 **SVG**、**PNG** 或 **独立 HTML** 文件。
- 🌈 **高度自定义**:支持深色/浅色模式,自动适配主题颜色,主标题加粗突出,卡片布局精美。
- 📱 **响应式设计**:生成的图表在桌面端和移动端均有良好的展示效果。
@@ -47,10 +50,11 @@
| 分类 | 模板名称 | 适用场景 |
| :--- | :--- | :--- |
| **列表与层级** | `list-grid`, `tree-vertical`, `mindmap` | 功能亮点、组织架构、思维导图 |
| **顺序与关系** | `sequence-roadmap`, `relation-circle` | 发展历程、循环关系、步骤说明 |
| **对比与分析** | `compare-binary`, `compare-swot`, `quadrant-quarter` | 优劣势对比、SWOT 分析、象限图 |
| **图表与数据** | `chart-bar`, `chart-line`, `chart-pie` | 数据趋势、比例分布、数值对比 |
| **时序与流程** | `sequence-timeline-simple`, `sequence-roadmap-vertical-simple`, `sequence-snake-steps-compact-card` | 时间线、路线图、步骤说明 |
| **列表与网格** | `list-grid-candy-card-lite`, `list-row-horizontal-icon-arrow`, `list-column-simple-vertical-arrow` | 功能亮点、要点列举、清单 |
| **对比与分析** | `compare-binary-horizontal-underline-text-vs`, `compare-swot`, `quadrant-quarter-simple-card` | 优劣势对比、SWOT 分析、象限图 |
| **层级与结构** | `hierarchy-tree-tech-style-capsule-item`, `hierarchy-structure` | 组织架构、层级关系 |
| **图表与数据** | `chart-column-simple`, `chart-bar-plain-text`, `chart-line-plain-text`, `chart-wordcloud` | 数据趋势、比例分布、数值对比 |
## 📝 语法示例 (高级用户)

View File

@@ -1,9 +1,9 @@
"""
title: 📊 Smart Infographic (AntV)
author: jeff
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
version: 1.4.1
version: 1.4.9
openwebui_id: ad6f0c7f-c571-4dea-821d-8e71697274cf
description: AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.
"""
@@ -47,24 +47,63 @@ Infographic syntax is a Mermaid-like declarative syntax for describing infograph
### Template Library & Selection Guide
Choose the most appropriate template based on the content structure:
Choose the most appropriate template based on content structure.
#### 1. List & Hierarchy
- **List**: `list-grid` (Grid Cards), `list-vertical` (Vertical List)
- **Tree**: `tree-vertical` (Vertical Tree), `tree-horizontal` (Horizontal Tree)
- **Mindmap**: `mindmap` (Mind Map)
**Template Selection Guidelines (Official):**
- Strict sequential order (processes/steps/trends) → `sequence-*` series
- Timeline → `sequence-timeline-simple`
- Roadmap → `sequence-roadmap-vertical-simple`
- Zigzag steps → `sequence-horizontal-zigzag-underline-text`
- Snake steps → `sequence-snake-steps-compact-card`
- Listing viewpoints → `list-row-horizontal-icon-arrow` or `list-column-simple-vertical-arrow`
- Comparative analysis (A vs B) → `compare-binary-horizontal-underline-text-vs`
- SWOT analysis → `compare-swot`
- Hierarchical structure (tree) → `hierarchy-tree-tech-style-capsule-item`
- Data charts → `chart-*` series
- Quadrant analysis → `quadrant-quarter-simple-card`
- Grid lists (bullet points) → `list-grid-candy-card-lite`
- Relationship display → `relation-circle-icon-badge`
#### 2. Sequence & Relationship
- **Process**: `sequence-roadmap` (Roadmap), `sequence-zigzag` (Zigzag Process), `sequence-horizontal` (Horizontal Process)
- **Relationship**: `relation-sankey` (Sankey Diagram), `relation-circle` (Circular Relationship)
**Available Templates:**
#### 3. Comparison & Analysis
- **Comparison**: `compare-binary` (Binary Comparison), `list-grid` (Multi-item Grid Comparison)
- **Analysis**: `compare-swot` (SWOT Analysis), `quadrant-quarter` (Quadrant Chart)
*Sequence (时序/流程):*
`sequence-timeline-simple`, `sequence-roadmap-vertical-simple`, `sequence-horizontal-zigzag-underline-text`,
`sequence-snake-steps-compact-card`, `sequence-zigzag-steps-underline-text`, `sequence-circular-simple`,
`sequence-pyramid-simple`, `sequence-ascending-steps`
#### 4. Charts & Data
- **Statistics**: `statistic-card` (Statistic Cards)
- **Charts**: `chart-bar` (Bar Chart), `chart-column` (Column Chart), `chart-line` (Line Chart), `chart-pie` (Pie Chart), `chart-doughnut` (Doughnut Chart), `chart-area` (Area Chart)
*List (列表):*
`list-grid-candy-card-lite`, `list-grid-badge-card`, `list-row-horizontal-icon-arrow`,
`list-column-simple-vertical-arrow`, `list-column-done-list`
*Compare (对比):*
`compare-binary-horizontal-underline-text-vs`, `compare-binary-horizontal-simple-fold`,
`compare-hierarchy-left-right-circle-node-pill-badge`, `compare-swot`
*Hierarchy (层级):*
`hierarchy-tree-tech-style-capsule-item`, `hierarchy-tree-curved-line-rounded-rect-node`, `hierarchy-structure`
*Chart (图表):*
`chart-column-simple`, `chart-bar-plain-text`, `chart-line-plain-text`,
`chart-pie-plain-text`, `chart-pie-donut-plain-text`, `chart-wordcloud`
*Other:*
`quadrant-quarter-simple-card`, `relation-circle-icon-badge`
**Text Capacity by Template Type:**
- HIGH capacity (long descriptions OK): `list-column-*`, `compare-binary-*`, `sequence-timeline-*`
- MEDIUM capacity: `list-row-*`, `sequence-roadmap-*`
- LOW capacity (short text only): `list-grid-*`, `hierarchy-*`, `sequence-steps`
### Icon and Illustration Resources
**Icons (Iconify):**
- Format: `<collection>/<icon-name>`, e.g., `mdi/rocket-launch`
- Popular: `mdi/*` (Material Design), `fa/*` (Font Awesome), `bi/*` (Bootstrap)
- Examples: `mdi/code-tags`, `mdi/chart-line`, `mdi/account-group`, `mdi/cloud`
**Illustrations (unDraw):**
- Format: filename without .svg, e.g., `coding`, `team-work`
- Use `illus` field instead of `icon`
### Data Structure Examples
@@ -211,6 +250,12 @@ data
- `children`: Nested items (for trees, SWOT, etc.)
- `illus`: Illustration icon (specific to some templates like Quadrant)
### Content Refinement Principles
1. **Brevity is King**: Infographics are visual. Keep text to a minimum.
2. **Title Limit**: Keep `label` (item titles) under 15 characters (approx. 10 Chinese characters).
3. **Description Limit**: Keep `desc` (item descriptions) under 40 characters (approx. 20 Chinese characters / 2 lines).
4. **Impact**: Use strong verbs and nouns. Avoid filler words.
## Output Requirements
1. **Language**: Output content in the user's language.
2. **Format**: Wrap output in ```infographic ... ```.
@@ -233,9 +278,18 @@ User Language: {user_language}
Please select the most appropriate infographic template based on text characteristics and output standard infographic syntax. Pay attention to correct indentation format (two spaces).
**Important Note:**
- If using `list-grid` format, ensure each card's `desc` description is limited to **maximum 30 Chinese characters** (or **approximately 60 English characters**) to maintain visual consistency with all descriptions fitting in 2 lines.
- Descriptions should be concise and highlight key points.
**Visual Optimization Guide (MUST FOLLOW):**
- **Point-based Generation:** Infographics are not articles. Extract KEYWORDS ONLY, avoid complete sentences.
- **Main Title (`data.title`):** **MUST** be ≤ **15 Chinese characters** (or ≤30 English characters). Trim version numbers or details if needed.
- **Subtitle (`data.desc`):** **MUST** be ≤ **20 Chinese characters** (or ≤40 English characters).
- **Card Title (`label`):** **MUST** be ≤ **6 Chinese characters** (or ≤12 English characters). Use 2-4 keywords only.
- **Card Description (`desc`):** **MUST** be ≤ **12 Chinese characters** (or ≤24 English characters). Use short phrases.
⚠️ **CRITICAL**: If the original text is too long, you MUST rephrase and shorten it. Do NOT simply truncate with "...".
Examples:
- ❌ "多步任务与工具协作能力" → ✅ "多步任务协作"
- ❌ "Open WebUI v0.7.x 重大版本更新" → ✅ "v0.7 核心更新"
- ❌ "自动查找历史聊天记录" → ✅ "历史检索"
"""
# =================================================================
@@ -340,8 +394,9 @@ CSS_TEMPLATE_INFOGRAPHIC = """
.infographic-container-wrapper .infographic-render-container {
border-radius: 8px;
padding: 16px;
min-height: 600px;
background: #fff;
overflow: visible; /* Ensure content is visible */
overflow: visible;
transition: height 0.3s ease;
}
.infographic-render-container svg text {
@@ -349,35 +404,59 @@ CSS_TEMPLATE_INFOGRAPHIC = """
}
.infographic-render-container svg foreignObject {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif !important;
line-height: 1.4 !important;
line-height: 1.3 !important;
overflow: visible !important;
}
/* Main title styles */
.infographic-render-container svg foreignObject[data-element-type="title"] > * {
font-size: 1.5em !important;
font-weight: bold !important;
line-height: 1.4 !important;
white-space: nowrap !important;
font-size: 1.3em !important;
font-weight: 800 !important;
line-height: 1.3 !important;
white-space: normal !important;
word-break: break-word !important;
display: -webkit-box !important;
-webkit-line-clamp: 2 !important;
-webkit-box-orient: vertical !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
text-align: center !important;
}
/* Page subtitle and card title styles */
.infographic-render-container svg foreignObject[data-element-type="desc"] > *,
.infographic-render-container svg foreignObject[data-element-type="item-label"] > * {
font-size: 0.6em !important;
line-height: 1.4 !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
/* Card title with extra bottom spacing */
.infographic-render-container svg foreignObject[data-element-type="item-label"] > * {
padding-bottom: 8px !important;
/* Page subtitle styles */
.infographic-render-container svg foreignObject[data-element-type="desc"] > * {
font-size: 0.85em !important;
line-height: 1.3 !important;
white-space: normal !important;
word-break: break-word !important;
overflow: visible !important;
text-align: center !important;
display: block !important;
color: var(--ig-muted-text-color) !important;
}
/* Card description text keeps normal wrapping */
/* Card title styles */
.infographic-render-container svg foreignObject[data-element-type="item-label"] > * {
font-size: 0.9em !important;
font-weight: 600 !important;
line-height: 1.3 !important;
white-space: normal !important;
word-break: break-word !important;
display: -webkit-box !important;
-webkit-line-clamp: 2 !important;
-webkit-box-orient: vertical !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
padding-bottom: 2px !important;
}
/* Card description text */
.infographic-render-container svg foreignObject[data-element-type="item-desc"] > * {
font-size: 0.8em !important;
line-height: 1.4 !important;
white-space: normal !important;
word-break: break-word !important;
display: -webkit-box !important;
-webkit-line-clamp: 2 !important;
-webkit-box-orient: vertical !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
.infographic-container-wrapper .download-area {
text-align: center;
@@ -533,37 +612,41 @@ SCRIPT_TEMPLATE_INFOGRAPHIC = """
}}
}}
// 2. Template Mapping Configuration
// 2. Template Mapping Configuration (Official AntV Structure IDs)
const TEMPLATE_MAPPING = {{
// List & Hierarchy
// List & Hierarchy - map short names to full template names
'list-grid': 'list-grid-compact-card',
'list-column': 'list-column-simple-vertical-arrow',
'list-row': 'list-row-simple-horizontal-arrow',
'hierarchy-tree': 'hierarchy-tree-tech-style-capsule-item',
// Sequence & Timeline
'sequence-roadmap-vertical': 'sequence-roadmap-vertical-simple',
'sequence-timeline': 'sequence-timeline-simple',
'sequence-steps': 'sequence-steps-simple',
'sequence-horizontal-zigzag': 'sequence-horizontal-zigzag-simple',
// Comparison
'compare-binary-horizontal': 'compare-binary-horizontal-simple-vs',
'compare-hierarchy-row': 'compare-hierarchy-row-simple',
// Charts
'chart-column': 'chart-column-simple',
'quadrant': 'quadrant-quarter-simple-card',
// Legacy mappings for backward compatibility
'list-vertical': 'list-column-simple-vertical-arrow',
'tree-vertical': 'hierarchy-tree-tech-style-capsule-item',
'tree-horizontal': 'hierarchy-tree-lr-tech-style-capsule-item',
'mindmap': 'hierarchy-mindmap-branch-gradient-capsule-item',
// Sequence & Relationship
'sequence-roadmap': 'sequence-roadmap-vertical-simple',
'sequence-zigzag': 'sequence-horizontal-zigzag-simple',
'sequence-horizontal': 'sequence-horizontal-zigzag-simple',
'relation-sankey': 'relation-sankey-simple',
'relation-circle': 'relation-circle-icon-badge',
// Comparison & Analysis
'compare-binary': 'compare-binary-horizontal-simple-vs',
'compare-swot': 'compare-swot',
'quadrant-quarter': 'quadrant-quarter-simple-card',
// Charts & Data
'statistic-card': 'list-grid-compact-card',
'chart-bar': 'chart-bar-plain-text',
'chart-column': 'chart-column-simple',
'chart-line': 'chart-line-plain-text',
'chart-area': 'chart-area-simple',
'chart-pie': 'chart-pie-plain-text',
'chart-doughnut': 'chart-pie-donut-plain-text'
}};
// 3. Apply Mapping Strategy
for (const [key, value] of Object.entries(TEMPLATE_MAPPING)) {{
const regex = new RegExp(`infographic\\\\s+${{key}}(?=\\\\s|$)`, 'i');
@@ -629,10 +712,48 @@ SCRIPT_TEMPLATE_INFOGRAPHIC = """
containerEl.dataset.infographicRendered = 'true';
console.log('[Infographic] Rendering complete');
// Auto-adjust height
// Auto-adjust height and tag elements
setTimeout(() => {
const svg = containerEl.querySelector('svg');
if (svg) {
// 1. Tag elements for CSS styling
const fos = Array.from(svg.querySelectorAll('foreignObject'));
let titleFound = false;
let descFound = false;
fos.forEach((fo) => {
const text = fo.textContent.trim();
if (!text || fo.querySelector('i') || (fo.querySelector('svg') && fo.querySelectorAll('*').length < 5)) {
fo.setAttribute('data-element-type', 'icon');
return;
}
// Dynamically increase height and width to accommodate wrapped text
const currentHeight = parseInt(fo.getAttribute('height') || '0');
if (currentHeight > 0 && currentHeight < 200) {
fo.setAttribute('height', Math.round(currentHeight * 1.8).toString());
}
const currentWidth = parseInt(fo.getAttribute('width') || '0');
if (currentWidth > 0 && currentWidth < 300) {
fo.setAttribute('width', Math.max(Math.round(currentWidth * 1.2), 180).toString());
}
if (!titleFound) {
fo.setAttribute('data-element-type', 'title');
titleFound = true;
} else if (!descFound) {
fo.setAttribute('data-element-type', 'desc');
descFound = true;
} else {
if (fo.querySelector('strong') || fo.style.fontWeight === 'bold' || text.length < 15) {
fo.setAttribute('data-element-type', 'item-label');
} else {
fo.setAttribute('data-element-type', 'item-desc');
}
}
});
// 2. Adjust height
const bbox = svg.getBoundingClientRect();
let contentHeight = bbox.height;
if (svg.viewBox && svg.viewBox.baseVal && svg.viewBox.baseVal.height) {

View File

@@ -1,9 +1,9 @@
"""
title: 📊 智能信息图 (AntV Infographic)
author: jeff
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
version: 1.4.1
version: 1.4.9
openwebui_id: e04a48ff-23ee-4a41-8ea7-66c19524e7c8
description: 基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。
"""
@@ -45,32 +45,61 @@ Infographic syntax is a Mermaid-like declarative syntax for describing infograph
- ❌ Wrong: `children:` `items:` `data:` (with colons)
- ✅ Correct: `children` `items` `data` (without colons)
### Template Library & Selection Guide
### 模板库与选择指南
#### 1. List & Hierarchy (Text-heavy)
- **Linear & Short (Steps/Phases)** -> `list-row-horizontal-icon-arrow`
- **Linear & Long (Rankings/Details)** -> `list-vertical`
- **Grouped / Parallel (Features/Catalog)** -> `list-grid`
- **Hierarchical (Org Chart/Taxonomy)** -> `tree-vertical` or `tree-horizontal`
- **Central Idea (Brainstorming)** -> `mindmap`
根据内容结构选择最合适的模板。
#### 2. Sequence & Relationship (Flow-based)
- **Time-based (History/Plan)** -> `sequence-roadmap-vertical-simple`
- **Process Flow (Complex)** -> `sequence-zigzag` or `sequence-horizontal`
- **Resource Flow / Distribution** -> `relation-sankey`
- **Circular Relationship** -> `relation-circle`
**模板选择指南 (官方):**
- 严格时序 (流程/步骤/趋势) → `sequence-*` 系列
- 时间线 → `sequence-timeline-simple`
- 路线图 → `sequence-roadmap-vertical-simple`
- 折线步骤 → `sequence-horizontal-zigzag-underline-text`
- 蛇形步骤 → `sequence-snake-steps-compact-card`
- 列举要点 → `list-row-horizontal-icon-arrow` 或 `list-column-simple-vertical-arrow`
- 对比分析 (A vs B) → `compare-binary-horizontal-underline-text-vs`
- SWOT 分析 → `compare-swot`
- 层级结构 (树状图) → `hierarchy-tree-tech-style-capsule-item`
- 数据图表 → `chart-*` 系列
- 象限分析 → `quadrant-quarter-simple-card`
- 网格列表 → `list-grid-candy-card-lite`
- 关系展示 → `relation-circle-icon-badge`
#### 3. Comparison & Analysis
- **Binary Comparison (A vs B)** -> `compare-binary`
- **SWOT Analysis** -> `compare-swot`
- **Quadrant Analysis (Importance vs Urgency)** -> `quadrant-quarter`
- **Multi-item Grid Comparison** -> `list-grid` (use for comparing multiple items)
**可用模板:**
#### 4. Charts & Data (Metric-heavy)
- **Key Metrics / Data Cards** -> `statistic-card`
- **Distribution / Comparison** -> `chart-bar` or `chart-column`
- **Trend over Time** -> `chart-line` or `chart-area`
- **Proportion / Part-to-Whole** -> `chart-pie` or `chart-doughnut`
*Sequence (时序/流程):*
`sequence-timeline-simple`, `sequence-roadmap-vertical-simple`, `sequence-horizontal-zigzag-underline-text`,
`sequence-snake-steps-compact-card`, `sequence-zigzag-steps-underline-text`, `sequence-circular-simple`
*List (列表):*
`list-grid-candy-card-lite`, `list-grid-badge-card`, `list-row-horizontal-icon-arrow`,
`list-column-simple-vertical-arrow`, `list-column-done-list`
*Compare (对比):*
`compare-binary-horizontal-underline-text-vs`, `compare-swot`
*Hierarchy (层级):*
`hierarchy-tree-tech-style-capsule-item`, `hierarchy-structure`
*Chart (图表):*
`chart-column-simple`, `chart-bar-plain-text`, `chart-pie-plain-text`, `chart-wordcloud`
*Other:*
`quadrant-quarter-simple-card`, `relation-circle-icon-badge`
**按容量分类:**
- 高容量 (长描述): `list-column-*`, `compare-binary-*`, `sequence-timeline-*`
- 中容量: `list-row-*`, `sequence-roadmap-*`
- 低容量 (短文本): `list-grid-*`, `hierarchy-*`
### 图标和插图资源
**图标 (Iconify):**
- 格式: `<集合>/<图标名>`, 如 `mdi/rocket-launch`
- 常用: `mdi/*`, `fa/*`, `bi/*`
**插图 (unDraw):**
- 格式: 文件名 (不含 .svg), 如 `coding`, `team-work`
- 使用 `illus` 字段
### Infographic Syntax Guide
@@ -203,6 +232,12 @@ data
desc Plan for next sprint
illus mdi/star
### Content Refinement Principles
1. **Brevity is King**: Infographics are visual. Keep text to a minimum.
2. **Title Limit**: Keep `label` (item titles) under 15 characters.
3. **Description Limit**: Keep `desc` (item descriptions) under 25 characters (approx. 2 lines).
4. **Impact**: Use strong verbs and nouns. Avoid filler words.
### Output Rules
1. **Strict Syntax**: Follow the indentation and formatting rules exactly.
2. **No Explanations**: Output ONLY the syntax code block.
@@ -224,9 +259,11 @@ USER_PROMPT_GENERATE_INFOGRAPHIC = """
请根据文本特点选择最合适的信息图模板,并输出规范的 infographic 语法。注意保持正确的缩进格式(两个空格)。
**重要提示**
- 如果使用 `list-grid` 格式,请确保每个卡片的 `desc` 描述文字控制在 **30个汉字**或约60个英文字符**以内**以保证所有卡片描述都只占用2行维持视觉一致性
- 描述应简洁精炼,突出核心要点
**视觉优化指南**
- **要点化生成:** 信息图不是文章。请将内容转化为“关键词+短语”的形式,严禁生成长难句
- **标题限制:** 每个卡片的 `label`(标题)请控制在 **8个汉字**以内
- **描述限制:** 每个卡片的 `desc`(描述)请控制在 **15个汉字**以内,确保即使在小屏幕上也能完整显示。
- **结构化思维:** 优先使用并列、递进或对比结构,使信息一目了然。
"""
# =================================================================
@@ -333,7 +370,7 @@ CSS_TEMPLATE_INFOGRAPHIC = """
padding: 16px;
min-height: 600px;
background: #fff;
overflow: visible; /* Ensure content is visible */
overflow: visible;
transition: height 0.3s ease;
}
.infographic-render-container svg text {
@@ -341,35 +378,58 @@ CSS_TEMPLATE_INFOGRAPHIC = """
}
.infographic-render-container svg foreignObject {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif !important;
line-height: 1.4 !important;
line-height: 1.3 !important;
overflow: visible !important;
}
/* 主标题样式 */
.infographic-render-container svg foreignObject[data-element-type="title"] > * {
font-size: 1.5em !important;
font-weight: bold !important;
line-height: 1.4 !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
/* 页面副标题和卡片标题样式 */
.infographic-render-container svg foreignObject[data-element-type="desc"] > *,
.infographic-render-container svg foreignObject[data-element-type="item-label"] > * {
font-size: 0.6em !important;
line-height: 1.4 !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
/* 卡片标题额外增加底部间距 */
.infographic-render-container svg foreignObject[data-element-type="item-label"] > * {
padding-bottom: 8px !important;
display: block !important;
}
/* 卡片描述文字保持正常换行 */
.infographic-render-container svg foreignObject[data-element-type="item-desc"] > * {
line-height: 1.4 !important;
font-size: 1.3em !important;
font-weight: 800 !important;
line-height: 1.3 !important;
white-space: normal !important;
word-break: break-word !important;
display: -webkit-box !important;
-webkit-line-clamp: 2 !important;
-webkit-box-orient: vertical !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
text-align: center !important;
}
/* 页面副标题样式 */
.infographic-render-container svg foreignObject[data-element-type="desc"] > * {
font-size: 0.85em !important;
line-height: 1.3 !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
text-align: center !important;
display: block !important;
color: var(--ig-muted-text-color) !important;
}
/* 卡片标题样式 */
.infographic-render-container svg foreignObject[data-element-type="item-label"] > * {
font-size: 0.9em !important;
font-weight: 600 !important;
line-height: 1.3 !important;
white-space: normal !important;
word-break: break-word !important;
display: -webkit-box !important;
-webkit-line-clamp: 2 !important;
-webkit-box-orient: vertical !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
padding-bottom: 2px !important;
}
/* 卡片描述文字 */
.infographic-render-container svg foreignObject[data-element-type="item-desc"] > * {
font-size: 0.82em !important;
line-height: 1.3 !important;
white-space: normal !important;
display: -webkit-box !important;
-webkit-line-clamp: 2 !important;
-webkit-box-orient: vertical !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
.infographic-container-wrapper .download-area {
text-align: center;
@@ -537,34 +597,36 @@ SCRIPT_TEMPLATE_INFOGRAPHIC = """
}
}
// 2. 模板映射配置
// 2. 模板映射配置
// 2. 模板映射配置 (官方 AntV 结构 ID)
const TEMPLATE_MAPPING = {
// 列表与层级
// 列表与层级 - 短名称映射到完整模板名
'list-grid': 'list-grid-compact-card',
'list-column': 'list-column-simple-vertical-arrow',
'list-row': 'list-row-simple-horizontal-arrow',
'hierarchy-tree': 'hierarchy-tree-tech-style-capsule-item',
// 时序与时间线
'sequence-roadmap-vertical': 'sequence-roadmap-vertical-simple',
'sequence-timeline': 'sequence-timeline-simple',
'sequence-steps': 'sequence-steps-simple',
'sequence-horizontal-zigzag': 'sequence-horizontal-zigzag-simple',
// 对比
'compare-binary-horizontal': 'compare-binary-horizontal-simple-vs',
'compare-hierarchy-row': 'compare-hierarchy-row-simple',
// 图表
'chart-column': 'chart-column-simple',
'quadrant': 'quadrant-quarter-simple-card',
// 向后兼容的旧映射
'list-vertical': 'list-column-simple-vertical-arrow',
'tree-vertical': 'hierarchy-tree-tech-style-capsule-item',
'tree-horizontal': 'hierarchy-tree-lr-tech-style-capsule-item',
'mindmap': 'hierarchy-mindmap-branch-gradient-capsule-item',
// 顺序与关系
'sequence-roadmap': 'sequence-roadmap-vertical-simple',
'sequence-zigzag': 'sequence-horizontal-zigzag-simple',
'sequence-horizontal': 'sequence-horizontal-zigzag-simple',
'relation-sankey': 'relation-sankey-simple', // 暂无直接对应,保留原值或需移除
'relation-circle': 'relation-circle-icon-badge',
// 对比与分析
'compare-binary': 'compare-binary-horizontal-simple-vs',
'compare-swot': 'compare-swot',
'quadrant-quarter': 'quadrant-quarter-simple-card',
// 图表与数据
'statistic-card': 'list-grid-compact-card',
'chart-bar': 'chart-bar-plain-text',
'chart-column': 'chart-column-simple',
'chart-line': 'chart-line-plain-text',
'chart-area': 'chart-area-simple', // 暂无直接对应
'chart-pie': 'chart-pie-plain-text',
'chart-doughnut': 'chart-pie-donut-plain-text'
};
@@ -657,10 +719,48 @@ SCRIPT_TEMPLATE_INFOGRAPHIC = """
containerEl.dataset.infographicRendered = 'true';
console.log('[Infographic] 渲染完成');
// 自动调整高度
// 自动调整高度与元素标记
setTimeout(() => {
const svg = containerEl.querySelector('svg');
if (svg) {
// 1. 标记元素以便 CSS 应用样式
const fos = Array.from(svg.querySelectorAll('foreignObject'));
let titleFound = false;
let descFound = false;
fos.forEach((fo) => {
const text = fo.textContent.trim();
if (!text || fo.querySelector('i') || (fo.querySelector('svg') && fo.querySelectorAll('*').length < 5)) {
fo.setAttribute('data-element-type', 'icon');
return;
}
// 动态增加高度和宽度,容纳换行后的文字
const currentHeight = parseInt(fo.getAttribute('height') || '0');
if (currentHeight > 0 && currentHeight < 200) {
fo.setAttribute('height', Math.round(currentHeight * 1.8).toString());
}
const currentWidth = parseInt(fo.getAttribute('width') || '0');
if (currentWidth > 0 && currentWidth < 300) {
fo.setAttribute('width', Math.max(Math.round(currentWidth * 1.2), 180).toString());
}
if (!titleFound) {
fo.setAttribute('data-element-type', 'title');
titleFound = true;
} else if (!descFound) {
fo.setAttribute('data-element-type', 'desc');
descFound = true;
} else {
if (fo.querySelector('strong') || fo.style.fontWeight === 'bold' || text.length < 15) {
fo.setAttribute('data-element-type', 'item-label');
} else {
fo.setAttribute('data-element-type', 'item-desc');
}
}
});
// 2. 调整高度
const bbox = svg.getBoundingClientRect();
let contentHeight = bbox.height;
if (svg.viewBox && svg.viewBox.baseVal && svg.viewBox.baseVal.height) {

View File

@@ -1,15 +1,26 @@
# Async Context Compression Filter
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.1.0 | **License:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.1.3 | **License:** MIT
This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent.
## What's new in 1.1.0
## What's new in 1.1.3
- **Improved Compatibility**: Changed summary injection role from `user` to `assistant` for better compatibility across different LLMs.
- **Enhanced Stability**: Fixed a race condition in state management that could cause "inlet state not found" warnings in high-concurrency scenarios.
- **Bug Fixes**: Corrected default model handling to prevent misleading logs when no model is specified.
## What's new in 1.1.2
- **Open WebUI v0.7.x Compatibility**: Resolved a critical database session binding error affecting Open WebUI v0.7.x users. The plugin now dynamically discovers the database engine and session context, ensuring compatibility across versions.
- **Enhanced Error Reporting**: Errors during background summary generation are now reported via both the status bar and browser console.
- **Robust Model Handling**: Improved handling of missing or invalid model IDs to prevent crashes.
## What's new in 1.1.1
- **Frontend Debugging**: Added `show_debug_log` option to print debug info to the browser console (F12).
- **Optimized Compression**: Improved token calculation logic to prevent aggressive truncation of history, ensuring more context is retained.
- Reuses Open WebUI's shared database connection by default (no custom engine or env vars required).
- Token-based thresholds (`compression_threshold_tokens`, `max_context_tokens`) for safer long-context handling.
- Per-model overrides via `model_thresholds` for mixed-model workflows.
- Documentation now mirrors the latest async workflow and retention-first injection.
---
@@ -54,6 +65,7 @@ It is recommended to keep this filter early in the chain so it runs before filte
| `summary_temperature` | `0.3` | Randomness for summary generation. Lower is more deterministic. |
| `model_thresholds` | `{}` | Per-model overrides for `compression_threshold_tokens` and `max_context_tokens` (useful for mixed models). |
| `debug_mode` | `true` | Log verbose debug info. Set to `false` in production. |
| `show_debug_log` | `false` | Print debug logs to browser console (F12). Useful for frontend debugging. |
---

View File

@@ -1,17 +1,28 @@
# 异步上下文压缩过滤器
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.2.0 | **许可证:** MIT
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.1.3 | **许可证:** MIT
> **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
本过滤器通过智能摘要和消息压缩技术,在保持对话连贯性的同时,显著降低长对话的 Token 消耗。
## 1.1.0 版本更新
## 1.1.3 版本更新
- **兼容性提升**: 将摘要注入角色从 `user` 改为 `assistant`,以提高在不同 LLM 之间的兼容性。
- **稳定性增强**: 修复了状态管理中的竞态条件,解决了高并发场景下可能出现的“无法获取 inlet 状态”警告。
- **Bug 修复**: 修正了默认模型处理逻辑,防止在未指定模型时产生误导性日志。
## 1.1.2 版本更新
- **Open WebUI v0.7.x 兼容性**: 修复了影响 Open WebUI v0.7.x 用户的严重数据库会话绑定错误。插件现在动态发现数据库引擎和会话上下文,确保跨版本兼容性。
- **增强错误报告**: 后台摘要生成过程中的错误现在会通过状态栏和浏览器控制台同时报告。
- **健壮的模型处理**: 改进了对缺失或无效模型 ID 的处理,防止程序崩溃。
## 1.1.1 版本更新
- **前端调试**: 新增 `show_debug_log` 选项,支持在浏览器控制台 (F12) 打印调试信息。
- **压缩优化**: 优化 Token 计算逻辑,防止历史记录被过度截断,保留更多上下文。
- 默认复用 OpenWebUI 内置数据库连接,无需自建引擎、无需配置 `DATABASE_URL`
- 基于 Token 的阈值控制(`compression_threshold_tokens``max_context_tokens`),长上下文更安全。
- 支持 `model_thresholds` 为不同模型设置专属阈值,适合混用多模型场景。
- 文档同步最新异步工作流与“先保留再注入”策略。
---
@@ -94,6 +105,11 @@
- **默认值**: `true`
- **描述**: 是否在 Open WebUI 的控制台日志中打印详细的调试信息(如 Token 计数、压缩进度、数据库操作等)。生产环境建议设为 `false`
#### `show_debug_log`
- **默认值**: `false`
- **描述**: 是否在浏览器控制台 (F12) 打印调试日志。便于前端调试。
---
## 故障排除

View File

@@ -5,7 +5,7 @@ author: Fu-Jie
author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui
description: Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.
version: 1.1.1
version: 1.1.3
openwebui_id: b1655bc8-6de9-4cad-8cb5-a6f7829a02ce
license: MIT
@@ -249,6 +249,7 @@ import asyncio
import json
import hashlib
import time
import contextlib
# Open WebUI built-in imports
from open_webui.utils.chat import generate_chat_completion
@@ -257,9 +258,10 @@ from fastapi.requests import Request
from open_webui.main import app as webui_app
# Open WebUI internal database (re-use shared connection)
from open_webui.internal.db import engine as owui_engine
from open_webui.internal.db import Session as owui_Session
from open_webui.internal.db import Base as owui_Base
try:
from open_webui.internal import db as owui_db
except ModuleNotFoundError: # pragma: no cover - filter runs inside Open WebUI
owui_db = None
# Try to import tiktoken
try:
@@ -269,14 +271,91 @@ except ImportError:
# Database imports
from sqlalchemy import Column, String, Text, DateTime, Integer, inspect
from sqlalchemy.orm import declarative_base, sessionmaker
from sqlalchemy.engine import Engine
from datetime import datetime
def _discover_owui_engine(db_module: Any) -> Optional[Engine]:
"""Discover the Open WebUI SQLAlchemy engine via provided db module helpers."""
if db_module is None:
return None
db_context = getattr(db_module, "get_db_context", None) or getattr(
db_module, "get_db", None
)
if callable(db_context):
try:
with db_context() as session:
try:
return session.get_bind()
except AttributeError:
return getattr(session, "bind", None) or getattr(
session, "engine", None
)
except Exception as exc:
print(f"[DB Discover] get_db_context failed: {exc}")
for attr in ("engine", "ENGINE", "bind", "BIND"):
candidate = getattr(db_module, attr, None)
if candidate is not None:
return candidate
return None
def _discover_owui_schema(db_module: Any) -> Optional[str]:
"""Discover the Open WebUI database schema name if configured."""
if db_module is None:
return None
try:
base = getattr(db_module, "Base", None)
metadata = getattr(base, "metadata", None) if base is not None else None
candidate = getattr(metadata, "schema", None) if metadata is not None else None
if isinstance(candidate, str) and candidate.strip():
return candidate.strip()
except Exception as exc:
print(f"[DB Discover] Base metadata schema lookup failed: {exc}")
try:
metadata_obj = getattr(db_module, "metadata_obj", None)
candidate = (
getattr(metadata_obj, "schema", None) if metadata_obj is not None else None
)
if isinstance(candidate, str) and candidate.strip():
return candidate.strip()
except Exception as exc:
print(f"[DB Discover] metadata_obj schema lookup failed: {exc}")
try:
from open_webui import env as owui_env
candidate = getattr(owui_env, "DATABASE_SCHEMA", None)
if isinstance(candidate, str) and candidate.strip():
return candidate.strip()
except Exception as exc:
print(f"[DB Discover] env schema lookup failed: {exc}")
return None
owui_engine = _discover_owui_engine(owui_db)
owui_schema = _discover_owui_schema(owui_db)
owui_Base = getattr(owui_db, "Base", None) if owui_db is not None else None
if owui_Base is None:
owui_Base = declarative_base()
class ChatSummary(owui_Base):
"""Chat Summary Storage Table"""
__tablename__ = "chat_summary"
__table_args__ = {"extend_existing": True}
__table_args__ = (
{"extend_existing": True, "schema": owui_schema}
if owui_schema
else {"extend_existing": True}
)
id = Column(Integer, primary_key=True, autoincrement=True)
chat_id = Column(String(255), unique=True, nullable=False, index=True)
@@ -289,14 +368,69 @@ class ChatSummary(owui_Base):
class Filter:
def __init__(self):
self.valves = self.Valves()
self._owui_db = owui_db
self._db_engine = owui_engine
self._SessionLocal = owui_Session
self.temp_state = {} # Used to pass temporary data between inlet and outlet
self._db_engine = owui_engine
self._fallback_session_factory = (
sessionmaker(bind=self._db_engine) if self._db_engine else None
)
self._fallback_session_factory = (
sessionmaker(bind=self._db_engine) if self._db_engine else None
)
self._init_database()
@contextlib.contextmanager
def _db_session(self):
"""Yield a database session using Open WebUI helpers with graceful fallbacks."""
db_module = self._owui_db
db_context = None
if db_module is not None:
db_context = getattr(db_module, "get_db_context", None) or getattr(
db_module, "get_db", None
)
if callable(db_context):
with db_context() as session:
yield session
return
factory = None
if db_module is not None:
factory = getattr(db_module, "SessionLocal", None) or getattr(
db_module, "ScopedSession", None
)
if callable(factory):
session = factory()
try:
yield session
finally:
close = getattr(session, "close", None)
if callable(close):
close()
return
if self._fallback_session_factory is None:
raise RuntimeError(
"Open WebUI database session is unavailable. Ensure Open WebUI's database layer is initialized."
)
session = self._fallback_session_factory()
try:
yield session
finally:
try:
session.close()
except Exception as exc: # pragma: no cover - best-effort cleanup
print(f"[Database] ⚠️ Failed to close fallback session: {exc}")
def _init_database(self):
"""Initializes the database table using Open WebUI's shared connection."""
try:
if self._db_engine is None:
raise RuntimeError(
"Open WebUI database engine is unavailable. Ensure Open WebUI is configured with a valid DATABASE_URL."
)
# Check if table exists using SQLAlchemy inspect
inspector = inspect(self._db_engine)
if not inspector.has_table("chat_summary"):
@@ -366,7 +500,7 @@ class Filter:
def _save_summary(self, chat_id: str, summary: str, compressed_count: int):
"""Saves the summary to the database."""
try:
with self._SessionLocal() as session:
with self._db_session() as session:
# Find existing record
existing = session.query(ChatSummary).filter_by(chat_id=chat_id).first()
@@ -406,7 +540,7 @@ class Filter:
def _load_summary_record(self, chat_id: str) -> Optional[ChatSummary]:
"""Loads the summary record object from the database."""
try:
with self._SessionLocal() as session:
with self._db_session() as session:
record = session.query(ChatSummary).filter_by(chat_id=chat_id).first()
if record:
# Detach the object from the session so it can be used after session close
@@ -487,41 +621,25 @@ class Filter:
"max_context_tokens": self.valves.max_context_tokens,
}
def _inject_summary_to_first_message(self, message: dict, summary: str) -> dict:
"""Injects the summary into the first message (prepended to content)."""
content = message.get("content", "")
summary_block = f"【Historical Conversation Summary】\n{summary}\n\n---\nBelow is the recent conversation:\n\n"
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")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
# Handle different content types
if isinstance(content, list): # Multimodal content
# Find the first text part and insert the summary before it
new_content = []
summary_inserted = False
body_metadata = body.get("metadata", {})
if isinstance(body_metadata, dict):
chat_id = body_metadata.get("chat_id")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
for part in content:
if (
isinstance(part, dict)
and part.get("type") == "text"
and not summary_inserted
):
# Prepend summary to the first text part
new_content.append(
{"type": "text", "text": summary_block + part.get("text", "")}
)
summary_inserted = True
else:
new_content.append(part)
if isinstance(metadata, dict):
chat_id = metadata.get("chat_id")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
# If no text part, insert at the beginning
if not summary_inserted:
new_content.insert(0, {"type": "text", "text": summary_block})
message["content"] = new_content
elif isinstance(content, str): # Plain text
message["content"] = summary_block + content
return message
return ""
async def _emit_debug_log(
self,
@@ -632,7 +750,15 @@ class Filter:
Compression Strategy: Only responsible for injecting existing summaries, no Token calculation.
"""
messages = body.get("messages", [])
chat_id = __metadata__["chat_id"]
chat_id = self._extract_chat_id(body, __metadata__)
if not chat_id:
await self._log(
"[Inlet] ❌ Missing chat_id in metadata, skipping compression",
type="error",
event_call=__event_call__,
)
return body
if self.valves.debug_mode or self.valves.show_debug_log:
await self._log(
@@ -644,15 +770,9 @@ class Filter:
# Target is to compress up to the (total - keep_last) message
target_compressed_count = max(0, len(messages) - self.valves.keep_last)
# [Optimization] Simple state cleanup check
if chat_id in self.temp_state:
await self._log(
f"[Inlet] ⚠️ Overwriting unconsumed old state (Chat ID: {chat_id})",
type="warning",
event_call=__event_call__,
)
self.temp_state[chat_id] = target_compressed_count
# Record the target compression progress for the original messages, for use in outlet
# Target is to compress up to the (total - keep_last) message
target_compressed_count = max(0, len(messages) - self.valves.keep_last)
await self._log(
f"[Inlet] Recorded target compression progress: {target_compressed_count}",
@@ -685,7 +805,7 @@ class Filter:
f"---\n"
f"Below is the recent conversation:"
)
summary_msg = {"role": "user", "content": summary_content}
summary_msg = {"role": "assistant", "content": summary_content}
# 3. Tail messages (Tail) - All messages starting from the last compression point
# Note: Must ensure head messages are not duplicated
@@ -747,19 +867,37 @@ class Filter:
Executed after the LLM response is complete.
Calculates Token count in the background and triggers summary generation (does not block current response, does not affect content output).
"""
chat_id = __metadata__["chat_id"]
model = body.get("model", "gpt-3.5-turbo")
chat_id = self._extract_chat_id(body, __metadata__)
if not chat_id:
await self._log(
"[Outlet] ❌ Missing chat_id in metadata, skipping compression",
type="error",
event_call=__event_call__,
)
return body
model = body.get("model") or ""
# Calculate target compression progress directly
# Assuming body['messages'] in outlet contains the full history (including new response)
messages = body.get("messages", [])
target_compressed_count = max(0, len(messages) - self.valves.keep_last)
if self.valves.debug_mode or self.valves.show_debug_log:
await self._log(
f"\n{'='*60}\n[Outlet] Chat ID: {chat_id}\n[Outlet] Response complete",
f"\n{'='*60}\n[Outlet] Chat ID: {chat_id}\n[Outlet] Response complete\n[Outlet] Calculated target compression progress: {target_compressed_count} (Messages: {len(messages)})",
event_call=__event_call__,
)
# Process Token calculation and summary generation asynchronously in the background (do not wait for completion, do not affect output)
asyncio.create_task(
self._check_and_generate_summary_async(
chat_id, model, body, __user__, __event_emitter__, __event_call__
chat_id,
model,
body,
__user__,
target_compressed_count,
__event_emitter__,
__event_call__,
)
)
@@ -776,6 +914,7 @@ class Filter:
model: str,
body: dict,
user_data: Optional[dict],
target_compressed_count: Optional[int],
__event_emitter__: Callable[[Any], Awaitable[None]] = None,
__event_call__: Callable[[Any], Awaitable[None]] = None,
):
@@ -820,6 +959,7 @@ class Filter:
chat_id,
body,
user_data,
target_compressed_count,
__event_emitter__,
__event_call__,
)
@@ -836,12 +976,20 @@ class Filter:
event_call=__event_call__,
)
def _clean_model_id(self, model_id: Optional[str]) -> Optional[str]:
"""Cleans the model ID by removing whitespace and quotes."""
if not model_id:
return None
cleaned = model_id.strip().strip('"').strip("'")
return cleaned if cleaned else None
async def _generate_summary_async(
self,
messages: list,
chat_id: str,
body: dict,
user_data: Optional[dict],
target_compressed_count: Optional[int],
__event_emitter__: Callable[[Any], Awaitable[None]] = None,
__event_call__: Callable[[Any], Awaitable[None]] = None,
):
@@ -858,12 +1006,11 @@ class Filter:
)
# 1. Get target compression progress
# Prioritize getting from temp_state (calculated by inlet). If unavailable (e.g., after restart), assume current is full history.
target_compressed_count = self.temp_state.pop(chat_id, None)
# If target_compressed_count is not passed (should not happen with new logic), estimate it
if target_compressed_count is None:
target_compressed_count = max(0, len(messages) - self.valves.keep_last)
await self._log(
f"[🤖 Async Summary Task] ⚠️ Could not get inlet state, estimating progress using current message count: {target_compressed_count}",
f"[🤖 Async Summary Task] ⚠️ target_compressed_count is None, estimating: {target_compressed_count}",
type="warning",
event_call=__event_call__,
)
@@ -892,9 +1039,17 @@ class Filter:
# 3. Check Token limit and truncate (Max Context Truncation)
# [Optimization] Use the summary model's (if any) threshold to decide how many middle messages can be processed
# This allows using a long-window model (like gemini-flash) to compress history exceeding the current model's window
summary_model_id = self.valves.summary_model or body.get(
"model", "gpt-3.5-turbo"
)
summary_model_id = self._clean_model_id(
self.valves.summary_model
) or self._clean_model_id(body.get("model"))
if not summary_model_id:
await self._log(
"[🤖 Async Summary Task] ⚠️ Summary model does not exist, skipping compression",
type="warning",
event_call=__event_call__,
)
return
thresholds = self._get_model_thresholds(summary_model_id)
# Note: Using the summary model's max context limit here
@@ -966,9 +1121,21 @@ class Filter:
)
new_summary = await self._call_summary_llm(
None, conversation_text, body, user_data, __event_call__
None,
conversation_text,
{**body, "model": summary_model_id},
user_data,
__event_call__,
)
if not new_summary:
await self._log(
"[🤖 Async Summary Task] ⚠️ Summary generation returned empty result, skipping save",
type="warning",
event_call=__event_call__,
)
return
# 6. Save new summary
await self._log(
"[Optimization] Saving summary in a background thread to avoid blocking the event loop.",
@@ -1007,6 +1174,18 @@ class Filter:
type="error",
event_call=__event_call__,
)
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"Summary Error: {str(e)[:100]}...",
"done": True,
},
}
)
import traceback
traceback.print_exc()
@@ -1088,7 +1267,17 @@ This conversation may contain previous summaries (as system messages or text) an
Based on the content above, generate the summary:
"""
# Determine the model to use
model = self.valves.summary_model or body.get("model", "")
model = self._clean_model_id(self.valves.summary_model) or self._clean_model_id(
body.get("model")
)
if not model:
await self._log(
"[🤖 LLM Call] ⚠️ Summary model does not exist, skipping summary generation",
type="warning",
event_call=__event_call__,
)
return ""
await self._log(f"[🤖 LLM Call] Model: {model}", event_call=__event_call__)
@@ -1142,7 +1331,12 @@ Based on the content above, generate the summary:
return summary
except Exception as e:
error_message = f"Error occurred while calling LLM ({model}) to generate summary: {str(e)}"
error_msg = str(e)
# Handle specific error messages
if "Model not found" in error_msg:
error_message = f"Summary model '{model}' not found."
else:
error_message = f"Summary LLM Error ({model}): {error_msg}"
if not self.valves.summary_model:
error_message += (
"\n[Hint] You did not specify a summary_model, so the filter attempted to use the current conversation's model. "

View File

@@ -5,7 +5,7 @@ author: Fu-Jie
author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui
description: 通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。
version: 1.1.1
version: 1.1.3
openwebui_id: 5c0617cb-a9e4-4bd6-a440-d276534ebd18
license: MIT
@@ -290,7 +290,8 @@ class Filter:
self.valves = self.Valves()
self._db_engine = owui_engine
self._SessionLocal = owui_Session
self.temp_state = {} # 用于在 inlet 和 outlet 之间传递临时数据
self._SessionLocal = owui_Session
self._init_database()
self._init_database()
def _init_database(self):
@@ -471,42 +472,6 @@ class Filter:
"max_context_tokens": self.valves.max_context_tokens,
}
def _inject_summary_to_first_message(self, message: dict, summary: str) -> dict:
"""将摘要注入到第一条消息中(追加到内容前面)"""
content = message.get("content", "")
summary_block = f"【历史对话摘要】\n{summary}\n\n---\n以下是最近的对话:\n\n"
# 处理不同内容类型
if isinstance(content, list): # 多模态内容
# 查找第一个文本部分并在其前面插入摘要
new_content = []
summary_inserted = False
for part in content:
if (
isinstance(part, dict)
and part.get("type") == "text"
and not summary_inserted
):
# 在第一个文本部分前插入摘要
new_content.append(
{"type": "text", "text": summary_block + part.get("text", "")}
)
summary_inserted = True
else:
new_content.append(part)
# 如果没有文本部分,在开头插入
if not summary_inserted:
new_content.insert(0, {"type": "text", "text": summary_block})
message["content"] = new_content
elif isinstance(content, str): # 纯文本
message["content"] = summary_block + content
return message
async def _emit_debug_log(
self,
__event_call__,
@@ -628,15 +593,9 @@ class Filter:
# 目标是压缩到倒数第 keep_last 条之前
target_compressed_count = max(0, len(messages) - self.valves.keep_last)
# [优化] 简单的状态清理检查
if chat_id in self.temp_state:
await self._log(
f"[Inlet] ⚠️ 覆盖未消费的旧状态 (Chat ID: {chat_id})",
type="warning",
event_call=__event_call__,
)
self.temp_state[chat_id] = target_compressed_count
# 记录原始消息的目标压缩进度,供 outlet 使用
# 目标是压缩到倒数第 keep_last 条之前
target_compressed_count = max(0, len(messages) - self.valves.keep_last)
await self._log(
f"[Inlet] 记录目标压缩进度: {target_compressed_count}",
@@ -669,7 +628,7 @@ class Filter:
f"---\n"
f"以下是最近的对话:"
)
summary_msg = {"role": "user", "content": summary_content}
summary_msg = {"role": "assistant", "content": summary_content}
# 3. 尾部消息 (Tail) - 从上次压缩点开始的所有消息
# 注意:这里必须确保不重复包含头部消息
@@ -732,18 +691,29 @@ class Filter:
在后台计算 Token 数并触发摘要生成(不阻塞当前响应,不影响内容输出)
"""
chat_id = __metadata__["chat_id"]
model = body.get("model", "gpt-3.5-turbo")
model = body.get("model") or ""
# 直接计算目标压缩进度
# 假设 outlet 中的 body['messages'] 包含完整历史(包括新响应)
messages = body.get("messages", [])
target_compressed_count = max(0, len(messages) - self.valves.keep_last)
if self.valves.debug_mode or self.valves.show_debug_log:
await self._log(
f"\n{'='*60}\n[Outlet] Chat ID: {chat_id}\n[Outlet] 响应完成",
f"\n{'='*60}\n[Outlet] Chat ID: {chat_id}\n[Outlet] 响应完成\n[Outlet] 计算目标压缩进度: {target_compressed_count} (消息数: {len(messages)})",
event_call=__event_call__,
)
# 在后台异步处理 Token 计算和摘要生成(不等待完成,不影响输出)
asyncio.create_task(
self._check_and_generate_summary_async(
chat_id, model, body, __user__, __event_emitter__, __event_call__
chat_id,
model,
body,
__user__,
target_compressed_count,
__event_emitter__,
__event_call__,
)
)
@@ -760,6 +730,7 @@ class Filter:
model: str,
body: dict,
user_data: Optional[dict],
target_compressed_count: Optional[int],
__event_emitter__: Callable[[Any], Awaitable[None]] = None,
__event_call__: Callable[[Any], Awaitable[None]] = None,
):
@@ -804,6 +775,7 @@ class Filter:
chat_id,
body,
user_data,
target_compressed_count,
__event_emitter__,
__event_call__,
)
@@ -820,12 +792,20 @@ class Filter:
event_call=__event_call__,
)
def _clean_model_id(self, model_id: Optional[str]) -> Optional[str]:
"""Cleans the model ID by removing whitespace and quotes."""
if not model_id:
return None
cleaned = model_id.strip().strip('"').strip("'")
return cleaned if cleaned else None
async def _generate_summary_async(
self,
messages: list,
chat_id: str,
body: dict,
user_data: Optional[dict],
target_compressed_count: Optional[int],
__event_emitter__: Callable[[Any], Awaitable[None]] = None,
__event_call__: Callable[[Any], Awaitable[None]] = None,
):
@@ -840,12 +820,11 @@ class Filter:
await self._log(f"\n[🤖 异步摘要任务] 开始...", event_call=__event_call__)
# 1. 获取目标压缩进度
# 优先从 temp_state 获取(由 inlet 计算),如果获取不到(例如重启后),则假设当前是完整历史
target_compressed_count = self.temp_state.pop(chat_id, None)
# 如果未传递 target_compressed_count新逻辑下不应发生则进行估算
if target_compressed_count is None:
target_compressed_count = max(0, len(messages) - self.valves.keep_last)
await self._log(
f"[🤖 异步摘要任务] ⚠️ 无法获取 inlet 状态,使用当前消息数估算进度: {target_compressed_count}",
f"[🤖 异步摘要任务] ⚠️ target_compressed_count 为 None进行估算: {target_compressed_count}",
type="warning",
event_call=__event_call__,
)
@@ -874,7 +853,17 @@ class Filter:
# 3. 检查 Token 上限并截断 (Max Context Truncation)
# [优化] 使用摘要模型(如果有)的阈值来决定能处理多少中间消息
# 这样可以用长窗口模型(如 gemini-flash)来压缩超过当前模型窗口的历史记录
summary_model_id = self.valves.summary_model or body.get("model")
summary_model_id = self._clean_model_id(
self.valves.summary_model
) or self._clean_model_id(body.get("model"))
if not summary_model_id:
await self._log(
"[🤖 异步摘要任务] ⚠️ 摘要模型不存在,跳过压缩",
type="warning",
event_call=__event_call__,
)
return
thresholds = self._get_model_thresholds(summary_model_id)
# 注意:这里使用的是摘要模型的最大上下文限制
@@ -946,9 +935,21 @@ class Filter:
)
new_summary = await self._call_summary_llm(
None, conversation_text, body, user_data, __event_call__
None,
conversation_text,
{**body, "model": summary_model_id},
user_data,
__event_call__,
)
if not new_summary:
await self._log(
"[🤖 异步摘要任务] ⚠️ 摘要生成返回空结果,跳过保存",
type="warning",
event_call=__event_call__,
)
return
# 6. 保存新摘要
await self._log(
"[优化] 在后台线程中保存摘要以避免阻塞事件循环。",
@@ -987,6 +988,18 @@ class Filter:
type="error",
event_call=__event_call__,
)
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"摘要生成错误: {str(e)[:100]}...",
"done": True,
},
}
)
import traceback
traceback.print_exc()
@@ -1068,7 +1081,17 @@ class Filter:
请根据上述内容,生成摘要:
"""
# 确定使用的模型
model = self.valves.summary_model or body.get("model", "")
model = self._clean_model_id(self.valves.summary_model) or self._clean_model_id(
body.get("model")
)
if not model:
await self._log(
"[🤖 LLM 调用] ⚠️ 摘要模型不存在,跳过摘要生成",
type="warning",
event_call=__event_call__,
)
return ""
await self._log(f"[🤖 LLM 调用] 模型: {model}", event_call=__event_call__)
@@ -1122,7 +1145,12 @@ class Filter:
return summary
except Exception as e:
error_message = f"调用 LLM ({model}) 生成摘要时发生错误: {str(e)}"
error_msg = str(e)
# Handle specific error messages
if "Model not found" in error_msg:
error_message = f"摘要模型 '{model}' 不存在。"
else:
error_message = f"摘要 LLM 错误 ({model}): {error_msg}"
if not self.valves.summary_model:
error_message += (
"\n[提示] 您未指定 summary_model因此过滤器尝试使用当前对话的模型。"

View File

@@ -125,19 +125,45 @@ if x == 1:
```
## 7. Mermaid 语法修复 (Mermaid Syntax Fix)
**功能**: 修复 Mermaid 图表中常见的语法错误,特别是未加引号的标签包含特殊字符的情况。
**功能**: 修复 Mermaid 图表中常见的语法错误,特别是未加引号的标签包含特殊字符、嵌套括号或 HTML 标签的情况。
**默认**: 开启 (`enable_mermaid_fix = True`)
**示例**:
* **Before**:
```mermaid
graph TD
A[Label with (parens)] --> B(Label with [brackets])
```
* **After**:
```mermaid
graph TD
A["Label with (parens)"] --> B("Label with [brackets]")
```
### 7.1 基础特殊字符
**Before**:
```mermaid
graph TD
A[Label with (parens)] --> B(Label with [brackets])
```
**After**:
```mermaid
graph TD
A["Label with (parens)"] --> B("Label with [brackets]")
```
### 7.2 嵌套括号修复 (v1.1.0+)
**Before**:
```mermaid
graph TD
A((开始: 发现可疑快照)) --> B[物理损坏(Allocation Errors)]
```
**After**:
```mermaid
graph TD
A(("开始: 发现可疑快照")) --> B["物理损坏(Allocation Errors)"]
```
### 7.3 包含 HTML 标签 (v1.1.0+)
**Before**:
```mermaid
graph TD
A[第一步<br/>环境隔离] --> B{状态?}
```
**After**:
```mermaid
graph TD
A["第一步<br/>环境隔离"] --> B{"状态?"}
```
*注:插件已优化 HTML 保护机制,允许包含 `<br/>` 等标签的 Mermaid 图表正常触发修复。*
## 8. XML 标签清理 (XML Cleanup)

View File

@@ -1,10 +1,13 @@
# Markdown Normalizer Filter
A production-grade content normalizer filter for Open WebUI that fixes common Markdown formatting issues in LLM outputs. It ensures that code blocks, LaTeX formulas, Mermaid diagrams, and other Markdown elements are rendered correctly.
**Author:** [Fu-Jie](https://github.com/Fu-Jie)
**Version:** 1.1.0
A content normalizer filter for Open WebUI that fixes common Markdown formatting issues in LLM outputs. It ensures that code blocks, LaTeX formulas, Mermaid diagrams, and other Markdown elements are rendered correctly.
## Features
* **Mermaid Syntax Fix**: Automatically fixes common Mermaid syntax errors, such as unquoted node labels and unclosed subgraphs, ensuring diagrams render correctly.
* **Mermaid Syntax Fix**: Automatically fixes common Mermaid syntax errors, such as unquoted node labels (including multi-line labels and citations) and unclosed subgraphs, ensuring diagrams render correctly.
* **Frontend Console Debugging**: Supports printing structured debug logs directly to the browser console (F12) for easier troubleshooting.
* **Code Block Formatting**: Fixes broken code block prefixes, suffixes, and indentation.
* **LaTeX Normalization**: Standardizes LaTeX formula delimiters (`\[` -> `$$`, `\(` -> `$`).
@@ -20,7 +23,9 @@ A production-grade content normalizer filter for Open WebUI that fixes common Ma
1. Install the plugin in Open WebUI.
2. Enable the filter globally or for specific models.
3. Configure the enabled fixes in the **Valves** settings.
4. (Optional) Enable **Show Debug Log** in Valves to view detailed logs in the browser console.
4. (Optional) **Show Debug Log** is enabled by default in Valves. This prints structured logs to the browser console (F12).
> [!WARNING]
> As this is an initial version, some "negative fixes" might occur (e.g., breaking valid Markdown). If you encounter issues, please check the console logs, copy the "Original" vs "Normalized" content, and submit an issue.
## Configuration (Valves)
@@ -39,6 +44,14 @@ A production-grade content normalizer filter for Open WebUI that fixes common Ma
* `show_status`: Show status notification when fixes are applied.
* `show_debug_log`: Print debug logs to browser console.
## Changelog
### v1.1.0
* **Mermaid Fix Refinement**: Improved regex to handle nested parentheses in node labels (e.g., `ID("Label (text)")`) and avoided matching connection labels.
* **HTML Safeguard Optimization**: Refined `_contains_html` to allow common tags like `<br/>`, `<b>`, `<i>`, etc., ensuring Mermaid diagrams with these tags are still normalized.
* **Full-width Symbol Cleanup**: Fixed duplicate keys and incorrect quote mapping in `FULLWIDTH_MAP`.
* **Bug Fixes**: Fixed missing `Dict` import in Python files.
## License
MIT

View File

@@ -1,10 +1,13 @@
# Markdown 格式化过滤器 (Markdown Normalizer)
这是一个用于 Open WebUI 的生产级内容格式化过滤器,旨在修复 LLM 输出中常见的 Markdown 格式问题。它能确保代码块、LaTeX 公式、Mermaid 图表和其他 Markdown 元素被正确渲染。
**作者:** [Fu-Jie](https://github.com/Fu-Jie)
**版本:** 1.1.0
这是一个用于 Open WebUI 的内容格式化过滤器,旨在修复 LLM 输出中常见的 Markdown 格式问题。它能确保代码块、LaTeX 公式、Mermaid 图表和其他 Markdown 元素被正确渲染。
## 功能特性
* **Mermaid 语法修复**: 自动修复常见的 Mermaid 语法错误,如未加引号的节点标签和未闭合的子图 (Subgraph),确保图表能正确渲染。
* **Mermaid 语法修复**: 自动修复常见的 Mermaid 语法错误,如未加引号的节点标签(支持多行标签和引用标记)和未闭合的子图 (Subgraph),确保图表能正确渲染。
* **前端控制台调试**: 支持将结构化的调试日志直接打印到浏览器控制台 (F12),方便排查问题。
* **代码块格式化**: 修复破损的代码块前缀、后缀和缩进问题。
* **LaTeX 规范化**: 标准化 LaTeX 公式定界符 (`\[` -> `$$`, `\(` -> `$`)。
@@ -20,7 +23,9 @@
1. 在 Open WebUI 中安装此插件。
2. 全局启用或为特定模型启用此过滤器。
3.**Valves** 设置中配置需要启用的修复项。
4. (可选) 在 Valves 中开启 **显示调试日志 (Show Debug Log)** 以在浏览器控制台中查看详细日志
4. (可选) **显示调试日志 (Show Debug Log)** 在 Valves 中默认开启。这会将结构化的日志打印到浏览器控制台 (F12)
> [!WARNING]
> 由于这是初版,可能会出现“负向修复”的情况(例如破坏了原本正确的格式)。如果您遇到问题,请务必查看控制台日志,复制“原始 (Original)”与“规范化 (Normalized)”的内容对比,并提交 Issue 反馈。
## 配置项 (Valves)
@@ -39,6 +44,14 @@
* `show_status`: 应用修复时显示状态通知。
* `show_debug_log`: 在浏览器控制台打印调试日志。
## 更新日志
### v1.1.0
* **Mermaid 修复优化**: 改进了正则表达式以处理节点标签中的嵌套括号(如 `ID("标签 (文本)")`),并避免误匹配连接线上的文字。
* **HTML 保护机制优化**: 优化了 `_contains_html` 检测,允许 `<br/>`, `<b>`, `<i>` 等常见标签,确保包含这些标签的 Mermaid 图表能被正常规范化。
* **全角符号清理**: 修复了 `FULLWIDTH_MAP` 中的重复键名和错误的引号映射。
* **Bug 修复**: 修复了 Python 文件中缺失的 `Dict` 类型导入。
## 许可证
MIT

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

View File

@@ -1,14 +1,14 @@
"""
title: Markdown Normalizer
author: Fu-Jie
author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 1.0.0
description: A production-grade content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting.
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 1.1.0
description: A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting.
"""
from pydantic import BaseModel, Field
from typing import Optional, List, Callable
from typing import Optional, List, Callable, Dict
import re
import logging
import logging
@@ -75,7 +75,7 @@ class ContentNormalizer:
# Priority: Longer delimiters match first
"mermaid_node": re.compile(
r'("[^"\\]*(?:\\.[^"\\]*)*")|' # Match quoted strings first (Group 1)
r"(\w+)\s*(?:"
r"(\w+)(?:"
r"(\(\(\()(?![\"])(.*?)(?<![\"])(\)\)\))|" # (((...))) Double Circle
r"(\(\()(?![\"])(.*?)(?<![\"])(\)\))|" # ((...)) Circle
r"(\(\[)(?![\"])(.*?)(?<![\"])(\]\))|" # ([...]) Stadium
@@ -86,11 +86,13 @@ class ContentNormalizer:
r"(\[\\)(?![\"])(.*?)(?<![\"])(\\\])|" # [\...\] Parallelogram Alt
r"(\[/)(?![\"])(.*?)(?<![\"])(\\\])|" # [/...\] Trapezoid
r"(\[\\)(?![\"])(.*?)(?<![\"])(/\])|" # [\.../] Trapezoid Alt
r"(\()(?![\"])(.*?)(?<![\"])(\))|" # (...) Round
r"(\()(?![\"])([^)]*?)(?<![\"])(\))|" # (...) Round - Modified to be safer
r"(\[)(?![\"])(.*?)(?<![\"])(\])|" # [...] Square
r"(\{)(?![\"])(.*?)(?<![\"])(\})|" # {...} Rhombus
r"(>)(?![\"])(.*?)(?<![\"])(\])" # >...] Asymmetric
r")"
r"(\s*\[\d+\])?", # Capture optional citation [1]
re.DOTALL,
),
# Heading: #Heading -> # Heading
"heading_space": re.compile(r"^(#+)([^ \n#])", re.MULTILINE),
@@ -265,9 +267,10 @@ class ContentNormalizer:
"": ":",
"": "?",
"": "!",
'"': '"',
'"': '"',
""": "'", """: "'",
"": '"',
"": '"',
"": "'",
"": "'",
}
parts = content.split("```")
@@ -290,15 +293,20 @@ class ContentNormalizer:
id_str = match.group(2)
# Find matching shape group
# Groups start at index 3 (in match.group terms) or index 2 (in match.groups() tuple)
# Tuple: (String, ID, Open1, Content1, Close1, ...)
groups = match.groups()
for i in range(2, len(groups), 3):
citation = groups[-1] or "" # Last group is citation
# Iterate over shape groups (excluding the last citation group)
for i in range(2, len(groups) - 1, 3):
if groups[i] is not None:
open_char = groups[i]
content = groups[i + 1]
close_char = groups[i + 2]
# Append citation to content if present
if citation:
content += citation
# Escape quotes in content
content = content.replace('"', '\\"')
@@ -397,15 +405,52 @@ class Filter:
default=True, description="Show status notification when fixes are applied"
)
show_debug_log: bool = Field(
default=False, description="Print debug logs to browser console (F12)"
default=True, description="Print debug logs to browser console (F12)"
)
def __init__(self):
self.valves = self.Valves()
def _get_chat_context(
self, body: dict, __metadata__: Optional[dict] = None
) -> Dict[str, str]:
"""
Unified extraction of chat context information (chat_id, message_id).
Prioritizes extraction from body, then metadata.
"""
chat_id = ""
message_id = ""
# 1. Try to get from body
if isinstance(body, dict):
chat_id = body.get("chat_id", "")
message_id = body.get("id", "") # message_id is usually 'id' in body
# Check body.metadata as fallback
if not chat_id or not message_id:
body_metadata = body.get("metadata", {})
if isinstance(body_metadata, dict):
if not chat_id:
chat_id = body_metadata.get("chat_id", "")
if not message_id:
message_id = body_metadata.get("message_id", "")
# 2. Try to get from __metadata__ (as supplement)
if __metadata__ and isinstance(__metadata__, dict):
if not chat_id:
chat_id = __metadata__.get("chat_id", "")
if not message_id:
message_id = __metadata__.get("message_id", "")
return {
"chat_id": str(chat_id).strip(),
"message_id": str(message_id).strip(),
}
def _contains_html(self, content: str) -> bool:
"""Check if content contains HTML tags (to avoid breaking HTML output)"""
pattern = r"<\s*/?\s*(?:html|head|body|div|span|p|br|hr|ul|ol|li|table|thead|tbody|tfoot|tr|td|th|img|a|b|i|strong|em|code|pre|blockquote|h[1-6]|script|style|form|input|button|label|select|option|iframe|link|meta|title)\b"
# Removed common Mermaid-compatible tags like br, b, i, strong, em, span
pattern = r"<\s*/?\s*(?:html|head|body|div|p|hr|ul|ol|li|table|thead|tbody|tfoot|tr|td|th|img|a|code|pre|blockquote|h[1-6]|script|style|form|input|button|label|select|option|iframe|link|meta|title)\b"
return bool(re.search(pattern, content, re.IGNORECASE))
async def _emit_status(self, __event_emitter__, applied_fixes: List[str]):
@@ -431,24 +476,23 @@ class Filter:
print(f"Error emitting status: {e}")
async def _emit_debug_log(
self, __event_call__, applied_fixes: List[str], original: str, normalized: str
self,
__event_call__,
applied_fixes: List[str],
original: str,
normalized: str,
chat_id: str = "",
):
"""Emit debug log to browser console via JS execution"""
if not self.valves.show_debug_log or not __event_call__:
return
try:
# Prepare data for JS
log_data = {
"fixes": applied_fixes,
"original": original,
"normalized": normalized,
}
# Construct JS code
js_code = f"""
(async function() {{
console.group("🛠️ Markdown Normalizer Debug");
console.log("Chat ID:", {json.dumps(chat_id)});
console.log("Applied Fixes:", {json.dumps(applied_fixes, ensure_ascii=False)});
console.log("Original Content:", {json.dumps(original, ensure_ascii=False)});
console.log("Normalized Content:", {json.dumps(normalized, ensure_ascii=False)});
@@ -514,11 +558,13 @@ class Filter:
await self._emit_status(
__event_emitter__, normalizer.applied_fixes
)
chat_ctx = self._get_chat_context(body, __metadata__)
await self._emit_debug_log(
__event_call__,
normalizer.applied_fixes,
content,
new_content,
chat_id=chat_ctx["chat_id"],
)
return body

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

View File

@@ -1,14 +1,14 @@
"""
title: Markdown 格式修复器 (Markdown Normalizer)
author: Fu-Jie
author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 1.0.0
description: 生产级内容规范化过滤器,修复 LLM 输出中常见的 Markdown 格式问题如损坏的代码块、LaTeX 公式、Mermaid 图表和列表格式。
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 1.1.0
description: 内容规范化过滤器,修复 LLM 输出中常见的 Markdown 格式问题如损坏的代码块、LaTeX 公式、Mermaid 图表和列表格式。
"""
from pydantic import BaseModel, Field
from typing import Optional, List, Callable
from typing import Optional, List, Callable, Dict
import re
import logging
import asyncio
@@ -70,7 +70,7 @@ class ContentNormalizer:
# 优先级:长定界符优先匹配
"mermaid_node": re.compile(
r'("[^"\\]*(?:\\.[^"\\]*)*")|' # Match quoted strings first (Group 1)
r"(\w+)\s*(?:"
r"(\w+)(?:"
r"(\(\(\()(?![\"])(.*?)(?<![\"])(\)\)\))|" # (((...))) Double Circle
r"(\(\()(?![\"])(.*?)(?<![\"])(\)\))|" # ((...)) Circle
r"(\(\[)(?![\"])(.*?)(?<![\"])(\]\))|" # ([...]) Stadium
@@ -81,11 +81,13 @@ class ContentNormalizer:
r"(\[\\)(?![\"])(.*?)(?<![\"])(\\\])|" # [\...\] Parallelogram Alt
r"(\[/)(?![\"])(.*?)(?<![\"])(\\\])|" # [/...\] Trapezoid
r"(\[\\)(?![\"])(.*?)(?<![\"])(/\])|" # [\.../] Trapezoid Alt
r"(\()(?![\"])(.*?)(?<![\"])(\))|" # (...) Round
r"(\()(?![\"])([^)]*?)(?<![\"])(\))|" # (...) Round - Modified to be safer
r"(\[)(?![\"])(.*?)(?<![\"])(\])|" # [...] Square
r"(\{)(?![\"])(.*?)(?<![\"])(\})|" # {...} Rhombus
r"(>)(?![\"])(.*?)(?<![\"])(\])" # >...] Asymmetric
r")"
r"(\s*\[\d+\])?", # Capture optional citation [1]
re.DOTALL,
),
# Heading: #Heading -> # Heading
"heading_space": re.compile(r"^(#+)([^ \n#])", re.MULTILINE),
@@ -260,9 +262,10 @@ class ContentNormalizer:
"": ":",
"": "?",
"": "!",
'"': '"',
'"': '"',
""": "'", """: "'",
"": '"',
"": '"',
"": "'",
"": "'",
}
parts = content.split("```")
@@ -285,15 +288,20 @@ class ContentNormalizer:
id_str = match.group(2)
# Find matching shape group
# Groups start at index 3 (in match.group terms) or index 2 (in match.groups() tuple)
# Tuple: (String, ID, Open1, Content1, Close1, ...)
groups = match.groups()
for i in range(2, len(groups), 3):
citation = groups[-1] or "" # Last group is citation
# Iterate over shape groups (excluding the last citation group)
for i in range(2, len(groups) - 1, 3):
if groups[i] is not None:
open_char = groups[i]
content = groups[i + 1]
close_char = groups[i + 2]
# Append citation to content if present
if citation:
content += citation
# 如果内容包含引号,进行转义
content = content.replace('"', '\\"')
@@ -397,15 +405,52 @@ class Filter:
)
show_status: bool = Field(default=True, description="应用修复时显示状态通知")
show_debug_log: bool = Field(
default=False, description="在浏览器控制台打印调试日志 (F12)"
default=True, description="在浏览器控制台打印调试日志 (F12)"
)
def __init__(self):
self.valves = self.Valves()
def _get_chat_context(
self, body: dict, __metadata__: Optional[dict] = None
) -> Dict[str, str]:
"""
统一提取聊天上下文信息 (chat_id, message_id)。
优先从 body 中提取,其次从 metadata 中提取。
"""
chat_id = ""
message_id = ""
# 1. 尝试从 body 获取
if isinstance(body, dict):
chat_id = body.get("chat_id", "")
message_id = body.get("id", "") # message_id 在 body 中通常是 id
# 再次检查 body.metadata
if not chat_id or not message_id:
body_metadata = body.get("metadata", {})
if isinstance(body_metadata, dict):
if not chat_id:
chat_id = body_metadata.get("chat_id", "")
if not message_id:
message_id = body_metadata.get("message_id", "")
# 2. 尝试从 __metadata__ 获取 (作为补充)
if __metadata__ and isinstance(__metadata__, dict):
if not chat_id:
chat_id = __metadata__.get("chat_id", "")
if not message_id:
message_id = __metadata__.get("message_id", "")
return {
"chat_id": str(chat_id).strip(),
"message_id": str(message_id).strip(),
}
def _contains_html(self, content: str) -> bool:
"""Check if content contains HTML tags (to avoid breaking HTML output)"""
pattern = r"<\s*/?\s*(?:html|head|body|div|span|p|br|hr|ul|ol|li|table|thead|tbody|tfoot|tr|td|th|img|a|b|i|strong|em|code|pre|blockquote|h[1-6]|script|style|form|input|button|label|select|option|iframe|link|meta|title)\b"
# Removed common Mermaid-compatible tags like br, b, i, strong, em, span
pattern = r"<\s*/?\s*(?:html|head|body|div|p|hr|ul|ol|li|table|thead|tbody|tfoot|tr|td|th|img|a|code|pre|blockquote|h[1-6]|script|style|form|input|button|label|select|option|iframe|link|meta|title)\b"
return bool(re.search(pattern, content, re.IGNORECASE))
async def _emit_status(self, __event_emitter__, applied_fixes: List[str]):
@@ -456,24 +501,23 @@ class Filter:
"""Emit debug log to browser console via JS execution"""
async def _emit_debug_log(
self, __event_call__, applied_fixes: List[str], original: str, normalized: str
self,
__event_call__,
applied_fixes: List[str],
original: str,
normalized: str,
chat_id: str = "",
):
"""Emit debug log to browser console via JS execution"""
if not self.valves.show_debug_log or not __event_call__:
return
try:
# Prepare data for JS
log_data = {
"fixes": applied_fixes,
"original": original,
"normalized": normalized,
}
# Construct JS code
js_code = f"""
(async function() {{
console.group("🛠️ Markdown Normalizer Debug");
console.log("Chat ID:", {json.dumps(chat_id)});
console.log("Applied Fixes:", {json.dumps(applied_fixes, ensure_ascii=False)});
console.log("Original Content:", {json.dumps(original, ensure_ascii=False)});
console.log("Normalized Content:", {json.dumps(normalized, ensure_ascii=False)});
@@ -539,11 +583,13 @@ class Filter:
await self._emit_status(
__event_emitter__, normalizer.applied_fixes
)
chat_ctx = self._get_chat_context(body, __metadata__)
await self._emit_debug_log(
__event_call__,
normalizer.applied_fixes,
content,
new_content,
chat_id=chat_ctx["chat_id"],
)
return body

View File

@@ -253,7 +253,24 @@ class OpenWebUICommunityClient:
是否成功
"""
url = f"{self.BASE_URL}/posts/{post_id}/update"
response = requests.post(url, headers=self.headers, json=post_data)
# 仅发送允许更新的字段,避免 422 错误
allowed_keys = ["title", "content", "type", "data", "media"]
payload = {k: v for k, v in post_data.items() if k in allowed_keys}
response = requests.post(url, headers=self.headers, json=payload)
if response.status_code != 200:
try:
error_detail = response.json()
print(
f" Error: Update failed ({response.status_code}): {json.dumps(error_detail, indent=2)}"
)
except Exception:
print(
f" Error: Update failed ({response.status_code}): {response.text[:500]}"
)
response.raise_for_status()
return True
@@ -282,37 +299,54 @@ class OpenWebUICommunityClient:
if not post_data:
return False
# 确保结构存在
if "data" not in post_data:
post_data["data"] = {}
if "function" not in post_data["data"]:
post_data["data"]["function"] = {}
if "meta" not in post_data["data"]["function"]:
post_data["data"]["function"]["meta"] = {}
if "manifest" not in post_data["data"]["function"]["meta"]:
post_data["data"]["function"]["meta"]["manifest"] = {}
# 严格重建 data 结构,避免包含只读字段(如 data.function.id
current_function = post_data.get("data", {}).get("function", {})
# 更新源代码
post_data["data"]["function"]["content"] = source_code
# 过滤 metadata移除 openwebui_id 等系统字段
clean_metadata = {
k: v
for k, v in (metadata or {}).items()
if k not in ["openwebui_id", "post_id"]
}
function_data = {
"id": current_function.get("id", ""),
"name": metadata.get("title", current_function.get("name", "Plugin")),
"type": current_function.get("type", "action"),
"content": source_code,
"meta": {
"description": metadata.get(
"description",
current_function.get("meta", {}).get("description", ""),
),
"manifest": clean_metadata,
},
}
post_data["data"] = {"function": function_data}
post_data["type"] = "function"
# 更新 README社区页面展示内容
if readme_content:
post_data["content"] = readme_content
# 更新元数据
if metadata:
post_data["data"]["function"]["meta"]["manifest"].update(metadata)
if "title" in metadata:
post_data["title"] = metadata["title"]
post_data["data"]["function"]["name"] = metadata["title"]
if "description" in metadata:
post_data["data"]["function"]["meta"]["description"] = metadata[
"description"
]
# 更新标题
if metadata and "title" in metadata:
post_data["title"] = metadata["title"]
# 更新图片
if media_urls:
post_data["media"] = media_urls
# 将字符串 URL 转换为字典格式 (API 要求)
media_list = []
for item in media_urls:
if isinstance(item, str):
media_list.append({"url": item})
elif isinstance(item, dict):
media_list.append(item)
post_data["media"] = media_list
else:
# 如果没有新图片,保留原有的(如果有)
pass
return self.update_post(post_id, post_data)
@@ -469,10 +503,42 @@ class OpenWebUICommunityClient:
return True, f"Created new post (ID: {new_post_id})"
return False, "Failed to create new post"
# 获取远程帖子信息(只需获取一次)
remote_post = None
if post_id:
remote_post = self.get_post(post_id)
# 版本检查(仅对更新有效)
if not force and local_version:
if not self.version_needs_update(post_id, local_version):
return True, f"Skipped: version {local_version} matches remote"
if not force and local_version and remote_post:
remote_version = (
remote_post.get("data", {})
.get("function", {})
.get("meta", {})
.get("manifest", {})
.get("version")
)
version_changed = local_version != remote_version
# 检查 README 是否变化
readme_changed = False
remote_content = remote_post.get("content", "")
local_content = readme_content or metadata.get("description", "")
# 简单的内容比较 (去除首尾空白)
if (local_content or "").strip() != (remote_content or "").strip():
readme_changed = True
if not version_changed and not readme_changed:
return (
True,
f"Skipped: version {local_version} matches remote and no README changes",
)
if readme_changed and not version_changed:
print(
f" Version match ({local_version}) but README changed. Updating..."
)
# 更新
success = self.update_plugin(
@@ -484,7 +550,9 @@ class OpenWebUICommunityClient:
)
if success:
return True, f"Updated to version {local_version}"
if local_version:
return True, f"Updated to version {local_version}"
return True, "Updated plugin"
return False, "Update failed"
def _parse_frontmatter(self, content: str) -> Dict[str, str]:

View File

@@ -414,7 +414,8 @@ class OpenWebUIStats:
"author_header": "| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |",
"header": "| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |",
"top6_title": "### 🔥 热门插件 Top 6",
"top6_header": "| 排名 | 插件 | 下载 | 浏览 |",
"top6_updated": f"> 🕐 自动更新于 {get_beijing_time().strftime('%Y-%m-%d %H:%M')}",
"top6_header": "| 排名 | 插件 | 版本 | 下载 | 浏览 | 更新日期 |",
"full_stats": "*完整统计请查看 [社区统计报告](./docs/community-stats.zh.md)*",
},
"en": {
@@ -423,7 +424,8 @@ class OpenWebUIStats:
"author_header": "| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |",
"header": "| 📝 Posts | ⬇️ Downloads | 👁️ Views | 👍 Upvotes | 💾 Saves |",
"top6_title": "### 🔥 Top 6 Popular Plugins",
"top6_header": "| Rank | Plugin | Downloads | Views |",
"top6_updated": f"> 🕐 Auto-updated: {get_beijing_time().strftime('%Y-%m-%d %H:%M')}",
"top6_header": "| Rank | Plugin | Version | Downloads | Views | Updated |",
"full_stats": "*See full stats in [Community Stats Report](./docs/community-stats.md)*",
},
}
@@ -462,14 +464,16 @@ class OpenWebUIStats:
# Top 6 热门插件
lines.append(t["top6_title"])
lines.append("")
lines.append(t["top6_updated"])
lines.append("")
lines.append(t["top6_header"])
lines.append("|:---:|------|:---:|:---:|")
lines.append("|:---:|------|:---:|:---:|:---:|:---:|")
medals = ["🥇", "🥈", "🥉", "4", "5", "6"]
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']} |"
f"| {medal} | [{post['title']}]({post['url']}) | {post['version']} | {post['downloads']} | {post['views']} | {post['updated_at']} |"
)
lines.append("")