Compare commits
123 Commits
v2026.01.1
...
v2026.01.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
813b019653 | ||
|
|
b0b1542939 | ||
|
|
15f19d8b8d | ||
|
|
82253b114c | ||
|
|
e0bfbf6dd4 | ||
|
|
4689e80e7a | ||
|
|
556e6c1c67 | ||
|
|
3ab84a526d | ||
|
|
bdce96f912 | ||
|
|
4811b99a4b | ||
|
|
fb2a64c07a | ||
|
|
e023e4f2e2 | ||
|
|
0b16b1e0f4 | ||
|
|
59073ad7ac | ||
|
|
8248644c45 | ||
|
|
f38e6394c9 | ||
|
|
0aaa529c6b | ||
|
|
b81a6562a1 | ||
|
|
c5b10db23a | ||
|
|
d16e444643 | ||
|
|
8202468099 | ||
|
|
766e8bd20f | ||
|
|
1214ab5a8c | ||
|
|
ebddbb25f8 | ||
|
|
59545e1110 | ||
|
|
500e090b11 | ||
|
|
a75ee555fa | ||
|
|
6a8c2164cd | ||
|
|
7f7efa325a | ||
|
|
9ba6cb08fc | ||
|
|
1872271a2d | ||
|
|
813b50864a | ||
|
|
b18cefe320 | ||
|
|
a54c359fcf | ||
|
|
8d83221a4a | ||
|
|
1879000720 | ||
|
|
ba92649a98 | ||
|
|
d2276dcaae | ||
|
|
25c9d20f3d | ||
|
|
0d853577df | ||
|
|
f91f3d8692 | ||
|
|
0f7cad8dfa | ||
|
|
db1a1e7ef0 | ||
|
|
e7de80a059 | ||
|
|
0d8c4e048e | ||
|
|
014a5a9d1f | ||
|
|
a6dd970859 | ||
|
|
aac730f5b1 | ||
|
|
ff95d9328e | ||
|
|
afe1d8cf52 | ||
|
|
67b819f3de | ||
|
|
9b6acb6b95 | ||
|
|
a9a59e1e34 | ||
|
|
5b05397356 | ||
|
|
7a7dbc0cfa | ||
|
|
6ac0ba6efe | ||
|
|
d3d008efb4 | ||
|
|
4f1528128a | ||
|
|
93c4326206 | ||
|
|
0fca7fe524 | ||
|
|
afdcab10c6 | ||
|
|
f8cc5eabe6 | ||
|
|
f304eb7633 | ||
|
|
827204e082 | ||
|
|
641d7ee8c8 | ||
|
|
3b11537b5e | ||
|
|
e51d87ae80 | ||
|
|
f16e7c996c | ||
|
|
55eb295c12 | ||
|
|
4767351c5e | ||
|
|
1d2502eb3f | ||
|
|
94540cc131 | ||
|
|
71bef146c8 | ||
|
|
87e47fd4b2 | ||
|
|
2da600838c | ||
|
|
4ee34c1dc6 | ||
|
|
9a854c33d3 | ||
|
|
ae19653a8f | ||
|
|
caf0acf2e1 | ||
|
|
b503ad6fd2 | ||
|
|
357e869a15 | ||
|
|
3035c79d91 | ||
|
|
a5e5e178a0 | ||
|
|
d20081d3ed | ||
|
|
e2d94ba5b5 | ||
|
|
49a19242a4 | ||
|
|
c26d3b30e5 | ||
|
|
60e681042d | ||
|
|
842d65b887 | ||
|
|
ff5cecca1c | ||
|
|
b447143a50 | ||
|
|
e4cbf231a6 | ||
|
|
8868b28a84 | ||
|
|
c4df24d2c2 | ||
|
|
70a96d0754 | ||
|
|
ab0daba80d | ||
|
|
505fb6ca96 | ||
|
|
385ee71bc8 | ||
|
|
cfa28e2c9a | ||
|
|
d08bede60e | ||
|
|
b686db353c | ||
|
|
2b543d51ff | ||
|
|
e8d09d79ec | ||
|
|
cdb544f891 | ||
|
|
3eff93e8c9 | ||
|
|
cdb03fce90 | ||
|
|
c1cecf0dbb | ||
|
|
08ecba3ee1 | ||
|
|
3b82f2364e | ||
|
|
a7b2032b20 | ||
|
|
3bc683dbf5 | ||
|
|
2a8065e80c | ||
|
|
ab60641265 | ||
|
|
9e88decc44 | ||
|
|
076598ba07 | ||
|
|
4f0c50db0f | ||
|
|
499690e30f | ||
|
|
12a531b9ae | ||
|
|
3a0e2ecc6e | ||
|
|
14954b03bf | ||
|
|
6f874db000 | ||
|
|
16cc45c0d5 | ||
|
|
ab96719ec4 |
30
.agent/rules/plugin_standards.md
Normal file
30
.agent/rules/plugin_standards.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
description: Standards for OpenWebUI Plugin Development, specifically README formatting.
|
||||||
|
globs: plugins/**
|
||||||
|
always_on: true
|
||||||
|
---
|
||||||
|
# Plugin Development Standards
|
||||||
|
|
||||||
|
## README Documentation
|
||||||
|
|
||||||
|
All plugins MUST follow the standard README template.
|
||||||
|
|
||||||
|
**Reference Template**: @docs/PLUGIN_README_TEMPLATE.md
|
||||||
|
|
||||||
|
### Language Requirements
|
||||||
|
- **English Version (`README.md`)**: The primary documentation source. Must follow the template strictly.
|
||||||
|
- **Chinese Version (`README_CN.md`)**: MUST be translated based on the English version (`README.md`) to ensure consistency in structure and content.
|
||||||
|
|
||||||
|
### Metadata Requirements
|
||||||
|
The metadata line must follow this format:
|
||||||
|
`**Author:** [Name](Link) | **Version:** [X.Y.Z] | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT`
|
||||||
|
|
||||||
|
### Structure Checklist
|
||||||
|
1. **Title & Description**
|
||||||
|
2. **Metadata Line** (Author, Version, Project, License)
|
||||||
|
3. **Preview** (Screenshots/GIFs)
|
||||||
|
4. **What's New** (Keep last 3 versions)
|
||||||
|
5. **Key Features**
|
||||||
|
6. **How to Use**
|
||||||
|
7. **Configuration (Valves)**
|
||||||
|
8. **Troubleshooting** (Must include link to GitHub Issues)
|
||||||
@@ -25,6 +25,8 @@ Every plugin **MUST** have bilingual versions for both code and documentation:
|
|||||||
- **Valves**: Use `pydantic` for configuration.
|
- **Valves**: Use `pydantic` for configuration.
|
||||||
- **Database**: Re-use `open_webui.internal.db` shared connection.
|
- **Database**: Re-use `open_webui.internal.db` shared connection.
|
||||||
- **User Context**: Use `_get_user_context` helper method.
|
- **User Context**: Use `_get_user_context` helper method.
|
||||||
|
- **Chat Context**: Use `_get_chat_context` helper method for `chat_id` and `message_id`.
|
||||||
|
- **Debugging**: Use `_emit_debug_log` for frontend console logging (requires `SHOW_DEBUG_LOG` valve).
|
||||||
- **Chat API**: For message updates, follow the "OpenWebUI Chat API 更新规范" in `.github/copilot-instructions.md`.
|
- **Chat API**: For message updates, follow the "OpenWebUI Chat API 更新规范" in `.github/copilot-instructions.md`.
|
||||||
- Use Event API for immediate UI updates
|
- Use Event API for immediate UI updates
|
||||||
- Use Chat Persistence API for database storage
|
- Use Chat Persistence API for database storage
|
||||||
@@ -86,7 +88,11 @@ Reference: `.github/workflows/release.yml`
|
|||||||
- Workflow: `.github/workflows/publish_plugin.yml`
|
- Workflow: `.github/workflows/publish_plugin.yml`
|
||||||
- Trigger: Release published.
|
- Trigger: Release published.
|
||||||
- Action: Automatically updates the plugin code and metadata on OpenWebUI.com using `scripts/publish_plugin.py`.
|
- Action: Automatically updates the plugin code and metadata on OpenWebUI.com using `scripts/publish_plugin.py`.
|
||||||
|
- **Auto-Sync**: If a local plugin has no ID but matches an existing published plugin by **Title**, the script will automatically fetch the ID, update the local file, and proceed with the update.
|
||||||
- Requirement: `OPENWEBUI_API_KEY` secret must be set.
|
- Requirement: `OPENWEBUI_API_KEY` secret must be set.
|
||||||
|
- **README Link**: When announcing a release, always include the GitHub README URL for the plugin:
|
||||||
|
- Format: `https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/{type}/{name}/README.md`
|
||||||
|
- Example: `https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/filters/folder-memory/README.md`
|
||||||
|
|
||||||
### Pull Request Check
|
### Pull Request Check
|
||||||
- Workflow: `.github/workflows/plugin-version-check.yml`
|
- Workflow: `.github/workflows/plugin-version-check.yml`
|
||||||
|
|||||||
@@ -36,6 +36,15 @@
|
|||||||
"bug",
|
"bug",
|
||||||
"ideas"
|
"ideas"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "nahoj",
|
||||||
|
"name": "Johan Grande",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/469017?v=4",
|
||||||
|
"profile": "https://perso.crans.org/grande/",
|
||||||
|
"contributions": [
|
||||||
|
"ideas"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
|||||||
2071
.github/copilot-instructions.md
vendored
2071
.github/copilot-instructions.md
vendored
File diff suppressed because it is too large
Load Diff
89
.github/workflows/community-stats.yml
vendored
89
.github/workflows/community-stats.yml
vendored
@@ -1,5 +1,9 @@
|
|||||||
# OpenWebUI 社区统计报告自动生成
|
# OpenWebUI 社区统计报告自动生成
|
||||||
# 只在统计数据变化时 commit,避免频繁提交
|
# 智能检测:只在有意义的变更时才 commit
|
||||||
|
# - 新增插件 (total_posts)
|
||||||
|
# - 插件版本变更 (version)
|
||||||
|
# - 积分增加 (total_points)
|
||||||
|
# - 粉丝增加 (followers)
|
||||||
|
|
||||||
name: Community Stats
|
name: Community Stats
|
||||||
|
|
||||||
@@ -31,9 +35,23 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
pip install requests python-dotenv
|
pip install requests python-dotenv
|
||||||
|
|
||||||
|
|
||||||
|
- name: Capture existing stats (before update)
|
||||||
|
id: old_stats
|
||||||
|
run: |
|
||||||
|
if [ -f docs/community-stats.json ]; then
|
||||||
|
echo "total_posts=$(jq -r '.total_posts // 0' docs/community-stats.json)" >> $GITHUB_OUTPUT
|
||||||
|
echo "total_points=$(jq -r '.user.total_points // 0' docs/community-stats.json)" >> $GITHUB_OUTPUT
|
||||||
|
echo "followers=$(jq -r '.user.followers // 0' docs/community-stats.json)" >> $GITHUB_OUTPUT
|
||||||
|
# 提取所有插件的版本号,生成一个排序后的字符串用于比较
|
||||||
|
echo "versions=$(jq -r '[.posts[].version] | sort | join(",")' docs/community-stats.json)" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "total_posts=0" >> $GITHUB_OUTPUT
|
||||||
|
echo "total_points=0" >> $GITHUB_OUTPUT
|
||||||
|
echo "followers=0" >> $GITHUB_OUTPUT
|
||||||
|
echo "versions=" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Generate stats report
|
- name: Generate stats report
|
||||||
env:
|
env:
|
||||||
OPENWEBUI_API_KEY: ${{ secrets.OPENWEBUI_API_KEY }}
|
OPENWEBUI_API_KEY: ${{ secrets.OPENWEBUI_API_KEY }}
|
||||||
@@ -41,10 +59,71 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python scripts/openwebui_stats.py
|
python scripts/openwebui_stats.py
|
||||||
|
|
||||||
|
- name: Capture new stats (after update)
|
||||||
|
id: new_stats
|
||||||
|
run: |
|
||||||
|
echo "total_posts=$(jq -r '.total_posts // 0' docs/community-stats.json)" >> $GITHUB_OUTPUT
|
||||||
|
echo "total_points=$(jq -r '.user.total_points // 0' docs/community-stats.json)" >> $GITHUB_OUTPUT
|
||||||
|
echo "followers=$(jq -r '.user.followers // 0' docs/community-stats.json)" >> $GITHUB_OUTPUT
|
||||||
|
echo "versions=$(jq -r '[.posts[].version] | sort | join(",")' docs/community-stats.json)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Check for significant changes
|
||||||
|
id: check_changes
|
||||||
|
run: |
|
||||||
|
OLD_POSTS="${{ steps.old_stats.outputs.total_posts }}"
|
||||||
|
NEW_POSTS="${{ steps.new_stats.outputs.total_posts }}"
|
||||||
|
OLD_POINTS="${{ steps.old_stats.outputs.total_points }}"
|
||||||
|
NEW_POINTS="${{ steps.new_stats.outputs.total_points }}"
|
||||||
|
OLD_FOLLOWERS="${{ steps.old_stats.outputs.followers }}"
|
||||||
|
NEW_FOLLOWERS="${{ steps.new_stats.outputs.followers }}"
|
||||||
|
OLD_VERSIONS="${{ steps.old_stats.outputs.versions }}"
|
||||||
|
NEW_VERSIONS="${{ steps.new_stats.outputs.versions }}"
|
||||||
|
|
||||||
|
SHOULD_COMMIT="false"
|
||||||
|
CHANGE_REASON=""
|
||||||
|
|
||||||
|
# 检查新增插件
|
||||||
|
if [ "$NEW_POSTS" -gt "$OLD_POSTS" ]; then
|
||||||
|
SHOULD_COMMIT="true"
|
||||||
|
CHANGE_REASON="new plugin added ($OLD_POSTS -> $NEW_POSTS)"
|
||||||
|
echo "📦 New plugin detected: $OLD_POSTS -> $NEW_POSTS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查版本变更
|
||||||
|
if [ "$OLD_VERSIONS" != "$NEW_VERSIONS" ]; then
|
||||||
|
SHOULD_COMMIT="true"
|
||||||
|
CHANGE_REASON="${CHANGE_REASON:+$CHANGE_REASON, }plugin version updated"
|
||||||
|
echo "🔄 Plugin version changed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查积分增加
|
||||||
|
if [ "$NEW_POINTS" -gt "$OLD_POINTS" ]; then
|
||||||
|
SHOULD_COMMIT="true"
|
||||||
|
CHANGE_REASON="${CHANGE_REASON:+$CHANGE_REASON, }points increased ($OLD_POINTS -> $NEW_POINTS)"
|
||||||
|
echo "⭐ Points increased: $OLD_POINTS -> $NEW_POINTS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查粉丝增加
|
||||||
|
if [ "$NEW_FOLLOWERS" -gt "$OLD_FOLLOWERS" ]; then
|
||||||
|
SHOULD_COMMIT="true"
|
||||||
|
CHANGE_REASON="${CHANGE_REASON:+$CHANGE_REASON, }followers increased ($OLD_FOLLOWERS -> $NEW_FOLLOWERS)"
|
||||||
|
echo "👥 Followers increased: $OLD_FOLLOWERS -> $NEW_FOLLOWERS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "should_commit=$SHOULD_COMMIT" >> $GITHUB_OUTPUT
|
||||||
|
echo "change_reason=$CHANGE_REASON" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
if [ "$SHOULD_COMMIT" = "false" ]; then
|
||||||
|
echo "ℹ️ No significant changes detected, skipping commit"
|
||||||
|
else
|
||||||
|
echo "✅ Significant changes detected: $CHANGE_REASON"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Commit and push changes
|
- name: Commit and push changes
|
||||||
|
if: steps.check_changes.outputs.should_commit == 'true'
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
git config --local user.name "github-actions[bot]"
|
git config --local user.name "github-actions[bot]"
|
||||||
git add docs/community-stats.zh.md docs/community-stats.md docs/community-stats.json README.md README_CN.md
|
git add docs/community-stats.zh.md docs/community-stats.md docs/community-stats.json docs/badges README.md README_CN.md
|
||||||
git diff --staged --quiet || git commit -m "chore: update community stats $(date +'%Y-%m-%d')"
|
git diff --staged --quiet || git commit -m "chore: update community stats - ${{ steps.check_changes.outputs.change_reason }}"
|
||||||
git push
|
git push
|
||||||
|
|||||||
35
README.md
35
README.md
@@ -1,6 +1,6 @@
|
|||||||
# OpenWebUI Extras
|
# OpenWebUI Extras
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
[](#contributors-)
|
[](#contributors-)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
English | [中文](./README_CN.md)
|
English | [中文](./README_CN.md)
|
||||||
@@ -10,28 +10,28 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
|
|||||||
<!-- STATS_START -->
|
<!-- STATS_START -->
|
||||||
## 📊 Community Stats
|
## 📊 Community Stats
|
||||||
|
|
||||||
> 🕐 Auto-updated: 2026-01-13 22:10
|
> 🕐 Auto-updated: 2026-01-26 15:14
|
||||||
|
|
||||||
| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |
|
| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |
|
||||||
|:---:|:---:|:---:|:---:|
|
|:---:|:---:|:---:|:---:|
|
||||||
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **96** | **100** | **23** |
|
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **158** | **152** | **31** |
|
||||||
|
|
||||||
| 📝 Posts | ⬇️ Downloads | 👁️ Views | 👍 Upvotes | 💾 Saves |
|
| 📝 Posts | ⬇️ Downloads | 👁️ Views | 👍 Upvotes | 💾 Saves |
|
||||||
|:---:|:---:|:---:|:---:|:---:|
|
|:---:|:---:|:---:|:---:|:---:|
|
||||||
| **15** | **1298** | **14813** | **88** | **92** |
|
| **19** | **2388** | **27294** | **138** | **183** |
|
||||||
|
|
||||||
### 🔥 Top 6 Popular Plugins
|
### 🔥 Top 6 Popular Plugins
|
||||||
|
|
||||||
> 🕐 Auto-updated: 2026-01-13 22:10
|
> 🕐 Auto-updated: 2026-01-26 15:14
|
||||||
|
|
||||||
| Rank | Plugin | Version | Downloads | Views | Updated |
|
| Rank | Plugin | Version | Downloads | Views | Updated |
|
||||||
|:---:|------|:---:|:---:|:---:|:---:|
|
|:---:|------|:---:|:---:|:---:|:---:|
|
||||||
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 412 | 3715 | 2026-01-07 |
|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 629 | 5600 | 2026-01-17 |
|
||||||
| 🥈 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 190 | 625 | 2026-01-07 |
|
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 410 | 3621 | 2026-01-25 |
|
||||||
| 🥉 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 153 | 1685 | 2026-01-11 |
|
| 🥉 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 255 | 1039 | 2026-01-07 |
|
||||||
| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.1.3 | 148 | 1643 | 2026-01-11 |
|
| 4️⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 229 | 1839 | 2026-01-17 |
|
||||||
| 5️⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 109 | 992 | 2026-01-07 |
|
| 5️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 227 | 2461 | 2026-01-21 |
|
||||||
| 6️⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 106 | 1956 | 2026-01-07 |
|
| 6️⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 165 | 2674 | 2026-01-17 |
|
||||||
|
|
||||||
*See full stats in [Community Stats Report](./docs/community-stats.md)*
|
*See full stats in [Community Stats Report](./docs/community-stats.md)*
|
||||||
<!-- STATS_END -->
|
<!-- STATS_END -->
|
||||||
@@ -43,6 +43,7 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
|
|||||||
Located in the `plugins/` directory, containing Python-based enhancements:
|
Located in the `plugins/` directory, containing Python-based enhancements:
|
||||||
|
|
||||||
#### Actions
|
#### Actions
|
||||||
|
|
||||||
- **Smart Mind Map** (`smart-mind-map`): Generates interactive mind maps from text.
|
- **Smart Mind Map** (`smart-mind-map`): Generates interactive mind maps from text.
|
||||||
- **Smart Infographic** (`infographic`): Transforms text into professional infographics using AntV.
|
- **Smart Infographic** (`infographic`): Transforms text into professional infographics using AntV.
|
||||||
- **Flash Card** (`flash-card`): Quickly generates beautiful flashcards for learning.
|
- **Flash Card** (`flash-card`): Quickly generates beautiful flashcards for learning.
|
||||||
@@ -51,18 +52,18 @@ Located in the `plugins/` directory, containing Python-based enhancements:
|
|||||||
- **Export to Word** (`export_to_docx`): Exports chat history to Word documents.
|
- **Export to Word** (`export_to_docx`): Exports chat history to Word documents.
|
||||||
|
|
||||||
#### Filters
|
#### Filters
|
||||||
|
|
||||||
- **Async Context Compression** (`async-context-compression`): Optimizes token usage via context compression.
|
- **Async Context Compression** (`async-context-compression`): Optimizes token usage via context compression.
|
||||||
- **Context Enhancement** (`context_enhancement_filter`): Enhances chat context.
|
- **Context Enhancement** (`context_enhancement_filter`): Enhances chat context.
|
||||||
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Companion filter for Gemini Manifold.
|
- **Folder Memory** (`folder-memory`): Automatically extracts project rules from conversations and injects them into the folder's system prompt.
|
||||||
- **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.
|
- **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
|
#### Pipes
|
||||||
- **Gemini Manifold** (`gemini_mainfold`): Pipeline for Gemini model integration.
|
|
||||||
|
- **GitHub Copilot SDK** (`github-copilot-sdk`): Official GitHub Copilot SDK integration. Supports dynamic models, multi-turn conversation, streaming, multimodal input, and infinite sessions.
|
||||||
|
|
||||||
#### Pipelines
|
#### Pipelines
|
||||||
|
|
||||||
- **MoE Prompt Refiner** (`moe_prompt_refiner`): Refines prompts for Mixture of Experts (MoE) summary requests to generate high-quality comprehensive reports.
|
- **MoE Prompt Refiner** (`moe_prompt_refiner`): Refines prompts for Mixture of Experts (MoE) summary requests to generate high-quality comprehensive reports.
|
||||||
|
|
||||||
### 🎯 Prompts
|
### 🎯 Prompts
|
||||||
@@ -107,6 +108,7 @@ This project is a collection of resources and does not require a Python environm
|
|||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
If you have great prompts or plugins to share:
|
If you have great prompts or plugins to share:
|
||||||
|
|
||||||
1. Fork this repository.
|
1. Fork this repository.
|
||||||
2. Add your files to the appropriate `prompts/` or `plugins/` directory.
|
2. Add your files to the appropriate `prompts/` or `plugins/` directory.
|
||||||
3. Submit a Pull Request.
|
3. Submit a Pull Request.
|
||||||
@@ -126,6 +128,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<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://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://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>
|
<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>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://perso.crans.org/grande/"><img src="https://avatars.githubusercontent.com/u/469017?v=4?s=100" width="100px;" alt="Johan Grande"/><br /><sub><b>Johan Grande</b></sub></a><br /><a href="#ideas-nahoj" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
27
README_CN.md
27
README_CN.md
@@ -7,28 +7,28 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
|||||||
<!-- STATS_START -->
|
<!-- STATS_START -->
|
||||||
## 📊 社区统计
|
## 📊 社区统计
|
||||||
|
|
||||||
> 🕐 自动更新于 2026-01-13 22:10
|
> 🕐 自动更新于 2026-01-26 15:14
|
||||||
|
|
||||||
| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |
|
| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |
|
||||||
|:---:|:---:|:---:|:---:|
|
|:---:|:---:|:---:|:---:|
|
||||||
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **96** | **100** | **23** |
|
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **158** | **152** | **31** |
|
||||||
|
|
||||||
| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |
|
| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |
|
||||||
|:---:|:---:|:---:|:---:|:---:|
|
|:---:|:---:|:---:|:---:|:---:|
|
||||||
| **15** | **1298** | **14813** | **88** | **92** |
|
| **19** | **2388** | **27294** | **138** | **183** |
|
||||||
|
|
||||||
### 🔥 热门插件 Top 6
|
### 🔥 热门插件 Top 6
|
||||||
|
|
||||||
> 🕐 自动更新于 2026-01-13 22:10
|
> 🕐 自动更新于 2026-01-26 15:14
|
||||||
|
|
||||||
| 排名 | 插件 | 版本 | 下载 | 浏览 | 更新日期 |
|
| 排名 | 插件 | 版本 | 下载 | 浏览 | 更新日期 |
|
||||||
|:---:|------|:---:|:---:|:---:|:---:|
|
|:---:|------|:---:|:---:|:---:|:---:|
|
||||||
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 412 | 3715 | 2026-01-07 |
|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.1 | 629 | 5600 | 2026-01-17 |
|
||||||
| 🥈 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 190 | 625 | 2026-01-07 |
|
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 410 | 3621 | 2026-01-25 |
|
||||||
| 🥉 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.4.9 | 153 | 1685 | 2026-01-11 |
|
| 🥉 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 255 | 1039 | 2026-01-07 |
|
||||||
| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.1.3 | 148 | 1643 | 2026-01-11 |
|
| 4️⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 229 | 1839 | 2026-01-17 |
|
||||||
| 5️⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 109 | 992 | 2026-01-07 |
|
| 5️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 227 | 2461 | 2026-01-21 |
|
||||||
| 6️⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 106 | 1956 | 2026-01-07 |
|
| 6️⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 0.2.4 | 165 | 2674 | 2026-01-17 |
|
||||||
|
|
||||||
*完整统计请查看 [社区统计报告](./docs/community-stats.zh.md)*
|
*完整统计请查看 [社区统计报告](./docs/community-stats.zh.md)*
|
||||||
<!-- STATS_END -->
|
<!-- STATS_END -->
|
||||||
@@ -40,6 +40,7 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
|||||||
位于 `plugins/` 目录,包含各类 Python 编写的功能增强插件:
|
位于 `plugins/` 目录,包含各类 Python 编写的功能增强插件:
|
||||||
|
|
||||||
#### Actions (交互增强)
|
#### Actions (交互增强)
|
||||||
|
|
||||||
- **Smart Mind Map** (`smart-mind-map`): 智能分析文本并生成交互式思维导图。
|
- **Smart Mind Map** (`smart-mind-map`): 智能分析文本并生成交互式思维导图。
|
||||||
- **Smart Infographic** (`infographic`): 基于 AntV 的智能信息图生成工具。
|
- **Smart Infographic** (`infographic`): 基于 AntV 的智能信息图生成工具。
|
||||||
- **Flash Card** (`flash-card`): 快速生成精美的学习记忆卡片。
|
- **Flash Card** (`flash-card`): 快速生成精美的学习记忆卡片。
|
||||||
@@ -48,17 +49,22 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
|||||||
- **Export to Word** (`export_to_docx`): 将对话内容导出为 Word 文档。
|
- **Export to Word** (`export_to_docx`): 将对话内容导出为 Word 文档。
|
||||||
|
|
||||||
#### Filters (消息处理)
|
#### Filters (消息处理)
|
||||||
|
|
||||||
- **Async Context Compression** (`async-context-compression`): 异步上下文压缩,优化 Token 使用。
|
- **Async Context Compression** (`async-context-compression`): 异步上下文压缩,优化 Token 使用。
|
||||||
- **Context Enhancement** (`context_enhancement_filter`): 上下文增强过滤器。
|
- **Context Enhancement** (`context_enhancement_filter`): 上下文增强过滤器。
|
||||||
|
- **Folder Memory** (`folder-memory`): 自动从对话中提取项目规则并注入到文件夹系统提示词中。
|
||||||
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Gemini Manifold 配套增强。
|
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Gemini Manifold 配套增强。
|
||||||
- **Gemini Multimodal Filter** (`web_gemini_multimodel_filter`): 为任意模型提供多模态能力(PDF、Office、视频等),支持智能路由和字幕精修。
|
- **Gemini Multimodal Filter** (`web_gemini_multimodel_filter`): 为任意模型提供多模态能力(PDF、Office、视频等),支持智能路由和字幕精修。
|
||||||
- **Markdown Normalizer** (`markdown_normalizer`): 修复 LLM 输出中常见的 Markdown 格式问题。
|
- **Markdown Normalizer** (`markdown_normalizer`): 修复 LLM 输出中常见的 Markdown 格式问题。
|
||||||
- **Multi-Model Context Merger** (`multi_model_context_merger`): 自动合并并注入多模型回答的上下文。
|
- **Multi-Model Context Merger** (`multi_model_context_merger`): 自动合并并注入多模型回答的上下文。
|
||||||
|
|
||||||
#### Pipes (模型管道)
|
#### Pipes (模型管道)
|
||||||
|
|
||||||
|
- **GitHub Copilot SDK** (`github-copilot-sdk`): GitHub Copilot SDK 官方集成。支持动态模型、多轮对话、流式输出、图片输入及无限会话。
|
||||||
- **Gemini Manifold** (`gemini_mainfold`): 集成 Gemini 模型的管道。
|
- **Gemini Manifold** (`gemini_mainfold`): 集成 Gemini 模型的管道。
|
||||||
|
|
||||||
#### Pipelines (工作流管道)
|
#### Pipelines (工作流管道)
|
||||||
|
|
||||||
- **MoE Prompt Refiner** (`moe_prompt_refiner`): 优化多模型 (MoE) 汇总请求的提示词,生成高质量的综合报告。
|
- **MoE Prompt Refiner** (`moe_prompt_refiner`): 优化多模型 (MoE) 汇总请求的提示词,生成高质量的综合报告。
|
||||||
|
|
||||||
### 🎯 提示词 (Prompts)
|
### 🎯 提示词 (Prompts)
|
||||||
@@ -106,6 +112,7 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
|||||||
### 贡献代码
|
### 贡献代码
|
||||||
|
|
||||||
如果你有优质的提示词或插件想要分享:
|
如果你有优质的提示词或插件想要分享:
|
||||||
|
|
||||||
1. Fork 本仓库。
|
1. Fork 本仓库。
|
||||||
2. 将你的文件添加到对应的 `prompts/` 或 `plugins/` 目录。
|
2. 将你的文件添加到对应的 `prompts/` 或 `plugins/` 目录。
|
||||||
3. 提交 Pull Request。
|
3. 提交 Pull Request。
|
||||||
|
|||||||
44
docs/PLUGIN_README_TEMPLATE.md
Normal file
44
docs/PLUGIN_README_TEMPLATE.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<!--
|
||||||
|
NOTE: This template is for the English version (README.md).
|
||||||
|
The Chinese version (README_CN.md) MUST be translated based on this English version to ensure consistency in structure and content.
|
||||||
|
-->
|
||||||
|
# [Plugin Name] [Optional Emoji]
|
||||||
|
|
||||||
|
[Brief description of what the plugin does. Keep it concise and engaging.]
|
||||||
|
|
||||||
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.0.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
|
|
||||||
|
## What's New
|
||||||
|
|
||||||
|
<!-- Keep the changelog for the last 3 versions here. Remove this section for the initial release. -->
|
||||||
|
|
||||||
|
### v1.0.0
|
||||||
|
- **Initial Release**: Released the first version of the plugin.
|
||||||
|
- **[Feature Name]**: [Brief description of the feature].
|
||||||
|
|
||||||
|
## Key Features 🔑
|
||||||
|
|
||||||
|
- **[Feature 1]**: [Description of feature 1].
|
||||||
|
- **[Feature 2]**: [Description of feature 2].
|
||||||
|
- **[Feature 3]**: [Description of feature 3].
|
||||||
|
|
||||||
|
## How to Use 🛠️
|
||||||
|
|
||||||
|
1. **Install**: Add the plugin to your OpenWebUI instance.
|
||||||
|
2. **Configure**: Adjust settings in the Valves menu (optional).
|
||||||
|
3. **[Action Step]**: Describe how to trigger or use the plugin.
|
||||||
|
4. **[Result Step]**: Describe the expected outcome.
|
||||||
|
|
||||||
|
## Configuration (Valves) ⚙️
|
||||||
|
|
||||||
|
| Valve | Default | Description |
|
||||||
|
|-------|---------|-------------|
|
||||||
|
| `VALVE_NAME` | `Default Value` | Description of what this setting does. |
|
||||||
|
| `ANOTHER_VALVE` | `True` | Another setting description. |
|
||||||
|
|
||||||
|
## Troubleshooting ❓
|
||||||
|
|
||||||
|
- **Plugin not working?**: Check if the filter/action is enabled in the model settings.
|
||||||
|
- **Debug Logs**: Enable `SHOW_DEBUG_LOG` in Valves and check the browser console (F12) for detailed logs.
|
||||||
|
- **Error Messages**: If you see an error, please copy the full error message and report it.
|
||||||
|
- **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
7
docs/badges/downloads.json
Normal file
7
docs/badges/downloads.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"label": "downloads",
|
||||||
|
"message": "2.4k",
|
||||||
|
"color": "blue",
|
||||||
|
"namedLogo": "openwebui"
|
||||||
|
}
|
||||||
6
docs/badges/followers.json
Normal file
6
docs/badges/followers.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"label": "followers",
|
||||||
|
"message": "158",
|
||||||
|
"color": "blue"
|
||||||
|
}
|
||||||
6
docs/badges/plugins.json
Normal file
6
docs/badges/plugins.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"label": "plugins",
|
||||||
|
"message": "19",
|
||||||
|
"color": "green"
|
||||||
|
}
|
||||||
6
docs/badges/points.json
Normal file
6
docs/badges/points.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"label": "points",
|
||||||
|
"message": "152",
|
||||||
|
"color": "orange"
|
||||||
|
}
|
||||||
6
docs/badges/upvotes.json
Normal file
6
docs/badges/upvotes.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"label": "upvotes",
|
||||||
|
"message": "138",
|
||||||
|
"color": "brightgreen"
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"total_posts": 15,
|
"total_posts": 19,
|
||||||
"total_downloads": 1298,
|
"total_downloads": 2388,
|
||||||
"total_views": 14813,
|
"total_views": 27294,
|
||||||
"total_upvotes": 88,
|
"total_upvotes": 138,
|
||||||
"total_downvotes": 2,
|
"total_downvotes": 2,
|
||||||
"total_saves": 92,
|
"total_saves": 183,
|
||||||
"total_comments": 20,
|
"total_comments": 33,
|
||||||
"by_type": {
|
"by_type": {
|
||||||
"filter": 1,
|
"pipe": 1,
|
||||||
"action": 13,
|
"action": 14,
|
||||||
"unknown": 1
|
"unknown": 3,
|
||||||
|
"filter": 1
|
||||||
},
|
},
|
||||||
"posts": [
|
"posts": [
|
||||||
{
|
{
|
||||||
@@ -19,15 +20,31 @@
|
|||||||
"version": "0.9.1",
|
"version": "0.9.1",
|
||||||
"author": "Fu-Jie",
|
"author": "Fu-Jie",
|
||||||
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
|
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
|
||||||
"downloads": 412,
|
"downloads": 629,
|
||||||
"views": 3715,
|
"views": 5600,
|
||||||
"upvotes": 11,
|
"upvotes": 16,
|
||||||
"saves": 24,
|
"saves": 37,
|
||||||
"comments": 11,
|
"comments": 11,
|
||||||
"created_at": "2025-12-30",
|
"created_at": "2025-12-30",
|
||||||
"updated_at": "2026-01-07",
|
"updated_at": "2026-01-17",
|
||||||
"url": "https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a"
|
"url": "https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Smart Infographic",
|
||||||
|
"slug": "smart_infographic_ad6f0c7f",
|
||||||
|
"type": "action",
|
||||||
|
"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": 410,
|
||||||
|
"views": 3621,
|
||||||
|
"upvotes": 18,
|
||||||
|
"saves": 27,
|
||||||
|
"comments": 7,
|
||||||
|
"created_at": "2025-12-28",
|
||||||
|
"updated_at": "2026-01-25",
|
||||||
|
"url": "https://openwebui.com/posts/smart_infographic_ad6f0c7f"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Export to Excel",
|
"title": "Export to Excel",
|
||||||
"slug": "export_mulit_table_to_excel_244b8f9d",
|
"slug": "export_mulit_table_to_excel_244b8f9d",
|
||||||
@@ -35,47 +52,15 @@
|
|||||||
"version": "0.3.7",
|
"version": "0.3.7",
|
||||||
"author": "Fu-Jie",
|
"author": "Fu-Jie",
|
||||||
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
|
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
|
||||||
"downloads": 190,
|
"downloads": 255,
|
||||||
"views": 625,
|
"views": 1039,
|
||||||
"upvotes": 3,
|
"upvotes": 4,
|
||||||
"saves": 4,
|
"saves": 6,
|
||||||
"comments": 0,
|
"comments": 0,
|
||||||
"created_at": "2025-05-30",
|
"created_at": "2025-05-30",
|
||||||
"updated_at": "2026-01-07",
|
"updated_at": "2026-01-07",
|
||||||
"url": "https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d"
|
"url": "https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"title": "📊 Smart Infographic (AntV)",
|
|
||||||
"slug": "smart_infographic_ad6f0c7f",
|
|
||||||
"type": "action",
|
|
||||||
"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": 153,
|
|
||||||
"views": 1685,
|
|
||||||
"upvotes": 8,
|
|
||||||
"saves": 11,
|
|
||||||
"comments": 2,
|
|
||||||
"created_at": "2025-12-28",
|
|
||||||
"updated_at": "2026-01-11",
|
|
||||||
"url": "https://openwebui.com/posts/smart_infographic_ad6f0c7f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Async Context Compression",
|
|
||||||
"slug": "async_context_compression_b1655bc8",
|
|
||||||
"type": "action",
|
|
||||||
"version": "1.1.3",
|
|
||||||
"author": "Fu-Jie",
|
|
||||||
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
|
|
||||||
"downloads": 148,
|
|
||||||
"views": 1643,
|
|
||||||
"upvotes": 7,
|
|
||||||
"saves": 12,
|
|
||||||
"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)",
|
"title": "Export to Word (Enhanced)",
|
||||||
"slug": "export_to_word_enhanced_formatting_fca6a315",
|
"slug": "export_to_word_enhanced_formatting_fca6a315",
|
||||||
@@ -83,15 +68,31 @@
|
|||||||
"version": "0.4.3",
|
"version": "0.4.3",
|
||||||
"author": "Fu-Jie",
|
"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.",
|
"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": 109,
|
"downloads": 229,
|
||||||
"views": 992,
|
"views": 1839,
|
||||||
"upvotes": 6,
|
"upvotes": 8,
|
||||||
"saves": 10,
|
"saves": 21,
|
||||||
"comments": 0,
|
"comments": 0,
|
||||||
"created_at": "2026-01-03",
|
"created_at": "2026-01-03",
|
||||||
"updated_at": "2026-01-07",
|
"updated_at": "2026-01-17",
|
||||||
"url": "https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315"
|
"url": "https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Async Context Compression",
|
||||||
|
"slug": "async_context_compression_b1655bc8",
|
||||||
|
"type": "action",
|
||||||
|
"version": "1.2.2",
|
||||||
|
"author": "Fu-Jie",
|
||||||
|
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
|
||||||
|
"downloads": 227,
|
||||||
|
"views": 2461,
|
||||||
|
"upvotes": 9,
|
||||||
|
"saves": 27,
|
||||||
|
"comments": 0,
|
||||||
|
"created_at": "2025-11-08",
|
||||||
|
"updated_at": "2026-01-21",
|
||||||
|
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Flash Card",
|
"title": "Flash Card",
|
||||||
"slug": "flash_card_65a2ea8f",
|
"slug": "flash_card_65a2ea8f",
|
||||||
@@ -99,15 +100,47 @@
|
|||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
"author": "Fu-Jie",
|
"author": "Fu-Jie",
|
||||||
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
|
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
|
||||||
"downloads": 106,
|
"downloads": 165,
|
||||||
"views": 1956,
|
"views": 2674,
|
||||||
"upvotes": 8,
|
"upvotes": 11,
|
||||||
"saves": 8,
|
"saves": 13,
|
||||||
"comments": 2,
|
"comments": 2,
|
||||||
"created_at": "2025-12-30",
|
"created_at": "2025-12-30",
|
||||||
"updated_at": "2026-01-07",
|
"updated_at": "2026-01-17",
|
||||||
"url": "https://openwebui.com/posts/flash_card_65a2ea8f"
|
"url": "https://openwebui.com/posts/flash_card_65a2ea8f"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Markdown Normalizer",
|
||||||
|
"slug": "markdown_normalizer_baaa8732",
|
||||||
|
"type": "action",
|
||||||
|
"version": "1.2.4",
|
||||||
|
"author": "Fu-Jie",
|
||||||
|
"description": "A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting.",
|
||||||
|
"downloads": 148,
|
||||||
|
"views": 2762,
|
||||||
|
"upvotes": 10,
|
||||||
|
"saves": 20,
|
||||||
|
"comments": 5,
|
||||||
|
"created_at": "2026-01-12",
|
||||||
|
"updated_at": "2026-01-19",
|
||||||
|
"url": "https://openwebui.com/posts/markdown_normalizer_baaa8732"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Deep Dive",
|
||||||
|
"slug": "deep_dive_c0b846e4",
|
||||||
|
"type": "action",
|
||||||
|
"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": 91,
|
||||||
|
"views": 839,
|
||||||
|
"upvotes": 4,
|
||||||
|
"saves": 8,
|
||||||
|
"comments": 0,
|
||||||
|
"created_at": "2026-01-08",
|
||||||
|
"updated_at": "2026-01-08",
|
||||||
|
"url": "https://openwebui.com/posts/deep_dive_c0b846e4"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "导出为 Word (增强版)",
|
"title": "导出为 Word (增强版)",
|
||||||
"slug": "导出为_word_支持公式流程图表格和代码块_8a6306c0",
|
"slug": "导出为_word_支持公式流程图表格和代码块_8a6306c0",
|
||||||
@@ -115,13 +148,13 @@
|
|||||||
"version": "0.4.3",
|
"version": "0.4.3",
|
||||||
"author": "Fu-Jie",
|
"author": "Fu-Jie",
|
||||||
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
|
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
|
||||||
"downloads": 45,
|
"downloads": 87,
|
||||||
"views": 1094,
|
"views": 1614,
|
||||||
"upvotes": 9,
|
"upvotes": 11,
|
||||||
"saves": 3,
|
"saves": 4,
|
||||||
"comments": 1,
|
"comments": 4,
|
||||||
"created_at": "2026-01-04",
|
"created_at": "2026-01-04",
|
||||||
"updated_at": "2026-01-07",
|
"updated_at": "2026-01-17",
|
||||||
"url": "https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0"
|
"url": "https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -131,31 +164,15 @@
|
|||||||
"version": "1.4.9",
|
"version": "1.4.9",
|
||||||
"author": "Fu-Jie",
|
"author": "Fu-Jie",
|
||||||
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
|
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
|
||||||
"downloads": 37,
|
"downloads": 46,
|
||||||
"views": 546,
|
"views": 781,
|
||||||
"upvotes": 4,
|
"upvotes": 6,
|
||||||
"saves": 0,
|
"saves": 0,
|
||||||
"comments": 0,
|
"comments": 0,
|
||||||
"created_at": "2025-12-28",
|
"created_at": "2025-12-28",
|
||||||
"updated_at": "2026-01-11",
|
"updated_at": "2026-01-17",
|
||||||
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
|
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"title": "Deep Dive",
|
|
||||||
"slug": "deep_dive_c0b846e4",
|
|
||||||
"type": "action",
|
|
||||||
"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": 37,
|
|
||||||
"views": 408,
|
|
||||||
"upvotes": 3,
|
|
||||||
"saves": 4,
|
|
||||||
"comments": 0,
|
|
||||||
"created_at": "2026-01-08",
|
|
||||||
"updated_at": "2026-01-08",
|
|
||||||
"url": "https://openwebui.com/posts/deep_dive_c0b846e4"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"title": "思维导图",
|
"title": "思维导图",
|
||||||
"slug": "智能生成交互式思维导图帮助用户可视化知识_8d4b097b",
|
"slug": "智能生成交互式思维导图帮助用户可视化知识_8d4b097b",
|
||||||
@@ -163,29 +180,45 @@
|
|||||||
"version": "0.9.1",
|
"version": "0.9.1",
|
||||||
"author": "Fu-Jie",
|
"author": "Fu-Jie",
|
||||||
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
|
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
|
||||||
"downloads": 20,
|
"downloads": 27,
|
||||||
"views": 347,
|
"views": 447,
|
||||||
"upvotes": 2,
|
"upvotes": 4,
|
||||||
"saves": 1,
|
"saves": 1,
|
||||||
"comments": 0,
|
"comments": 0,
|
||||||
"created_at": "2025-12-31",
|
"created_at": "2025-12-31",
|
||||||
"updated_at": "2026-01-07",
|
"updated_at": "2026-01-17",
|
||||||
"url": "https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b"
|
"url": "https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "📂 Folder Memory – Auto-Evolving Project Context",
|
||||||
|
"slug": "folder_memory_auto_evolving_project_context_4a9875b2",
|
||||||
|
"type": "filter",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"author": "Fu-Jie",
|
||||||
|
"description": "Automatically extracts project rules from conversations and injects them into the folder's system prompt.",
|
||||||
|
"downloads": 26,
|
||||||
|
"views": 725,
|
||||||
|
"upvotes": 3,
|
||||||
|
"saves": 4,
|
||||||
|
"comments": 0,
|
||||||
|
"created_at": "2026-01-20",
|
||||||
|
"updated_at": "2026-01-20",
|
||||||
|
"url": "https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "异步上下文压缩",
|
"title": "异步上下文压缩",
|
||||||
"slug": "异步上下文压缩_5c0617cb",
|
"slug": "异步上下文压缩_5c0617cb",
|
||||||
"type": "action",
|
"type": "action",
|
||||||
"version": "1.1.3",
|
"version": "1.2.2",
|
||||||
"author": "Fu-Jie",
|
"author": "Fu-Jie",
|
||||||
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
|
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
|
||||||
"downloads": 13,
|
"downloads": 20,
|
||||||
"views": 274,
|
"views": 486,
|
||||||
"upvotes": 4,
|
"upvotes": 5,
|
||||||
"saves": 1,
|
"saves": 1,
|
||||||
"comments": 0,
|
"comments": 0,
|
||||||
"created_at": "2025-11-08",
|
"created_at": "2025-11-08",
|
||||||
"updated_at": "2026-01-11",
|
"updated_at": "2026-01-21",
|
||||||
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
|
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -195,31 +228,15 @@
|
|||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
"author": "Fu-Jie",
|
"author": "Fu-Jie",
|
||||||
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
|
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
|
||||||
"downloads": 12,
|
"downloads": 19,
|
||||||
"views": 383,
|
"views": 507,
|
||||||
"upvotes": 4,
|
"upvotes": 6,
|
||||||
"saves": 1,
|
"saves": 1,
|
||||||
"comments": 0,
|
"comments": 0,
|
||||||
"created_at": "2025-12-30",
|
"created_at": "2025-12-30",
|
||||||
"updated_at": "2026-01-07",
|
"updated_at": "2026-01-17",
|
||||||
"url": "https://openwebui.com/posts/闪记卡生成插件_4a31eac3"
|
"url": "https://openwebui.com/posts/闪记卡生成插件_4a31eac3"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"title": "Markdown Normalizer",
|
|
||||||
"slug": "markdown_normalizer_baaa8732",
|
|
||||||
"type": "filter",
|
|
||||||
"version": "1.1.0",
|
|
||||||
"author": "Fu-Jie",
|
|
||||||
"description": "Fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting.",
|
|
||||||
"downloads": 11,
|
|
||||||
"views": 406,
|
|
||||||
"upvotes": 6,
|
|
||||||
"saves": 4,
|
|
||||||
"comments": 2,
|
|
||||||
"created_at": "2026-01-12",
|
|
||||||
"updated_at": "2026-01-12",
|
|
||||||
"url": "https://openwebui.com/posts/markdown_normalizer_baaa8732"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"title": "精读",
|
"title": "精读",
|
||||||
"slug": "精读_99830b0f",
|
"slug": "精读_99830b0f",
|
||||||
@@ -227,15 +244,63 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "Fu-Jie",
|
"author": "Fu-Jie",
|
||||||
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
|
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
|
||||||
"downloads": 5,
|
"downloads": 9,
|
||||||
"views": 150,
|
"views": 306,
|
||||||
"upvotes": 2,
|
"upvotes": 3,
|
||||||
"saves": 2,
|
"saves": 1,
|
||||||
"comments": 0,
|
"comments": 0,
|
||||||
"created_at": "2026-01-08",
|
"created_at": "2026-01-08",
|
||||||
"updated_at": "2026-01-08",
|
"updated_at": "2026-01-08",
|
||||||
"url": "https://openwebui.com/posts/精读_99830b0f"
|
"url": "https://openwebui.com/posts/精读_99830b0f"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "GitHub Copilot Official SDK Pipe",
|
||||||
|
"slug": "github_copilot_official_sdk_pipe_ce96f7b4",
|
||||||
|
"type": "pipe",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"author": "Fu-Jie",
|
||||||
|
"description": "Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, and infinite sessions (context compaction).",
|
||||||
|
"downloads": 0,
|
||||||
|
"views": 8,
|
||||||
|
"upvotes": 1,
|
||||||
|
"saves": 0,
|
||||||
|
"comments": 0,
|
||||||
|
"created_at": "2026-01-26",
|
||||||
|
"updated_at": "2026-01-26",
|
||||||
|
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager",
|
||||||
|
"slug": "open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e",
|
||||||
|
"type": "unknown",
|
||||||
|
"version": "",
|
||||||
|
"author": "",
|
||||||
|
"description": "",
|
||||||
|
"downloads": 0,
|
||||||
|
"views": 222,
|
||||||
|
"upvotes": 6,
|
||||||
|
"saves": 4,
|
||||||
|
"comments": 2,
|
||||||
|
"created_at": "2026-01-25",
|
||||||
|
"updated_at": "2026-01-25",
|
||||||
|
"url": "https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Review of Claude Haiku 4.5",
|
||||||
|
"slug": "review_of_claude_haiku_45_41b0db39",
|
||||||
|
"type": "unknown",
|
||||||
|
"version": "",
|
||||||
|
"author": "",
|
||||||
|
"description": "",
|
||||||
|
"downloads": 0,
|
||||||
|
"views": 93,
|
||||||
|
"upvotes": 1,
|
||||||
|
"saves": 0,
|
||||||
|
"comments": 0,
|
||||||
|
"created_at": "2026-01-14",
|
||||||
|
"updated_at": "2026-01-14",
|
||||||
|
"url": "https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": " 🛠️ Debug Open WebUI Plugins in Your Browser",
|
"title": " 🛠️ Debug Open WebUI Plugins in Your Browser",
|
||||||
"slug": "debug_open_webui_plugins_in_your_browser_81bf7960",
|
"slug": "debug_open_webui_plugins_in_your_browser_81bf7960",
|
||||||
@@ -244,9 +309,9 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"downloads": 0,
|
"downloads": 0,
|
||||||
"views": 589,
|
"views": 1270,
|
||||||
"upvotes": 11,
|
"upvotes": 12,
|
||||||
"saves": 7,
|
"saves": 8,
|
||||||
"comments": 2,
|
"comments": 2,
|
||||||
"created_at": "2026-01-10",
|
"created_at": "2026-01-10",
|
||||||
"updated_at": "2026-01-10",
|
"updated_at": "2026-01-10",
|
||||||
@@ -258,11 +323,11 @@
|
|||||||
"name": "Fu-Jie",
|
"name": "Fu-Jie",
|
||||||
"profile_url": "https://openwebui.com/u/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",
|
"profile_image": "https://community.s3.openwebui.com/uploads/users/b15d1348-4347-42b4-b815-e053342d6cb0/profile_d9510745-4bd4-4f8f-a997-4a21847d9300.webp",
|
||||||
"followers": 96,
|
"followers": 158,
|
||||||
"following": 2,
|
"following": 3,
|
||||||
"total_points": 100,
|
"total_points": 152,
|
||||||
"post_points": 86,
|
"post_points": 136,
|
||||||
"comment_points": 14,
|
"comment_points": 16,
|
||||||
"contributions": 23
|
"contributions": 31
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,40 +1,45 @@
|
|||||||
# 📊 OpenWebUI Community Stats Report
|
# 📊 OpenWebUI Community Stats Report
|
||||||
|
|
||||||
> 📅 Updated: 2026-01-13 22:10
|
> 📅 Updated: 2026-01-26 15:14
|
||||||
|
|
||||||
## 📈 Overview
|
## 📈 Overview
|
||||||
|
|
||||||
| Metric | Value |
|
| Metric | Value |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 📝 Total Posts | 15 |
|
| 📝 Total Posts | 19 |
|
||||||
| ⬇️ Total Downloads | 1298 |
|
| ⬇️ Total Downloads | 2388 |
|
||||||
| 👁️ Total Views | 14813 |
|
| 👁️ Total Views | 27294 |
|
||||||
| 👍 Total Upvotes | 88 |
|
| 👍 Total Upvotes | 138 |
|
||||||
| 💾 Total Saves | 92 |
|
| 💾 Total Saves | 183 |
|
||||||
| 💬 Total Comments | 20 |
|
| 💬 Total Comments | 33 |
|
||||||
|
|
||||||
## 📂 By Type
|
## 📂 By Type
|
||||||
|
|
||||||
|
- **pipe**: 1
|
||||||
|
- **action**: 14
|
||||||
|
- **unknown**: 3
|
||||||
- **filter**: 1
|
- **filter**: 1
|
||||||
- **action**: 13
|
|
||||||
- **unknown**: 1
|
|
||||||
|
|
||||||
## 📋 Posts List
|
## 📋 Posts List
|
||||||
|
|
||||||
| Rank | Title | Type | Version | Downloads | Views | Upvotes | Saves | Updated |
|
| 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 | 412 | 3715 | 11 | 24 | 2026-01-07 |
|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 629 | 5600 | 16 | 37 | 2026-01-17 |
|
||||||
| 2 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 190 | 625 | 3 | 4 | 2026-01-07 |
|
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 410 | 3621 | 18 | 27 | 2026-01-25 |
|
||||||
| 3 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 153 | 1685 | 8 | 11 | 2026-01-11 |
|
| 3 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 255 | 1039 | 4 | 6 | 2026-01-07 |
|
||||||
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.1.3 | 148 | 1643 | 7 | 12 | 2026-01-11 |
|
| 4 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 229 | 1839 | 8 | 21 | 2026-01-17 |
|
||||||
| 5 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 109 | 992 | 6 | 10 | 2026-01-07 |
|
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 227 | 2461 | 9 | 27 | 2026-01-21 |
|
||||||
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 106 | 1956 | 8 | 8 | 2026-01-07 |
|
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 165 | 2674 | 11 | 13 | 2026-01-17 |
|
||||||
| 7 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 45 | 1094 | 9 | 3 | 2026-01-07 |
|
| 7 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 148 | 2762 | 10 | 20 | 2026-01-19 |
|
||||||
| 8 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 37 | 546 | 4 | 0 | 2026-01-11 |
|
| 8 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 91 | 839 | 4 | 8 | 2026-01-08 |
|
||||||
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 37 | 408 | 3 | 4 | 2026-01-08 |
|
| 9 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 87 | 1614 | 11 | 4 | 2026-01-17 |
|
||||||
| 10 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 20 | 347 | 2 | 1 | 2026-01-07 |
|
| 10 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 46 | 781 | 6 | 0 | 2026-01-17 |
|
||||||
| 11 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.1.3 | 13 | 274 | 4 | 1 | 2026-01-11 |
|
| 11 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 27 | 447 | 4 | 1 | 2026-01-17 |
|
||||||
| 12 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 12 | 383 | 4 | 1 | 2026-01-07 |
|
| 12 | [📂 Folder Memory – Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 26 | 725 | 3 | 4 | 2026-01-20 |
|
||||||
| 13 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter | 1.1.0 | 11 | 406 | 6 | 4 | 2026-01-12 |
|
| 13 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 20 | 486 | 5 | 1 | 2026-01-21 |
|
||||||
| 14 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 5 | 150 | 2 | 2 | 2026-01-08 |
|
| 14 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 19 | 507 | 6 | 1 | 2026-01-17 |
|
||||||
| 15 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 589 | 11 | 7 | 2026-01-10 |
|
| 15 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 9 | 306 | 3 | 1 | 2026-01-08 |
|
||||||
|
| 16 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | 0.1.1 | 0 | 8 | 1 | 0 | 2026-01-26 |
|
||||||
|
| 17 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 222 | 6 | 4 | 2026-01-25 |
|
||||||
|
| 18 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 93 | 1 | 0 | 2026-01-14 |
|
||||||
|
| 19 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1270 | 12 | 8 | 2026-01-10 |
|
||||||
|
|||||||
@@ -1,40 +1,45 @@
|
|||||||
# 📊 OpenWebUI 社区统计报告
|
# 📊 OpenWebUI 社区统计报告
|
||||||
|
|
||||||
> 📅 更新时间: 2026-01-13 22:10
|
> 📅 更新时间: 2026-01-26 15:14
|
||||||
|
|
||||||
## 📈 总览
|
## 📈 总览
|
||||||
|
|
||||||
| 指标 | 数值 |
|
| 指标 | 数值 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 📝 发布数量 | 15 |
|
| 📝 发布数量 | 19 |
|
||||||
| ⬇️ 总下载量 | 1298 |
|
| ⬇️ 总下载量 | 2388 |
|
||||||
| 👁️ 总浏览量 | 14813 |
|
| 👁️ 总浏览量 | 27294 |
|
||||||
| 👍 总点赞数 | 88 |
|
| 👍 总点赞数 | 138 |
|
||||||
| 💾 总收藏数 | 92 |
|
| 💾 总收藏数 | 183 |
|
||||||
| 💬 总评论数 | 20 |
|
| 💬 总评论数 | 33 |
|
||||||
|
|
||||||
## 📂 按类型分类
|
## 📂 按类型分类
|
||||||
|
|
||||||
|
- **pipe**: 1
|
||||||
|
- **action**: 14
|
||||||
|
- **unknown**: 3
|
||||||
- **filter**: 1
|
- **filter**: 1
|
||||||
- **action**: 13
|
|
||||||
- **unknown**: 1
|
|
||||||
|
|
||||||
## 📋 发布列表
|
## 📋 发布列表
|
||||||
|
|
||||||
| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |
|
| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |
|
||||||
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 412 | 3715 | 11 | 24 | 2026-01-07 |
|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 629 | 5600 | 16 | 37 | 2026-01-17 |
|
||||||
| 2 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 190 | 625 | 3 | 4 | 2026-01-07 |
|
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 410 | 3621 | 18 | 27 | 2026-01-25 |
|
||||||
| 3 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 153 | 1685 | 8 | 11 | 2026-01-11 |
|
| 3 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 255 | 1039 | 4 | 6 | 2026-01-07 |
|
||||||
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.1.3 | 148 | 1643 | 7 | 12 | 2026-01-11 |
|
| 4 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 229 | 1839 | 8 | 21 | 2026-01-17 |
|
||||||
| 5 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 109 | 992 | 6 | 10 | 2026-01-07 |
|
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 227 | 2461 | 9 | 27 | 2026-01-21 |
|
||||||
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 106 | 1956 | 8 | 8 | 2026-01-07 |
|
| 6 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 165 | 2674 | 11 | 13 | 2026-01-17 |
|
||||||
| 7 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 45 | 1094 | 9 | 3 | 2026-01-07 |
|
| 7 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 148 | 2762 | 10 | 20 | 2026-01-19 |
|
||||||
| 8 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 37 | 546 | 4 | 0 | 2026-01-11 |
|
| 8 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 91 | 839 | 4 | 8 | 2026-01-08 |
|
||||||
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 37 | 408 | 3 | 4 | 2026-01-08 |
|
| 9 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 87 | 1614 | 11 | 4 | 2026-01-17 |
|
||||||
| 10 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 20 | 347 | 2 | 1 | 2026-01-07 |
|
| 10 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 46 | 781 | 6 | 0 | 2026-01-17 |
|
||||||
| 11 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.1.3 | 13 | 274 | 4 | 1 | 2026-01-11 |
|
| 11 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 27 | 447 | 4 | 1 | 2026-01-17 |
|
||||||
| 12 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 12 | 383 | 4 | 1 | 2026-01-07 |
|
| 12 | [📂 Folder Memory – Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 26 | 725 | 3 | 4 | 2026-01-20 |
|
||||||
| 13 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter | 1.1.0 | 11 | 406 | 6 | 4 | 2026-01-12 |
|
| 13 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 20 | 486 | 5 | 1 | 2026-01-21 |
|
||||||
| 14 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 5 | 150 | 2 | 2 | 2026-01-08 |
|
| 14 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 19 | 507 | 6 | 1 | 2026-01-17 |
|
||||||
| 15 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 589 | 11 | 7 | 2026-01-10 |
|
| 15 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 9 | 306 | 3 | 1 | 2026-01-08 |
|
||||||
|
| 16 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | 0.1.1 | 0 | 8 | 1 | 0 | 2026-01-26 |
|
||||||
|
| 17 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 222 | 6 | 4 | 2026-01-25 |
|
||||||
|
| 18 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 93 | 1 | 0 | 2026-01-14 |
|
||||||
|
| 19 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1270 | 12 | 8 | 2026-01-10 |
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
## 📚 Table of Contents
|
## 📚 Table of Contents
|
||||||
|
|
||||||
1. [Quick Start](#1-quick-start)
|
1. [Quick Start](#1-quick-start)
|
||||||
2. [Core Concepts & SDK Details](#2-core-concepts--sdk-details)
|
2. [Core Concepts & SDK Details](#2-core-concepts-sdk-details)
|
||||||
3. [Deep Dive into Plugin Types](#3-deep-dive-into-plugin-types)
|
3. [Deep Dive into Plugin Types](#3-deep-dive-into-plugin-types)
|
||||||
4. [Advanced Development Patterns](#4-advanced-development-patterns)
|
4. [Advanced Development Patterns](#4-advanced-development-patterns)
|
||||||
5. [Best Practices & Design Principles](#5-best-practices--design-principles)
|
5. [Best Practices & Design Principles](#5-best-practices-design-principles)
|
||||||
6. [Troubleshooting](#6-troubleshooting)
|
6. [Troubleshooting](#6-troubleshooting)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -351,8 +351,7 @@ async def action(self, body, __event_call__, __metadata__, ...):
|
|||||||
|
|
||||||
#### Reference Implementations
|
#### Reference Implementations
|
||||||
|
|
||||||
- `plugins/actions/js-render-poc/infographic_markdown.py` - AntV Infographic + Data URL
|
- `plugins/actions/infographic/infographic.py` - Production-ready implementation using AntV + Data URL
|
||||||
- `plugins/actions/js-render-poc/js_render_poc.py` - Basic proof of concept
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,19 @@
|
|||||||
|
|
||||||
## 📚 目录
|
## 📚 目录
|
||||||
|
|
||||||
1. [插件开发快速入门](#1-插件开发快速入门)
|
1. [插件开发快速入门](#1-quick-start)
|
||||||
2. [核心概念与 SDK 详解](#2-核心概念与-sdk-详解)
|
2. [核心概念与 SDK 详解](#2-core-concepts-sdk-details)
|
||||||
3. [插件类型深度解析](#3-插件类型深度解析)
|
3. [插件类型深度解析](#3-plugin-types)
|
||||||
* [Action (动作)](#31-action-动作)
|
* [Action (动作)](#31-action)
|
||||||
* [Filter (过滤器)](#32-filter-过滤器)
|
* [Filter (过滤器)](#32-filter)
|
||||||
* [Pipe (管道)](#33-pipe-管道)
|
* [Pipe (管道)](#33-pipe)
|
||||||
4. [高级开发模式](#4-高级开发模式)
|
4. [高级开发模式](#4-advanced-patterns)
|
||||||
5. [最佳实践与设计原则](#5-最佳实践与设计原则)
|
5. [最佳实践与设计原则](#5-best-practices)
|
||||||
6. [故障排查](#6-故障排查)
|
6. [故障排查](#6-troubleshooting)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 插件开发快速入门
|
## 1. 插件开发快速入门 {: #1-quick-start }
|
||||||
|
|
||||||
### 1.1 什么是 OpenWebUI 插件?
|
### 1.1 什么是 OpenWebUI 插件?
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ class Action:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 核心概念与 SDK 详解
|
## 2. 核心概念与 SDK 详解 {: #2-core-concepts-sdk-details }
|
||||||
|
|
||||||
### 2.1 ⚠️ 重要:同步与异步
|
### 2.1 ⚠️ 重要:同步与异步
|
||||||
|
|
||||||
@@ -107,9 +107,9 @@ class Filter:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 插件类型深度解析
|
## 3. 插件类型深度解析 {: #3-plugin-types }
|
||||||
|
|
||||||
### 3.1 Action (动作)
|
### 3.1 Action (动作) {: #31-action }
|
||||||
|
|
||||||
**定位**:在消息下方添加按钮,用户点击触发。
|
**定位**:在消息下方添加按钮,用户点击触发。
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ async def action(self, body, __event_call__):
|
|||||||
await __event_call__({"type": "execute", "data": {"code": js}})
|
await __event_call__({"type": "execute", "data": {"code": js}})
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.2 Filter (过滤器)
|
### 3.2 Filter (过滤器) {: #32-filter }
|
||||||
|
|
||||||
**定位**:中间件,拦截并修改请求/响应。
|
**定位**:中间件,拦截并修改请求/响应。
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ async def inlet(self, body, __metadata__):
|
|||||||
return body
|
return body
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.3 Pipe (管道)
|
### 3.3 Pipe (管道) {: #33-pipe }
|
||||||
|
|
||||||
**定位**:自定义模型/代理。
|
**定位**:自定义模型/代理。
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ class Pipe:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 高级开发模式
|
## 4. 高级开发模式 {: #4-advanced-patterns }
|
||||||
|
|
||||||
### 4.1 Pipe 与 Filter 协同
|
### 4.1 Pipe 与 Filter 协同
|
||||||
利用 `__request__.app.state` 在不同插件间共享数据。
|
利用 `__request__.app.state` 在不同插件间共享数据。
|
||||||
@@ -315,10 +315,9 @@ async def action(self, body, __event_call__, __metadata__, ...):
|
|||||||
|
|
||||||
#### 参考实现
|
#### 参考实现
|
||||||
|
|
||||||
- `plugins/actions/js-render-poc/infographic_markdown.py` - AntV 信息图 + Data URL
|
- `plugins/actions/infographic/infographic.py` - 基于 AntV + Data URL 的生产级实现
|
||||||
- `plugins/actions/js-render-poc/js_render_poc.py` - 基础概念验证
|
|
||||||
|
|
||||||
## 5. 最佳实践与设计原则
|
## 5. 最佳实践与设计原则 {: #5-best-practices }
|
||||||
|
|
||||||
### 5.1 命名与定位
|
### 5.1 命名与定位
|
||||||
* **简短有力**:如 "闪记卡", "精读"。避免 "文本分析助手" 这种泛词。
|
* **简短有力**:如 "闪记卡", "精读"。避免 "文本分析助手" 这种泛词。
|
||||||
@@ -344,7 +343,7 @@ except Exception as e:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. 故障排查
|
## 6. 故障排查 {: #6-troubleshooting }
|
||||||
|
|
||||||
* **HTML 不显示?** 确保包裹在 ` ```html ... ``` ` 代码块中。
|
* **HTML 不显示?** 确保包裹在 ` ```html ... ``` ` 代码块中。
|
||||||
* **数据库报错?** 检查是否在 `async` 函数中直接调用了同步的 DB 方法,请使用 `asyncio.to_thread`。
|
* **数据库报错?** 检查是否在 `async` 函数中直接调用了同步的 DB 方法,请使用 `asyncio.to_thread`。
|
||||||
|
|||||||
@@ -73,13 +73,13 @@ hide:
|
|||||||
|
|
||||||
[:octicons-arrow-right-24: Learn More](plugins/actions/smart-mind-map.md)
|
[:octicons-arrow-right-24: Learn More](plugins/actions/smart-mind-map.md)
|
||||||
|
|
||||||
- :material-card-text:{ .lg .middle } **Knowledge Card**
|
- :material-card-text:{ .lg .middle } **Flash Card**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Quickly generates beautiful learning memory cards, perfect for studying and quick memorization.
|
Quickly generates beautiful flashcards from text, extracting key points and categories.
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Learn More](plugins/actions/knowledge-card.md)
|
[:octicons-arrow-right-24: Learn More](plugins/actions/flash-card.md)
|
||||||
|
|
||||||
- :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
|
- :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
|
||||||
|
|
||||||
|
|||||||
@@ -73,13 +73,13 @@ hide:
|
|||||||
|
|
||||||
[:octicons-arrow-right-24: 了解更多](plugins/actions/smart-mind-map.md)
|
[:octicons-arrow-right-24: 了解更多](plugins/actions/smart-mind-map.md)
|
||||||
|
|
||||||
- :material-card-text:{ .lg .middle } **知识卡片**
|
- :material-card-text:{ .lg .middle } **Flash Card(闪记卡)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
快速生成精美的学习记忆卡片,非常适合学习和快速记忆。
|
快速生成精美的学习记忆卡片,非常适合学习和快速记忆。
|
||||||
|
|
||||||
[:octicons-arrow-right-24: 了解更多](plugins/actions/knowledge-card.md)
|
[:octicons-arrow-right-24: 了解更多](plugins/actions/flash-card.md)
|
||||||
|
|
||||||
- :material-arrow-collapse-vertical:{ .lg .middle } **异步上下文压缩**
|
- :material-arrow-collapse-vertical:{ .lg .middle } **异步上下文压缩**
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Knowledge Card
|
# Flash Card
|
||||||
|
|
||||||
<span class="category-badge action">Action</span>
|
<span class="category-badge action">Action</span>
|
||||||
<span class="version-badge">v0.2.2</span>
|
<span class="version-badge">v0.2.4</span>
|
||||||
|
|
||||||
Quickly generates beautiful learning memory cards, perfect for studying and quick memorization.
|
Quickly generates beautiful flashcards from text, extracting key points and categories.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ The Knowledge Card plugin (also known as Flash Card / 闪记卡) transforms cont
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Download the plugin file: [`knowledge_card.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/knowledge-card)
|
1. Download the plugin file: [`flash_card.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/flash-card)
|
||||||
2. Upload to OpenWebUI: **Admin Panel** → **Settings** → **Functions**
|
2. Upload to OpenWebUI: **Admin Panel** → **Settings** → **Functions**
|
||||||
3. Enable the plugin
|
3. Enable the plugin
|
||||||
|
|
||||||
@@ -85,4 +85,4 @@ The Knowledge Card plugin (also known as Flash Card / 闪记卡) transforms cont
|
|||||||
|
|
||||||
## Source Code
|
## Source Code
|
||||||
|
|
||||||
[:fontawesome-brands-github: View on GitHub](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/knowledge-card){ .md-button }
|
[:fontawesome-brands-github: View on GitHub](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/flash-card){ .md-button }
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Knowledge Card(知识卡片)
|
# Flash Card(闪记卡)
|
||||||
|
|
||||||
<span class="category-badge action">Action</span>
|
<span class="category-badge action">Action</span>
|
||||||
<span class="version-badge">v0.2.0</span>
|
<span class="version-badge">v0.2.4</span>
|
||||||
|
|
||||||
快速生成精美的学习记忆卡片,适合学习和速记。
|
快速生成精美的学习记忆卡片,适合学习和速记。
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Knowledge Card 插件(又名 Flash Card / 闪记卡)会把内容转成视觉
|
|||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
1. 下载插件文件:[`knowledge_card.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/knowledge-card)
|
1. 下载插件文件:[`flash_card.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/flash-card)
|
||||||
2. 上传到 OpenWebUI:**Admin Panel** → **Settings** → **Functions**
|
2. 上传到 OpenWebUI:**Admin Panel** → **Settings** → **Functions**
|
||||||
3. 启用插件
|
3. 启用插件
|
||||||
|
|
||||||
@@ -85,4 +85,4 @@ Knowledge Card 插件(又名 Flash Card / 闪记卡)会把内容转成视觉
|
|||||||
|
|
||||||
## 源码
|
## 源码
|
||||||
|
|
||||||
[:fontawesome-brands-github: 在 GitHub 查看](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/knowledge-card){ .md-button }
|
[:fontawesome-brands-github: 在 GitHub 查看](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/flash-card){ .md-button }
|
||||||
@@ -23,7 +23,7 @@ Actions are interactive plugins that:
|
|||||||
|
|
||||||
Intelligently analyzes text content and generates interactive mind maps with beautiful visualizations.
|
Intelligently analyzes text content and generates interactive mind maps with beautiful visualizations.
|
||||||
|
|
||||||
**Version:** 0.8.0
|
**Version:** 0.9.1
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](smart-mind-map.md)
|
[:octicons-arrow-right-24: Documentation](smart-mind-map.md)
|
||||||
|
|
||||||
@@ -37,15 +37,15 @@ Actions are interactive plugins that:
|
|||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](smart-infographic.md)
|
[:octicons-arrow-right-24: Documentation](smart-infographic.md)
|
||||||
|
|
||||||
- :material-card-text:{ .lg .middle } **Knowledge Card**
|
- :material-card-text:{ .lg .middle } **Flash Card**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Quickly generates beautiful learning memory cards, perfect for studying and memorization.
|
Quickly generates beautiful flashcards from text, extracting key points and categories.
|
||||||
|
|
||||||
**Version:** 0.2.2
|
**Version:** 0.2.4
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](knowledge-card.md)
|
[:octicons-arrow-right-24: Documentation](flash-card.md)
|
||||||
|
|
||||||
- :material-file-excel:{ .lg .middle } **Export to Excel**
|
- :material-file-excel:{ .lg .middle } **Export to Excel**
|
||||||
|
|
||||||
@@ -77,15 +77,7 @@ Actions are interactive plugins that:
|
|||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](deep-dive.md)
|
[:octicons-arrow-right-24: Documentation](deep-dive.md)
|
||||||
|
|
||||||
- :material-image-text:{ .lg .middle } **Infographic to Markdown**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
AI-powered infographic generator that renders SVG and embeds it as Markdown Data URL image.
|
|
||||||
|
|
||||||
**Version:** 1.0.0
|
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](infographic-markdown.md)
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -37,15 +37,15 @@ Actions 是交互式插件,能够:
|
|||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](smart-infographic.md)
|
[:octicons-arrow-right-24: 查看文档](smart-infographic.md)
|
||||||
|
|
||||||
- :material-card-text:{ .lg .middle } **Knowledge Card**
|
- :material-card-text:{ .lg .middle } **Flash Card(闪记卡)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
快速生成精美的学习记忆卡片,适合学习与记忆。
|
快速生成精美的学习记忆卡片,非常适合学习和快速记忆。
|
||||||
|
|
||||||
**版本:** 0.2.2
|
**版本:** 0.2.4
|
||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](knowledge-card.md)
|
[:octicons-arrow-right-24: 查看文档](flash-card.md)
|
||||||
|
|
||||||
- :material-file-excel:{ .lg .middle } **Export to Excel**
|
- :material-file-excel:{ .lg .middle } **Export to Excel**
|
||||||
|
|
||||||
@@ -77,15 +77,7 @@ Actions 是交互式插件,能够:
|
|||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](deep-dive.zh.md)
|
[:octicons-arrow-right-24: 查看文档](deep-dive.zh.md)
|
||||||
|
|
||||||
- :material-image-text:{ .lg .middle } **信息图转 Markdown**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
AI 驱动的信息图生成器,渲染 SVG 并以 Markdown Data URL 图片嵌入。
|
|
||||||
|
|
||||||
**版本:** 1.0.0
|
|
||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](infographic-markdown.zh.md)
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Smart Mind Map
|
# Smart Mind Map
|
||||||
|
|
||||||
<span class="category-badge action">Action</span>
|
<span class="category-badge action">Action</span>
|
||||||
<span class="version-badge">v0.8.0</span>
|
<span class="version-badge">v0.9.1</span>
|
||||||
|
|
||||||
Intelligently analyzes text content and generates interactive mind maps for better visualization and understanding.
|
Intelligently analyzes text content and generates interactive mind maps for better visualization and understanding.
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ The Smart Mind Map plugin transforms text content into beautiful, interactive mi
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Download the plugin file: [`思维导图.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/smart-mind-map)
|
1. Download the plugin file: [`smart_mind_map.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/smart-mind-map)
|
||||||
2. Upload to OpenWebUI: **Admin Panel** → **Settings** → **Functions** (Actions)
|
2. Upload to OpenWebUI: **Admin Panel** → **Settings** → **Functions** (Actions)
|
||||||
3. Enable the plugin, and optionally allow iframe same-origin access so theme auto-detection works
|
3. Enable the plugin, and optionally allow iframe same-origin access so theme auto-detection works
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Smart Mind Map(智能思维导图)
|
# Smart Mind Map(智能思维导图)
|
||||||
|
|
||||||
<span class="category-badge action">Action</span>
|
<span class="category-badge action">Action</span>
|
||||||
<span class="version-badge">v0.8.0</span>
|
<span class="version-badge">v0.9.1</span>
|
||||||
|
|
||||||
智能分析文本内容,生成交互式思维导图,帮助你更直观地理解信息结构。
|
智能分析文本内容,生成交互式思维导图,帮助你更直观地理解信息结构。
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Smart Mind Map 会将文本转换成漂亮的交互式思维导图。插件会
|
|||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
1. 下载插件文件:[`思维导图.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/smart-mind-map)
|
1. 下载插件文件:[`smart_mind_map.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/smart-mind-map)
|
||||||
2. 上传到 OpenWebUI:**Admin Panel** → **Settings** → **Functions**(Actions)
|
2. 上传到 OpenWebUI:**Admin Panel** → **Settings** → **Functions**(Actions)
|
||||||
3. 启用插件,并可在设置中允许 iframe same-origin 以启用主题自动检测
|
3. 启用插件,并可在设置中允许 iframe same-origin 以启用主题自动检测
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Async Context Compression
|
# Async Context Compression
|
||||||
|
|
||||||
<span class="category-badge filter">Filter</span>
|
<span class="category-badge filter">Filter</span>
|
||||||
<span class="version-badge">v1.1.3</span>
|
<span class="version-badge">v1.2.2</span>
|
||||||
|
|
||||||
Reduces token consumption in long conversations through intelligent summarization while maintaining conversational coherence.
|
Reduces token consumption in long conversations through intelligent summarization while maintaining conversational coherence.
|
||||||
|
|
||||||
@@ -34,6 +34,12 @@ This is especially useful for:
|
|||||||
- :material-check-all: **Open WebUI v0.7.x Compatibility**: Dynamic DB session handling
|
- :material-check-all: **Open WebUI v0.7.x Compatibility**: Dynamic DB session handling
|
||||||
- :material-account-convert: **Improved Compatibility**: Summary role changed to `assistant`
|
- :material-account-convert: **Improved Compatibility**: Summary role changed to `assistant`
|
||||||
- :material-shield-check: **Enhanced Stability**: Resolved race conditions in state management
|
- :material-shield-check: **Enhanced Stability**: Resolved race conditions in state management
|
||||||
|
- :material-ruler: **Preflight Context Check**: Validates context fit before sending
|
||||||
|
- :material-format-align-justify: **Structure-Aware Trimming**: Preserves document structure
|
||||||
|
- :material-content-cut: **Native Tool Output Trimming**: Trims verbose tool outputs (Note: Non-native tool outputs are not fully injected into context)
|
||||||
|
- :material-chart-bar: **Detailed Token Logging**: Granular token breakdown
|
||||||
|
- :material-account-search: **Smart Model Matching**: Inherit config from base models
|
||||||
|
- :material-image-off: **Multimodal Support**: Images are preserved but tokens are **NOT** calculated
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -64,10 +70,14 @@ graph TD
|
|||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
|--------|------|---------|-------------|
|
|--------|------|---------|-------------|
|
||||||
| `token_threshold` | integer | `4000` | Trigger compression above this token count |
|
| `compression_threshold_tokens` | integer | `64000` | Trigger compression above this token count |
|
||||||
| `preserve_recent` | integer | `5` | Number of recent messages to keep uncompressed |
|
| `max_context_tokens` | integer | `128000` | Hard limit for context |
|
||||||
| `summary_model` | string | `"auto"` | Model to use for summarization |
|
| `keep_first` | integer | `1` | Always keep the first N messages |
|
||||||
| `compression_ratio` | float | `0.3` | Target compression ratio |
|
| `keep_last` | integer | `6` | Always keep the last N messages |
|
||||||
|
| `summary_model` | string | `None` | Model to use for summarization |
|
||||||
|
| `summary_model_max_context` | integer | `0` | Max context tokens for summary model |
|
||||||
|
| `max_summary_tokens` | integer | `16384` | Maximum tokens for the summary |
|
||||||
|
| `enable_tool_output_trimming` | boolean | `false` | Enable trimming of large tool outputs |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Async Context Compression(异步上下文压缩)
|
# Async Context Compression(异步上下文压缩)
|
||||||
|
|
||||||
<span class="category-badge filter">Filter</span>
|
<span class="category-badge filter">Filter</span>
|
||||||
<span class="version-badge">v1.1.3</span>
|
<span class="version-badge">v1.2.2</span>
|
||||||
|
|
||||||
通过智能摘要减少长对话的 token 消耗,同时保持对话连贯。
|
通过智能摘要减少长对话的 token 消耗,同时保持对话连贯。
|
||||||
|
|
||||||
@@ -34,6 +34,12 @@ Async Context Compression 过滤器通过以下方式帮助管理长对话的 to
|
|||||||
- :material-check-all: **Open WebUI v0.7.x 兼容性**:动态数据库会话处理
|
- :material-check-all: **Open WebUI v0.7.x 兼容性**:动态数据库会话处理
|
||||||
- :material-account-convert: **兼容性提升**:摘要角色改为 `assistant`
|
- :material-account-convert: **兼容性提升**:摘要角色改为 `assistant`
|
||||||
- :material-shield-check: **稳定性增强**:解决状态管理竞态条件
|
- :material-shield-check: **稳定性增强**:解决状态管理竞态条件
|
||||||
|
- :material-ruler: **预检上下文检查**:发送前验证上下文是否超限
|
||||||
|
- :material-format-align-justify: **结构感知裁剪**:保留文档结构的智能裁剪
|
||||||
|
- :material-content-cut: **原生工具输出裁剪**:自动裁剪冗长的工具输出(注意:非原生工具调用输出不会完整注入上下文)
|
||||||
|
- :material-chart-bar: **详细 Token 日志**:提供细粒度的 Token 统计
|
||||||
|
- :material-account-search: **智能模型匹配**:自定义模型自动继承基础模型配置
|
||||||
|
- :material-image-off: **多模态支持**:图片内容保留但 Token **不参与计算**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -64,10 +70,14 @@ graph TD
|
|||||||
|
|
||||||
| 选项 | 类型 | 默认值 | 说明 |
|
| 选项 | 类型 | 默认值 | 说明 |
|
||||||
|--------|------|---------|-------------|
|
|--------|------|---------|-------------|
|
||||||
| `token_threshold` | integer | `4000` | 超过该 token 数触发压缩 |
|
| `compression_threshold_tokens` | integer | `64000` | 超过该 token 数触发压缩 |
|
||||||
| `preserve_recent` | integer | `5` | 保留不压缩的最近消息数量 |
|
| `max_context_tokens` | integer | `128000` | 上下文硬性上限 |
|
||||||
| `summary_model` | string | `"auto"` | 用于摘要的模型 |
|
| `keep_first` | integer | `1` | 始终保留的前 N 条消息 |
|
||||||
| `compression_ratio` | float | `0.3` | 目标压缩比例 |
|
| `keep_last` | integer | `6` | 始终保留的后 N 条消息 |
|
||||||
|
| `summary_model` | string | `None` | 用于摘要的模型 |
|
||||||
|
| `summary_model_max_context` | integer | `0` | 摘要模型的最大上下文 Token 数 |
|
||||||
|
| `max_summary_tokens` | integer | `16384` | 摘要的最大 token 数 |
|
||||||
|
| `enable_tool_output_trimming` | boolean | `false` | 启用长工具输出裁剪 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
57
docs/plugins/filters/folder-memory.md
Normal file
57
docs/plugins/filters/folder-memory.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Folder Memory
|
||||||
|
|
||||||
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.1.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📌 What's new in 0.1.0
|
||||||
|
- **Initial Release**: Automated "Project Rules" management for OpenWebUI folders.
|
||||||
|
- **Folder-Level Persistence**: Automatically updates folder system prompts with extracted rules.
|
||||||
|
- **Optimized Performance**: Runs asynchronously and supports `PRIORITY` configuration for seamless integration with other filters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Folder Memory** is an intelligent context filter plugin for OpenWebUI. It automatically extracts consistent "Project Rules" from ongoing conversations within a folder and injects them back into the folder's system prompt.
|
||||||
|
|
||||||
|
This ensures that all future conversations within that folder share the same evolved context and rules, without manual updates.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Automatic Extraction**: Analyzes chat history every N messages to extract project rules.
|
||||||
|
- **Non-destructive Injection**: Updates only the specific "Project Rules" block in the system prompt, preserving other instructions.
|
||||||
|
- **Async Processing**: Runs in the background without blocking the user's chat experience.
|
||||||
|
- **ORM Integration**: Directly updates folder data using OpenWebUI's internal models for reliability.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Conversations must occur inside a folder.** This plugin only triggers when a chat belongs to a folder (i.e., you need to create a folder in OpenWebUI and start a conversation within it).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Copy `folder_memory.py` to your OpenWebUI `plugins/filters/` directory (or upload via Admin UI).
|
||||||
|
2. Enable the filter in your **Settings** -> **Filters**.
|
||||||
|
3. (Optional) Configure the triggering threshold (default: every 10 messages).
|
||||||
|
|
||||||
|
## Configuration (Valves)
|
||||||
|
|
||||||
|
| Valve | Default | Description |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `PRIORITY` | `20` | Priority level for the filter operations. |
|
||||||
|
| `MESSAGE_TRIGGER_COUNT` | `10` | The number of messages required to trigger a rule analysis. |
|
||||||
|
| `MODEL_ID` | `""` | The model used to generate rules. If empty, uses the current chat model. |
|
||||||
|
| `RULES_BLOCK_TITLE` | `## 📂 Project Rules` | The title displayed above the injected rules block. |
|
||||||
|
| `SHOW_DEBUG_LOG` | `False` | Show detailed debug logs in the browser console. |
|
||||||
|
| `UPDATE_ROOT_FOLDER` | `False` | If enabled, finds and updates the root folder rules instead of the current subfolder. |
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. **Trigger**: When a conversation reaches `MESSAGE_TRIGGER_COUNT` (e.g., 10, 20 messages).
|
||||||
|
2. **Analysis**: The plugin sends the recent conversation + existing rules to the LLM.
|
||||||
|
3. **Synthesis**: The LLM merges new insights with old rules, removing obsolete ones.
|
||||||
|
4. **Update**: The new rule set replaces the `<!-- OWUI_PROJECT_RULES_START -->` block in the folder's system prompt.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
See [ROADMAP](https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/filters/folder-memory/ROADMAP.md) for future plans, including "Project Knowledge" collection.
|
||||||
57
docs/plugins/filters/folder-memory.zh.md
Normal file
57
docs/plugins/filters/folder-memory.zh.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# 文件夹记忆 (Folder Memory)
|
||||||
|
|
||||||
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.1.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📌 0.1.0 版本特性
|
||||||
|
- **首个版本发布**:专注于自动化的“项目规则”管理。
|
||||||
|
- **文件夹级持久化**:自动将提取的规则回写到文件夹系统提示词中。
|
||||||
|
- **性能优化**:采用异步处理机制,并支持 `PRIORITY` 配置,确保与其他过滤器(如上下文压缩)完美协作。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文件夹记忆 (Folder Memory)** 是一个 OpenWebUI 的智能上下文过滤器插件。它能自动从文件夹内的对话中提取一致性的“项目规则”,并将其回写到文件夹的系统提示词中。
|
||||||
|
|
||||||
|
这确保了该文件夹内的所有未来对话都能共享相同的进化上下文和规则,无需手动更新。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- **自动提取**:每隔 N 条消息分析一次聊天记录,提取项目规则。
|
||||||
|
- **无损注入**:仅更新系统提示词中的特定“项目规则”块,保留其他指令。
|
||||||
|
- **异步处理**:在后台运行,不阻塞用户的聊天体验。
|
||||||
|
- **ORM 集成**:直接使用 OpenWebUI 的内部模型更新文件夹数据,确保可靠性。
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
- **对话必须在文件夹内进行。** 此插件仅在聊天属于某个文件夹时触发(即您需要先在 OpenWebUI 中创建一个文件夹,并在其内部开始对话)。
|
||||||
|
|
||||||
|
## 安装指南
|
||||||
|
|
||||||
|
1. 将 `folder_memory.py` (或中文版 `folder_memory_cn.py`) 复制到 OpenWebUI 的 `plugins/filters/` 目录(或通过管理员 UI 上传)。
|
||||||
|
2. 在 **设置** -> **过滤器** 中启用该插件。
|
||||||
|
3. (可选)配置触发阈值(默认:每 10 条消息)。
|
||||||
|
|
||||||
|
## 配置 (Valves)
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `PRIORITY` | `20` | 过滤器操作的优先级。 |
|
||||||
|
| `MESSAGE_TRIGGER_COUNT` | `10` | 触发规则分析的消息数量阈值。 |
|
||||||
|
| `MODEL_ID` | `""` | 用于生成规则的模型 ID。若为空,则使用当前对话模型。 |
|
||||||
|
| `RULES_BLOCK_TITLE` | `## 📂 项目规则` | 显示在注入规则块上方的标题。 |
|
||||||
|
| `SHOW_DEBUG_LOG` | `False` | 在浏览器控制台显示详细调试日志。 |
|
||||||
|
| `UPDATE_ROOT_FOLDER` | `False` | 如果启用,将向上查找并更新根文件夹的规则,而不是当前子文件夹。 |
|
||||||
|
|
||||||
|
## 工作原理
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. **触发**:当对话达到 `MESSAGE_TRIGGER_COUNT`(例如 10、20 条消息)时。
|
||||||
|
2. **分析**:插件将最近的对话 + 现有规则发送给 LLM。
|
||||||
|
3. **综合**:LLM 将新见解与旧规则合并,移除过时的规则。
|
||||||
|
4. **更新**:新的规则集替换文件夹系统提示词中的 `<!-- OWUI_PROJECT_RULES_START -->` 块。
|
||||||
|
|
||||||
|
## 路线图
|
||||||
|
|
||||||
|
查看 [ROADMAP](https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/filters/folder-memory/ROADMAP.md) 了解未来计划,包括“项目知识”收集功能。
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# Gemini Manifold Companion
|
|
||||||
|
|
||||||
<span class="category-badge filter">Filter</span>
|
|
||||||
<span class="version-badge">v0.3.2</span>
|
|
||||||
|
|
||||||
Companion filter for the Gemini Manifold pipe plugin, providing enhanced functionality.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Gemini Manifold Companion works alongside the [Gemini Manifold Pipe](../pipes/gemini-manifold.md) to provide additional processing and enhancement for Gemini model integrations.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- :material-handshake: **Seamless Integration**: Works with Gemini Manifold pipe
|
|
||||||
- :material-format-text: **Message Formatting**: Optimizes messages for Gemini
|
|
||||||
- :material-shield: **Error Handling**: Graceful handling of API issues
|
|
||||||
- :material-tune: **Fine-tuning**: Additional configuration options
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. First, install the [Gemini Manifold Pipe](../pipes/gemini-manifold.md)
|
|
||||||
2. Download the companion filter: [`gemini_manifold_companion.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/filters/gemini_manifold_companion)
|
|
||||||
3. Upload to OpenWebUI: **Admin Panel** → **Settings** → **Functions**
|
|
||||||
4. Enable the filter
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
|
||||||
|--------|------|---------|-------------|
|
|
||||||
| `auto_format` | boolean | `true` | Auto-format messages for Gemini |
|
|
||||||
| `handle_errors` | boolean | `true` | Enable error handling |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
!!! warning "Dependency"
|
|
||||||
This filter requires the **Gemini Manifold Pipe** to be installed and configured.
|
|
||||||
|
|
||||||
!!! note "Prerequisites"
|
|
||||||
- OpenWebUI v0.3.0 or later
|
|
||||||
- Gemini Manifold Pipe installed
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Source Code
|
|
||||||
|
|
||||||
[:fontawesome-brands-github: View on GitHub](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/filters/gemini_manifold_companion){ .md-button }
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# Gemini Manifold Companion
|
|
||||||
|
|
||||||
<span class="category-badge filter">Filter</span>
|
|
||||||
<span class="version-badge">v0.3.2</span>
|
|
||||||
|
|
||||||
Gemini Manifold Pipe 的伴随过滤器,用于增强 Gemini 集成的处理效果。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 概览
|
|
||||||
|
|
||||||
Gemini Manifold Companion 与 [Gemini Manifold Pipe](../pipes/gemini-manifold.md) 搭配使用,为 Gemini 模型集成提供额外的处理与优化。
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
- :material-handshake: **无缝协同**:与 Gemini Manifold Pipe 配合工作
|
|
||||||
- :material-format-text: **消息格式化**:针对 Gemini 优化消息
|
|
||||||
- :material-shield: **错误处理**:更友好的 API 异常处理
|
|
||||||
- :material-tune: **精细配置**:提供额外调优选项
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
1. 先安装 [Gemini Manifold Pipe](../pipes/gemini-manifold.md)
|
|
||||||
2. 下载伴随过滤器:[`gemini_manifold_companion.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/filters/gemini_manifold_companion)
|
|
||||||
3. 上传到 OpenWebUI:**Admin Panel** → **Settings** → **Functions**
|
|
||||||
4. 启用过滤器
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 配置项
|
|
||||||
|
|
||||||
| 选项 | 类型 | 默认值 | 说明 |
|
|
||||||
|--------|------|---------|-------------|
|
|
||||||
| `auto_format` | boolean | `true` | 为 Gemini 自动格式化消息 |
|
|
||||||
| `handle_errors` | boolean | `true` | 开启错误处理 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 运行要求
|
|
||||||
|
|
||||||
!!! warning "依赖"
|
|
||||||
本过滤器需要先安装并配置 **Gemini Manifold Pipe**。
|
|
||||||
|
|
||||||
!!! note "前置条件"
|
|
||||||
- OpenWebUI v0.3.0 及以上
|
|
||||||
- 已安装 Gemini Manifold Pipe
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 源码
|
|
||||||
|
|
||||||
[:fontawesome-brands-github: 在 GitHub 查看](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/filters/gemini_manifold_companion){ .md-button }
|
|
||||||
@@ -22,7 +22,7 @@ Filters act as middleware in the message pipeline:
|
|||||||
|
|
||||||
Reduces token consumption in long conversations through intelligent summarization while maintaining coherence.
|
Reduces token consumption in long conversations through intelligent summarization while maintaining coherence.
|
||||||
|
|
||||||
**Version:** 1.1.3
|
**Version:** 1.2.2
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](async-context-compression.md)
|
[:octicons-arrow-right-24: Documentation](async-context-compression.md)
|
||||||
|
|
||||||
@@ -36,15 +36,15 @@ Filters act as middleware in the message pipeline:
|
|||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](context-enhancement.md)
|
[:octicons-arrow-right-24: Documentation](context-enhancement.md)
|
||||||
|
|
||||||
- :material-google:{ .lg .middle } **Gemini Manifold Companion**
|
- :material-folder-refresh:{ .lg .middle } **Folder Memory**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Companion filter for the Gemini Manifold pipe plugin.
|
Automatically extracts consistent "Project Rules" from ongoing conversations within a folder and injects them back into the folder's system prompt.
|
||||||
|
|
||||||
**Version:** 1.7.0
|
**Version:** 0.1.0
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](gemini-manifold-companion.md)
|
[:octicons-arrow-right-24: Documentation](folder-memory.md)
|
||||||
|
|
||||||
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
|
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
|
||||||
|
|
||||||
@@ -52,10 +52,30 @@ Filters act as middleware in the message pipeline:
|
|||||||
|
|
||||||
Fixes common Markdown formatting issues in LLM outputs, including Mermaid syntax, code blocks, and LaTeX formulas.
|
Fixes common Markdown formatting issues in LLM outputs, including Mermaid syntax, code blocks, and LaTeX formulas.
|
||||||
|
|
||||||
**Version:** 1.0.1
|
**Version:** 1.2.4
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](markdown_normalizer.md)
|
[:octicons-arrow-right-24: Documentation](markdown_normalizer.md)
|
||||||
|
|
||||||
|
- :material-merge:{ .lg .middle } **Multi-Model Context Merger**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Automatically merges context from multiple model responses in the previous turn, enabling collaborative answers.
|
||||||
|
|
||||||
|
**Version:** 0.1.0
|
||||||
|
|
||||||
|
[:octicons-arrow-right-24: Documentation](multi-model-context-merger.md)
|
||||||
|
|
||||||
|
- :material-file-document-multiple:{ .lg .middle } **Web Gemini Multimodal Filter**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
A powerful filter that provides multimodal capabilities (PDF, Office, Images, Audio, Video) to any model in OpenWebUI.
|
||||||
|
|
||||||
|
**Version:** 0.3.2
|
||||||
|
|
||||||
|
[:octicons-arrow-right-24: Documentation](web-gemini-multimodel.md)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Filter 充当消息管线中的中间件:
|
|||||||
|
|
||||||
通过智能总结减少长对话的 token 消耗,同时保持连贯性。
|
通过智能总结减少长对话的 token 消耗,同时保持连贯性。
|
||||||
|
|
||||||
**版本:** 1.1.3
|
**版本:** 1.2.2
|
||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](async-context-compression.md)
|
[:octicons-arrow-right-24: 查看文档](async-context-compression.md)
|
||||||
|
|
||||||
@@ -36,15 +36,15 @@ Filter 充当消息管线中的中间件:
|
|||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](context-enhancement.md)
|
[:octicons-arrow-right-24: 查看文档](context-enhancement.md)
|
||||||
|
|
||||||
- :material-google:{ .lg .middle } **Gemini Manifold Companion**
|
- :material-folder-refresh:{ .lg .middle } **Folder Memory**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Gemini Manifold Pipe 插件的伴随过滤器。
|
自动从文件夹内的对话中提取一致性的“项目规则”,并将其回写到文件夹的系统提示词中。
|
||||||
|
|
||||||
**版本:** 1.7.0
|
**版本:** 0.1.0
|
||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](gemini-manifold-companion.md)
|
[:octicons-arrow-right-24: 查看文档](folder-memory.zh.md)
|
||||||
|
|
||||||
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
|
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ Filter 充当消息管线中的中间件:
|
|||||||
|
|
||||||
修复 LLM 输出中常见的 Markdown 格式问题,包括 Mermaid 语法、代码块和 LaTeX 公式。
|
修复 LLM 输出中常见的 Markdown 格式问题,包括 Mermaid 语法、代码块和 LaTeX 公式。
|
||||||
|
|
||||||
**版本:** 1.0.1
|
**版本:** 1.2.4
|
||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](markdown_normalizer.zh.md)
|
[:octicons-arrow-right-24: 查看文档](markdown_normalizer.zh.md)
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1,97 @@
|
|||||||
# Markdown Normalizer Filter
|
# 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.
|
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
|
## 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.
|
* **Details Tag Normalization**: Ensures proper spacing for `<details>` tags (used for thought chains). Adds a blank line after `</details>` and ensures a newline after self-closing `<details />` tags to prevent rendering issues.
|
||||||
* **Frontend Console Debugging**: Supports printing structured debug logs directly to the browser console (F12) for easier troubleshooting.
|
* **Emphasis Spacing Fix**: Fixes extra spaces inside emphasis markers (e.g., `** text **` -> `**text**`) which can cause rendering failures. Includes safeguards to protect math expressions (e.g., `2 * 3 * 4`) and list variables.
|
||||||
* **Code Block Formatting**: Fixes broken code block prefixes, suffixes, and indentation.
|
* **Mermaid Syntax Fix**: Automatically fixes common Mermaid syntax errors, such as unquoted node labels (including multi-line labels and citations) and unclosed subgraphs. **New in v1.1.2**: Comprehensive protection for edge labels (text on connecting lines) across all link types (solid, dotted, thick).
|
||||||
* **LaTeX Normalization**: Standardizes LaTeX formula delimiters (`\[` -> `$$`, `\(` -> `$`).
|
* **Frontend Console Debugging**: Supports printing structured debug logs directly to the browser console (F12) for easier troubleshooting.
|
||||||
* **Thought Tag Normalization**: Unifies thought tags (`<think>`, `<thinking>` -> `<thought>`).
|
* **Code Block Formatting**: Fixes broken code block prefixes, suffixes, and indentation.
|
||||||
* **Escape Character Fix**: Cleans up excessive escape characters (`\\n`, `\\t`).
|
* **LaTeX Normalization**: Standardizes LaTeX formula delimiters (`\[` -> `$$`, `\(` -> `$`).
|
||||||
* **List Formatting**: Ensures proper newlines in list items.
|
* **Thought Tag Normalization**: Unifies thought tags (`<think>`, `<thinking>` -> `<thought>`).
|
||||||
* **Heading Fix**: Adds missing spaces in headings (`#Heading` -> `# Heading`).
|
* **Escape Character Fix**: Cleans up excessive escape characters (`\\n`, `\\t`).
|
||||||
* **Table Fix**: Adds missing closing pipes in tables.
|
* **List Formatting**: Ensures proper newlines in list items.
|
||||||
* **XML Cleanup**: Removes leftover XML artifacts.
|
* **Heading Fix**: Adds missing spaces in headings (`#Heading` -> `# Heading`).
|
||||||
|
* **Table Fix**: Adds missing closing pipes in tables.
|
||||||
|
* **XML Cleanup**: Removes leftover XML artifacts.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Install the plugin in Open WebUI.
|
1. Install the plugin in Open WebUI.
|
||||||
2. Enable the filter globally or for specific models.
|
2. Enable the filter globally or for specific models.
|
||||||
3. Configure the enabled fixes in the **Valves** settings.
|
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).
|
4. (Optional) **Show Debug Log** is enabled by default in Valves. This prints structured logs to the browser console (F12).
|
||||||
> [!WARNING]
|
> [!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.
|
> 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)
|
## Configuration (Valves)
|
||||||
|
|
||||||
* `priority`: Filter priority (default: 50).
|
* `priority`: Filter priority (default: 50).
|
||||||
* `enable_escape_fix`: Fix excessive escape characters.
|
* `enable_escape_fix`: Fix excessive escape characters.
|
||||||
* `enable_thought_tag_fix`: Normalize thought tags.
|
* `enable_thought_tag_fix`: Normalize thought tags.
|
||||||
* `enable_code_block_fix`: Fix code block formatting.
|
* `enable_details_tag_fix`: Normalize details tags (default: True).
|
||||||
* `enable_latex_fix`: Normalize LaTeX formulas.
|
* `enable_code_block_fix`: Fix code block formatting.
|
||||||
* `enable_list_fix`: Fix list item newlines (Experimental).
|
* `enable_latex_fix`: Normalize LaTeX formulas.
|
||||||
* `enable_unclosed_block_fix`: Auto-close unclosed code blocks.
|
* `enable_list_fix`: Fix list item newlines (Experimental).
|
||||||
* `enable_fullwidth_symbol_fix`: Fix full-width symbols in code blocks.
|
* `enable_unclosed_block_fix`: Auto-close unclosed code blocks.
|
||||||
* `enable_mermaid_fix`: Fix Mermaid syntax errors.
|
* `enable_fullwidth_symbol_fix`: Fix full-width symbols in code blocks.
|
||||||
* `enable_heading_fix`: Fix missing space in headings.
|
* `enable_mermaid_fix`: Fix Mermaid syntax errors.
|
||||||
* `enable_table_fix`: Fix missing closing pipe in tables.
|
* `enable_heading_fix`: Fix missing space in headings.
|
||||||
* `enable_xml_tag_cleanup`: Cleanup leftover XML tags.
|
* `enable_table_fix`: Fix missing closing pipe in tables.
|
||||||
* `show_status`: Show status notification when fixes are applied.
|
* `enable_xml_tag_cleanup`: Cleanup leftover XML tags.
|
||||||
* `show_debug_log`: Print debug logs to browser console.
|
* `enable_emphasis_spacing_fix`: Fix extra spaces in emphasis (default: True).
|
||||||
|
* `show_status`: Show status notification when fixes are applied.
|
||||||
|
* `show_debug_log`: Print debug logs to browser console.
|
||||||
|
|
||||||
|
## Troubleshooting ❓
|
||||||
|
|
||||||
|
* **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v1.2.4
|
||||||
|
|
||||||
|
* **Documentation Updates**: Synchronized version numbers across all documentation and code files.
|
||||||
|
|
||||||
|
### v1.2.3
|
||||||
|
|
||||||
|
* **List Marker Protection Enhancement**: Fixed a bug where list markers (`*`) followed by plain text and emphasis were having their spaces incorrectly stripped (e.g., `* U16 forward` became `*U16 forward`).
|
||||||
|
* **Placeholder Support**: Confirmed that 4 or more underscores (e.g., `____`) are correctly treated as placeholders and not modified by the emphasis fix.
|
||||||
|
|
||||||
|
### v1.2.2
|
||||||
|
|
||||||
|
* **Code Block Indentation Fix**: Fixed an issue where code blocks nested inside lists were having their indentation incorrectly stripped. Now preserves proper indentation for nested code blocks.
|
||||||
|
* **Underscore Emphasis Support**: Extended emphasis spacing fix to support `__` (double underscore for bold) and `___` (triple underscore for bold+italic) syntax.
|
||||||
|
* **List Marker Protection**: Fixed a bug where list markers (`*`) followed by emphasis markers (`**`) were incorrectly merged (e.g., `* **Yes**` became `***Yes**`). Added safeguard to prevent this.
|
||||||
|
* **Test Suite**: Added comprehensive pytest test suite with 56 test cases covering all major features.
|
||||||
|
|
||||||
|
### v1.2.1
|
||||||
|
|
||||||
|
* **Emphasis Spacing Fix**: Added a new fix for extra spaces inside emphasis markers (e.g., `** text **` -> `**text**`).
|
||||||
|
* Uses a recursive approach to handle nested emphasis (e.g., `**bold _italic _**`).
|
||||||
|
* Includes safeguards to prevent modifying math expressions (e.g., `2 * 3 * 4`) or list variables.
|
||||||
|
* Controlled by the `enable_emphasis_spacing_fix` valve (default: True).
|
||||||
|
|
||||||
|
### v1.2.0
|
||||||
|
|
||||||
|
* **Details Tag Support**: Added normalization for `<details>` tags.
|
||||||
|
* Ensures a blank line is added after `</details>` closing tags to separate thought content from the main response.
|
||||||
|
* Ensures a newline is added after self-closing `<details ... />` tags to prevent them from interfering with subsequent Markdown headings (e.g., fixing `<details/>#Heading`).
|
||||||
|
* Includes safeguard to prevent modification of `<details>` tags inside code blocks.
|
||||||
|
|
||||||
|
### v1.1.2
|
||||||
|
|
||||||
|
* **Mermaid Edge Label Protection**: Implemented comprehensive protection for edge labels (text on connecting lines) to prevent them from being incorrectly modified. Now supports all Mermaid link types including solid (`--`), dotted (`-.`), and thick (`==`) lines with or without arrows.
|
||||||
|
* **Bug Fixes**: Fixed an issue where lines without arrows (e.g., `A -- text --- B`) were not correctly protected.
|
||||||
|
|
||||||
|
### 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
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1,97 @@
|
|||||||
# Markdown 格式化过滤器 (Markdown Normalizer)
|
# Markdown 格式化过滤器 (Markdown Normalizer)
|
||||||
|
|
||||||
这是一个用于 Open WebUI 的生产级内容格式化过滤器,旨在修复 LLM 输出中常见的 Markdown 格式问题。它能确保代码块、LaTeX 公式、Mermaid 图表和其他 Markdown 元素被正确渲染。
|
这是一个用于 Open WebUI 的内容格式化过滤器,旨在修复 LLM 输出中常见的 Markdown 格式问题。它能确保代码块、LaTeX 公式、Mermaid 图表和其他 Markdown 元素被正确渲染。
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
* **Mermaid 语法修复**: 自动修复常见的 Mermaid 语法错误,如未加引号的节点标签(支持多行标签和引用标记)和未闭合的子图 (Subgraph),确保图表能正确渲染。
|
* **Details 标签规范化**: 确保 `<details>` 标签(常用于思维链)有正确的间距。在 `</details>` 后添加空行,并在自闭合 `<details />` 标签后添加换行,防止渲染问题。
|
||||||
* **前端控制台调试**: 支持将结构化的调试日志直接打印到浏览器控制台 (F12),方便排查问题。
|
* **强调空格修复**: 修复强调标记内部的多余空格(例如 `** 文本 **` -> `**文本**`),这会导致 Markdown 渲染失败。包含保护机制,防止误修改数学表达式(如 `2 * 3 * 4`)或列表变量。
|
||||||
* **代码块格式化**: 修复破损的代码块前缀、后缀和缩进问题。
|
* **Mermaid 语法修复**: 自动修复常见的 Mermaid 语法错误,如未加引号的节点标签(支持多行标签和引用标记)和未闭合的子图 (Subgraph)。**v1.1.2 新增**: 全面保护各种类型的连线标签(实线、虚线、粗线),防止被误修改。
|
||||||
* **LaTeX 规范化**: 标准化 LaTeX 公式定界符 (`\[` -> `$$`, `\(` -> `$`)。
|
* **前端控制台调试**: 支持将结构化的调试日志直接打印到浏览器控制台 (F12),方便排查问题。
|
||||||
* **思维标签规范化**: 统一思维链标签 (`<think>`, `<thinking>` -> `<thought>`)。
|
* **代码块格式化**: 修复破损的代码块前缀、后缀和缩进问题。
|
||||||
* **转义字符修复**: 清理过度的转义字符 (`\\n`, `\\t`)。
|
* **LaTeX 规范化**: 标准化 LaTeX 公式定界符 (`\[` -> `$$`, `\(` -> `$`)。
|
||||||
* **列表格式化**: 确保列表项有正确的换行。
|
* **思维标签规范化**: 统一思维链标签 (`<think>`, `<thinking>` -> `<thought>`)。
|
||||||
* **标题修复**: 修复标题中缺失的空格 (`#标题` -> `# 标题`)。
|
* **转义字符修复**: 清理过度的转义字符 (`\\n`, `\\t`)。
|
||||||
* **表格修复**: 修复表格中缺失的闭合管道符。
|
* **列表格式化**: 确保列表项有正确的换行。
|
||||||
* **XML 清理**: 移除残留的 XML 标签。
|
* **标题修复**: 修复标题中缺失的空格 (`#标题` -> `# 标题`)。
|
||||||
|
* **表格修复**: 修复表格中缺失的闭合管道符。
|
||||||
|
* **XML 清理**: 移除残留的 XML 标签。
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
1. 在 Open WebUI 中安装此插件。
|
1. 在 Open WebUI 中安装此插件。
|
||||||
2. 全局启用或为特定模型启用此过滤器。
|
2. 全局启用或为特定模型启用此过滤器。
|
||||||
3. 在 **Valves** 设置中配置需要启用的修复项。
|
3. 在 **Valves** 设置中配置需要启用的修复项。
|
||||||
4. (可选) **显示调试日志 (Show Debug Log)** 在 Valves 中默认开启。这会将结构化的日志打印到浏览器控制台 (F12)。
|
4. (可选) **显示调试日志 (Show Debug Log)** 在 Valves 中默认开启。这会将结构化的日志打印到浏览器控制台 (F12)。
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> 由于这是初版,可能会出现“负向修复”的情况(例如破坏了原本正确的格式)。如果您遇到问题,请务必查看控制台日志,复制“原始 (Original)”与“规范化 (Normalized)”的内容对比,并提交 Issue 反馈。
|
> 由于这是初版,可能会出现“负向修复”的情况(例如破坏了原本正确的格式)。如果您遇到问题,请务必查看控制台日志,复制“原始 (Original)”与“规范化 (Normalized)”的内容对比,并提交 Issue 反馈。
|
||||||
|
|
||||||
## 配置项 (Valves)
|
## 配置项 (Valves)
|
||||||
|
|
||||||
* `priority`: 过滤器优先级 (默认: 50)。
|
* `priority`: 过滤器优先级 (默认: 50)。
|
||||||
* `enable_escape_fix`: 修复过度的转义字符。
|
* `enable_escape_fix`: 修复过度的转义字符。
|
||||||
* `enable_thought_tag_fix`: 规范化思维标签。
|
* `enable_thought_tag_fix`: 规范化思维标签。
|
||||||
* `enable_code_block_fix`: 修复代码块格式。
|
* `enable_details_tag_fix`: 规范化 Details 标签 (默认: True)。
|
||||||
* `enable_latex_fix`: 规范化 LaTeX 公式。
|
* `enable_code_block_fix`: 修复代码块格式。
|
||||||
* `enable_list_fix`: 修复列表项换行 (实验性)。
|
* `enable_latex_fix`: 规范化 LaTeX 公式。
|
||||||
* `enable_unclosed_block_fix`: 自动闭合未闭合的代码块。
|
* `enable_list_fix`: 修复列表项换行 (实验性)。
|
||||||
* `enable_fullwidth_symbol_fix`: 修复代码块中的全角符号。
|
* `enable_unclosed_block_fix`: 自动闭合未闭合的代码块。
|
||||||
* `enable_mermaid_fix`: 修复 Mermaid 语法错误。
|
* `enable_fullwidth_symbol_fix`: 修复代码块中的全角符号。
|
||||||
* `enable_heading_fix`: 修复标题中缺失的空格。
|
* `enable_mermaid_fix`: 修复 Mermaid 语法错误。
|
||||||
* `enable_table_fix`: 修复表格中缺失的闭合管道符。
|
* `enable_heading_fix`: 修复标题中缺失的空格。
|
||||||
* `enable_xml_tag_cleanup`: 清理残留的 XML 标签。
|
* `enable_table_fix`: 修复表格中缺失的闭合管道符。
|
||||||
* `show_status`: 应用修复时显示状态通知。
|
* `enable_xml_tag_cleanup`: 清理残留的 XML 标签。
|
||||||
* `show_debug_log`: 在浏览器控制台打印调试日志。
|
* `enable_emphasis_spacing_fix`: 修复强调语法中的多余空格 (默认: True)。
|
||||||
|
* `show_status`: 应用修复时显示状态通知。
|
||||||
|
* `show_debug_log`: 在浏览器控制台打印调试日志。
|
||||||
|
|
||||||
|
## 故障排除 (Troubleshooting) ❓
|
||||||
|
|
||||||
|
* **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### v1.2.4
|
||||||
|
|
||||||
|
* **文档更新**: 同步了所有文档和代码文件的版本号。
|
||||||
|
|
||||||
|
### v1.2.3
|
||||||
|
|
||||||
|
* **列表标记保护增强**: 修复了列表标记 (`*`) 后跟普通文本和强调标记时,空格被错误剥离的问题(例如 `* U16 前锋` 变成 `*U16 前锋`)。
|
||||||
|
* **占位符支持**: 确认 4 个或更多下划线(如 `____`)会被正确视为占位符,不会被强调修复逻辑修改。
|
||||||
|
|
||||||
|
### v1.2.2
|
||||||
|
|
||||||
|
* **代码块缩进修复**: 修复了列表中嵌套代码块的缩进被错误剥离的问题。现在会正确保留嵌套代码块的缩进。
|
||||||
|
* **下划线强调语法支持**: 扩展强调空格修复以支持 `__` (双下划线加粗) 和 `___` (三下划线加粗斜体) 语法。
|
||||||
|
* **列表标记保护**: 修复了列表标记 (`*`) 后跟强调标记 (`**`) 被错误合并的 Bug(例如 `* **是**` 变成 `***是**`)。添加了保护逻辑防止此问题。
|
||||||
|
* **测试套件**: 新增完整的 pytest 测试套件,包含 56 个测试用例,覆盖所有主要功能。
|
||||||
|
|
||||||
|
### v1.2.1
|
||||||
|
|
||||||
|
* **强调空格修复**: 新增了对强调标记内部多余空格的修复(例如 `** 文本 **` -> `**文本**`)。
|
||||||
|
* 采用递归方法处理嵌套强调(例如 `**加粗 _斜体 _**`)。
|
||||||
|
* 包含保护机制,防止误修改数学表达式(如 `2 * 3 * 4`)或列表变量。
|
||||||
|
* 通过 `enable_emphasis_spacing_fix` 开关控制(默认:开启)。
|
||||||
|
|
||||||
|
### v1.2.0
|
||||||
|
|
||||||
|
* **Details 标签支持**: 新增了对 `<details>` 标签的规范化支持。
|
||||||
|
* 确保在 `</details>` 闭合标签后添加空行,将思维内容与正文分隔开。
|
||||||
|
* 确保在自闭合 `<details ... />` 标签后添加换行,防止其干扰后续的 Markdown 标题(例如修复 `<details/>#标题`)。
|
||||||
|
* 包含保护机制,防止修改代码块内部的 `<details>` 标签。
|
||||||
|
|
||||||
|
### v1.1.2
|
||||||
|
|
||||||
|
* **Mermaid 连线标签保护**: 实现了全面的连线标签保护机制,防止连接线上的文字被误修改。现在支持所有 Mermaid 连线类型,包括实线 (`--`)、虚线 (`-.`) 和粗线 (`==`),无论是否带有箭头。
|
||||||
|
* **Bug 修复**: 修复了无箭头连线(如 `A -- text --- B`)未被正确保护的问题。
|
||||||
|
|
||||||
|
### v1.1.0
|
||||||
|
|
||||||
|
* **Mermaid 修复优化**: 改进了正则表达式以处理节点标签中的嵌套括号(如 `ID("标签 (文本)")`),并避免误匹配连接线上的文字。
|
||||||
|
* **HTML 保护机制优化**: 优化了 `_contains_html` 检测,允许 `<br/>`, `<b>`, `<i>` 等常见标签,确保包含这些标签的 Mermaid 图表能被正常规范化。
|
||||||
|
* **全角符号清理**: 修复了 `FULLWIDTH_MAP` 中的重复键名和错误的引号映射。
|
||||||
|
* **Bug 修复**: 修复了 Python 文件中缺失的 `Dict` 类型导入。
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
|
|||||||
35
docs/plugins/filters/multi-model-context-merger.md
Normal file
35
docs/plugins/filters/multi-model-context-merger.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Multi-Model Context Merger
|
||||||
|
|
||||||
|
<span class="category-badge filter">Filter</span>
|
||||||
|
<span class="version-badge">v0.1.0</span>
|
||||||
|
|
||||||
|
Automatically merges context from multiple model responses in the previous turn, enabling collaborative answers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This filter detects when multiple models have responded in the previous turn (e.g., using "Arena" mode or multiple models selected). It consolidates these responses and injects them as context for the current turn, allowing the next model to see what others have said.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- :material-merge: **Auto-Merge**: Consolidates responses from multiple models into a single context block.
|
||||||
|
- :material-format-list-group: **Structured Injection**: Uses XML-like tags (`<response>`) to separate different model outputs.
|
||||||
|
- :material-robot-confused: **Collaboration**: Enables models to build upon or critique each other's answers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Download the plugin file: [`multi_model_context_merger.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/filters)
|
||||||
|
2. Upload to OpenWebUI: **Admin Panel** → **Settings** → **Functions**
|
||||||
|
3. Enable the filter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Select **multiple models** in the chat (or use Arena mode).
|
||||||
|
2. Ask a question. All models will respond.
|
||||||
|
3. Ask a follow-up question.
|
||||||
|
4. The filter will inject the previous responses from ALL models into the context of the current model(s).
|
||||||
35
docs/plugins/filters/multi-model-context-merger.zh.md
Normal file
35
docs/plugins/filters/multi-model-context-merger.zh.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# 多模型上下文合并 (Multi-Model Context Merger)
|
||||||
|
|
||||||
|
<span class="category-badge filter">Filter</span>
|
||||||
|
<span class="version-badge">v0.1.0</span>
|
||||||
|
|
||||||
|
自动合并上一轮中多个模型的回答上下文,实现协作问答。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
此过滤器检测上一轮是否由多个模型回复(例如使用“竞技场”模式或选择了多个模型)。它将这些回复合并并作为上下文注入到当前轮次,使下一个模型能够看到其他模型之前所说的内容。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- :material-merge: **自动合并**: 将多个模型的回复合并为单个上下文块。
|
||||||
|
- :material-format-list-group: **结构化注入**: 使用类似 XML 的标签 (`<response>`) 分隔不同模型的输出。
|
||||||
|
- :material-robot-confused: **协作**: 允许模型基于彼此的回答进行构建或评论。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
1. 下载插件文件: [`multi_model_context_merger.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/filters)
|
||||||
|
2. 上传到 OpenWebUI: **管理员面板** → **设置** → **函数**
|
||||||
|
3. 启用过滤器。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
1. 在聊天中选择 **多个模型** (或使用竞技场模式)。
|
||||||
|
2. 提问。所有模型都会回答。
|
||||||
|
3. 提出后续问题。
|
||||||
|
4. 过滤器会将所有模型之前的回答注入到当前模型的上下文中。
|
||||||
51
docs/plugins/filters/web-gemini-multimodel.md
Normal file
51
docs/plugins/filters/web-gemini-multimodel.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Web Gemini Multimodal Filter
|
||||||
|
|
||||||
|
<span class="category-badge filter">Filter</span>
|
||||||
|
<span class="version-badge">v0.3.2</span>
|
||||||
|
|
||||||
|
A powerful filter that provides multimodal capabilities (PDF, Office, Images, Audio, Video) to any model in OpenWebUI.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This plugin enables multimodal processing for any model by leveraging Gemini as an analyzer. It supports direct file processing for Gemini models and "Analyzer Mode" for other models (like DeepSeek, Llama), where Gemini analyzes the file and injects the result as context.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- :material-file-document-multiple: **Multimodal Support**: Process PDF, Word, Excel, PowerPoint, EPUB, MP3, MP4, and Images.
|
||||||
|
- :material-router-network: **Smart Routing**:
|
||||||
|
- **Direct Mode**: Files are passed directly to Gemini models.
|
||||||
|
- **Analyzer Mode**: Files are analyzed by Gemini, and results are injected into the context for other models.
|
||||||
|
- :material-history: **Persistent Context**: Maintains session history across multiple turns using OpenWebUI Chat ID.
|
||||||
|
- :material-database-check: **Deduplication**: Automatically tracks analyzed file hashes to prevent redundant processing.
|
||||||
|
- :material-subtitles: **Subtitle Enhancement**: Specialized mode for generating high-quality SRT subtitles from video/audio.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Download the plugin file: [`web_gemini_multimodel.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/filters/web_gemini_multimodel_filter)
|
||||||
|
2. Upload to OpenWebUI: **Admin Panel** → **Settings** → **Functions**
|
||||||
|
3. Configure the Gemini Adapter URL and other settings.
|
||||||
|
4. Enable the filter globally or per chat.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Option | Type | Default | Description |
|
||||||
|
|--------|------|---------|-------------|
|
||||||
|
| `gemini_adapter_url` | string | `http://...` | URL of the Gemini Adapter service |
|
||||||
|
| `target_model_keyword` | string | `"webgemini"` | Keyword to identify Gemini models |
|
||||||
|
| `mode` | string | `"auto"` | `auto`, `direct`, or `analyzer` |
|
||||||
|
| `analyzer_base_model_id` | string | `"gemini-3.0-pro"` | Model used for document analysis |
|
||||||
|
| `subtitle_keywords` | string | `"字幕,srt"` | Keywords to trigger subtitle flow |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. **Upload a file** (PDF, Image, Video, etc.) in the chat.
|
||||||
|
2. **Ask a question** about the file.
|
||||||
|
3. The plugin will automatically process the file and provide context to your selected model.
|
||||||
51
docs/plugins/filters/web-gemini-multimodel.zh.md
Normal file
51
docs/plugins/filters/web-gemini-multimodel.zh.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Web Gemini 多模态过滤器
|
||||||
|
|
||||||
|
<span class="category-badge filter">Filter</span>
|
||||||
|
<span class="version-badge">v0.3.2</span>
|
||||||
|
|
||||||
|
一个强大的过滤器,为 OpenWebUI 中的任何模型提供多模态能力:PDF、Office、图片、音频、视频等。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
此插件利用 Gemini 作为分析器,为任何模型提供多模态处理能力。它支持 Gemini 模型的直接文件处理,以及其他模型(如 DeepSeek, Llama)的“分析器模式”,即由 Gemini 分析文件并将结果注入上下文。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- :material-file-document-multiple: **多模态支持**: 处理 PDF, Word, Excel, PowerPoint, EPUB, MP3, MP4 和图片。
|
||||||
|
- :material-router-network: **智能路由**:
|
||||||
|
- **直连模式 (Direct Mode)**: 对于 Gemini 模型,文件直接传递(原生多模态)。
|
||||||
|
- **分析器模式 (Analyzer Mode)**: 对于非 Gemini 模型,文件由 Gemini 分析,结果注入为上下文。
|
||||||
|
- :material-history: **持久上下文**: 利用 OpenWebUI 的 Chat ID 跨多轮对话维护会话历史。
|
||||||
|
- :material-database-check: **数据库去重**: 自动记录已分析文件的哈希值,防止重复上传和分析。
|
||||||
|
- :material-subtitles: **字幕增强**: 针对视频/音频上传的专用模式,生成高质量 SRT 字幕。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
1. 下载插件文件: [`web_gemini_multimodel.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/filters/web_gemini_multimodel_filter)
|
||||||
|
2. 上传到 OpenWebUI: **管理员面板** → **设置** → **函数**
|
||||||
|
3. 配置 Gemini Adapter URL 和其他设置。
|
||||||
|
4. 启用过滤器。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
|
||||||
|
| 选项 | 类型 | 默认值 | 描述 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `gemini_adapter_url` | string | `http://...` | Gemini Adapter 服务的 URL |
|
||||||
|
| `target_model_keyword` | string | `"webgemini"` | 识别 Gemini 模型的关键字 |
|
||||||
|
| `mode` | string | `"auto"` | `auto` (自动), `direct` (直连), 或 `analyzer` (分析器) |
|
||||||
|
| `analyzer_base_model_id` | string | `"gemini-3.0-pro"` | 用于文档分析的模型 |
|
||||||
|
| `subtitle_keywords` | string | `"字幕,srt"` | 触发字幕流程的关键字 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
1. 在聊天中 **上传文件** (PDF, 图片, 视频等)。
|
||||||
|
2. 关于文件 **提问**。
|
||||||
|
3. 插件会自动处理文件并为所选模型提供上下文。
|
||||||
@@ -48,15 +48,15 @@ OpenWebUI supports four types of plugins, each serving a different purpose:
|
|||||||
|
|
||||||
| Plugin | Type | Description | Version |
|
| Plugin | Type | Description | Version |
|
||||||
|--------|------|-------------|---------|
|
|--------|------|-------------|---------|
|
||||||
| [Smart Mind Map](actions/smart-mind-map.md) | Action | Generate interactive mind maps from text | 0.8.0 |
|
| [Smart Mind Map](actions/smart-mind-map.md) | Action | Generate interactive mind maps from text | 0.9.1 |
|
||||||
| [Smart Infographic](actions/smart-infographic.md) | Action | Transform text into professional infographics | 1.0.0 |
|
| [Smart Infographic](actions/smart-infographic.md) | Action | Transform text into professional infographics | 1.4.9 |
|
||||||
| [Knowledge Card](actions/knowledge-card.md) | Action | Create beautiful learning flashcards | 0.2.0 |
|
| [Flash Card](actions/flash-card.md) | Action | Create beautiful learning flashcards | 0.2.4 |
|
||||||
| [Export to Excel](actions/export-to-excel.md) | Action | Export chat history to Excel files | 1.0.0 |
|
| [Export to Excel](actions/export-to-excel.md) | Action | Export chat history to Excel files | 0.3.7 |
|
||||||
| [Export to Word](actions/export-to-word.md) | Action | Export chat content to Word (.docx) with formatting | 0.1.0 |
|
| [Export to Word](actions/export-to-word.md) | Action | Export chat content to Word (.docx) with formatting | 0.4.3 |
|
||||||
| [Async Context Compression](filters/async-context-compression.md) | Filter | Intelligent context compression | 1.0.0 |
|
| [Async Context Compression](filters/async-context-compression.md) | Filter | Intelligent context compression | 1.1.3 |
|
||||||
| [Context Enhancement](filters/context-enhancement.md) | Filter | Enhance chat context | 1.0.0 |
|
| [Context Enhancement](filters/context-enhancement.md) | Filter | Enhance chat context | 0.3.0 |
|
||||||
| [Gemini Manifold Companion](filters/gemini-manifold-companion.md) | Filter | Companion for Gemini Manifold | 1.0.0 |
|
| [Multi-Model Context Merger](filters/multi-model-context-merger.md) | Filter | Merge context from multiple models | 0.1.0 |
|
||||||
| [Gemini Manifold](pipes/gemini-manifold.md) | Pipe | Gemini model integration | 1.0.0 |
|
| [Web Gemini Multimodal Filter](filters/web-gemini-multimodel.md) | Filter | Multimodal capabilities for any model | 0.3.2 |
|
||||||
| [MoE Prompt Refiner](pipelines/moe-prompt-refiner.md) | Pipeline | Multi-model prompt refinement | 1.0.0 |
|
| [MoE Prompt Refiner](pipelines/moe-prompt-refiner.md) | Pipeline | Multi-model prompt refinement | 1.0.0 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -48,15 +48,15 @@ OpenWebUI 支持四种类型的插件,每种都有不同的用途:
|
|||||||
|
|
||||||
| 插件 | 类型 | 描述 | 版本 |
|
| 插件 | 类型 | 描述 | 版本 |
|
||||||
|--------|------|-------------|---------|
|
|--------|------|-------------|---------|
|
||||||
| [Smart Mind Map(智能思维导图)](actions/smart-mind-map.md) | Action | 从文本生成交互式思维导图 | 0.8.0 |
|
| [Smart Mind Map(智能思维导图)](actions/smart-mind-map.md) | Action | 从文本生成交互式思维导图 | 0.9.1 |
|
||||||
| [Smart Infographic(智能信息图)](actions/smart-infographic.md) | Action | 将文本转成专业信息图 | 1.0.0 |
|
| [Smart Infographic(智能信息图)](actions/smart-infographic.md) | Action | 将文本转成专业信息图 | 1.4.9 |
|
||||||
| [Knowledge Card(知识卡片)](actions/knowledge-card.md) | Action | 生成精美学习卡片 | 0.2.0 |
|
| [Flash Card(闪记卡)](actions/flash-card.md) | Action | 生成精美学习卡片 | 0.2.4 |
|
||||||
| [Export to Excel(导出到 Excel)](actions/export-to-excel.md) | Action | 导出聊天记录为 Excel | 1.0.0 |
|
| [Export to Excel(导出到 Excel)](actions/export-to-excel.md) | Action | 导出聊天记录为 Excel | 0.3.7 |
|
||||||
| [Export to Word(导出为 Word)](actions/export-to-word.md) | Action | 将聊天内容导出为 Word (.docx) 并保留格式 | 0.1.0 |
|
| [Export to Word(导出为 Word)](actions/export-to-word.md) | Action | 将聊天内容导出为 Word (.docx) 并保留格式 | 0.4.3 |
|
||||||
| [Async Context Compression(异步上下文压缩)](filters/async-context-compression.md) | Filter | 智能上下文压缩 | 1.0.0 |
|
| [Async Context Compression(异步上下文压缩)](filters/async-context-compression.md) | Filter | 智能上下文压缩 | 1.1.3 |
|
||||||
| [Context Enhancement(上下文增强)](filters/context-enhancement.md) | Filter | 提升对话上下文 | 1.0.0 |
|
| [Context Enhancement(上下文增强)](filters/context-enhancement.md) | Filter | 提升对话上下文 | 0.3.0 |
|
||||||
| [Gemini Manifold Companion](filters/gemini-manifold-companion.md) | Filter | Gemini Manifold 伴侣 | 1.0.0 |
|
| [Multi-Model Context Merger(多模型上下文合并)](filters/multi-model-context-merger.md) | Filter | 合并多个模型的上下文 | 0.1.0 |
|
||||||
| [Gemini Manifold](pipes/gemini-manifold.md) | Pipe | Gemini 模型集成 | 1.0.0 |
|
| [Web Gemini Multimodal Filter(Web Gemini 多模态过滤器)](filters/web-gemini-multimodel.md) | Filter | 为任何模型提供多模态能力 | 0.3.2 |
|
||||||
| [MoE Prompt Refiner](pipelines/moe-prompt-refiner.md) | Pipeline | 多模型提示词优化 | 1.0.0 |
|
| [MoE Prompt Refiner](pipelines/moe-prompt-refiner.md) | Pipeline | 多模型提示词优化 | 1.0.0 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
# Gemini Manifold
|
|
||||||
|
|
||||||
<span class="category-badge pipe">Pipe</span>
|
|
||||||
<span class="version-badge">v1.0.0</span>
|
|
||||||
|
|
||||||
Integration pipeline for Google's Gemini models with full streaming support.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Gemini Manifold pipe provides seamless integration with Google's Gemini AI models. It exposes Gemini models as selectable options in OpenWebUI, allowing you to use them just like any other model.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- :material-google: **Full Gemini Support**: Access all Gemini model variants
|
|
||||||
- :material-stream: **Streaming**: Real-time response streaming
|
|
||||||
- :material-image: **Multimodal**: Support for images and text
|
|
||||||
- :material-shield: **Error Handling**: Robust error management
|
|
||||||
- :material-tune: **Configurable**: Customize model parameters
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Download the plugin file: [`gemini_manifold.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/pipes/gemini_mainfold)
|
|
||||||
2. Upload to OpenWebUI: **Admin Panel** → **Settings** → **Functions**
|
|
||||||
3. Configure your Gemini API key
|
|
||||||
4. Select Gemini models from the model dropdown
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
| Option | Type | Required | Description |
|
|
||||||
|--------|------|----------|-------------|
|
|
||||||
| `GEMINI_API_KEY` | string | Yes | Your Google AI Studio API key |
|
|
||||||
| `DEFAULT_MODEL` | string | No | Default Gemini model to use |
|
|
||||||
| `TEMPERATURE` | float | No | Response temperature (0-1) |
|
|
||||||
| `MAX_TOKENS` | integer | No | Maximum response tokens |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Available Models
|
|
||||||
|
|
||||||
When configured, the following models become available:
|
|
||||||
|
|
||||||
- `gemini-pro` - Text-only model
|
|
||||||
- `gemini-pro-vision` - Multimodal model
|
|
||||||
- `gemini-1.5-pro` - Latest Pro model
|
|
||||||
- `gemini-1.5-flash` - Fast response model
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
1. After installation, go to any chat
|
|
||||||
2. Open the model selector dropdown
|
|
||||||
3. Look for models prefixed with your pipe name
|
|
||||||
4. Select a Gemini model
|
|
||||||
5. Start chatting!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Getting an API Key
|
|
||||||
|
|
||||||
1. Visit [Google AI Studio](https://makersuite.google.com/app/apikey)
|
|
||||||
2. Create a new API key
|
|
||||||
3. Copy the key and paste it in the plugin configuration
|
|
||||||
|
|
||||||
!!! warning "API Key Security"
|
|
||||||
Keep your API key secure. Never share it publicly or commit it to version control.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Companion Filter
|
|
||||||
|
|
||||||
For enhanced functionality, consider installing the [Gemini Manifold Companion](../filters/gemini-manifold-companion.md) filter.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
!!! note "Prerequisites"
|
|
||||||
- OpenWebUI v0.3.0 or later
|
|
||||||
- Valid Gemini API key
|
|
||||||
- Internet access to Google AI APIs
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
??? question "Models not appearing?"
|
|
||||||
Ensure your API key is correctly configured and the plugin is enabled.
|
|
||||||
|
|
||||||
??? question "API errors?"
|
|
||||||
Check your API key validity and quota limits in Google AI Studio.
|
|
||||||
|
|
||||||
??? question "Slow responses?"
|
|
||||||
Consider using `gemini-1.5-flash` for faster response times.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Source Code
|
|
||||||
|
|
||||||
[:fontawesome-brands-github: View on GitHub](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/pipes/gemini_mainfold){ .md-button }
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
# Gemini Manifold
|
|
||||||
|
|
||||||
<span class="category-badge pipe">Pipe</span>
|
|
||||||
<span class="version-badge">v1.0.0</span>
|
|
||||||
|
|
||||||
面向 Google Gemini 模型的集成流水线,支持完整流式返回。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 概览
|
|
||||||
|
|
||||||
Gemini Manifold Pipe 提供与 Google Gemini AI 模型的无缝集成。它会将 Gemini 模型作为可选项暴露在 OpenWebUI 中,你可以像使用其他模型一样使用它们。
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
- :material-google: **完整 Gemini 支持**:可使用所有 Gemini 模型变体
|
|
||||||
- :material-stream: **流式输出**:实时流式响应
|
|
||||||
- :material-image: **多模态**:支持图像与文本
|
|
||||||
- :material-shield: **错误处理**:健壮的错误管理
|
|
||||||
- :material-tune: **可配置**:可自定义模型参数
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
1. 下载插件文件:[`gemini_manifold.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/pipes/gemini_mainfold)
|
|
||||||
2. 上传到 OpenWebUI:**Admin Panel** → **Settings** → **Functions**
|
|
||||||
3. 配置你的 Gemini API Key
|
|
||||||
4. 在模型下拉中选择 Gemini 模型
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 配置
|
|
||||||
|
|
||||||
| 选项 | 类型 | 是否必填 | 说明 |
|
|
||||||
|--------|------|----------|-------------|
|
|
||||||
| `GEMINI_API_KEY` | string | 是 | 你的 Google AI Studio API Key |
|
|
||||||
| `DEFAULT_MODEL` | string | 否 | 默认使用的 Gemini 模型 |
|
|
||||||
| `TEMPERATURE` | float | 否 | 输出温度(0-1) |
|
|
||||||
| `MAX_TOKENS` | integer | 否 | 最大回复 token 数 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 可用模型
|
|
||||||
|
|
||||||
配置完成后,你可以选择以下模型:
|
|
||||||
|
|
||||||
- `gemini-pro` —— 纯文本模型
|
|
||||||
- `gemini-pro-vision` —— 多模态模型
|
|
||||||
- `gemini-1.5-pro` —— 最新 Pro 模型
|
|
||||||
- `gemini-1.5-flash` —— 快速响应模型
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
1. 安装后进入任意对话
|
|
||||||
2. 打开模型选择下拉
|
|
||||||
3. 查找以 Pipe 名称前缀的模型
|
|
||||||
4. 选择 Gemini 模型
|
|
||||||
5. 开始聊天!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 获取 API Key
|
|
||||||
|
|
||||||
1. 访问 [Google AI Studio](https://makersuite.google.com/app/apikey)
|
|
||||||
2. 创建新的 API Key
|
|
||||||
3. 复制并粘贴到插件配置中
|
|
||||||
|
|
||||||
!!! warning "API Key 安全"
|
|
||||||
请妥善保管你的 API Key,不要公开或提交到版本库。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 伴随过滤器
|
|
||||||
|
|
||||||
如需增强功能,可安装 [Gemini Manifold Companion](../filters/gemini-manifold-companion.md) 过滤器。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 运行要求
|
|
||||||
|
|
||||||
!!! note "前置条件"
|
|
||||||
- OpenWebUI v0.3.0 及以上
|
|
||||||
- 有效的 Gemini API Key
|
|
||||||
- 可访问 Google AI API 的网络
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
??? question "模型没有出现?"
|
|
||||||
请确认 API Key 配置正确且插件已启用。
|
|
||||||
|
|
||||||
??? question "出现 API 错误?"
|
|
||||||
检查 Google AI Studio 中的 Key 有效性和额度限制。
|
|
||||||
|
|
||||||
??? question "响应较慢?"
|
|
||||||
可尝试使用 `gemini-1.5-flash` 获得更快速度。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 源码
|
|
||||||
|
|
||||||
[:fontawesome-brands-github: 在 GitHub 查看](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/pipes/gemini_mainfold){ .md-button }
|
|
||||||
84
docs/plugins/pipes/github-copilot-sdk.md
Normal file
84
docs/plugins/pipes/github-copilot-sdk.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# GitHub Copilot SDK Pipe for OpenWebUI
|
||||||
|
|
||||||
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.1.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
|
|
||||||
|
This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/open-webui) that allows you to use GitHub Copilot models (such as `gpt-5`, `gpt-5-mini`, `claude-sonnet-4.5`) directly within OpenWebUI. It is built upon the official [GitHub Copilot SDK for Python](https://github.com/github/copilot-sdk), providing a native integration experience.
|
||||||
|
|
||||||
|
## 🚀 What's New (v0.1.0)
|
||||||
|
|
||||||
|
* **♾️ Infinite Sessions**: Automatic context compaction for long-running conversations. No more context limit errors!
|
||||||
|
* **🧠 Thinking Process**: Real-time display of model reasoning/thinking process (for supported models).
|
||||||
|
* **📂 Workspace Control**: Restricted workspace directory for secure file operations.
|
||||||
|
* **🔍 Model Filtering**: Exclude specific models using keywords (e.g., `codex`, `haiku`).
|
||||||
|
* **💾 Session Persistence**: Improved session resume logic using OpenWebUI chat ID mapping.
|
||||||
|
|
||||||
|
## ✨ Core Features
|
||||||
|
|
||||||
|
* **🚀 Official SDK Integration**: Built on the official SDK for stability and reliability.
|
||||||
|
* **💬 Multi-turn Conversation**: Automatically concatenates history context so Copilot understands your previous messages.
|
||||||
|
* **🌊 Streaming Output**: Supports typewriter effect for fast responses.
|
||||||
|
* **🖼️ Multimodal Support**: Supports image uploads, automatically converting them to attachments for Copilot (requires model support).
|
||||||
|
* **🛠️ Zero-config Installation**: Automatically detects and downloads the GitHub Copilot CLI, ready to use out of the box.
|
||||||
|
* **🔑 Secure Authentication**: Supports Fine-grained Personal Access Tokens for minimized permissions.
|
||||||
|
* **🐛 Debug Mode**: Built-in detailed log output for easy connection troubleshooting.
|
||||||
|
|
||||||
|
## 📦 Installation & Usage
|
||||||
|
|
||||||
|
### 1. Import Function
|
||||||
|
|
||||||
|
1. Open OpenWebUI.
|
||||||
|
2. Go to **Workspace** -> **Functions**.
|
||||||
|
3. Click **+** (Create Function).
|
||||||
|
4. Paste the content of `github_copilot_sdk.py` (or `github_copilot_sdk_cn.py` for Chinese) completely.
|
||||||
|
5. Save.
|
||||||
|
|
||||||
|
### 2. Configure Valves (Settings)
|
||||||
|
|
||||||
|
Find "GitHub Copilot" in the function list and click the **⚙️ (Valves)** icon to configure:
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **GH_TOKEN** | **(Required)** Your GitHub Token. | - |
|
||||||
|
| **MODEL_ID** | The model name to use. Recommended `gpt-5-mini` or `gpt-5`. | `gpt-5-mini` |
|
||||||
|
| **CLI_PATH** | Path to the Copilot CLI. Will download automatically if not found. | `/usr/local/bin/copilot` |
|
||||||
|
| **DEBUG** | Whether to enable debug logs (output to chat). | `True` |
|
||||||
|
| **SHOW_THINKING** | Show model reasoning/thinking process. | `True` |
|
||||||
|
| **EXCLUDE_KEYWORDS** | Exclude models containing these keywords (comma separated). | - |
|
||||||
|
| **WORKSPACE_DIR** | Restricted workspace directory for file operations. | - |
|
||||||
|
| **INFINITE_SESSION** | Enable Infinite Sessions (automatic context compaction). | `True` |
|
||||||
|
| **COMPACTION_THRESHOLD** | Background compaction threshold (0.0-1.0). | `0.8` |
|
||||||
|
| **BUFFER_THRESHOLD** | Buffer exhaustion threshold (0.0-1.0). | `0.95` |
|
||||||
|
|
||||||
|
### 3. Get GH_TOKEN
|
||||||
|
|
||||||
|
For security, it is recommended to use a **Fine-grained Personal Access Token**:
|
||||||
|
|
||||||
|
1. Visit [GitHub Token Settings](https://github.com/settings/tokens?type=beta).
|
||||||
|
2. Click **Generate new token**.
|
||||||
|
3. **Repository access**: Select `All repositories` or `Public Repositories`.
|
||||||
|
4. **Permissions**:
|
||||||
|
* Click **Account permissions**.
|
||||||
|
* Find **Copilot Requests**, select **Read and write** (or Access).
|
||||||
|
5. Generate and copy the Token.
|
||||||
|
|
||||||
|
## 📋 Dependencies
|
||||||
|
|
||||||
|
This Pipe will automatically attempt to install the following dependencies:
|
||||||
|
|
||||||
|
* `github-copilot-sdk` (Python package)
|
||||||
|
* `github-copilot-cli` (Binary file, installed via official script)
|
||||||
|
|
||||||
|
## ⚠️ FAQ
|
||||||
|
|
||||||
|
* **Stuck on "Waiting..."**:
|
||||||
|
* Check if `GH_TOKEN` is correct and has `Copilot Requests` permission.
|
||||||
|
* Try changing `MODEL_ID` to `gpt-4o` or `copilot-chat`.
|
||||||
|
* **Images not recognized**:
|
||||||
|
* Ensure `MODEL_ID` is a model that supports multimodal input.
|
||||||
|
* **CLI Installation Failed**:
|
||||||
|
* Ensure the OpenWebUI container has internet access.
|
||||||
|
* You can manually download the CLI and specify `CLI_PATH` in Valves.
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT
|
||||||
84
docs/plugins/pipes/github-copilot-sdk.zh.md
Normal file
84
docs/plugins/pipes/github-copilot-sdk.zh.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# GitHub Copilot SDK 官方管道
|
||||||
|
|
||||||
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.1.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||||
|
|
||||||
|
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,允许你直接在 OpenWebUI 中使用 GitHub Copilot 模型(如 `gpt-5`, `gpt-5-mini`, `claude-sonnet-4.5`)。它基于官方 [GitHub Copilot SDK for Python](https://github.com/github/copilot-sdk) 构建,提供了原生级的集成体验。
|
||||||
|
|
||||||
|
## 🚀 最新特性 (v0.1.0)
|
||||||
|
|
||||||
|
* **♾️ 无限会话 (Infinite Sessions)**:支持长对话的自动上下文压缩,告别上下文超限错误!
|
||||||
|
* **🧠 思考过程展示**:实时显示模型的推理/思考过程(需模型支持)。
|
||||||
|
* **📂 工作目录控制**:支持设置受限工作目录,确保文件操作安全。
|
||||||
|
* **🔍 模型过滤**:支持通过关键词排除特定模型(如 `codex`, `haiku`)。
|
||||||
|
* **💾 会话持久化**: 改进的会话恢复逻辑,直接关联 OpenWebUI 聊天 ID,连接更稳定。
|
||||||
|
|
||||||
|
## ✨ 核心特性
|
||||||
|
|
||||||
|
* **🚀 官方 SDK 集成**:基于官方 SDK,稳定可靠。
|
||||||
|
* **💬 多轮对话支持**:自动拼接历史上下文,Copilot 能理解你的前文。
|
||||||
|
* **🌊 流式输出 (Streaming)**:支持打字机效果,响应迅速。
|
||||||
|
* **🖼️ 多模态支持**:支持上传图片,自动转换为附件发送给 Copilot(需模型支持)。
|
||||||
|
* **🛠️ 零配置安装**:自动检测并下载 GitHub Copilot CLI,开箱即用。
|
||||||
|
* **🔑 安全认证**:支持 Fine-grained Personal Access Tokens,权限最小化。
|
||||||
|
* **🐛 调试模式**:内置详细的日志输出,方便排查连接问题。
|
||||||
|
|
||||||
|
## 📦 安装与使用
|
||||||
|
|
||||||
|
### 1. 导入函数
|
||||||
|
|
||||||
|
1. 打开 OpenWebUI。
|
||||||
|
2. 进入 **Workspace** -> **Functions**。
|
||||||
|
3. 点击 **+** (创建函数)。
|
||||||
|
4. 将 `github_copilot_sdk_cn.py` 的内容完整粘贴进去。
|
||||||
|
5. 保存。
|
||||||
|
|
||||||
|
### 2. 配置 Valves (设置)
|
||||||
|
|
||||||
|
在函数列表中找到 "GitHub Copilot",点击 **⚙️ (Valves)** 图标进行配置:
|
||||||
|
|
||||||
|
| 参数 | 说明 | 默认值 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **GH_TOKEN** | **(必填)** 你的 GitHub Token。 | - |
|
||||||
|
| **MODEL_ID** | 使用的模型名称。推荐 `gpt-5-mini` 或 `gpt-5`。 | `gpt-5-mini` |
|
||||||
|
| **CLI_PATH** | Copilot CLI 的路径。如果未找到会自动下载。 | `/usr/local/bin/copilot` |
|
||||||
|
| **DEBUG** | 是否开启调试日志(输出到对话框)。 | `True` |
|
||||||
|
| **SHOW_THINKING** | 是否显示模型推理/思考过程。 | `True` |
|
||||||
|
| **EXCLUDE_KEYWORDS** | 排除包含这些关键词的模型 (逗号分隔)。 | - |
|
||||||
|
| **WORKSPACE_DIR** | 文件操作的受限工作目录。 | - |
|
||||||
|
| **INFINITE_SESSION** | 启用无限会话 (自动上下文压缩)。 | `True` |
|
||||||
|
| **COMPACTION_THRESHOLD** | 后台压缩阈值 (0.0-1.0)。 | `0.8` |
|
||||||
|
| **BUFFER_THRESHOLD** | 缓冲耗尽阈值 (0.0-1.0)。 | `0.95` |
|
||||||
|
|
||||||
|
### 3. 获取 GH_TOKEN
|
||||||
|
|
||||||
|
为了安全起见,推荐使用 **Fine-grained Personal Access Token**:
|
||||||
|
|
||||||
|
1. 访问 [GitHub Token Settings](https://github.com/settings/tokens?type=beta)。
|
||||||
|
2. 点击 **Generate new token**。
|
||||||
|
3. **Repository access**: 选择 `All repositories` 或 `Public Repositories`。
|
||||||
|
4. **Permissions**:
|
||||||
|
* 点击 **Account permissions**。
|
||||||
|
* 找到 **Copilot Requests**,选择 **Read and write** (或 Access)。
|
||||||
|
5. 生成并复制 Token。
|
||||||
|
|
||||||
|
## 📋 依赖说明
|
||||||
|
|
||||||
|
该 Pipe 会自动尝试安装以下依赖(如果环境中缺失):
|
||||||
|
|
||||||
|
* `github-copilot-sdk` (Python 包)
|
||||||
|
* `github-copilot-cli` (二进制文件,通过官方脚本安装)
|
||||||
|
|
||||||
|
## ⚠️ 常见问题
|
||||||
|
|
||||||
|
* **一直显示 "Waiting..."**:
|
||||||
|
* 检查 `GH_TOKEN` 是否正确且拥有 `Copilot Requests` 权限。
|
||||||
|
* 尝试将 `MODEL_ID` 改为 `gpt-4o` 或 `copilot-chat`。
|
||||||
|
* **图片无法识别**:
|
||||||
|
* 确保 `MODEL_ID` 是支持多模态的模型。
|
||||||
|
* **CLI 安装失败**:
|
||||||
|
* 确保 OpenWebUI 容器有外网访问权限。
|
||||||
|
* 你可以手动下载 CLI 并挂载到容器中,然后在 Valves 中指定 `CLI_PATH`。
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -15,19 +15,7 @@ Pipes allow you to:
|
|||||||
|
|
||||||
## Available Pipe Plugins
|
## Available Pipe Plugins
|
||||||
|
|
||||||
<div class="grid cards" markdown>
|
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.1.1) - Official GitHub Copilot SDK integration. Supports dynamic models, multi-turn conversation, streaming, multimodal input, and infinite sessions.
|
||||||
|
|
||||||
- :material-google:{ .lg .middle } **Gemini Manifold**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Integration pipeline for Google's Gemini models with full streaming support.
|
|
||||||
|
|
||||||
**Version:** 1.0.0
|
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](gemini-manifold.md)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -15,19 +15,7 @@ Pipes 可以用于:
|
|||||||
|
|
||||||
## 可用的 Pipe 插件
|
## 可用的 Pipe 插件
|
||||||
|
|
||||||
<div class="grid cards" markdown>
|
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.1.1) - GitHub Copilot SDK 官方集成。支持动态模型、多轮对话、流式输出、图片输入及无限会话。
|
||||||
|
|
||||||
- :material-google:{ .lg .middle } **Gemini Manifold**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
面向 Google Gemini 的集成流水线,支持完整流式返回。
|
|
||||||
|
|
||||||
**版本:** 1.0.0
|
|
||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](gemini-manifold.md)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
12
mkdocs.yml
12
mkdocs.yml
@@ -97,14 +97,14 @@ plugins:
|
|||||||
Documentation Guide: 文档编写指南
|
Documentation Guide: 文档编写指南
|
||||||
Smart Mind Map: 智能思维导图
|
Smart Mind Map: 智能思维导图
|
||||||
Smart Infographic: 智能信息图
|
Smart Infographic: 智能信息图
|
||||||
Knowledge Card: 知识卡片
|
Flash Card: 闪记卡
|
||||||
Export to Excel: 导出到 Excel
|
Export to Excel: 导出到 Excel
|
||||||
Export to Word: 导出为 Word
|
Export to Word: 导出为 Word
|
||||||
Summary: 摘要
|
Summary: 摘要
|
||||||
Async Context Compression: 异步上下文压缩
|
Async Context Compression: 异步上下文压缩
|
||||||
Context Enhancement: 上下文增强
|
Context Enhancement: 上下文增强
|
||||||
Gemini Manifold Companion: Gemini Manifold 伴侣
|
Multi-Model Context Merger: 多模型上下文合并
|
||||||
Gemini Manifold: Gemini Manifold
|
Web Gemini Multimodal Filter: Web Gemini 多模态过滤器
|
||||||
MoE Prompt Refiner: MoE 提示词优化器
|
MoE Prompt Refiner: MoE 提示词优化器
|
||||||
- minify:
|
- minify:
|
||||||
minify_html: true
|
minify_html: true
|
||||||
@@ -184,17 +184,17 @@ nav:
|
|||||||
- plugins/actions/index.md
|
- plugins/actions/index.md
|
||||||
- Smart Mind Map: plugins/actions/smart-mind-map.md
|
- Smart Mind Map: plugins/actions/smart-mind-map.md
|
||||||
- Smart Infographic: plugins/actions/smart-infographic.md
|
- Smart Infographic: plugins/actions/smart-infographic.md
|
||||||
- Knowledge Card: plugins/actions/knowledge-card.md
|
- Flash Card: plugins/actions/flash-card.md
|
||||||
- Export to Excel: plugins/actions/export-to-excel.md
|
- Export to Excel: plugins/actions/export-to-excel.md
|
||||||
- Export to Word: plugins/actions/export-to-word.md
|
- Export to Word: plugins/actions/export-to-word.md
|
||||||
- Filters:
|
- Filters:
|
||||||
- plugins/filters/index.md
|
- plugins/filters/index.md
|
||||||
- Async Context Compression: plugins/filters/async-context-compression.md
|
- Async Context Compression: plugins/filters/async-context-compression.md
|
||||||
- Context Enhancement: plugins/filters/context-enhancement.md
|
- Context Enhancement: plugins/filters/context-enhancement.md
|
||||||
- Gemini Manifold Companion: plugins/filters/gemini-manifold-companion.md
|
- Multi-Model Context Merger: plugins/filters/multi-model-context-merger.md
|
||||||
|
- Web Gemini Multimodal Filter: plugins/filters/web-gemini-multimodel.md
|
||||||
- Pipes:
|
- Pipes:
|
||||||
- plugins/pipes/index.md
|
- plugins/pipes/index.md
|
||||||
- Gemini Manifold: plugins/pipes/gemini-manifold.md
|
|
||||||
- Pipelines:
|
- Pipelines:
|
||||||
- plugins/pipelines/index.md
|
- plugins/pipelines/index.md
|
||||||
- MoE Prompt Refiner: plugins/pipelines/moe-prompt-refiner.md
|
- MoE Prompt Refiner: plugins/pipelines/moe-prompt-refiner.md
|
||||||
|
|||||||
@@ -124,10 +124,6 @@ Each plugin should include:
|
|||||||
Fu-Jie
|
Fu-Jie
|
||||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> **Note**: For detailed information about each plugin type, see the respective README files in each plugin type directory.
|
> **Note**: For detailed information about each plugin type, see the respective README files in each plugin type directory.
|
||||||
|
|||||||
@@ -124,10 +124,6 @@ plugins/
|
|||||||
Fu-Jie
|
Fu-Jie
|
||||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> **注意**:有关每种插件类型的详细信息,请参阅每个插件类型目录中的相应 README 文件。
|
> **注意**:有关每种插件类型的详细信息,请参阅每个插件类型目录中的相应 README 文件。
|
||||||
|
|||||||
@@ -230,7 +230,3 @@ except Exception as e:
|
|||||||
|
|
||||||
Fu-Jie
|
Fu-Jie
|
||||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|||||||
@@ -229,7 +229,3 @@ except Exception as e:
|
|||||||
|
|
||||||
Fu-Jie
|
Fu-Jie
|
||||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 🌊 Deep Dive
|
# 🌊 Deep Dive
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.0.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.0.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
|
|
||||||
A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.
|
A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.
|
||||||
|
|
||||||
@@ -81,3 +81,10 @@ The plugin generates a structured thinking timeline:
|
|||||||
|
|
||||||
- `deep_dive.py` - English version
|
- `deep_dive.py` - English version
|
||||||
- `deep_dive_cn.py` - Chinese version (精读)
|
- `deep_dive_cn.py` - Chinese version (精读)
|
||||||
|
|
||||||
|
## Troubleshooting ❓
|
||||||
|
|
||||||
|
- **Plugin not working?**: Check if the filter/action is enabled in the model settings.
|
||||||
|
- **Debug Logs**: Enable `SHOW_STATUS` in Valves to see progress updates.
|
||||||
|
- **Error Messages**: If you see an error, please copy the full error message and report it.
|
||||||
|
- **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 📖 精读
|
# 📖 精读
|
||||||
|
|
||||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.0.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.0.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||||
|
|
||||||
全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。
|
全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。
|
||||||
|
|
||||||
@@ -81,3 +81,10 @@
|
|||||||
|
|
||||||
- `deep_dive.py` - 英文版 (Deep Dive)
|
- `deep_dive.py` - 英文版 (Deep Dive)
|
||||||
- `deep_dive_cn.py` - 中文版 (精读)
|
- `deep_dive_cn.py` - 中文版 (精读)
|
||||||
|
|
||||||
|
## 故障排除 (Troubleshooting) ❓
|
||||||
|
|
||||||
|
- **插件不工作?**: 请检查是否在模型设置中启用了该过滤器/动作。
|
||||||
|
- **调试日志**: 在 Valves 中启用 `SHOW_STATUS` 以查看进度更新。
|
||||||
|
- **错误信息**: 如果看到错误,请复制完整的错误信息并报告。
|
||||||
|
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: Deep Dive
|
title: Deep Dive
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/open-webui
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg==
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg==
|
||||||
requirements: markdown
|
requirements: markdown
|
||||||
@@ -466,6 +466,10 @@ class Action:
|
|||||||
default=True,
|
default=True,
|
||||||
description="Whether to show operation status updates.",
|
description="Whether to show operation status updates.",
|
||||||
)
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="Whether to print debug logs in the browser console.",
|
||||||
|
)
|
||||||
MODEL_ID: str = Field(
|
MODEL_ID: str = Field(
|
||||||
default="",
|
default="",
|
||||||
description="LLM Model ID for analysis. Empty = use current model.",
|
description="LLM Model ID for analysis. Empty = use current model.",
|
||||||
@@ -501,6 +505,42 @@ class Action:
|
|||||||
"user_language": user_data.get("language", "en-US"),
|
"user_language": user_data.get("language", "en-US"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 _process_llm_output(self, llm_output: str) -> Dict[str, str]:
|
def _process_llm_output(self, llm_output: str) -> Dict[str, str]:
|
||||||
"""Parse LLM output and convert to styled HTML."""
|
"""Parse LLM output and convert to styled HTML."""
|
||||||
# Extract sections using flexible regex
|
# Extract sections using flexible regex
|
||||||
@@ -700,6 +740,26 @@ class Action:
|
|||||||
{"type": "notification", "data": {"type": ntype, "content": content}}
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""Print structured debug logs in the browser console"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
def _remove_existing_html(self, content: str) -> str:
|
def _remove_existing_html(self, content: str) -> str:
|
||||||
"""Removes existing plugin-generated HTML."""
|
"""Removes existing plugin-generated HTML."""
|
||||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: 精读
|
title: 精读
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/open-webui
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg==
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg==
|
||||||
requirements: markdown
|
requirements: markdown
|
||||||
@@ -466,6 +466,10 @@ class Action:
|
|||||||
default=True,
|
default=True,
|
||||||
description="是否显示操作状态更新。",
|
description="是否显示操作状态更新。",
|
||||||
)
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="是否在浏览器控制台打印调试日志。",
|
||||||
|
)
|
||||||
MODEL_ID: str = Field(
|
MODEL_ID: str = Field(
|
||||||
default="",
|
default="",
|
||||||
description="用于分析的 LLM 模型 ID。留空则使用当前模型。",
|
description="用于分析的 LLM 模型 ID。留空则使用当前模型。",
|
||||||
@@ -501,6 +505,42 @@ class Action:
|
|||||||
"user_language": user_data.get("language", "zh-CN"),
|
"user_language": user_data.get("language", "zh-CN"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 _process_llm_output(self, llm_output: str) -> Dict[str, str]:
|
def _process_llm_output(self, llm_output: str) -> Dict[str, str]:
|
||||||
"""解析 LLM 输出并转换为样式化 HTML。"""
|
"""解析 LLM 输出并转换为样式化 HTML。"""
|
||||||
# 使用灵活的正则提取各部分
|
# 使用灵活的正则提取各部分
|
||||||
@@ -694,6 +734,26 @@ class Action:
|
|||||||
{"type": "notification", "data": {"type": ntype, "content": content}}
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""在浏览器控制台打印结构化调试日志"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
def _remove_existing_html(self, content: str) -> str:
|
def _remove_existing_html(self, content: str) -> str:
|
||||||
"""移除已有的插件生成的 HTML。"""
|
"""移除已有的插件生成的 HTML。"""
|
||||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 📝 Export to Word (Enhanced)
|
# 📝 Export to Word (Enhanced)
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
|
|
||||||
Export conversation to Word (.docx) with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.
|
Export conversation to Word (.docx) with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.
|
||||||
|
|
||||||
@@ -86,3 +86,10 @@ Export conversation to Word (.docx) with **syntax highlighting**, **native math
|
|||||||
- **Font & Style Configuration**: Customizable fonts and table colors.
|
- **Font & Style Configuration**: Customizable fonts and table colors.
|
||||||
- **Mermaid Enhancements**: Hybrid SVG+PNG rendering, background color config.
|
- **Mermaid Enhancements**: Hybrid SVG+PNG rendering, background color config.
|
||||||
- **Performance**: Real-time progress updates for large exports.
|
- **Performance**: Real-time progress updates for large exports.
|
||||||
|
|
||||||
|
## Troubleshooting ❓
|
||||||
|
|
||||||
|
- **Plugin not working?**: Check if the filter/action is enabled in the model settings.
|
||||||
|
- **Debug Logs**: Check the browser console (F12) for detailed logs if available.
|
||||||
|
- **Error Messages**: If you see an error, please copy the full error message and report it.
|
||||||
|
- **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 📝 导出为 Word (增强版)
|
# 📝 导出为 Word (增强版)
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||||
|
|
||||||
将对话导出为 Word (.docx),支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用参考**和**增强表格格式**。
|
将对话导出为 Word (.docx),支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用参考**和**增强表格格式**。
|
||||||
|
|
||||||
@@ -86,3 +86,10 @@
|
|||||||
- **字体与样式配置**: 支持自定义中英文字体、代码字体以及表格颜色。
|
- **字体与样式配置**: 支持自定义中英文字体、代码字体以及表格颜色。
|
||||||
- **Mermaid 增强**: 混合 SVG+PNG 渲染,支持背景色配置。
|
- **Mermaid 增强**: 混合 SVG+PNG 渲染,支持背景色配置。
|
||||||
- **性能优化**: 导出大型文档时提供实时进度反馈。
|
- **性能优化**: 导出大型文档时提供实时进度反馈。
|
||||||
|
|
||||||
|
## 故障排除 (Troubleshooting) ❓
|
||||||
|
|
||||||
|
- **插件不工作?**: 请检查是否在模型设置中启用了该过滤器/动作。
|
||||||
|
- **调试日志**: 请查看浏览器控制台 (F12) 获取详细日志(如果可用)。
|
||||||
|
- **错误信息**: 如果看到错误,请复制完整的错误信息并报告。
|
||||||
|
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: Export to Word (Enhanced)
|
title: Export to Word (Enhanced)
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/open-webui
|
||||||
version: 0.4.3
|
version: 0.4.3
|
||||||
openwebui_id: fca6a315-2a45-42cc-8c96-55cbc85f87f2
|
openwebui_id: fca6a315-2a45-42cc-8c96-55cbc85f87f2
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
|
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
|
||||||
@@ -150,6 +150,14 @@ class Action:
|
|||||||
default="chat_title",
|
default="chat_title",
|
||||||
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
|
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
|
||||||
)
|
)
|
||||||
|
SHOW_STATUS: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="Whether to show operation status updates.",
|
||||||
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="Whether to print debug logs in the browser console.",
|
||||||
|
)
|
||||||
|
|
||||||
MAX_EMBED_IMAGE_MB: int = Field(
|
MAX_EMBED_IMAGE_MB: int = Field(
|
||||||
default=20,
|
default=20,
|
||||||
@@ -320,10 +328,100 @@ class Action:
|
|||||||
return msg
|
return msg
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
async def _send_notification(self, emitter: Callable, type: str, content: str):
|
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
||||||
await emitter(
|
"""Safely extracts user context information."""
|
||||||
{"type": "notification", "data": {"type": type, "content": content}}
|
if isinstance(__user__, (list, tuple)):
|
||||||
)
|
user_data = __user__[0] if __user__ else {}
|
||||||
|
elif isinstance(__user__, dict):
|
||||||
|
user_data = __user__
|
||||||
|
else:
|
||||||
|
user_data = {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"user_id": user_data.get("id", "unknown_user"),
|
||||||
|
"user_name": user_data.get("name", "User"),
|
||||||
|
"user_language": user_data.get("language", "en-US"),
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _emit_status(
|
||||||
|
self,
|
||||||
|
emitter: Optional[Callable[[Any], Awaitable[None]]],
|
||||||
|
description: str,
|
||||||
|
done: bool = False,
|
||||||
|
):
|
||||||
|
"""Emits a status update event."""
|
||||||
|
if self.valves.SHOW_STATUS and emitter:
|
||||||
|
await emitter(
|
||||||
|
{"type": "status", "data": {"description": description, "done": done}}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _emit_notification(
|
||||||
|
self,
|
||||||
|
emitter: Optional[Callable[[Any], Awaitable[None]]],
|
||||||
|
content: str,
|
||||||
|
ntype: str = "info",
|
||||||
|
):
|
||||||
|
"""Emits a notification event (info, success, warning, error)."""
|
||||||
|
if emitter:
|
||||||
|
await emitter(
|
||||||
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""Print structured debug logs in the browser console"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
async def action(
|
async def action(
|
||||||
self,
|
self,
|
||||||
@@ -397,14 +495,15 @@ class Action:
|
|||||||
message_content = self._strip_reasoning_blocks(message_content)
|
message_content = self._strip_reasoning_blocks(message_content)
|
||||||
|
|
||||||
if not message_content or not message_content.strip():
|
if not message_content or not message_content.strip():
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__, "error", self._get_msg("error_no_content")
|
__event_emitter__, self._get_msg("error_no_content"), "error"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Generate filename
|
# Generate filename
|
||||||
title = ""
|
title = ""
|
||||||
chat_id = self.extract_chat_id(body, __metadata__)
|
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||||
|
chat_id = chat_ctx["chat_id"]
|
||||||
|
|
||||||
# Fetch chat_title directly via chat_id as it's usually missing in body
|
# Fetch chat_title directly via chat_id as it's usually missing in body
|
||||||
chat_title = ""
|
chat_title = ""
|
||||||
@@ -873,10 +972,10 @@ class Action:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
"success",
|
|
||||||
self._get_msg("success", filename=filename),
|
self._get_msg("success", filename=filename),
|
||||||
|
"success",
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"message": "Download triggered"}
|
return {"message": "Download triggered"}
|
||||||
@@ -892,10 +991,10 @@ class Action:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
"error",
|
|
||||||
self._get_msg("error_export", error=str(e)),
|
self._get_msg("error_export", error=str(e)),
|
||||||
|
"error",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def generate_title_using_ai(
|
async def generate_title_using_ai(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: 导出为 Word (增强版)
|
title: 导出为 Word (增强版)
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/open-webui
|
||||||
version: 0.4.3
|
version: 0.4.3
|
||||||
openwebui_id: 8a6306c0-d005-4e46-aaae-8db3532c9ed5
|
openwebui_id: 8a6306c0-d005-4e46-aaae-8db3532c9ed5
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
|
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
|
||||||
@@ -150,6 +150,14 @@ class Action:
|
|||||||
default="chat_title",
|
default="chat_title",
|
||||||
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
|
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
|
||||||
)
|
)
|
||||||
|
SHOW_STATUS: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="是否显示操作状态更新。",
|
||||||
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="是否在浏览器控制台打印调试日志。",
|
||||||
|
)
|
||||||
|
|
||||||
最大嵌入图片大小MB: int = Field(
|
最大嵌入图片大小MB: int = Field(
|
||||||
default=20,
|
default=20,
|
||||||
@@ -320,10 +328,100 @@ class Action:
|
|||||||
return msg
|
return msg
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
async def _send_notification(self, emitter: Callable, type: str, content: str):
|
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
||||||
await emitter(
|
"""安全提取用户上下文信息。"""
|
||||||
{"type": "notification", "data": {"type": type, "content": content}}
|
if isinstance(__user__, (list, tuple)):
|
||||||
)
|
user_data = __user__[0] if __user__ else {}
|
||||||
|
elif isinstance(__user__, dict):
|
||||||
|
user_data = __user__
|
||||||
|
else:
|
||||||
|
user_data = {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"user_id": user_data.get("id", "unknown_user"),
|
||||||
|
"user_name": user_data.get("name", "用户"),
|
||||||
|
"user_language": user_data.get("language", "zh-CN"),
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _emit_status(
|
||||||
|
self,
|
||||||
|
emitter: Optional[Callable[[Any], Awaitable[None]]],
|
||||||
|
description: str,
|
||||||
|
done: bool = False,
|
||||||
|
):
|
||||||
|
"""Emits a status update event."""
|
||||||
|
if self.valves.SHOW_STATUS and emitter:
|
||||||
|
await emitter(
|
||||||
|
{"type": "status", "data": {"description": description, "done": done}}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _emit_notification(
|
||||||
|
self,
|
||||||
|
emitter: Optional[Callable[[Any], Awaitable[None]]],
|
||||||
|
content: str,
|
||||||
|
ntype: str = "info",
|
||||||
|
):
|
||||||
|
"""Emits a notification event (info, success, warning, error)."""
|
||||||
|
if emitter:
|
||||||
|
await emitter(
|
||||||
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""在浏览器控制台打印结构化调试日志"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
async def action(
|
async def action(
|
||||||
self,
|
self,
|
||||||
@@ -395,14 +493,15 @@ class Action:
|
|||||||
message_content = self._strip_reasoning_blocks(message_content)
|
message_content = self._strip_reasoning_blocks(message_content)
|
||||||
|
|
||||||
if not message_content or not message_content.strip():
|
if not message_content or not message_content.strip():
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__, "error", self._get_msg("error_no_content")
|
__event_emitter__, self._get_msg("error_no_content"), "error"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Generate filename
|
# Generate filename
|
||||||
title = ""
|
title = ""
|
||||||
chat_id = self.extract_chat_id(body, __metadata__)
|
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||||
|
chat_id = chat_ctx["chat_id"]
|
||||||
|
|
||||||
# Fetch chat_title directly via chat_id as it's usually missing in body
|
# Fetch chat_title directly via chat_id as it's usually missing in body
|
||||||
chat_title = ""
|
chat_title = ""
|
||||||
@@ -871,10 +970,10 @@ class Action:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
"success",
|
|
||||||
self._get_msg("success", filename=filename),
|
self._get_msg("success", filename=filename),
|
||||||
|
"success",
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"message": "Download triggered"}
|
return {"message": "Download triggered"}
|
||||||
@@ -890,10 +989,10 @@ class Action:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
"error",
|
|
||||||
self._get_msg("error_export", error=str(e)),
|
self._get_msg("error_export", error=str(e)),
|
||||||
|
"error",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def generate_title_using_ai(
|
async def generate_title_using_ai(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: Export to Excel
|
title: Export to Excel
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/open-webui
|
||||||
version: 0.3.7
|
version: 0.3.7
|
||||||
openwebui_id: 244b8f9d-7459-47d6-84d3-c7ae8e3ec710
|
openwebui_id: 244b8f9d-7459-47d6-84d3-c7ae8e3ec710
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
|
||||||
@@ -32,6 +32,10 @@ class Action:
|
|||||||
default="chat_title",
|
default="chat_title",
|
||||||
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
|
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
|
||||||
)
|
)
|
||||||
|
SHOW_STATUS: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="Whether to show operation status updates.",
|
||||||
|
)
|
||||||
EXPORT_SCOPE: Literal["last_message", "all_messages"] = Field(
|
EXPORT_SCOPE: Literal["last_message", "all_messages"] = Field(
|
||||||
default="last_message",
|
default="last_message",
|
||||||
description="Export Scope: 'last_message' (Last Message Only), 'all_messages' (All Messages)",
|
description="Export Scope: 'last_message' (Last Message Only), 'all_messages' (All Messages)",
|
||||||
@@ -40,14 +44,57 @@ class Action:
|
|||||||
default="",
|
default="",
|
||||||
description="Model ID for AI title generation. Leave empty to use the current chat model.",
|
description="Model ID for AI title generation. Leave empty to use the current chat model.",
|
||||||
)
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="Whether to print debug logs in the browser console.",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.valves = self.Valves()
|
self.valves = self.Valves()
|
||||||
|
|
||||||
async def _send_notification(self, emitter: Callable, type: str, content: str):
|
async def _emit_status(
|
||||||
await emitter(
|
self,
|
||||||
{"type": "notification", "data": {"type": type, "content": content}}
|
emitter: Optional[Callable[[Any], Awaitable[None]]],
|
||||||
)
|
description: str,
|
||||||
|
done: bool = False,
|
||||||
|
):
|
||||||
|
"""Emits a status update event."""
|
||||||
|
if self.valves.SHOW_STATUS and emitter:
|
||||||
|
await emitter(
|
||||||
|
{"type": "status", "data": {"description": description, "done": done}}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _emit_notification(
|
||||||
|
self,
|
||||||
|
emitter: Optional[Callable[[Any], Awaitable[None]]],
|
||||||
|
content: str,
|
||||||
|
ntype: str = "info",
|
||||||
|
):
|
||||||
|
"""Emits a notification event (info, success, warning, error)."""
|
||||||
|
if emitter:
|
||||||
|
await emitter(
|
||||||
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""Print structured debug logs in the browser console"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
async def action(
|
async def action(
|
||||||
self,
|
self,
|
||||||
@@ -190,17 +237,18 @@ class Action:
|
|||||||
# Notify user about the number of tables found
|
# Notify user about the number of tables found
|
||||||
table_count = len(all_tables)
|
table_count = len(all_tables)
|
||||||
if self.valves.EXPORT_SCOPE == "all_messages":
|
if self.valves.EXPORT_SCOPE == "all_messages":
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
"info",
|
|
||||||
f"Found {table_count} table(s) in all messages.",
|
f"Found {table_count} table(s) in all messages.",
|
||||||
|
"info",
|
||||||
)
|
)
|
||||||
# Wait a moment for user to see the notification before download dialog
|
# Wait a moment for user to see the notification before download dialog
|
||||||
await asyncio.sleep(1.5)
|
await asyncio.sleep(1.5)
|
||||||
# Generate Workbook Title (Filename)
|
# Generate Workbook Title (Filename)
|
||||||
# Use the title of the chat, or the first header of the first message with tables
|
# Use the title of the chat, or the first header of the first message with tables
|
||||||
title = ""
|
title = ""
|
||||||
chat_id = self.extract_chat_id(body, None)
|
chat_ctx = self._get_chat_context(body, None)
|
||||||
|
chat_id = chat_ctx["chat_id"]
|
||||||
chat_title = ""
|
chat_title = ""
|
||||||
if chat_id:
|
if chat_id:
|
||||||
chat_title = await self.fetch_chat_title(chat_id, user_id)
|
chat_title = await self.fetch_chat_title(chat_id, user_id)
|
||||||
@@ -330,8 +378,8 @@ class Action:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__, "error", "No tables found to export!"
|
__event_emitter__, "No tables found to export!", "error"
|
||||||
)
|
)
|
||||||
raise e
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -345,8 +393,8 @@ class Action:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__, "error", "No tables found to export!"
|
__event_emitter__, "No tables found to export!", "error"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def generate_title_using_ai(
|
async def generate_title_using_ai(
|
||||||
@@ -389,20 +437,20 @@ class Action:
|
|||||||
async def notification_task():
|
async def notification_task():
|
||||||
# Send initial notification immediately
|
# Send initial notification immediately
|
||||||
if event_emitter:
|
if event_emitter:
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
event_emitter,
|
event_emitter,
|
||||||
"info",
|
|
||||||
"AI is generating a filename for your Excel file...",
|
"AI is generating a filename for your Excel file...",
|
||||||
|
"info",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Subsequent notifications every 5 seconds
|
# Subsequent notifications every 5 seconds
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
if event_emitter:
|
if event_emitter:
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
event_emitter,
|
event_emitter,
|
||||||
"info",
|
|
||||||
"Still generating filename, please be patient...",
|
"Still generating filename, please be patient...",
|
||||||
|
"info",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run tasks concurrently
|
# Run tasks concurrently
|
||||||
@@ -432,10 +480,10 @@ class Action:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error generating title: {e}")
|
print(f"Error generating title: {e}")
|
||||||
if event_emitter:
|
if event_emitter:
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
event_emitter,
|
event_emitter,
|
||||||
"warning",
|
|
||||||
f"AI title generation failed, using default title. Error: {str(e)}",
|
f"AI title generation failed, using default title. Error: {str(e)}",
|
||||||
|
"warning",
|
||||||
)
|
)
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
@@ -450,24 +498,56 @@ class Action:
|
|||||||
return match.group(1).strip()
|
return match.group(1).strip()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
||||||
"""Extract chat_id from body or metadata"""
|
"""Safely extracts user context information."""
|
||||||
if isinstance(body, dict):
|
if isinstance(__user__, (list, tuple)):
|
||||||
chat_id = body.get("chat_id") or body.get("id")
|
user_data = __user__[0] if __user__ else {}
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
elif isinstance(__user__, dict):
|
||||||
return chat_id.strip()
|
user_data = __user__
|
||||||
|
else:
|
||||||
|
user_data = {}
|
||||||
|
|
||||||
for key in ("chat", "conversation"):
|
return {
|
||||||
nested = body.get(key)
|
"user_id": user_data.get("id", "unknown_user"),
|
||||||
if isinstance(nested, dict):
|
"user_name": user_data.get("name", "User"),
|
||||||
nested_id = nested.get("id") or nested.get("chat_id")
|
"user_language": user_data.get("language", "en-US"),
|
||||||
if isinstance(nested_id, str) and nested_id.strip():
|
}
|
||||||
return nested_id.strip()
|
|
||||||
if isinstance(metadata, dict):
|
def _get_chat_context(
|
||||||
chat_id = metadata.get("chat_id")
|
self, body: dict, __metadata__: Optional[dict] = None
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
) -> Dict[str, str]:
|
||||||
return chat_id.strip()
|
"""
|
||||||
return ""
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
async def fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
|
async def fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
|
||||||
"""Fetch chat title from database by chat_id"""
|
"""Fetch chat title from database by chat_id"""
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: 导出为 Excel
|
title: 导出为 Excel
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/open-webui
|
||||||
version: 0.3.7
|
version: 0.3.7
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
|
||||||
description: 从聊天消息中提取表格并导出为 Excel (.xlsx) 文件,支持智能格式化。
|
description: 从聊天消息中提取表格并导出为 Excel (.xlsx) 文件,支持智能格式化。
|
||||||
@@ -31,6 +31,10 @@ class Action:
|
|||||||
default="chat_title",
|
default="chat_title",
|
||||||
description="标题来源: 'chat_title' (对话标题), 'ai_generated' (AI生成), 'markdown_title' (Markdown标题)",
|
description="标题来源: 'chat_title' (对话标题), 'ai_generated' (AI生成), 'markdown_title' (Markdown标题)",
|
||||||
)
|
)
|
||||||
|
SHOW_STATUS: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="是否显示操作状态更新。",
|
||||||
|
)
|
||||||
EXPORT_SCOPE: Literal["last_message", "all_messages"] = Field(
|
EXPORT_SCOPE: Literal["last_message", "all_messages"] = Field(
|
||||||
default="last_message",
|
default="last_message",
|
||||||
description="导出范围: 'last_message' (仅最后一条消息), 'all_messages' (所有消息)",
|
description="导出范围: 'last_message' (仅最后一条消息), 'all_messages' (所有消息)",
|
||||||
@@ -39,14 +43,57 @@ class Action:
|
|||||||
default="",
|
default="",
|
||||||
description="AI 标题生成模型 ID。留空则使用当前对话模型。",
|
description="AI 标题生成模型 ID。留空则使用当前对话模型。",
|
||||||
)
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="是否在浏览器控制台打印调试日志。",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.valves = self.Valves()
|
self.valves = self.Valves()
|
||||||
|
|
||||||
async def _send_notification(self, emitter: Callable, type: str, content: str):
|
async def _emit_status(
|
||||||
await emitter(
|
self,
|
||||||
{"type": "notification", "data": {"type": type, "content": content}}
|
emitter: Optional[Callable[[Any], Awaitable[None]]],
|
||||||
)
|
description: str,
|
||||||
|
done: bool = False,
|
||||||
|
):
|
||||||
|
"""Emits a status update event."""
|
||||||
|
if self.valves.SHOW_STATUS and emitter:
|
||||||
|
await emitter(
|
||||||
|
{"type": "status", "data": {"description": description, "done": done}}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _emit_notification(
|
||||||
|
self,
|
||||||
|
emitter: Optional[Callable[[Any], Awaitable[None]]],
|
||||||
|
content: str,
|
||||||
|
ntype: str = "info",
|
||||||
|
):
|
||||||
|
"""Emits a notification event (info, success, warning, error)."""
|
||||||
|
if emitter:
|
||||||
|
await emitter(
|
||||||
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""在浏览器控制台打印结构化调试日志"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
async def action(
|
async def action(
|
||||||
self,
|
self,
|
||||||
@@ -180,17 +227,18 @@ class Action:
|
|||||||
# 通知用户提取到的表格数量
|
# 通知用户提取到的表格数量
|
||||||
table_count = len(all_tables)
|
table_count = len(all_tables)
|
||||||
if self.valves.EXPORT_SCOPE == "all_messages":
|
if self.valves.EXPORT_SCOPE == "all_messages":
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
"info",
|
|
||||||
f"从所有消息中提取到 {table_count} 个表格。",
|
f"从所有消息中提取到 {table_count} 个表格。",
|
||||||
|
"info",
|
||||||
)
|
)
|
||||||
# 等待片刻让用户看到通知,再触发下载
|
# 等待片刻让用户看到通知,再触发下载
|
||||||
await asyncio.sleep(1.5)
|
await asyncio.sleep(1.5)
|
||||||
|
|
||||||
# Generate Workbook Title (Filename)
|
# Generate Workbook Title (Filename)
|
||||||
title = ""
|
title = ""
|
||||||
chat_id = self.extract_chat_id(body, None)
|
chat_ctx = self._get_chat_context(body, None)
|
||||||
|
chat_id = chat_ctx["chat_id"]
|
||||||
chat_title = ""
|
chat_title = ""
|
||||||
if chat_id:
|
if chat_id:
|
||||||
chat_title = await self.fetch_chat_title(chat_id, user_id)
|
chat_title = await self.fetch_chat_title(chat_id, user_id)
|
||||||
@@ -318,8 +366,8 @@ class Action:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__, "error", "未找到可导出的表格!"
|
__event_emitter__, "未找到可导出的表格!", "error"
|
||||||
)
|
)
|
||||||
raise e
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -333,8 +381,8 @@ class Action:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
__event_emitter__, "error", "未找到可导出的表格!"
|
__event_emitter__, "未找到可导出的表格!", "error"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def generate_title_using_ai(
|
async def generate_title_using_ai(
|
||||||
@@ -377,20 +425,20 @@ class Action:
|
|||||||
async def notification_task():
|
async def notification_task():
|
||||||
# 立即发送首次通知
|
# 立即发送首次通知
|
||||||
if event_emitter:
|
if event_emitter:
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
event_emitter,
|
event_emitter,
|
||||||
"info",
|
|
||||||
"AI 正在为您生成文件名,请稍候...",
|
"AI 正在为您生成文件名,请稍候...",
|
||||||
|
"info",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 之后每5秒通知一次
|
# 之后每5秒通知一次
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
if event_emitter:
|
if event_emitter:
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
event_emitter,
|
event_emitter,
|
||||||
"info",
|
|
||||||
"文件名生成中,请耐心等待...",
|
"文件名生成中,请耐心等待...",
|
||||||
|
"info",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 并发运行任务
|
# 并发运行任务
|
||||||
@@ -420,10 +468,10 @@ class Action:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"生成标题时出错: {e}")
|
print(f"生成标题时出错: {e}")
|
||||||
if event_emitter:
|
if event_emitter:
|
||||||
await self._send_notification(
|
await self._emit_notification(
|
||||||
event_emitter,
|
event_emitter,
|
||||||
"warning",
|
|
||||||
f"AI 文件名生成失败,将使用默认名称。错误: {str(e)}",
|
f"AI 文件名生成失败,将使用默认名称。错误: {str(e)}",
|
||||||
|
"warning",
|
||||||
)
|
)
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
@@ -438,24 +486,56 @@ class Action:
|
|||||||
return match.group(1).strip()
|
return match.group(1).strip()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
||||||
"""从 body 或 metadata 中提取 chat_id"""
|
"""安全提取用户上下文信息。"""
|
||||||
if isinstance(body, dict):
|
if isinstance(__user__, (list, tuple)):
|
||||||
chat_id = body.get("chat_id") or body.get("id")
|
user_data = __user__[0] if __user__ else {}
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
elif isinstance(__user__, dict):
|
||||||
return chat_id.strip()
|
user_data = __user__
|
||||||
|
else:
|
||||||
|
user_data = {}
|
||||||
|
|
||||||
for key in ("chat", "conversation"):
|
return {
|
||||||
nested = body.get(key)
|
"user_id": user_data.get("id", "unknown_user"),
|
||||||
if isinstance(nested, dict):
|
"user_name": user_data.get("name", "用户"),
|
||||||
nested_id = nested.get("id") or nested.get("chat_id")
|
"user_language": user_data.get("language", "zh-CN"),
|
||||||
if isinstance(nested_id, str) and nested_id.strip():
|
}
|
||||||
return nested_id.strip()
|
|
||||||
if isinstance(metadata, dict):
|
def _get_chat_context(
|
||||||
chat_id = metadata.get("chat_id")
|
self, body: dict, __metadata__: Optional[dict] = None
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
) -> Dict[str, str]:
|
||||||
return chat_id.strip()
|
"""
|
||||||
return ""
|
统一提取聊天上下文信息 (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(),
|
||||||
|
}
|
||||||
|
|
||||||
async def fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
|
async def fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
|
||||||
"""通过 chat_id 从数据库获取对话标题"""
|
"""通过 chat_id 从数据库获取对话标题"""
|
||||||
|
|||||||
@@ -2,9 +2,18 @@
|
|||||||
|
|
||||||
Generate polished learning flashcards from any text—title, summary, key points, tags, and category—ready for review and sharing.
|
Generate polished learning flashcards from any text—title, summary, key points, tags, and category—ready for review and sharing.
|
||||||
|
|
||||||
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.2.4 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
|
|
||||||
|
## Preview 📸
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Highlights
|
## What's New
|
||||||
|
|
||||||
|
### v0.2.4
|
||||||
|
- **Clean Output**: Removed debug messages from output.
|
||||||
|
|
||||||
|
## Key Features 🔑
|
||||||
|
|
||||||
- **One-click generation**: Drop in text, get a structured card.
|
- **One-click generation**: Drop in text, get a structured card.
|
||||||
- **Concise extraction**: 3–5 key points and 2–4 tags automatically surfaced.
|
- **Concise extraction**: 3–5 key points and 2–4 tags automatically surfaced.
|
||||||
@@ -12,7 +21,14 @@ Generate polished learning flashcards from any text—title, summary, key points
|
|||||||
- **Progressive merge**: Multiple runs append cards into the same HTML container; enable clearing to reset.
|
- **Progressive merge**: Multiple runs append cards into the same HTML container; enable clearing to reset.
|
||||||
- **Status updates**: Live notifications for generating/done/error.
|
- **Status updates**: Live notifications for generating/done/error.
|
||||||
|
|
||||||
## Parameters
|
## How to Use 🛠️
|
||||||
|
|
||||||
|
1. **Install**: Add the plugin to your OpenWebUI instance.
|
||||||
|
2. **Configure**: Adjust settings in the Valves menu (optional).
|
||||||
|
3. **Trigger**: Send text to the chat.
|
||||||
|
4. **Result**: Watch status updates; the card HTML is embedded into the latest message.
|
||||||
|
|
||||||
|
## Configuration (Valves) ⚙️
|
||||||
|
|
||||||
| Param | Description | Default |
|
| Param | Description | Default |
|
||||||
| ------------------- | ------------------------------------------------------------ | ------- |
|
| ------------------- | ------------------------------------------------------------ | ------- |
|
||||||
@@ -23,34 +39,9 @@ Generate polished learning flashcards from any text—title, summary, key points
|
|||||||
| CLEAR_PREVIOUS_HTML | Whether to clear previous card HTML (otherwise append/merge) | false |
|
| CLEAR_PREVIOUS_HTML | Whether to clear previous card HTML (otherwise append/merge) | false |
|
||||||
| MESSAGE_COUNT | Use the latest N messages to build the card | 1 |
|
| MESSAGE_COUNT | Use the latest N messages to build the card | 1 |
|
||||||
|
|
||||||
## How to Use
|
## Troubleshooting ❓
|
||||||
|
|
||||||
1. Install and enable “Flash Card”.
|
- **Plugin not working?**: Check if the filter/action is enabled in the model settings.
|
||||||
2. Send the text to the chat (multi-turn supported; governed by MESSAGE_COUNT).
|
- **Debug Logs**: Enable `SHOW_STATUS` in Valves to see progress updates.
|
||||||
3. Watch status updates; the card HTML is embedded into the latest message.
|
- **Error Messages**: If you see an error, please copy the full error message and report it.
|
||||||
4. To regenerate from scratch, toggle CLEAR_PREVIOUS_HTML or resend text.
|
- **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
- JSON fields: `title`, `summary`, `key_points` (3–5), `tags` (2–4), `category`.
|
|
||||||
- UI: gradient-styled card with tags, key-point list; supports stacking multiple cards.
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
- Very short text triggers a prompt to add more; consider summarizing first.
|
|
||||||
- Long text is accepted; for deep analysis, pre-condense with other tools before card creation.
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
Fu-Jie
|
|
||||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
### v0.2.4
|
|
||||||
|
|
||||||
- Removed debug messages from output
|
|
||||||
|
|||||||
@@ -2,9 +2,18 @@
|
|||||||
|
|
||||||
快速将文本提炼为精美的学习记忆卡片,自动抽取标题、摘要、关键要点、标签和分类,适合复习与分享。
|
快速将文本提炼为精美的学习记忆卡片,自动抽取标题、摘要、关键要点、标签和分类,适合复习与分享。
|
||||||
|
|
||||||
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.2.4 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||||
|
|
||||||
|
## 预览 📸
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 功能亮点
|
## 更新日志
|
||||||
|
|
||||||
|
### v0.2.4
|
||||||
|
- **输出优化**: 移除输出中的调试信息。
|
||||||
|
|
||||||
|
## 核心特性 🔑
|
||||||
|
|
||||||
- **一键生成**:输入任意文本,直接产出结构化卡片。
|
- **一键生成**:输入任意文本,直接产出结构化卡片。
|
||||||
- **要点聚合**:自动提取 3-5 个记忆要点与 2-4 个标签。
|
- **要点聚合**:自动提取 3-5 个记忆要点与 2-4 个标签。
|
||||||
@@ -12,7 +21,14 @@
|
|||||||
- **渐进合并**:多次调用会将新卡片合并到同一 HTML 容器中;如需重置可启用清空选项。
|
- **渐进合并**:多次调用会将新卡片合并到同一 HTML 容器中;如需重置可启用清空选项。
|
||||||
- **状态提示**:实时推送“生成中/完成/错误”等状态与通知。
|
- **状态提示**:实时推送“生成中/完成/错误”等状态与通知。
|
||||||
|
|
||||||
## 参数说明
|
## 使用方法 🛠️
|
||||||
|
|
||||||
|
1. **安装**: 在插件市场安装并启用“闪记卡”。
|
||||||
|
2. **配置**: 根据需要调整 Valves 设置(可选)。
|
||||||
|
3. **触发**: 将待整理的文本发送到聊天框。
|
||||||
|
4. **结果**: 等待状态提示,卡片将以 HTML 形式嵌入到最新消息中。
|
||||||
|
|
||||||
|
## 配置参数 (Valves) ⚙️
|
||||||
|
|
||||||
| 参数 | 说明 | 默认值 |
|
| 参数 | 说明 | 默认值 |
|
||||||
| ------------------- | ------------------------------------- | ------ |
|
| ------------------- | ------------------------------------- | ------ |
|
||||||
@@ -23,34 +39,9 @@
|
|||||||
| CLEAR_PREVIOUS_HTML | 是否清空旧的卡片 HTML(否则合并追加) | false |
|
| CLEAR_PREVIOUS_HTML | 是否清空旧的卡片 HTML(否则合并追加) | false |
|
||||||
| MESSAGE_COUNT | 取最近 N 条消息生成卡片 | 1 |
|
| MESSAGE_COUNT | 取最近 N 条消息生成卡片 | 1 |
|
||||||
|
|
||||||
## 使用步骤
|
## 故障排除 (Troubleshooting) ❓
|
||||||
|
|
||||||
1. 在插件市场安装并启用“闪记卡”。
|
- **插件不工作?**: 请检查是否在模型设置中启用了该过滤器/动作。
|
||||||
2. 将待整理的文本发送到聊天框(可多轮对话,受 MESSAGE_COUNT 控制)。
|
- **调试日志**: 在 Valves 中启用 `SHOW_STATUS` 以查看进度更新。
|
||||||
3. 等待状态提示,卡片将以 HTML 形式嵌入到最新消息中。
|
- **错误信息**: 如果看到错误,请复制完整的错误信息并报告。
|
||||||
4. 若需重新生成,开启 CLEAR_PREVIOUS_HTML 或直接重发文本。
|
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|
||||||
## 输出格式
|
|
||||||
|
|
||||||
- JSON 字段:`title`、`summary`、`key_points`(3-5 条)、`tags`(2-4 条)、`category`。
|
|
||||||
- 前端呈现:单卡片带渐变主题、标签胶囊、要点列表,可连续追加多张卡片。
|
|
||||||
|
|
||||||
## 使用建议
|
|
||||||
|
|
||||||
- 文本过短会提醒补充,可先汇总再生成卡片。
|
|
||||||
- 长文本无需截断,直接生成;如需深度分析可先用其他工具精炼后再制作卡片。
|
|
||||||
|
|
||||||
## 作者
|
|
||||||
|
|
||||||
Fu-Jie
|
|
||||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
### v0.2.4
|
|
||||||
|
|
||||||
- 移除输出中的调试信息
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: Flash Card
|
title: Flash Card
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/open-webui
|
||||||
version: 0.2.4
|
version: 0.2.4
|
||||||
openwebui_id: 65a2ea8f-2a13-4587-9d76-55eea0035cc8
|
openwebui_id: 65a2ea8f-2a13-4587-9d76-55eea0035cc8
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
|
||||||
@@ -89,6 +89,10 @@ class Action:
|
|||||||
default=True,
|
default=True,
|
||||||
description="Whether to show status updates in the chat interface.",
|
description="Whether to show status updates in the chat interface.",
|
||||||
)
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="Whether to print debug logs in the browser console.",
|
||||||
|
)
|
||||||
CLEAR_PREVIOUS_HTML: bool = Field(
|
CLEAR_PREVIOUS_HTML: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
description="Whether to force clear previous plugin results (if True, overwrites instead of merging).",
|
description="Whether to force clear previous plugin results (if True, overwrites instead of merging).",
|
||||||
@@ -116,6 +120,42 @@ class Action:
|
|||||||
"user_language": user_data.get("language", "en-US"),
|
"user_language": user_data.get("language", "en-US"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
async def action(
|
async def action(
|
||||||
self,
|
self,
|
||||||
body: dict,
|
body: dict,
|
||||||
@@ -331,6 +371,26 @@ Important Principles:
|
|||||||
{"type": "notification", "data": {"type": ntype, "content": content}}
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""Print structured debug logs in the browser console"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
def _remove_existing_html(self, content: str) -> str:
|
def _remove_existing_html(self, content: str) -> str:
|
||||||
"""Removes existing plugin-generated HTML code blocks from the content."""
|
"""Removes existing plugin-generated HTML code blocks from the content."""
|
||||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: 闪记卡 (Flash Card)
|
title: 闪记卡 (Flash Card)
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/open-webui
|
||||||
version: 0.2.4
|
version: 0.2.4
|
||||||
openwebui_id: 4a31eac3-a3c4-4c30-9ca5-dab36b5fac65
|
openwebui_id: 4a31eac3-a3c4-4c30-9ca5-dab36b5fac65
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
|
||||||
@@ -86,6 +86,10 @@ class Action:
|
|||||||
SHOW_STATUS: bool = Field(
|
SHOW_STATUS: bool = Field(
|
||||||
default=True, description="是否在聊天界面显示状态更新。"
|
default=True, description="是否在聊天界面显示状态更新。"
|
||||||
)
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="是否在浏览器控制台打印调试日志。",
|
||||||
|
)
|
||||||
CLEAR_PREVIOUS_HTML: bool = Field(
|
CLEAR_PREVIOUS_HTML: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
description="是否强制清除旧的插件结果(如果为 True,则不合并,直接覆盖)。",
|
description="是否强制清除旧的插件结果(如果为 True,则不合并,直接覆盖)。",
|
||||||
@@ -113,6 +117,42 @@ class Action:
|
|||||||
"user_language": user_data.get("language", "zh-CN"),
|
"user_language": user_data.get("language", "zh-CN"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
async def action(
|
async def action(
|
||||||
self,
|
self,
|
||||||
body: dict,
|
body: dict,
|
||||||
@@ -314,6 +354,26 @@ class Action:
|
|||||||
{"type": "notification", "data": {"type": ntype, "content": content}}
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""在浏览器控制台打印结构化调试日志"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
def _remove_existing_html(self, content: str) -> str:
|
def _remove_existing_html(self, content: str) -> str:
|
||||||
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
|
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
|
||||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 📊 Smart Infographic (AntV)
|
# 📊 Smart Infographic (AntV)
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.4.9 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.4.9 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
|
|
||||||
An Open WebUI plugin powered by the AntV Infographic engine. It transforms long text into professional, beautiful infographics with a single click.
|
An Open WebUI plugin powered by the AntV Infographic engine. It transforms long text into professional, beautiful infographics with a single click.
|
||||||
|
|
||||||
@@ -56,6 +56,14 @@ You can adjust the following parameters in the plugin settings to optimize the g
|
|||||||
| **Hierarchy** | `hierarchy-tree-tech-style-capsule-item`, `hierarchy-structure` | Org Charts, Structures |
|
| **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 |
|
| **Charts** | `chart-column-simple`, `chart-bar-plain-text`, `chart-line-plain-text`, `chart-wordcloud` | Trends, Distributions, Metrics |
|
||||||
|
|
||||||
|
## Troubleshooting ❓
|
||||||
|
|
||||||
|
- **Plugin not working?**: Check if the filter/action is enabled in the model settings.
|
||||||
|
- **Debug Logs**: Enable `SHOW_STATUS` in Valves to see progress updates.
|
||||||
|
- **Error Messages**: If you see an error, please copy the full error message and report it.
|
||||||
|
- **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|
||||||
|
|
||||||
## 📝 Syntax Example (For Advanced Users)
|
## 📝 Syntax Example (For Advanced Users)
|
||||||
|
|
||||||
You can also input this syntax directly for AI to render:
|
You can also input this syntax directly for AI to render:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 📊 智能信息图 (AntV Infographic)
|
# 📊 智能信息图 (AntV Infographic)
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.4.9 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.4.9 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||||
|
|
||||||
基于 AntV Infographic 引擎的 Open WebUI 插件,能够将长文本内容一键转换为专业、美观的信息图表。
|
基于 AntV Infographic 引擎的 Open WebUI 插件,能够将长文本内容一键转换为专业、美观的信息图表。
|
||||||
|
|
||||||
@@ -56,6 +56,14 @@
|
|||||||
| **层级与结构** | `hierarchy-tree-tech-style-capsule-item`, `hierarchy-structure` | 组织架构、层级关系 |
|
| **层级与结构** | `hierarchy-tree-tech-style-capsule-item`, `hierarchy-structure` | 组织架构、层级关系 |
|
||||||
| **图表与数据** | `chart-column-simple`, `chart-bar-plain-text`, `chart-line-plain-text`, `chart-wordcloud` | 数据趋势、比例分布、数值对比 |
|
| **图表与数据** | `chart-column-simple`, `chart-bar-plain-text`, `chart-line-plain-text`, `chart-wordcloud` | 数据趋势、比例分布、数值对比 |
|
||||||
|
|
||||||
|
## 故障排除 (Troubleshooting) ❓
|
||||||
|
|
||||||
|
- **插件不工作?**: 请检查是否在模型设置中启用了该过滤器/动作。
|
||||||
|
- **调试日志**: 在 Valves 中启用 `SHOW_STATUS` 以查看进度更新。
|
||||||
|
- **错误信息**: 如果看到错误,请复制完整的错误信息并报告。
|
||||||
|
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|
||||||
|
|
||||||
## 📝 语法示例 (高级用户)
|
## 📝 语法示例 (高级用户)
|
||||||
|
|
||||||
你也可以直接输入以下语法让 AI 渲染:
|
你也可以直接输入以下语法让 AI 渲染:
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
# 📊 Smart Infographic (AntV)
|
|
||||||
|
|
||||||
An Open WebUI plugin powered by the AntV Infographic engine. It transforms long text into professional, beautiful infographics with a single click.
|
|
||||||
|
|
||||||
## ✨ 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.
|
|
||||||
- 📥 **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.
|
|
||||||
|
|
||||||
## 🛠️ Supported Template Types
|
|
||||||
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
## 🚀 How to Use
|
|
||||||
|
|
||||||
1. **Install**: Search for "Smart Infographic" in the Open WebUI Community and install.
|
|
||||||
2. **Trigger**: Enter your text in the chat, then click the **Action Button** (📊 icon) next to the input box.
|
|
||||||
3. **AI Processing**: The AI analyzes the text and generates the infographic syntax.
|
|
||||||
4. **Preview & Download**: Preview the result and use the download buttons below to save your infographic.
|
|
||||||
|
|
||||||
## ⚙️ Configuration (Valves)
|
|
||||||
|
|
||||||
You can adjust the following parameters in the plugin settings to optimize the generation:
|
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
|
||||||
| :--- | :--- | :--- |
|
|
||||||
| **Show Status (SHOW_STATUS)** | `True` | Whether to show real-time AI analysis and generation status in the chat. |
|
|
||||||
| **Model ID (MODEL_ID)** | `Empty` | Specify the LLM model for text analysis. If empty, the current chat model is used. |
|
|
||||||
| **Min Text Length (MIN_TEXT_LENGTH)** | `100` | Minimum characters required to trigger analysis, preventing accidental triggers on short text. |
|
|
||||||
| **Clear Previous (CLEAR_PREVIOUS_HTML)** | `False` | Whether to clear previous charts. If `False`, new charts will be appended below. |
|
|
||||||
| **Message Count (MESSAGE_COUNT)** | `1` | Number of recent messages to use for analysis. Increase this for more context. |
|
|
||||||
|
|
||||||
## 📝 Syntax Example (For Advanced Users)
|
|
||||||
|
|
||||||
You can also input this syntax directly for AI to render:
|
|
||||||
|
|
||||||
```infographic
|
|
||||||
infographic list-grid
|
|
||||||
data
|
|
||||||
title 🚀 Plugin Benefits
|
|
||||||
desc Why use the Smart Infographic plugin
|
|
||||||
items
|
|
||||||
- label Fast Generation
|
|
||||||
desc Convert text to charts in seconds
|
|
||||||
- label Beautiful Design
|
|
||||||
desc Uses AntV professional design standards
|
|
||||||
```
|
|
||||||
|
|
||||||
## 👨💻 Author
|
|
||||||
|
|
||||||
**jeff**
|
|
||||||
- GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
|
||||||
|
|
||||||
## 📄 License
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 234 KiB |
@@ -2,6 +2,7 @@
|
|||||||
title: 📊 Smart Infographic (AntV)
|
title: 📊 Smart Infographic (AntV)
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
|
funding_url: https://github.com/open-webui
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
|
||||||
version: 1.4.9
|
version: 1.4.9
|
||||||
openwebui_id: ad6f0c7f-c571-4dea-821d-8e71697274cf
|
openwebui_id: ad6f0c7f-c571-4dea-821d-8e71697274cf
|
||||||
@@ -263,6 +264,8 @@ data
|
|||||||
4. **Indentation**: Use 2 spaces.
|
4. **Indentation**: Use 2 spaces.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
USER_PROMPT_GENERATE_INFOGRAPHIC = """
|
USER_PROMPT_GENERATE_INFOGRAPHIC = """
|
||||||
Please analyze the following text content and convert its core information into AntV Infographic syntax format.
|
Please analyze the following text content and convert its core information into AntV Infographic syntax format.
|
||||||
|
|
||||||
@@ -947,49 +950,64 @@ class Action:
|
|||||||
default="image",
|
default="image",
|
||||||
description="Output mode: 'html' for interactive HTML, or 'image' to embed as Markdown image (default).",
|
description="Output mode: 'html' for interactive HTML, or 'image' to embed as Markdown image (default).",
|
||||||
)
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="Whether to print debug logs in the browser console.",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.valves = self.Valves()
|
self.valves = self.Valves()
|
||||||
|
|
||||||
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
||||||
"""Extract chat_id from body or metadata"""
|
"""Safely extracts user context information."""
|
||||||
|
if isinstance(__user__, (list, tuple)):
|
||||||
|
user_data = __user__[0] if __user__ else {}
|
||||||
|
elif isinstance(__user__, dict):
|
||||||
|
user_data = __user__
|
||||||
|
else:
|
||||||
|
user_data = {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"user_id": user_data.get("id", "unknown_user"),
|
||||||
|
"user_name": user_data.get("name", "User"),
|
||||||
|
"user_language": user_data.get("language", "en-US"),
|
||||||
|
}
|
||||||
|
|
||||||
|
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):
|
if isinstance(body, dict):
|
||||||
chat_id = body.get("chat_id")
|
chat_id = body.get("chat_id", "")
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
message_id = body.get("id", "") # message_id is usually 'id' in body
|
||||||
return chat_id.strip()
|
|
||||||
|
|
||||||
body_metadata = body.get("metadata", {})
|
# Check body.metadata as fallback
|
||||||
if isinstance(body_metadata, dict):
|
if not chat_id or not message_id:
|
||||||
chat_id = body_metadata.get("chat_id")
|
body_metadata = body.get("metadata", {})
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
if isinstance(body_metadata, dict):
|
||||||
return chat_id.strip()
|
if not chat_id:
|
||||||
|
chat_id = body_metadata.get("chat_id", "")
|
||||||
|
if not message_id:
|
||||||
|
message_id = body_metadata.get("message_id", "")
|
||||||
|
|
||||||
if isinstance(metadata, dict):
|
# 2. Try to get from __metadata__ (as supplement)
|
||||||
chat_id = metadata.get("chat_id")
|
if __metadata__ and isinstance(__metadata__, dict):
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
if not chat_id:
|
||||||
return chat_id.strip()
|
chat_id = __metadata__.get("chat_id", "")
|
||||||
|
if not message_id:
|
||||||
|
message_id = __metadata__.get("message_id", "")
|
||||||
|
|
||||||
return ""
|
return {
|
||||||
|
"chat_id": str(chat_id).strip(),
|
||||||
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str:
|
"message_id": str(message_id).strip(),
|
||||||
"""Extract message_id from body or metadata"""
|
}
|
||||||
if isinstance(body, dict):
|
|
||||||
message_id = body.get("id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
body_metadata = body.get("metadata", {})
|
|
||||||
if isinstance(body_metadata, dict):
|
|
||||||
message_id = body_metadata.get("message_id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
if isinstance(metadata, dict):
|
|
||||||
message_id = metadata.get("message_id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _extract_infographic_syntax(self, llm_output: str) -> str:
|
def _extract_infographic_syntax(self, llm_output: str) -> str:
|
||||||
"""Extract infographic syntax from LLM output"""
|
"""Extract infographic syntax from LLM output"""
|
||||||
@@ -1018,6 +1036,24 @@ class Action:
|
|||||||
{"type": "notification", "data": {"type": ntype, "content": content}}
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""Print structured debug logs in the browser console"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
def _remove_existing_html(self, content: str) -> str:
|
def _remove_existing_html(self, content: str) -> str:
|
||||||
"""Remove existing plugin-generated HTML code blocks from content"""
|
"""Remove existing plugin-generated HTML code blocks from content"""
|
||||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||||
@@ -1628,8 +1664,9 @@ class Action:
|
|||||||
# Check output mode
|
# Check output mode
|
||||||
if self.valves.OUTPUT_MODE == "image":
|
if self.valves.OUTPUT_MODE == "image":
|
||||||
# Image mode: use JavaScript to render and embed as Markdown image
|
# Image mode: use JavaScript to render and embed as Markdown image
|
||||||
chat_id = self._extract_chat_id(body, body.get("metadata"))
|
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||||
message_id = self._extract_message_id(body, body.get("metadata"))
|
chat_id = chat_ctx["chat_id"]
|
||||||
|
message_id = chat_ctx["message_id"]
|
||||||
|
|
||||||
await self._emit_status(
|
await self._emit_status(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
title: 📊 智能信息图 (AntV Infographic)
|
title: 📊 智能信息图 (AntV Infographic)
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
|
funding_url: https://github.com/open-webui
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
|
||||||
version: 1.4.9
|
version: 1.4.9
|
||||||
openwebui_id: e04a48ff-23ee-4a41-8ea7-66c19524e7c8
|
openwebui_id: e04a48ff-23ee-4a41-8ea7-66c19524e7c8
|
||||||
@@ -244,6 +245,8 @@ data
|
|||||||
3. **Language**: Use the user's requested language for content.
|
3. **Language**: Use the user's requested language for content.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
USER_PROMPT_GENERATE_INFOGRAPHIC = """
|
USER_PROMPT_GENERATE_INFOGRAPHIC = """
|
||||||
请分析以下文本内容,将其核心信息转换为 AntV Infographic 语法格式。
|
请分析以下文本内容,将其核心信息转换为 AntV Infographic 语法格式。
|
||||||
|
|
||||||
@@ -954,6 +957,10 @@ class Action:
|
|||||||
default="image",
|
default="image",
|
||||||
description="输出模式:'html' 为交互式HTML,'image' 将嵌入为Markdown图片(默认)。",
|
description="输出模式:'html' 为交互式HTML,'image' 将嵌入为Markdown图片(默认)。",
|
||||||
)
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="是否在浏览器控制台打印调试日志。",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.valves = self.Valves()
|
self.valves = self.Valves()
|
||||||
@@ -967,45 +974,56 @@ class Action:
|
|||||||
"Sunday": "星期日",
|
"Sunday": "星期日",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
||||||
"""从 body 或 metadata 中提取 chat_id"""
|
"""安全提取用户上下文信息。"""
|
||||||
|
if isinstance(__user__, (list, tuple)):
|
||||||
|
user_data = __user__[0] if __user__ else {}
|
||||||
|
elif isinstance(__user__, dict):
|
||||||
|
user_data = __user__
|
||||||
|
else:
|
||||||
|
user_data = {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"user_id": user_data.get("id", "unknown_user"),
|
||||||
|
"user_name": user_data.get("name", "用户"),
|
||||||
|
"user_language": user_data.get("language", "zh-CN"),
|
||||||
|
}
|
||||||
|
|
||||||
|
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):
|
if isinstance(body, dict):
|
||||||
chat_id = body.get("chat_id")
|
chat_id = body.get("chat_id", "")
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
message_id = body.get("id", "") # message_id 在 body 中通常是 id
|
||||||
return chat_id.strip()
|
|
||||||
|
|
||||||
body_metadata = body.get("metadata", {})
|
# 再次检查 body.metadata
|
||||||
if isinstance(body_metadata, dict):
|
if not chat_id or not message_id:
|
||||||
chat_id = body_metadata.get("chat_id")
|
body_metadata = body.get("metadata", {})
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
if isinstance(body_metadata, dict):
|
||||||
return chat_id.strip()
|
if not chat_id:
|
||||||
|
chat_id = body_metadata.get("chat_id", "")
|
||||||
|
if not message_id:
|
||||||
|
message_id = body_metadata.get("message_id", "")
|
||||||
|
|
||||||
if isinstance(metadata, dict):
|
# 2. 尝试从 __metadata__ 获取 (作为补充)
|
||||||
chat_id = metadata.get("chat_id")
|
if __metadata__ and isinstance(__metadata__, dict):
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
if not chat_id:
|
||||||
return chat_id.strip()
|
chat_id = __metadata__.get("chat_id", "")
|
||||||
|
if not message_id:
|
||||||
|
message_id = __metadata__.get("message_id", "")
|
||||||
|
|
||||||
return ""
|
return {
|
||||||
|
"chat_id": str(chat_id).strip(),
|
||||||
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str:
|
"message_id": str(message_id).strip(),
|
||||||
"""从 body 或 metadata 中提取 message_id"""
|
}
|
||||||
if isinstance(body, dict):
|
|
||||||
message_id = body.get("id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
body_metadata = body.get("metadata", {})
|
|
||||||
if isinstance(body_metadata, dict):
|
|
||||||
message_id = body_metadata.get("message_id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
if isinstance(metadata, dict):
|
|
||||||
message_id = metadata.get("message_id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _extract_infographic_syntax(self, llm_output: str) -> str:
|
def _extract_infographic_syntax(self, llm_output: str) -> str:
|
||||||
"""提取LLM输出中的infographic语法"""
|
"""提取LLM输出中的infographic语法"""
|
||||||
@@ -1058,6 +1076,24 @@ class Action:
|
|||||||
{"type": "notification", "data": {"type": ntype, "content": content}}
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""在浏览器控制台打印结构化调试日志"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
def _remove_existing_html(self, content: str) -> str:
|
def _remove_existing_html(self, content: str) -> str:
|
||||||
"""移除内容中已有的插件生成 HTML 代码块"""
|
"""移除内容中已有的插件生成 HTML 代码块"""
|
||||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||||
@@ -1662,8 +1698,9 @@ class Action:
|
|||||||
# 检查输出模式
|
# 检查输出模式
|
||||||
if self.valves.OUTPUT_MODE == "image":
|
if self.valves.OUTPUT_MODE == "image":
|
||||||
# 图片模式:使用 JavaScript 渲染并嵌入为 Markdown 图片
|
# 图片模式:使用 JavaScript 渲染并嵌入为 Markdown 图片
|
||||||
chat_id = self._extract_chat_id(body, body.get("metadata"))
|
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||||
message_id = self._extract_message_id(body, body.get("metadata"))
|
chat_id = chat_ctx["chat_id"]
|
||||||
|
message_id = chat_ctx["message_id"]
|
||||||
|
|
||||||
await self._emit_status(
|
await self._emit_status(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
# Smart Mind Map - Mind Mapping Generation Plugin
|
# Smart Mind Map - Mind Mapping Generation Plugin
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.9.1 | **License:** MIT
|
|
||||||
|
|
||||||
> **Important**: To ensure the maintainability and usability of all plugins, each plugin should be accompanied by clear and comprehensive documentation to ensure its functionality, configuration, and usage are well explained.
|
|
||||||
|
|
||||||
Smart Mind Map is a powerful OpenWebUI action plugin that intelligently analyzes long-form text content and automatically generates interactive mind maps, helping users structure and visualize knowledge.
|
Smart Mind Map is a powerful OpenWebUI action plugin that intelligently analyzes long-form text content and automatically generates interactive mind maps, helping users structure and visualize knowledge.
|
||||||
|
|
||||||
---
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.9.1 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
|
|
||||||
## 🔥 What's New in v0.9.1
|
## What's New in v0.9.1
|
||||||
|
|
||||||
**New Feature: Image Output Mode**
|
**New Feature: Image Output Mode**
|
||||||
|
|
||||||
@@ -18,362 +14,51 @@ Smart Mind Map is a powerful OpenWebUI action plugin that intelligently analyzes
|
|||||||
- **Efficient Storage**: Image mode uploads SVG to `/api/v1/files`, avoiding huge base64 strings in chat history.
|
- **Efficient Storage**: Image mode uploads SVG to `/api/v1/files`, avoiding huge base64 strings in chat history.
|
||||||
- **Smart Features**: Auto-responsive width and automatic theme detection (light/dark) for generated images.
|
- **Smart Features**: Auto-responsive width and automatic theme detection (light/dark) for generated images.
|
||||||
|
|
||||||
| Feature | HTML Mode (Default) | Image Mode |
|
## Key Features 🔑
|
||||||
| :--- | :--- | :--- |
|
|
||||||
| **Output Format** | Interactive HTML Block | Static Markdown Image |
|
|
||||||
| **Interactivity** | Zoom, Pan, Expand/Collapse | None (Static Image) |
|
|
||||||
| **Chat History** | Contains HTML Code | Clean (Image URL only) |
|
|
||||||
| **Storage** | Browser Rendering | `/api/v1/files` Upload |
|
|
||||||
|
|
||||||
---
|
- ✅ **Intelligent Text Analysis**: Automatically identifies core themes, key concepts, and hierarchical structures.
|
||||||
|
- ✅ **Interactive Visualization**: Generates beautiful interactive mind maps based on Markmap.js.
|
||||||
|
- ✅ **High-Resolution PNG Export**: Export mind maps as high-quality PNG images (9x scale).
|
||||||
|
- ✅ **Complete Control Panel**: Zoom controls, expand level selection, and fullscreen mode.
|
||||||
|
- ✅ **Theme Switching**: Manual theme toggle button with automatic theme detection.
|
||||||
|
- ✅ **Image Output Mode**: Generate static SVG images embedded directly in Markdown for cleaner history.
|
||||||
|
|
||||||
## Core Features
|
## How to Use 🛠️
|
||||||
|
|
||||||
- ✅ **Intelligent Text Analysis**: Automatically identifies core themes, key concepts, and hierarchical structures
|
1. **Install**: Upload the `smart_mind_map.py` file in OpenWebUI Admin Settings -> Plugins -> Actions.
|
||||||
- ✅ **Interactive Visualization**: Generates beautiful interactive mind maps based on Markmap.js
|
2. **Configure**: Ensure you have an LLM model configured (e.g., `gemini-2.5-flash`).
|
||||||
- ✅ **High-Resolution PNG Export**: Export mind maps as high-quality PNG images (9x scale, ~1-2MB file size)
|
3. **Trigger**: Enable the "Smart Mind Map" action in chat settings and send text (at least 100 characters).
|
||||||
- ✅ **Complete Control Panel**: Zoom controls (+/-/reset), expand level selection (All/2/3 levels), and fullscreen mode
|
4. **Result**: The mind map will be rendered directly in the chat interface.
|
||||||
- ✅ **Theme Switching**: Manual theme toggle button (light/dark) with automatic theme detection
|
|
||||||
- ✅ **Dark Mode Support**: Full dark mode support with automatic detection and manual override
|
|
||||||
- ✅ **Multi-language Support**: Automatically adjusts output based on user language
|
|
||||||
- ✅ **Real-time Rendering**: Renders mind maps directly in the chat interface without navigation
|
|
||||||
- ✅ **Export Capabilities**: Supports PNG, SVG code, and Markdown source export
|
|
||||||
- ✅ **Customizable Configuration**: Configurable LLM model, minimum text length, and other parameters
|
|
||||||
- ✅ **Image Output Mode**: Generate static SVG images embedded directly in Markdown (**No HTML code output**, cleaner chat history)
|
|
||||||
|
|
||||||
---
|
## Configuration (Valves) ⚙️
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
1. **Text Extraction**: Extracts text content from user messages (automatically filters HTML code blocks)
|
|
||||||
2. **Intelligent Analysis**: Analyzes text structure using the configured LLM model
|
|
||||||
3. **Markdown Generation**: Converts analysis results to Markmap-compatible Markdown format
|
|
||||||
4. **Visual Rendering**: Renders the mind map using Markmap.js in an HTML template with optimized font hierarchy (H1: 22px bold, H2: 18px bold)
|
|
||||||
5. **Interactive Display**: Presents the mind map to users in an interactive format with complete control panel
|
|
||||||
6. **Theme Detection**: Automatically detects and applies the current OpenWebUI theme (light/dark mode)
|
|
||||||
7. **Export Options**: Provides PNG (high-resolution), SVG, and Markdown export functionality
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Installation and Configuration
|
|
||||||
|
|
||||||
### 1. Plugin Installation
|
|
||||||
|
|
||||||
1. Download the `smart_mind_map_cn.py` file to your local computer
|
|
||||||
2. In OpenWebUI Admin Settings, find the "Plugins" section
|
|
||||||
3. Select "Actions" type
|
|
||||||
4. Upload the downloaded file
|
|
||||||
5. Refresh the page, and the plugin will be available
|
|
||||||
|
|
||||||
### 2. Model Configuration
|
|
||||||
|
|
||||||
The plugin requires access to an LLM model for text analysis. Please ensure:
|
|
||||||
|
|
||||||
- Your OpenWebUI instance has at least one available LLM model configured
|
|
||||||
- Recommended to use fast, economical models (e.g., `gemini-2.5-flash`) for the best experience
|
|
||||||
- Configure the `LLM_MODEL_ID` parameter in the plugin settings
|
|
||||||
|
|
||||||
### 3. Plugin Activation
|
|
||||||
|
|
||||||
Select the "Smart Mind Map" action plugin in chat settings to enable it.
|
|
||||||
|
|
||||||
### 4. Theme Color Consistency (Optional)
|
|
||||||
|
|
||||||
To keep the mind map visually consistent with the OpenWebUI theme colors, enable same-origin access for artifacts in OpenWebUI:
|
|
||||||
|
|
||||||
- **Configuration Location**: In OpenWebUI User Settings: **Interface** → **Artifacts** → **iframe Sandbox Allow Same Origin**
|
|
||||||
- **Enable Option**: Check the "Allow same-origin access for artifacts" / "iframe sandbox allow-same-origin" option
|
|
||||||
- **Sandbox Attributes**: Ensure the iframe's sandbox attribute includes both `allow-same-origin` and `allow-scripts`
|
|
||||||
|
|
||||||
Once enabled, the mind map will automatically detect and apply the current OpenWebUI theme (light/dark) without any manual configuration.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration Parameters
|
|
||||||
|
|
||||||
You can adjust the following parameters in the plugin's settings (Valves):
|
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
| Parameter | Default | Description |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
| `show_status` | `true` | Whether to display operation status updates in the chat interface (e.g., "Analyzing..."). |
|
| `show_status` | `true` | Whether to display operation status updates. |
|
||||||
| `LLM_MODEL_ID` | `gemini-2.5-flash` | LLM model ID for text analysis. Recommended to use fast and economical models. |
|
| `LLM_MODEL_ID` | `gemini-2.5-flash` | LLM model ID for text analysis. |
|
||||||
| `MIN_TEXT_LENGTH` | `100` | Minimum text length (in characters) required for mind map analysis. Text that's too short cannot generate valid mind maps. |
|
| `MIN_TEXT_LENGTH` | `100` | Minimum text length required for analysis. |
|
||||||
| `CLEAR_PREVIOUS_HTML` | `false` | Whether to clear previous plugin-generated HTML content when generating a new mind map. |
|
| `CLEAR_PREVIOUS_HTML` | `false` | Whether to clear previous plugin-generated HTML content. |
|
||||||
| `MESSAGE_COUNT` | `1` | Number of recent messages to use for mind map generation (1-5). |
|
| `MESSAGE_COUNT` | `1` | Number of recent messages to use for generation (1-5). |
|
||||||
| `OUTPUT_MODE` | `html` | Output mode: `html` for interactive HTML (default), or `image` to embed as static Markdown image. |
|
| `OUTPUT_MODE` | `html` | Output mode: `html` (interactive) or `image` (static). |
|
||||||
|
|
||||||
---
|
## Troubleshooting ❓
|
||||||
|
|
||||||
## Usage
|
- **Plugin not working?**: Check if the action is enabled in the chat settings.
|
||||||
|
- **Text too short**: Ensure input text contains at least 100 characters.
|
||||||
### Basic Usage
|
- **Rendering failed**: Check browser console for errors related to Markmap.js or D3.js.
|
||||||
|
- **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
1. Enable the "Smart Mind Map" action in chat settings
|
|
||||||
2. Input or paste long-form text content (at least 100 characters) in the conversation
|
|
||||||
3. After sending the message, the plugin will automatically analyze and generate a mind map
|
|
||||||
4. The mind map will be rendered directly in the chat interface
|
|
||||||
|
|
||||||
### Usage Example
|
|
||||||
|
|
||||||
**Input Text:**
|
|
||||||
|
|
||||||
```
|
|
||||||
Artificial Intelligence (AI) is a branch of computer science dedicated to creating systems capable of performing tasks that typically require human intelligence.
|
|
||||||
Main application areas include:
|
|
||||||
1. Machine Learning - Enables computers to learn from data
|
|
||||||
2. Natural Language Processing - Understanding and generating human language
|
|
||||||
3. Computer Vision - Recognizing and processing images
|
|
||||||
4. Robotics - Creating intelligent systems that can interact with the physical world
|
|
||||||
```
|
|
||||||
|
|
||||||
**Generated Result:**
|
|
||||||
The plugin will generate an interactive mind map centered on "Artificial Intelligence", including major application areas and their sub-concepts.
|
|
||||||
|
|
||||||
### Export Features
|
|
||||||
|
|
||||||
Generated mind maps support three export methods:
|
|
||||||
|
|
||||||
1. **Download PNG**: Click the "📥 Download PNG" button to export the mind map as a high-resolution PNG image (9x scale, ~1-2MB file size)
|
|
||||||
2. **Copy SVG Code**: Click the "Copy SVG Code" button to copy the mind map in SVG format to the clipboard
|
|
||||||
3. **Copy Markdown**: Click the "Copy Markdown" button to copy the raw Markdown format to the clipboard
|
|
||||||
|
|
||||||
### Control Panel
|
|
||||||
|
|
||||||
The interactive mind map includes a comprehensive control panel:
|
|
||||||
|
|
||||||
- **Zoom Controls**: `+` (zoom in), `-` (zoom out), `↻` (reset view)
|
|
||||||
- **Expand Level**: Switch between "All", "2 Levels", "3 Levels" to control node expansion depth
|
|
||||||
- **Fullscreen**: Enter fullscreen mode for better viewing experience
|
|
||||||
- **Theme Toggle**: Manually switch between light and dark themes
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Technical Architecture
|
## Technical Architecture
|
||||||
|
|
||||||
### Frontend Rendering
|
- **Markmap.js**: Open-source mind mapping rendering engine.
|
||||||
|
- **PNG Export**: 9x scale factor for print-quality output (~1-2MB file size).
|
||||||
- **Markmap.js**: Open-source mind mapping rendering engine
|
- **Theme Detection**: 4-level priority detection (Manual > Meta > Class > System).
|
||||||
- **D3.js**: Data visualization foundation library
|
- **Security**: XSS protection and input validation.
|
||||||
- **Responsive Design**: Adapts to different screen sizes
|
|
||||||
- **Font Hierarchy**: Optimized typography with H1 (22px bold) and H2 (18px bold) for better readability
|
|
||||||
|
|
||||||
### PNG Export Technology
|
|
||||||
|
|
||||||
- **SVG to Canvas Conversion**: Converts mind map SVG to canvas for PNG export
|
|
||||||
- **ForeignObject Handling**: Properly processes HTML content within SVG elements
|
|
||||||
- **High Resolution**: 9x scale factor for print-quality output (~1-2MB file size)
|
|
||||||
- **Theme Preservation**: Maintains current theme (light/dark) in exported PNG
|
|
||||||
|
|
||||||
### Theme Detection Mechanism
|
|
||||||
|
|
||||||
Automatically detects and applies themes with a 4-level priority:
|
|
||||||
|
|
||||||
1. **Explicit Toggle**: User manually clicks theme toggle button (highest priority)
|
|
||||||
2. **Meta Tag**: Reads `<meta name="theme-color">` from parent document
|
|
||||||
3. **Class/Data-Theme**: Checks `class` or `data-theme` attributes on parent HTML/body
|
|
||||||
4. **System Preference**: Falls back to `prefers-color-scheme` media query
|
|
||||||
|
|
||||||
### Backend Processing
|
|
||||||
|
|
||||||
- **LLM Integration**: Calls configured models via `generate_chat_completion`
|
|
||||||
- **Text Preprocessing**: Automatically filters HTML code blocks, extracts plain text content
|
|
||||||
- **Format Conversion**: Converts LLM output to Markmap-compatible Markdown format
|
|
||||||
|
|
||||||
### Security Enhancements
|
|
||||||
|
|
||||||
- **XSS Protection**: Automatically escapes `</script>` tags to prevent script injection
|
|
||||||
- **Input Validation**: Checks text length to avoid invalid requests
|
|
||||||
- **Non-Bubbling Events**: Button clicks use `stopPropagation()` to prevent navigation interception
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: Plugin Won't Start
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
- Check OpenWebUI logs for error messages
|
|
||||||
- Confirm the plugin is correctly uploaded and enabled
|
|
||||||
- Verify OpenWebUI version supports action plugins
|
|
||||||
|
|
||||||
### Issue: Text Content Too Short
|
|
||||||
|
|
||||||
**Symptom:** Prompt shows "Text content is too short for effective analysis"
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
- Ensure input text contains at least 100 characters (default configuration)
|
|
||||||
- Lower the `MIN_TEXT_LENGTH` parameter value in plugin settings
|
|
||||||
- Provide more detailed, structured text content
|
|
||||||
|
|
||||||
### Issue: Mind Map Not Generated
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
- Check if `LLM_MODEL_ID` is configured correctly
|
|
||||||
- Confirm the configured model is available in OpenWebUI
|
|
||||||
- Review backend logs for LLM call failures
|
|
||||||
- Verify user has sufficient permissions to access the configured model
|
|
||||||
|
|
||||||
### Issue: Mind Map Display Error
|
|
||||||
|
|
||||||
**Symptom:** Shows "⚠️ Mind map rendering failed"
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
- Check browser console for error messages
|
|
||||||
- Confirm Markmap.js and D3.js libraries are loading correctly
|
|
||||||
- Verify generated Markdown format conforms to Markmap specifications
|
|
||||||
- Try refreshing the page to re-render
|
|
||||||
|
|
||||||
### Issue: PNG Export Not Working
|
|
||||||
|
|
||||||
**Symptom:** PNG download button doesn't work or produces blank/corrupted images
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
- Ensure browser supports HTML5 Canvas API (all modern browsers do)
|
|
||||||
- Check browser console for errors related to `toDataURL()` or canvas rendering
|
|
||||||
- Verify the mind map is fully rendered before clicking export
|
|
||||||
- Try refreshing the page and re-generating the mind map
|
|
||||||
- Use Chrome or Firefox for best PNG export compatibility
|
|
||||||
|
|
||||||
### Issue: Theme Not Auto-Detected
|
|
||||||
|
|
||||||
**Symptom:** Mind map doesn't match OpenWebUI theme colors
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
- Enable "iframe Sandbox Allow Same Origin" in OpenWebUI Settings → Interface → Artifacts
|
|
||||||
- Verify the iframe's sandbox attribute includes both `allow-same-origin` and `allow-scripts`
|
|
||||||
- Ensure parent document has `<meta name="theme-color">` tag or theme class/attribute
|
|
||||||
- Use the manual theme toggle button to override automatic detection
|
|
||||||
- Check browser console for cross-origin errors
|
|
||||||
|
|
||||||
### Issue: Export Function Not Working
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
- Confirm browser supports Clipboard API
|
|
||||||
- Check if browser is blocking clipboard access permissions
|
|
||||||
- Use modern browsers (Chrome, Firefox, Edge, etc.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
1. **Text Preparation**
|
1. **Text Preparation**: Provide text with clear structure and distinct hierarchies.
|
||||||
- Provide text content with clear structure and distinct hierarchies
|
2. **Model Selection**: Use fast models like `gemini-2.5-flash` for daily use.
|
||||||
- Use paragraphs, lists, and other formatting to help LLM understand text structure
|
3. **Export Quality**: Use PNG for presentations and SVG for further editing.
|
||||||
- Avoid excessively lengthy or unstructured text
|
|
||||||
|
|
||||||
2. **Model Selection**
|
|
||||||
- For daily use, recommend fast models like `gemini-2.5-flash`
|
|
||||||
- For complex text analysis, use more powerful models (e.g., GPT-4)
|
|
||||||
- Balance speed and analysis quality based on needs
|
|
||||||
|
|
||||||
3. **Performance Optimization**
|
|
||||||
- Set `MIN_TEXT_LENGTH` appropriately to avoid processing text that's too short
|
|
||||||
- For particularly long texts, consider summarizing before generating mind maps
|
|
||||||
- Disable `show_status` in production environments to reduce interface updates
|
|
||||||
|
|
||||||
4. **Export Quality**
|
|
||||||
- **PNG Export**: Best for presentations, documents, and sharing (9x resolution suitable for printing)
|
|
||||||
- **SVG Export**: Best for further editing in vector graphics tools (infinite scalability)
|
|
||||||
- **Markdown Export**: Best for version control, collaboration, and regeneration
|
|
||||||
|
|
||||||
5. **Theme Consistency**
|
|
||||||
- Enable same-origin access for automatic theme detection
|
|
||||||
- Use manual theme toggle if automatic detection fails
|
|
||||||
- Export PNG after switching to desired theme for consistent visuals
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
This plugin uses only OpenWebUI's built-in dependencies. **No additional packages need to be installed.**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
### v0.9.1
|
|
||||||
|
|
||||||
**New Feature: Image Output Mode**
|
|
||||||
|
|
||||||
- Added `OUTPUT_MODE` configuration parameter with two options:
|
|
||||||
- `html` (default): Interactive HTML mind map with full control panel
|
|
||||||
- `image`: Static SVG image embedded directly in Markdown (uploaded to `/api/v1/files`)
|
|
||||||
- Image mode features:
|
|
||||||
- Auto-responsive width (adapts to chat container)
|
|
||||||
- Automatic theme detection (light/dark)
|
|
||||||
- Persistent storage via Chat API (survives page refresh)
|
|
||||||
- Efficient file storage (no huge base64 strings in chat history)
|
|
||||||
|
|
||||||
**Improvements:**
|
|
||||||
|
|
||||||
- Implemented robust Chat API update mechanism with retry logic
|
|
||||||
- Fixed message persistence using both `messages[]` and `history.messages`
|
|
||||||
- Added Event API for immediate frontend updates
|
|
||||||
- Removed unnecessary `SVG_WIDTH` and `SVG_HEIGHT` parameters (now auto-calculated)
|
|
||||||
|
|
||||||
**Technical Details:**
|
|
||||||
|
|
||||||
- Image mode uses `__event_call__` to execute JavaScript in the browser
|
|
||||||
- SVG is rendered offline, converted to Blob, and uploaded to OpenWebUI Files API
|
|
||||||
- Updates chat message with `/api/v1/files/{id}/content` URL via OpenWebUI Backend-Controlled API flow
|
|
||||||
|
|
||||||
### v0.8.2
|
|
||||||
|
|
||||||
- Removed debug messages from output
|
|
||||||
|
|
||||||
### v0.8.0 (Previous Version)
|
|
||||||
|
|
||||||
**Major Features:**
|
|
||||||
|
|
||||||
- Added high-resolution PNG export (9x scale, ~1-2MB file size)
|
|
||||||
- Implemented complete control panel with zoom controls (+/-/reset)
|
|
||||||
- Added expand level selection (All/2/3 levels)
|
|
||||||
- Integrated fullscreen mode with auto-fit
|
|
||||||
- Added manual theme toggle button (light/dark)
|
|
||||||
- Implemented automatic theme detection with 4-level priority
|
|
||||||
|
|
||||||
**Improvements:**
|
|
||||||
|
|
||||||
- Optimized font hierarchy (H1: 22px bold, H2: 18px bold)
|
|
||||||
- Enhanced dark mode with full theme support
|
|
||||||
- Improved PNG export technology (SVG to Canvas with foreignObject handling)
|
|
||||||
- Added theme preservation in exported PNG images
|
|
||||||
- Enhanced security with non-bubbling button events
|
|
||||||
|
|
||||||
**Bug Fixes:**
|
|
||||||
|
|
||||||
- Fixed theme detection in cross-origin iframes
|
|
||||||
- Resolved PNG export issues with HTML content in SVG
|
|
||||||
- Improved compatibility with OpenWebUI theme system
|
|
||||||
|
|
||||||
### v0.7.2
|
|
||||||
|
|
||||||
- Optimized text extraction logic, automatically filters HTML code blocks
|
|
||||||
- Improved error handling and user feedback
|
|
||||||
- Enhanced export functionality compatibility
|
|
||||||
- Optimized UI styling and interactive experience
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This plugin is released under the MIT License.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Welcome to submit issue reports and improvement suggestions! Please visit the project repository: [awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Resources
|
|
||||||
|
|
||||||
- [Markmap Official Website](https://markmap.js.org/)
|
|
||||||
- [OpenWebUI Documentation](https://docs.openwebui.com/)
|
|
||||||
- [D3.js Official Website](https://d3js.org/)
|
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
# 思维导图 - 思维导图生成插件
|
# 思维导图 - 思维导图生成插件
|
||||||
|
|
||||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 0.9.1 | **许可证:** MIT
|
|
||||||
|
|
||||||
> **重要提示**:为了确保所有插件的可维护性和易用性,每个插件都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
|
|
||||||
|
|
||||||
思维导图是一个强大的 OpenWebUI 动作插件,能够智能分析长篇文本内容,自动生成交互式思维导图,帮助用户结构化和可视化知识。
|
思维导图是一个强大的 OpenWebUI 动作插件,能够智能分析长篇文本内容,自动生成交互式思维导图,帮助用户结构化和可视化知识。
|
||||||
|
|
||||||
---
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.9.1 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||||
|
|
||||||
## 🔥 v0.9.1 更新亮点
|
## v0.9.1 更新亮点
|
||||||
|
|
||||||
**新功能:图片输出模式**
|
**新功能:图片输出模式**
|
||||||
|
|
||||||
@@ -18,362 +14,51 @@
|
|||||||
- **高效存储**:图片模式将 SVG 上传至 `/api/v1/files`,避免聊天记录中出现超长 Base64 字符串。
|
- **高效存储**:图片模式将 SVG 上传至 `/api/v1/files`,避免聊天记录中出现超长 Base64 字符串。
|
||||||
- **智能特性**:生成的图片支持自动响应式宽度和自动主题检测(亮色/暗色)。
|
- **智能特性**:生成的图片支持自动响应式宽度和自动主题检测(亮色/暗色)。
|
||||||
|
|
||||||
| 特性 | HTML 模式 (默认) | 图片模式 |
|
## 核心特性 🔑
|
||||||
| :--- | :--- | :--- |
|
|
||||||
| **输出格式** | 交互式 HTML 代码块 | 静态 Markdown 图片 |
|
|
||||||
| **交互性** | 缩放、拖拽、展开/折叠 | 无 (静态图片) |
|
|
||||||
| **聊天记录** | 包含 HTML 代码 | 简洁 (仅图片链接) |
|
|
||||||
| **存储方式** | 浏览器实时渲染 | `/api/v1/files` 上传 |
|
|
||||||
|
|
||||||
---
|
- ✅ **智能文本分析**:自动识别文本的核心主题、关键概念和层次结构。
|
||||||
|
- ✅ **交互式可视化**:基于 Markmap.js 生成美观的交互式思维导图。
|
||||||
|
- ✅ **高分辨率 PNG 导出**:导出高质量的 PNG 图片(9 倍分辨率)。
|
||||||
|
- ✅ **完整控制面板**:缩放控制、展开层级选择、全屏模式。
|
||||||
|
- ✅ **主题切换**:手动主题切换按钮与自动主题检测。
|
||||||
|
- ✅ **图片输出模式**:生成静态 SVG 图片直接嵌入 Markdown,聊天记录更简洁。
|
||||||
|
|
||||||
## 核心特性
|
## 使用方法 🛠️
|
||||||
|
|
||||||
- ✅ **智能文本分析**:自动识别文本的核心主题、关键概念和层次结构
|
1. **安装**: 在 OpenWebUI 管理员设置 -> 插件 -> 动作中上传 `smart_mind_map_cn.py`。
|
||||||
- ✅ **交互式可视化**:基于 Markmap.js 生成美观的交互式思维导图
|
2. **配置**: 确保配置了 LLM 模型(如 `gemini-2.5-flash`)。
|
||||||
- ✅ **高分辨率 PNG 导出**:导出高质量的 PNG 图片(9 倍分辨率,约 1-2MB 文件大小)
|
3. **触发**: 在聊天设置中启用“思维导图”动作,并发送文本(至少 100 字符)。
|
||||||
- ✅ **完整控制面板**:缩放控制(+/-/重置)、展开层级选择(全部/2级/3级)、全屏模式
|
4. **结果**: 思维导图将在聊天界面中直接渲染显示。
|
||||||
- ✅ **主题切换**:手动主题切换按钮(亮色/暗色)与自动主题检测
|
|
||||||
- ✅ **深色模式支持**:完整的深色模式支持,自动检测与手动覆盖
|
|
||||||
- ✅ **多语言支持**:根据用户语言自动调整输出
|
|
||||||
- ✅ **实时渲染**:在聊天界面中直接渲染思维导图,无需跳转
|
|
||||||
- ✅ **导出功能**:支持 PNG、SVG 代码和 Markdown 源码导出
|
|
||||||
- ✅ **自定义配置**:可配置 LLM 模型、最小文本长度等参数
|
|
||||||
- ✅ **图片输出模式**:生成静态 SVG 图片直接嵌入 Markdown(**不输出 HTML 代码**,聊天记录更简洁)
|
|
||||||
|
|
||||||
---
|
## 配置参数 (Valves) ⚙️
|
||||||
|
|
||||||
## 工作原理
|
|
||||||
|
|
||||||
1. **文本提取**:从用户消息中提取文本内容(自动过滤 HTML 代码块)
|
|
||||||
2. **智能分析**:使用配置的 LLM 模型分析文本结构
|
|
||||||
3. **Markdown 生成**:将分析结果转换为 Markmap 兼容的 Markdown 格式
|
|
||||||
4. **可视化渲染**:在 HTML 模板中使用 Markmap.js 渲染思维导图,并优化字体层级(H1:22px 粗体,H2:18px 粗体)
|
|
||||||
5. **交互展示**:以可交互的形式展示给用户,并提供完整的控制面板
|
|
||||||
6. **主题检测**:自动检测并应用当前 OpenWebUI 的主题(亮色/暗色模式)
|
|
||||||
7. **导出选项**:提供 PNG(高分辨率)、SVG 和 Markdown 导出功能
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 安装与配置
|
|
||||||
|
|
||||||
### 1. 插件安装
|
|
||||||
|
|
||||||
1. 下载 `smart_mind_map_cn.py` 文件到本地
|
|
||||||
2. 在 OpenWebUI 管理员设置中找到"插件"(Plugins)部分
|
|
||||||
3. 选择"动作"(Actions)类型
|
|
||||||
4. 上传下载的文件
|
|
||||||
5. 刷新页面,插件即可使用
|
|
||||||
|
|
||||||
### 2. 模型配置
|
|
||||||
|
|
||||||
插件需要访问 LLM 模型来分析文本。请确保:
|
|
||||||
|
|
||||||
- 您的 OpenWebUI 实例中配置了至少一个可用的 LLM 模型
|
|
||||||
- 推荐使用快速、经济的模型(如 `gemini-2.5-flash`)来获得最佳体验
|
|
||||||
- 在插件设置中配置 `LLM_MODEL_ID` 参数
|
|
||||||
|
|
||||||
### 3. 插件启用
|
|
||||||
|
|
||||||
在聊天设置中选择"思维导图"动作插件即可启用。
|
|
||||||
|
|
||||||
### 4. 主题颜色风格一致性(可选)
|
|
||||||
|
|
||||||
为了使思维导图与 OpenWebUI 主题颜色风格保持一致,需要在 OpenWebUI 中启用 artifact 的同源访问:
|
|
||||||
|
|
||||||
- **配置位置**:在 OpenWebUI 用户设置中找到"界面"→"产物"部分(Settings → Interface → Products/Artifacts)
|
|
||||||
- **启用选项**:勾选 "iframe 沙盒允许同源访问"(Allow same-origin access for artifacts / iframe sandbox allow-same-origin)
|
|
||||||
- **沙箱属性**:确保 iframe 的 sandbox 属性包含 `allow-same-origin` 和 `allow-scripts`
|
|
||||||
|
|
||||||
启用后,思维导图会自动检测并应用 OpenWebUI 的当前主题(亮色/暗色),无需手动配置。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 配置参数
|
|
||||||
|
|
||||||
您可以在插件的设置(Valves)中调整以下参数:
|
|
||||||
|
|
||||||
| 参数 | 默认值 | 描述 |
|
| 参数 | 默认值 | 描述 |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
| `show_status` | `true` | 是否在聊天界面显示操作状态更新(如"正在分析...")。 |
|
| `show_status` | `true` | 是否在聊天界面显示操作状态更新。 |
|
||||||
| `LLM_MODEL_ID` | `gemini-2.5-flash` | 用于文本分析的 LLM 模型 ID。推荐使用快速且经济的模型。 |
|
| `LLM_MODEL_ID` | `gemini-2.5-flash` | 用于文本分析的 LLM 模型 ID。 |
|
||||||
| `MIN_TEXT_LENGTH` | `100` | 进行思维导图分析所需的最小文本长度(字符数)。文本过短将无法生成有效的导图。 |
|
| `MIN_TEXT_LENGTH` | `100` | 进行思维导图分析所需的最小文本长度。 |
|
||||||
| `CLEAR_PREVIOUS_HTML` | `false` | 在生成新的思维导图时,是否清除之前由插件生成的 HTML 内容。 |
|
| `CLEAR_PREVIOUS_HTML` | `false` | 在生成新的思维导图时,是否清除之前的 HTML 内容。 |
|
||||||
| `MESSAGE_COUNT` | `1` | 用于生成思维导图的最近消息数量(1-5)。 |
|
| `MESSAGE_COUNT` | `1` | 用于生成思维导图的最近消息数量(1-5)。 |
|
||||||
| `OUTPUT_MODE` | `html` | 输出模式:`html` 为交互式 HTML(默认),`image` 为嵌入静态 Markdown 图片。 |
|
| `OUTPUT_MODE` | `html` | 输出模式:`html`(交互式)或 `image`(静态图片)。 |
|
||||||
|
|
||||||
---
|
## 故障排除 (Troubleshooting) ❓
|
||||||
|
|
||||||
## 使用方法
|
- **插件无法启动**:检查 OpenWebUI 日志,确认插件已正确上传并启用。
|
||||||
|
- **文本内容过短**:确保输入的文本至少包含 100 个字符。
|
||||||
### 基本使用
|
- **渲染失败**:检查浏览器控制台,确认 Markmap.js 和 D3.js 库是否正确加载。
|
||||||
|
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
1. 在聊天设置中启用"思维导图"动作
|
|
||||||
2. 在对话中输入或粘贴长篇文本内容(至少 100 字符)
|
|
||||||
3. 发送消息后,插件会自动分析并生成思维导图
|
|
||||||
4. 思维导图将在聊天界面中直接渲染显示
|
|
||||||
|
|
||||||
### 使用示例
|
|
||||||
|
|
||||||
**输入文本:**
|
|
||||||
|
|
||||||
```
|
|
||||||
人工智能(AI)是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。
|
|
||||||
主要应用领域包括:
|
|
||||||
1. 机器学习 - 使计算机能够从数据中学习
|
|
||||||
2. 自然语言处理 - 理解和生成人类语言
|
|
||||||
3. 计算机视觉 - 识别和处理图像
|
|
||||||
4. 机器人技术 - 创建能够与物理世界交互的智能系统
|
|
||||||
```
|
|
||||||
|
|
||||||
**生成结果:**
|
|
||||||
插件会生成一个以"人工智能"为中心主题的交互式思维导图,包含主要应用领域及其子概念。
|
|
||||||
|
|
||||||
### 导出功能
|
|
||||||
|
|
||||||
生成的思维导图支持三种导出方式:
|
|
||||||
|
|
||||||
1. **下载 PNG**:点击“📥 下载 PNG”按钮,可将思维导图导出为高分辨率 PNG 图片(9 倍分辨率,约 1-2MB 文件大小)
|
|
||||||
2. **复制 SVG 代码**:点击“复制 SVG 代码”按钮,可将思维导图的 SVG 格式复制到剪贴板
|
|
||||||
3. **复制 Markdown**:点击“复制 Markdown”按钮,可将原始 Markdown 格式复制到剪贴板
|
|
||||||
|
|
||||||
### 控制面板
|
|
||||||
|
|
||||||
交互式思维导图包含完整的控制面板:
|
|
||||||
|
|
||||||
- **缩放控制**:`+`(放大)、`-`(缩小)、`↻`(重置视图)
|
|
||||||
- **展开层级**:在“全部”、“2 级”、“3 级”之间切换,控制节点展开深度
|
|
||||||
- **全屏模式**:进入全屏模式,获得更好的查看体验
|
|
||||||
- **主题切换**:手动在亮色和暗色主题之间切换
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 技术架构
|
## 技术架构
|
||||||
|
|
||||||
### 前端渲染
|
- **Markmap.js**:开源的思维导图渲染引擎。
|
||||||
|
- **PNG 导出技术**:9 倍缩放因子,输出打印级质量。
|
||||||
- **Markmap.js**:开源的思维导图渲染引擎
|
- **主题检测机制**:4 级优先级检测(手动 > Meta > Class > 系统)。
|
||||||
- **D3.js**:数据可视化基础库
|
- **安全性增强**:XSS 防护与输入验证。
|
||||||
- **响应式设计**:适配不同屏幕尺寸
|
|
||||||
- **字体层级**:优化的字体排版,H1(22px 粗体)和 H2(18px 粗体),提供更好的可读性
|
|
||||||
|
|
||||||
### PNG 导出技术
|
|
||||||
|
|
||||||
- **SVG 转 Canvas**:将思维导图 SVG 转换为 Canvas 以导出 PNG
|
|
||||||
- **ForeignObject 处理**:正确处理 SVG 元素中的 HTML 内容
|
|
||||||
- **高分辨率**:9 倍缩放因子,输出打印级质量(约 1-2MB 文件大小)
|
|
||||||
- **主题保持**:在导出的 PNG 中保持当前主题(亮色/暗色)
|
|
||||||
|
|
||||||
### 主题检测机制
|
|
||||||
|
|
||||||
自动检测并应用主题,具有 4 级优先级:
|
|
||||||
|
|
||||||
1. **显式切换**:用户手动点击主题切换按钮(最高优先级)
|
|
||||||
2. **Meta 标签**:从父文档读取 `<meta name="theme-color">`
|
|
||||||
3. **Class/Data-Theme**:检查父文档 HTML/body 的 `class` 或 `data-theme` 属性
|
|
||||||
4. **系统偏好**:回退到 `prefers-color-scheme` 媒体查询
|
|
||||||
|
|
||||||
### 后端处理
|
|
||||||
|
|
||||||
- **LLM 集成**:通过 `generate_chat_completion` 调用配置的模型
|
|
||||||
- **文本预处理**:自动过滤 HTML 代码块,提取纯文本内容
|
|
||||||
- **格式转换**:将 LLM 输出转换为 Markmap 兼容的 Markdown 格式
|
|
||||||
|
|
||||||
### 安全性增强
|
|
||||||
|
|
||||||
- **XSS 防护**:自动转义 `</script>` 标签,防止脚本注入
|
|
||||||
- **输入验证**:检查文本长度,避免无效请求
|
|
||||||
- **非冒泡事件**:按钮点击使用 `stopPropagation()` 防止导航拦截
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 故障排除
|
|
||||||
|
|
||||||
### 问题:插件无法启动
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
|
|
||||||
- 检查 OpenWebUI 日志,查看是否有错误信息
|
|
||||||
- 确认插件已正确上传并启用
|
|
||||||
- 验证 OpenWebUI 版本是否支持动作插件
|
|
||||||
|
|
||||||
### 问题:文本内容过短
|
|
||||||
|
|
||||||
**现象:** 提示"文本内容过短,无法进行有效分析"
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
|
|
||||||
- 确保输入的文本至少包含 100 个字符(默认配置)
|
|
||||||
- 可以在插件设置中降低 `MIN_TEXT_LENGTH` 参数值
|
|
||||||
- 提供更详细、结构化的文本内容
|
|
||||||
|
|
||||||
### 问题:思维导图未生成
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
|
|
||||||
- 检查 `LLM_MODEL_ID` 是否配置正确
|
|
||||||
- 确认配置的模型在 OpenWebUI 中可用
|
|
||||||
- 查看后端日志,检查是否有 LLM 调用失败的错误
|
|
||||||
- 验证用户是否有足够的权限访问配置的模型
|
|
||||||
|
|
||||||
### 问题:思维导图显示错误
|
|
||||||
|
|
||||||
**现象:** 显示"⚠️ 思维导图渲染失败"
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
|
|
||||||
- 检查浏览器控制台的错误信息
|
|
||||||
- 确认 Markmap.js 和 D3.js 库是否正确加载
|
|
||||||
- 验证生成的 Markdown 格式是否符合 Markmap 规范
|
|
||||||
- 尝试刷新页面重新渲染
|
|
||||||
|
|
||||||
### 问题:PNG 导出不工作
|
|
||||||
|
|
||||||
**现象:**PNG 下载按钮不工作或生成空白/损坏的图片
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
|
|
||||||
- 确保浏览器支持 HTML5 Canvas API(所有现代浏览器都支持)
|
|
||||||
- 检查浏览器控制台是否有与 `toDataURL()` 或 Canvas 渲染相关的错误
|
|
||||||
- 确保思维导图在点击导出前已完全渲染
|
|
||||||
- 尝试刷新页面并重新生成思维导图
|
|
||||||
- 使用 Chrome 或 Firefox,获得最佳 PNG 导出兼容性
|
|
||||||
|
|
||||||
### 问题:主题未自动检测
|
|
||||||
|
|
||||||
**现象:**思维导图不匹配 OpenWebUI 主题颜色
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
|
|
||||||
- 在 OpenWebUI 设置 → 界面 → 产物中,启用“iframe 沙盒允许同源访问”
|
|
||||||
- 验证 iframe 的 sandbox 属性包含 `allow-same-origin` 和 `allow-scripts`
|
|
||||||
- 确保父文档有 `<meta name="theme-color">` 标签或主题 class/属性
|
|
||||||
- 使用手动主题切换按钮覆盖自动检测
|
|
||||||
- 检查浏览器控制台是否有跨域错误
|
|
||||||
|
|
||||||
### 问题:导出功能不工作
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
|
|
||||||
- 确认浏览器支持剪贴板 API
|
|
||||||
- 检查浏览器是否阻止了剪贴板访问权限
|
|
||||||
- 使用现代浏览器(Chrome、Firefox、Edge 等)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 最佳实践
|
## 最佳实践
|
||||||
|
|
||||||
1. **文本准备**
|
1. **文本准备**:提供结构清晰、层次分明的文本内容。
|
||||||
- 提供结构清晰、层次分明的文本内容
|
2. **模型选择**:日常使用推荐 `gemini-2.5-flash` 等快速模型。
|
||||||
- 使用段落、列表等格式帮助 LLM 理解文本结构
|
3. **导出质量**:PNG 适合演示分享,SVG 适合进一步矢量编辑。
|
||||||
- 避免过于冗长或无结构的文本
|
|
||||||
|
|
||||||
2. **模型选择**
|
|
||||||
- 对于日常使用,推荐 `gemini-2.5-flash` 等快速模型
|
|
||||||
- 对于复杂文本分析,可以使用更强大的模型(如 GPT-4)
|
|
||||||
- 根据需求平衡速度和分析质量
|
|
||||||
|
|
||||||
3. **性能优化**
|
|
||||||
- 合理设置 `MIN_TEXT_LENGTH`,避免处理过短的文本
|
|
||||||
- 对于特别长的文本,考虑先进行摘要再生成思维导图
|
|
||||||
- 在生产环境中关闭 `show_status` 以减少界面更新
|
|
||||||
|
|
||||||
4. **导出质量**
|
|
||||||
- **PNG 导出**:最适合演示、文档和分享(9 倍分辨率适合打印)
|
|
||||||
- **SVG 导出**:最适合在矢量图形工具中进一步编辑(无限缩放)
|
|
||||||
- **Markdown 导出**:最适合版本控制、协作和重新生成
|
|
||||||
|
|
||||||
5. **主题一致性**
|
|
||||||
- 启用同源访问以实现自动主题检测
|
|
||||||
- 如果自动检测失败,使用手动主题切换
|
|
||||||
- 在切换到所需主题后导出 PNG,以保持视觉一致性
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 依赖要求
|
|
||||||
|
|
||||||
本插件仅使用 OpenWebUI 的内置依赖,**无需安装额外的软件包。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
### v0.9.1
|
|
||||||
|
|
||||||
**新功能:图片输出模式**
|
|
||||||
|
|
||||||
- 新增 `OUTPUT_MODE` 配置参数,支持两种模式:
|
|
||||||
- `html`(默认):交互式 HTML 思维导图,带完整控制面板
|
|
||||||
- `image`:静态 SVG 图片直接嵌入 Markdown(上传至 `/api/v1/files`)
|
|
||||||
- 图片模式特性:
|
|
||||||
- 自动响应式宽度(适应聊天容器)
|
|
||||||
- 自动主题检测(亮色/暗色)
|
|
||||||
- 通过 Chat API 持久化存储(刷新页面后保留)
|
|
||||||
- 高效文件存储(聊天记录中无超长 Base64 字符串)
|
|
||||||
|
|
||||||
**改进项:**
|
|
||||||
|
|
||||||
- 实现健壮的 Chat API 更新机制,带重试逻辑
|
|
||||||
- 修复消息持久化,同时更新 `messages[]` 和 `history.messages`
|
|
||||||
- 添加 Event API 实现即时前端更新
|
|
||||||
- 移除不必要的 `SVG_WIDTH` 和 `SVG_HEIGHT` 参数(现已自动计算)
|
|
||||||
|
|
||||||
**技术细节:**
|
|
||||||
|
|
||||||
- 图片模式使用 `__event_call__` 在浏览器中执行 JavaScript
|
|
||||||
- SVG 离屏渲染,转换为 Blob,并上传至 OpenWebUI Files API
|
|
||||||
- 通过 OpenWebUI Backend-Controlled API 流程更新聊天消息为 `/api/v1/files/{id}/content` URL
|
|
||||||
|
|
||||||
### v0.8.2
|
|
||||||
|
|
||||||
- 移除输出中的调试信息
|
|
||||||
|
|
||||||
### v0.8.0 (Previous Version)
|
|
||||||
|
|
||||||
**主要功能:**
|
|
||||||
|
|
||||||
- 添加高分辨率 PNG 导出(9 倍分辨率,约 1-2MB 文件大小)
|
|
||||||
- 实现完整的控制面板,包含缩放控制(+/-/重置)
|
|
||||||
- 添加展开层级选择(全部/2级/3级)
|
|
||||||
- 集成全屏模式,自动适应
|
|
||||||
- 添加手动主题切换按钮(亮色/暗色)
|
|
||||||
- 实现 4 级优先级的自动主题检测
|
|
||||||
|
|
||||||
**改进项:**
|
|
||||||
|
|
||||||
- 优化字体层级(H1:22px 粗体,H2:18px 粗体)
|
|
||||||
- 增强深色模式,完整的主题支持
|
|
||||||
- 改进 PNG 导出技术(SVG 转 Canvas,处理 foreignObject)
|
|
||||||
- 在导出的 PNG 图片中保持主题
|
|
||||||
- 增强安全性,按钮事件使用非冒泡机制
|
|
||||||
|
|
||||||
**Bug 修复:**
|
|
||||||
|
|
||||||
- 修复跨域 iframe 中的主题检测问题
|
|
||||||
- 解决 SVG 中 HTML 内容的 PNG 导出问题
|
|
||||||
- 改进与 OpenWebUI 主题系统的兼容性
|
|
||||||
|
|
||||||
### v0.7.2
|
|
||||||
|
|
||||||
- 优化文本提取逻辑,自动过滤 HTML 代码块
|
|
||||||
- 改进错误处理和用户反馈
|
|
||||||
- 增强导出功能的兼容性
|
|
||||||
- 优化 UI 样式和交互体验
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
本插件采用 MIT 许可证发布。
|
|
||||||
|
|
||||||
## 贡献
|
|
||||||
|
|
||||||
欢迎提交问题报告和改进建议!请访问项目仓库:[awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 相关资源
|
|
||||||
|
|
||||||
- [Markmap 官方网站](https://markmap.js.org/)
|
|
||||||
- [OpenWebUI 文档](https://docs.openwebui.com/)
|
|
||||||
- [D3.js 官方网站](https://d3js.org/)
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: Smart Mind Map
|
title: Smart Mind Map
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
|
funding_url: https://github.com/open-webui
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
version: 0.9.1
|
version: 0.9.1
|
||||||
openwebui_id: 3094c59a-b4dd-4e0c-9449-15e2dd547dc4
|
openwebui_id: 3094c59a-b4dd-4e0c-9449-15e2dd547dc4
|
||||||
@@ -49,6 +50,8 @@ Please strictly follow these guidelines:
|
|||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
USER_PROMPT_GENERATE_MINDMAP = """
|
USER_PROMPT_GENERATE_MINDMAP = """
|
||||||
Please analyze the following long-form text and structure its core themes, key concepts, branches, and sub-branches into standard Markdown list syntax for Markmap.js rendering.
|
Please analyze the following long-form text and structure its core themes, key concepts, branches, and sub-branches into standard Markdown list syntax for Markmap.js rendering.
|
||||||
|
|
||||||
@@ -791,6 +794,10 @@ class Action:
|
|||||||
default="html",
|
default="html",
|
||||||
description="Output mode: 'html' for interactive HTML (default), or 'image' to embed as Markdown image.",
|
description="Output mode: 'html' for interactive HTML (default), or 'image' to embed as Markdown image.",
|
||||||
)
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="Whether to print debug logs in the browser console.",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.valves = self.Valves()
|
self.valves = self.Valves()
|
||||||
@@ -819,45 +826,41 @@ class Action:
|
|||||||
"user_language": user_data.get("language", "en-US"),
|
"user_language": user_data.get("language", "en-US"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
def _get_chat_context(
|
||||||
"""Extract chat_id from body or metadata"""
|
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):
|
if isinstance(body, dict):
|
||||||
chat_id = body.get("chat_id")
|
chat_id = body.get("chat_id", "")
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
message_id = body.get("id", "") # message_id is usually 'id' in body
|
||||||
return chat_id.strip()
|
|
||||||
|
|
||||||
body_metadata = body.get("metadata", {})
|
# Check body.metadata as fallback
|
||||||
if isinstance(body_metadata, dict):
|
if not chat_id or not message_id:
|
||||||
chat_id = body_metadata.get("chat_id")
|
body_metadata = body.get("metadata", {})
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
if isinstance(body_metadata, dict):
|
||||||
return chat_id.strip()
|
if not chat_id:
|
||||||
|
chat_id = body_metadata.get("chat_id", "")
|
||||||
|
if not message_id:
|
||||||
|
message_id = body_metadata.get("message_id", "")
|
||||||
|
|
||||||
if isinstance(metadata, dict):
|
# 2. Try to get from __metadata__ (as supplement)
|
||||||
chat_id = metadata.get("chat_id")
|
if __metadata__ and isinstance(__metadata__, dict):
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
if not chat_id:
|
||||||
return chat_id.strip()
|
chat_id = __metadata__.get("chat_id", "")
|
||||||
|
if not message_id:
|
||||||
|
message_id = __metadata__.get("message_id", "")
|
||||||
|
|
||||||
return ""
|
return {
|
||||||
|
"chat_id": str(chat_id).strip(),
|
||||||
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str:
|
"message_id": str(message_id).strip(),
|
||||||
"""Extract message_id from body or metadata"""
|
}
|
||||||
if isinstance(body, dict):
|
|
||||||
message_id = body.get("id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
body_metadata = body.get("metadata", {})
|
|
||||||
if isinstance(body_metadata, dict):
|
|
||||||
message_id = body_metadata.get("message_id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
if isinstance(metadata, dict):
|
|
||||||
message_id = metadata.get("message_id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _extract_markdown_syntax(self, llm_output: str) -> str:
|
def _extract_markdown_syntax(self, llm_output: str) -> str:
|
||||||
match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL)
|
match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL)
|
||||||
@@ -884,6 +887,42 @@ class Action:
|
|||||||
{"type": "notification", "data": {"type": ntype, "content": content}}
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""Print structured debug logs in the browser console"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""Print structured debug logs in the browser console"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
def _remove_existing_html(self, content: str) -> str:
|
def _remove_existing_html(self, content: str) -> str:
|
||||||
"""Removes existing plugin-generated HTML code blocks from the content."""
|
"""Removes existing plugin-generated HTML code blocks from the content."""
|
||||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||||
@@ -1515,8 +1554,9 @@ class Action:
|
|||||||
# Check output mode
|
# Check output mode
|
||||||
if self.valves.OUTPUT_MODE == "image":
|
if self.valves.OUTPUT_MODE == "image":
|
||||||
# Image mode: use JavaScript to render and embed as Markdown image
|
# Image mode: use JavaScript to render and embed as Markdown image
|
||||||
chat_id = self._extract_chat_id(body, __metadata__)
|
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||||
message_id = self._extract_message_id(body, __metadata__)
|
chat_id = chat_ctx["chat_id"]
|
||||||
|
message_id = chat_ctx["message_id"]
|
||||||
|
|
||||||
await self._emit_status(
|
await self._emit_status(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: 思维导图
|
title: 思维导图
|
||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/open-webui
|
||||||
version: 0.9.1
|
version: 0.9.1
|
||||||
openwebui_id: 8d4b097b-219b-4dd2-b509-05fbe6388335
|
openwebui_id: 8d4b097b-219b-4dd2-b509-05fbe6388335
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
|
||||||
@@ -49,6 +49,8 @@ SYSTEM_PROMPT_MINDMAP_ASSISTANT = """
|
|||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
USER_PROMPT_GENERATE_MINDMAP = """
|
USER_PROMPT_GENERATE_MINDMAP = """
|
||||||
请分析以下长篇文本,并将其核心主题、关键概念、分支和子分支结构化为标准的Markdown列表语法,以供Markmap.js渲染。
|
请分析以下长篇文本,并将其核心主题、关键概念、分支和子分支结构化为标准的Markdown列表语法,以供Markmap.js渲染。
|
||||||
|
|
||||||
@@ -790,6 +792,10 @@ class Action:
|
|||||||
default="html",
|
default="html",
|
||||||
description="输出模式: 'html' 为交互式HTML(默认),'image' 为嵌入Markdown图片。",
|
description="输出模式: 'html' 为交互式HTML(默认),'image' 为嵌入Markdown图片。",
|
||||||
)
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="是否在浏览器控制台打印调试日志。",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.valves = self.Valves()
|
self.valves = self.Valves()
|
||||||
@@ -818,45 +824,41 @@ class Action:
|
|||||||
"user_language": user_data.get("language", "zh-CN"),
|
"user_language": user_data.get("language", "zh-CN"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
def _get_chat_context(
|
||||||
"""从 body 或 metadata 中提取 chat_id"""
|
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):
|
if isinstance(body, dict):
|
||||||
chat_id = body.get("chat_id")
|
chat_id = body.get("chat_id", "")
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
message_id = body.get("id", "") # message_id 在 body 中通常是 id
|
||||||
return chat_id.strip()
|
|
||||||
|
|
||||||
body_metadata = body.get("metadata", {})
|
# 再次检查 body.metadata
|
||||||
if isinstance(body_metadata, dict):
|
if not chat_id or not message_id:
|
||||||
chat_id = body_metadata.get("chat_id")
|
body_metadata = body.get("metadata", {})
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
if isinstance(body_metadata, dict):
|
||||||
return chat_id.strip()
|
if not chat_id:
|
||||||
|
chat_id = body_metadata.get("chat_id", "")
|
||||||
|
if not message_id:
|
||||||
|
message_id = body_metadata.get("message_id", "")
|
||||||
|
|
||||||
if isinstance(metadata, dict):
|
# 2. 尝试从 __metadata__ 获取 (作为补充)
|
||||||
chat_id = metadata.get("chat_id")
|
if __metadata__ and isinstance(__metadata__, dict):
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
if not chat_id:
|
||||||
return chat_id.strip()
|
chat_id = __metadata__.get("chat_id", "")
|
||||||
|
if not message_id:
|
||||||
|
message_id = __metadata__.get("message_id", "")
|
||||||
|
|
||||||
return ""
|
return {
|
||||||
|
"chat_id": str(chat_id).strip(),
|
||||||
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str:
|
"message_id": str(message_id).strip(),
|
||||||
"""从 body 或 metadata 中提取 message_id"""
|
}
|
||||||
if isinstance(body, dict):
|
|
||||||
message_id = body.get("id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
body_metadata = body.get("metadata", {})
|
|
||||||
if isinstance(body_metadata, dict):
|
|
||||||
message_id = body_metadata.get("message_id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
if isinstance(metadata, dict):
|
|
||||||
message_id = metadata.get("message_id")
|
|
||||||
if isinstance(message_id, str) and message_id.strip():
|
|
||||||
return message_id.strip()
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _extract_markdown_syntax(self, llm_output: str) -> str:
|
def _extract_markdown_syntax(self, llm_output: str) -> str:
|
||||||
match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL)
|
match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL)
|
||||||
@@ -881,6 +883,24 @@ class Action:
|
|||||||
{"type": "notification", "data": {"type": ntype, "content": content}}
|
{"type": "notification", "data": {"type": ntype, "content": content}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, emitter, title: str, data: dict):
|
||||||
|
"""在浏览器控制台打印结构化调试日志"""
|
||||||
|
if not self.valves.SHOW_DEBUG_LOG or not emitter:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
js_code = f"""
|
||||||
|
(async function() {{
|
||||||
|
console.group("🛠️ {title}");
|
||||||
|
console.log({json.dumps(data, ensure_ascii=False)});
|
||||||
|
console.groupEnd();
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
await emitter({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error emitting debug log: {e}")
|
||||||
|
|
||||||
def _remove_existing_html(self, content: str) -> str:
|
def _remove_existing_html(self, content: str) -> str:
|
||||||
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
|
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
|
||||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||||
@@ -1508,8 +1528,9 @@ class Action:
|
|||||||
# 检查输出模式
|
# 检查输出模式
|
||||||
if self.valves.OUTPUT_MODE == "image":
|
if self.valves.OUTPUT_MODE == "image":
|
||||||
# 图片模式: 使用 JavaScript 渲染并嵌入为 Markdown 图片
|
# 图片模式: 使用 JavaScript 渲染并嵌入为 Markdown 图片
|
||||||
chat_id = self._extract_chat_id(body, __metadata__)
|
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||||
message_id = self._extract_message_id(body, __metadata__)
|
chat_id = chat_ctx["chat_id"]
|
||||||
|
message_id = chat_ctx["message_id"]
|
||||||
|
|
||||||
await self._emit_status(
|
await self._emit_status(
|
||||||
__event_emitter__,
|
__event_emitter__,
|
||||||
|
|||||||
@@ -48,7 +48,3 @@ When adding a new filter, please follow these steps:
|
|||||||
|
|
||||||
Fu-Jie
|
Fu-Jie
|
||||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|||||||
@@ -70,7 +70,3 @@
|
|||||||
|
|
||||||
Fu-Jie
|
Fu-Jie
|
||||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|||||||
@@ -1,26 +1,34 @@
|
|||||||
# Async Context Compression Filter
|
# Async Context Compression Filter
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.1.3 | **License:** MIT
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.2.2 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
|
|
||||||
This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent.
|
This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent.
|
||||||
|
|
||||||
|
## What's new in 1.2.2
|
||||||
|
- **Critical Fix**: Resolved `TypeError: 'str' object is not callable` caused by variable name conflict in logging function.
|
||||||
|
- **Compatibility**: Enhanced `params` handling to support Pydantic objects, improving compatibility with different OpenWebUI versions.
|
||||||
|
|
||||||
|
## What's new in 1.2.1
|
||||||
|
|
||||||
|
- **Smart Configuration**: Automatically detects base model settings for custom models and adds `summary_model_max_context` for independent summary limits.
|
||||||
|
- **Performance & Refactoring**: Optimized threshold parsing with caching, removed redundant code, and improved LLM response handling (JSONResponse support).
|
||||||
|
- **Bug Fixes & Modernization**: Fixed `datetime` deprecation warnings, corrected type annotations, and replaced print statements with proper logging.
|
||||||
|
|
||||||
|
## What's new in 1.2.0
|
||||||
|
|
||||||
|
- **Preflight Context Check**: Before sending to the model, validates that total tokens fit within the context window. Automatically trims or drops oldest messages if exceeded.
|
||||||
|
- **Structure-Aware Assistant Trimming**: When context exceeds the limit, long AI responses are intelligently collapsed while preserving their structure (headers H1-H6, first line, last line).
|
||||||
|
- **Native Tool Output Trimming**: Detects and trims native tool outputs (`function_calling: "native"`), extracting only the final answer. Enable via `enable_tool_output_trimming`. **Note**: Non-native tool outputs are not fully injected into context.
|
||||||
|
- **Consolidated Status Notifications**: Unified "Context Usage" and "Context Summary Updated" notifications with appended warnings (e.g., `| ⚠️ High Usage`) for clearer feedback.
|
||||||
|
- **Context Usage Warning**: Emits a warning notification when context usage exceeds 90%.
|
||||||
|
- **Enhanced Header Detection**: Optimized regex (`^#{1,6}\s+`) to avoid false positives like `#hashtag`.
|
||||||
|
- **Detailed Token Logging**: Logs now show token breakdown for System, Head, Summary, and Tail sections with total.
|
||||||
|
|
||||||
## What's new in 1.1.3
|
## What's new in 1.1.3
|
||||||
- **Improved Compatibility**: Changed summary injection role from `user` to `assistant` for better compatibility across different LLMs.
|
- **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.
|
- **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.
|
- **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.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -31,6 +39,12 @@ This filter reduces token consumption in long conversations through intelligent
|
|||||||
- ✅ Persistent storage via Open WebUI's shared database connection (PostgreSQL, SQLite, etc.).
|
- ✅ Persistent storage via Open WebUI's shared database connection (PostgreSQL, SQLite, etc.).
|
||||||
- ✅ Flexible retention policy to keep the first and last N messages.
|
- ✅ Flexible retention policy to keep the first and last N messages.
|
||||||
- ✅ Smart injection of historical summaries back into the context.
|
- ✅ Smart injection of historical summaries back into the context.
|
||||||
|
- ✅ Structure-aware trimming that preserves document structure (headers, intro, conclusion).
|
||||||
|
- ✅ Native tool output trimming for cleaner context when using function calling.
|
||||||
|
- ✅ Real-time context usage monitoring with warning notifications (>90%).
|
||||||
|
- ✅ Detailed token logging for precise debugging and optimization.
|
||||||
|
- ✅ **Smart Model Matching**: Automatically inherits configuration from base models for custom presets.
|
||||||
|
- ⚠ **Multimodal Support**: Images are preserved but their tokens are **NOT** calculated. Please adjust thresholds accordingly.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -61,17 +75,16 @@ It is recommended to keep this filter early in the chain so it runs before filte
|
|||||||
| `keep_first` | `1` | Always keep the first N messages (protects system prompts). |
|
| `keep_first` | `1` | Always keep the first N messages (protects system prompts). |
|
||||||
| `keep_last` | `6` | Always keep the last N messages to preserve recent context. |
|
| `keep_last` | `6` | Always keep the last N messages to preserve recent context. |
|
||||||
| `summary_model` | `None` | Model for summaries. Strongly recommended to set a fast, economical model (e.g., `gemini-2.5-flash`, `deepseek-v3`). Falls back to the current chat model when empty. |
|
| `summary_model` | `None` | Model for summaries. Strongly recommended to set a fast, economical model (e.g., `gemini-2.5-flash`, `deepseek-v3`). Falls back to the current chat model when empty. |
|
||||||
| `max_summary_tokens` | `4000` | Maximum tokens for the generated summary. |
|
| `summary_model_max_context` | `0` | Max context tokens for the summary model. If 0, falls back to `model_thresholds` or global `max_context_tokens`. |
|
||||||
|
| `max_summary_tokens` | `16384` | Maximum tokens for the generated summary. |
|
||||||
| `summary_temperature` | `0.3` | Randomness for summary generation. Lower is more deterministic. |
|
| `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). |
|
| `model_thresholds` | `{}` | Per-model overrides for `compression_threshold_tokens` and `max_context_tokens` (useful for mixed models). |
|
||||||
|
| `enable_tool_output_trimming` | `false` | When enabled and `function_calling: "native"` is active, trims verbose tool outputs to extract only the final answer. |
|
||||||
| `debug_mode` | `true` | Log verbose debug info. Set to `false` in production. |
|
| `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. |
|
| `show_debug_log` | `false` | Print debug logs to browser console (F12). Useful for frontend debugging. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
- **Database table not created**: Ensure Open WebUI is configured with a database and check Open WebUI logs for errors.
|
|
||||||
- **Summary not generated**: Confirm `compression_threshold_tokens` was hit and `summary_model` is compatible. Review logs for details.
|
|
||||||
- **Initial system prompt is lost**: Keep `keep_first` greater than 0 to protect the initial message.
|
- **Initial system prompt is lost**: Keep `keep_first` greater than 0 to protect the initial message.
|
||||||
- **Compression effect is weak**: Raise `compression_threshold_tokens` or lower `keep_first` / `keep_last` to allow more aggressive compression.
|
- **Compression effect is weak**: Raise `compression_threshold_tokens` or lower `keep_first` / `keep_last` to allow more aggressive compression.
|
||||||
|
- **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|||||||
@@ -1,28 +1,36 @@
|
|||||||
# 异步上下文压缩过滤器
|
# 异步上下文压缩过滤器
|
||||||
|
|
||||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.1.3 | **许可证:** MIT
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.2.2 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||||
|
|
||||||
> **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
|
> **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
|
||||||
|
|
||||||
本过滤器通过智能摘要和消息压缩技术,在保持对话连贯性的同时,显著降低长对话的 Token 消耗。
|
本过滤器通过智能摘要和消息压缩技术,在保持对话连贯性的同时,显著降低长对话的 Token 消耗。
|
||||||
|
|
||||||
|
## 1.2.2 版本更新
|
||||||
|
- **严重错误修复**: 解决了因日志函数变量名冲突导致的 `TypeError: 'str' object is not callable` 错误。
|
||||||
|
- **兼容性增强**: 改进了 `params` 处理逻辑以支持 Pydantic 对象,提高了对不同 OpenWebUI 版本的兼容性。
|
||||||
|
|
||||||
|
## 1.2.1 版本更新
|
||||||
|
|
||||||
|
- **智能配置增强**: 自动检测自定义模型的基础模型配置,并新增 `summary_model_max_context` 参数以独立控制摘要模型的上下文限制。
|
||||||
|
- **性能优化与重构**: 重构了阈值解析逻辑并增加缓存,移除了冗余的处理代码,并增强了 LLM 响应处理(支持 JSONResponse)。
|
||||||
|
- **稳定性改进**: 修复了 `datetime` 弃用警告,修正了类型注解,并将 print 语句替换为标准日志记录。
|
||||||
|
|
||||||
|
## 1.2.0 版本更新
|
||||||
|
|
||||||
|
- **预检上下文检查 (Preflight Context Check)**: 在发送给模型之前,验证总 Token 是否符合上下文窗口。如果超出,自动裁剪或丢弃最旧的消息。
|
||||||
|
- **结构感知助手裁剪 (Structure-Aware Assistant Trimming)**: 当上下文超出限制时,智能折叠过长的 AI 回复,同时保留其结构(标题 H1-H6、首行、尾行)。
|
||||||
|
- **原生工具输出裁剪 (Native Tool Output Trimming)**: 检测并裁剪原生工具输出 (`function_calling: "native"`),仅提取最终答案。通过 `enable_tool_output_trimming` 启用。**注意**:非原生工具调用输出不会完整注入上下文。
|
||||||
|
- **统一状态通知**: 统一了“上下文使用情况”和“上下文摘要更新”的通知,并附加警告(例如 `| ⚠️ 高负载`),反馈更清晰。
|
||||||
|
- **上下文使用警告**: 当上下文使用率超过 90% 时发出警告通知。
|
||||||
|
- **增强的标题检测**: 优化了正则表达式 (`^#{1,6}\s+`) 以避免误判(如 `#hashtag`)。
|
||||||
|
- **详细 Token 日志**: 日志现在显示 System、Head、Summary 和 Tail 部分的 Token 细分及总计。
|
||||||
|
|
||||||
## 1.1.3 版本更新
|
## 1.1.3 版本更新
|
||||||
- **兼容性提升**: 将摘要注入角色从 `user` 改为 `assistant`,以提高在不同 LLM 之间的兼容性。
|
- **兼容性提升**: 将摘要注入角色从 `user` 改为 `assistant`,以提高在不同 LLM 之间的兼容性。
|
||||||
- **稳定性增强**: 修复了状态管理中的竞态条件,解决了高并发场景下可能出现的“无法获取 inlet 状态”警告。
|
- **稳定性增强**: 修复了状态管理中的竞态条件,解决了高并发场景下可能出现的“无法获取 inlet 状态”警告。
|
||||||
- **Bug 修复**: 修正了默认模型处理逻辑,防止在未指定模型时产生误导性日志。
|
- **Bug 修复**: 修正了默认模型处理逻辑,防止在未指定模型时产生误导性日志。
|
||||||
|
|
||||||
## 1.1.2 版本更新
|
|
||||||
|
|
||||||
- **Open WebUI v0.7.x 兼容性**: 修复了影响 Open WebUI v0.7.x 用户的严重数据库会话绑定错误。插件现在动态发现数据库引擎和会话上下文,确保跨版本兼容性。
|
|
||||||
- **增强错误报告**: 后台摘要生成过程中的错误现在会通过状态栏和浏览器控制台同时报告。
|
|
||||||
- **健壮的模型处理**: 改进了对缺失或无效模型 ID 的处理,防止程序崩溃。
|
|
||||||
|
|
||||||
## 1.1.1 版本更新
|
|
||||||
|
|
||||||
- **前端调试**: 新增 `show_debug_log` 选项,支持在浏览器控制台 (F12) 打印调试信息。
|
|
||||||
- **压缩优化**: 优化 Token 计算逻辑,防止历史记录被过度截断,保留更多上下文。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -33,6 +41,12 @@
|
|||||||
- ✅ **持久化存储**: 复用 Open WebUI 共享数据库连接,自动支持 PostgreSQL/SQLite 等。
|
- ✅ **持久化存储**: 复用 Open WebUI 共享数据库连接,自动支持 PostgreSQL/SQLite 等。
|
||||||
- ✅ **灵活保留策略**: 可配置保留对话头部和尾部消息,确保关键信息连贯。
|
- ✅ **灵活保留策略**: 可配置保留对话头部和尾部消息,确保关键信息连贯。
|
||||||
- ✅ **智能注入**: 将历史摘要智能注入到新上下文中。
|
- ✅ **智能注入**: 将历史摘要智能注入到新上下文中。
|
||||||
|
- ✅ **结构感知裁剪**: 智能折叠过长消息,保留文档骨架(标题、首尾)。
|
||||||
|
- ✅ **原生工具输出裁剪**: 支持裁剪冗长的工具调用输出。
|
||||||
|
- ✅ **实时监控**: 实时监控上下文使用情况,超过 90% 发出警告。
|
||||||
|
- ✅ **详细日志**: 提供精确的 Token 统计日志,便于调试。
|
||||||
|
- ✅ **智能模型匹配**: 自定义模型自动继承基础模型的阈值配置。
|
||||||
|
- ⚠ **多模态支持**: 图片内容会被保留,但其 Token **不参与计算**。请相应调整阈值。
|
||||||
|
|
||||||
详细的工作原理和流程请参考 [工作流程指南](WORKFLOW_GUIDE_CN.md)。
|
详细的工作原理和流程请参考 [工作流程指南](WORKFLOW_GUIDE_CN.md)。
|
||||||
|
|
||||||
@@ -74,6 +88,7 @@
|
|||||||
| 参数 | 默认值 | 描述 |
|
| 参数 | 默认值 | 描述 |
|
||||||
| :-------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ |
|
| :-------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `summary_model` | `None` | 用于生成摘要的模型 ID。**强烈建议**配置快速、经济、上下文窗口大的模型(如 `gemini-2.5-flash`、`deepseek-v3`)。留空则尝试复用当前对话模型。 |
|
| `summary_model` | `None` | 用于生成摘要的模型 ID。**强烈建议**配置快速、经济、上下文窗口大的模型(如 `gemini-2.5-flash`、`deepseek-v3`)。留空则尝试复用当前对话模型。 |
|
||||||
|
| `summary_model_max_context` | `0` | 摘要模型的最大上下文 Token 数。如果为 0,则回退到 `model_thresholds` 或全局 `max_context_tokens`。 |
|
||||||
| `max_summary_tokens` | `16384` | 生成摘要时允许的最大 Token 数。 |
|
| `max_summary_tokens` | `16384` | 生成摘要时允许的最大 Token 数。 |
|
||||||
| `summary_temperature` | `0.1` | 控制摘要生成的随机性,较低的值结果更稳定。 |
|
| `summary_temperature` | `0.1` | 控制摘要生成的随机性,较低的值结果更稳定。 |
|
||||||
|
|
||||||
@@ -100,21 +115,15 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `debug_mode`
|
| 参数 | 默认值 | 描述 |
|
||||||
|
| :----------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
- **默认值**: `true`
|
| `enable_tool_output_trimming` | `false` | 启用时,若 `function_calling: "native"` 激活,将裁剪冗长的工具输出以仅提取最终答案。 |
|
||||||
- **描述**: 是否在 Open WebUI 的控制台日志中打印详细的调试信息(如 Token 计数、压缩进度、数据库操作等)。生产环境建议设为 `false`。
|
| `debug_mode` | `true` | 是否在 Open WebUI 的控制台日志中打印详细的调试信息(如 Token 计数、压缩进度、数据库操作等)。生产环境建议设为 `false`。 |
|
||||||
|
| `show_debug_log` | `false` | 是否在浏览器控制台 (F12) 打印调试日志。便于前端调试。 |
|
||||||
#### `show_debug_log`
|
| `show_token_usage_status` | `true` | 是否在对话结束时显示 Token 使用情况的状态通知。 |
|
||||||
|
|
||||||
- **默认值**: `false`
|
|
||||||
- **描述**: 是否在浏览器控制台 (F12) 打印调试日志。便于前端调试。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 故障排除
|
|
||||||
|
|
||||||
- **数据库表未创建**:确保 Open WebUI 已配置数据库,并查看日志获取错误信息。
|
|
||||||
- **摘要未生成**:检查是否达到 `compression_threshold_tokens`,确认 `summary_model` 可用,并查看日志。
|
|
||||||
- **初始系统提示丢失**:将 `keep_first` 设置为大于 0。
|
- **初始系统提示丢失**:将 `keep_first` 设置为大于 0。
|
||||||
- **压缩效果不明显**:提高 `compression_threshold_tokens`,或降低 `keep_first` / `keep_last` 以增强压缩力度。
|
- **压缩效果不明显**:提高 `compression_threshold_tokens`,或降低 `keep_first` / `keep_last` 以增强压缩力度。
|
||||||
|
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
60
plugins/filters/folder-memory/README.md
Normal file
60
plugins/filters/folder-memory/README.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Folder Memory
|
||||||
|
|
||||||
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.1.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📌 What's new in 0.1.0
|
||||||
|
- **Initial Release**: Automated "Project Rules" management for OpenWebUI folders.
|
||||||
|
- **Folder-Level Persistence**: Automatically updates folder system prompts with extracted rules.
|
||||||
|
- **Optimized Performance**: Runs asynchronously and supports `PRIORITY` configuration for seamless integration with other filters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Folder Memory** is an intelligent context filter plugin for OpenWebUI. It automatically extracts consistent "Project Rules" from ongoing conversations within a folder and injects them back into the folder's system prompt.
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- **Automatic Extraction**: Analyzes chat history every N messages to extract project rules.
|
||||||
|
- **Non-destructive Injection**: Updates only the specific "Project Rules" block in the system prompt, preserving other instructions.
|
||||||
|
- **Async Processing**: Runs in the background without blocking the user's chat experience.
|
||||||
|
- **ORM Integration**: Directly updates folder data using OpenWebUI's internal models for reliability.
|
||||||
|
|
||||||
|
## ⚠️ Prerequisites
|
||||||
|
|
||||||
|
- **Conversations must occur inside a folder.** This plugin only triggers when a chat belongs to a folder (i.e., you need to create a folder in OpenWebUI and start a conversation within it).
|
||||||
|
|
||||||
|
## 📦 Installation
|
||||||
|
|
||||||
|
1. Copy `folder_memory.py` to your OpenWebUI `plugins/filters/` directory (or upload via Admin UI).
|
||||||
|
2. Enable the filter in your **Settings** -> **Filters**.
|
||||||
|
3. (Optional) Configure the triggering threshold (default: every 10 messages).
|
||||||
|
|
||||||
|
## ⚙️ Configuration (Valves)
|
||||||
|
|
||||||
|
| Valve | Default | Description |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `PRIORITY` | `20` | Priority level for the filter operations. |
|
||||||
|
| `MESSAGE_TRIGGER_COUNT` | `10` | The number of messages required to trigger a rule analysis. |
|
||||||
|
| `MODEL_ID` | `""` | The model used to generate rules. If empty, uses the current chat model. |
|
||||||
|
| `RULES_BLOCK_TITLE` | `## 📂 Project Rules` | The title displayed above the injected rules block. |
|
||||||
|
| `SHOW_DEBUG_LOG` | `False` | Show detailed debug logs in the browser console. |
|
||||||
|
| `UPDATE_ROOT_FOLDER` | `False` | If enabled, finds and updates the root folder rules instead of the current subfolder. |
|
||||||
|
|
||||||
|
## 🛠️ How It Works
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. **Trigger**: When a conversation reaches `MESSAGE_TRIGGER_COUNT` (e.g., 10, 20 messages).
|
||||||
|
2. **Analysis**: The plugin sends the recent conversation + existing rules to the LLM.
|
||||||
|
3. **Synthesis**: The LLM merges new insights with old rules, removing obsolete ones.
|
||||||
|
4. **Update**: The new rule set replaces the `<!-- OWUI_PROJECT_RULES_START -->` block in the folder's system prompt.
|
||||||
|
|
||||||
|
## ⚠️ Notes
|
||||||
|
|
||||||
|
- This plugin modifies the `system_prompt` of your folders.
|
||||||
|
- It uses a specific marker `<!-- OWUI_PROJECT_RULES_START -->` to locate its content. Do not manually remove these markers if you want the plugin to continue managing that section.
|
||||||
|
|
||||||
|
## 🗺️ Roadmap
|
||||||
|
|
||||||
|
See [ROADMAP.md](./ROADMAP.md) for future plans, including "Project Knowledge" collection.
|
||||||
62
plugins/filters/folder-memory/README_CN.md
Normal file
62
plugins/filters/folder-memory/README_CN.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# 文件夹记忆 (Folder Memory)
|
||||||
|
|
||||||
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.1.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📌 0.1.0 版本特性
|
||||||
|
- **首个版本发布**:专注于自动化的“项目规则”管理。
|
||||||
|
- **文件夹级持久化**:自动将提取的规则回写到文件夹系统提示词中。
|
||||||
|
- **性能优化**:采用异步处理机制,并支持 `PRIORITY` 配置,确保与其他过滤器(如上下文压缩)完美协作。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文件夹记忆 (Folder Memory)** 是一个 OpenWebUI 的智能上下文过滤器插件。它能自动从文件夹内的对话中提取一致性的“项目规则”,并将其回写到文件夹的系统提示词中。
|
||||||
|
|
||||||
|
这确保了该文件夹内的所有未来对话都能共享相同的进化上下文和规则,无需手动更新。
|
||||||
|
|
||||||
|
## ✨ 功能特性
|
||||||
|
|
||||||
|
- **自动提取**:每隔 N 条消息分析一次聊天记录,提取项目规则。
|
||||||
|
- **无损注入**:仅更新系统提示词中的特定“项目规则”块,保留其他指令。
|
||||||
|
- **异步处理**:在后台运行,不阻塞用户的聊天体验。
|
||||||
|
- **ORM 集成**:直接使用 OpenWebUI 的内部模型更新文件夹数据,确保可靠性。
|
||||||
|
|
||||||
|
## ⚠️ 前置条件
|
||||||
|
|
||||||
|
- **对话必须在文件夹内进行。** 此插件仅在聊天属于某个文件夹时触发(即您需要先在 OpenWebUI 中创建一个文件夹,并在其内部开始对话)。
|
||||||
|
|
||||||
|
## 📦 安装指南
|
||||||
|
|
||||||
|
1. 将 `folder_memory.py` (或中文版 `folder_memory_cn.py`) 复制到 OpenWebUI 的 `plugins/filters/` 目录(或通过管理员 UI 上传)。
|
||||||
|
2. 在 **设置** -> **过滤器** 中启用该插件。
|
||||||
|
3. (可选)配置触发阈值(默认:每 10 条消息)。
|
||||||
|
|
||||||
|
## ⚙️ 配置 (Valves)
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `PRIORITY` | `20` | 过滤器操作的优先级。 |
|
||||||
|
| `MESSAGE_TRIGGER_COUNT` | `10` | 触发规则分析的消息数量阈值。 |
|
||||||
|
| `MODEL_ID` | `""` | 用于生成规则的模型 ID。若为空,则使用当前对话模型。 |
|
||||||
|
| `RULES_BLOCK_TITLE` | `## 📂 项目规则` | 显示在注入规则块上方的标题。 |
|
||||||
|
| `SHOW_DEBUG_LOG` | `False` | 在浏览器控制台显示详细调试日志。 |
|
||||||
|
| `UPDATE_ROOT_FOLDER` | `False` | 如果启用,将向上查找并更新根文件夹的规则,而不是当前子文件夹。 |
|
||||||
|
|
||||||
|
## 🛠️ 工作原理
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. **触发**:当对话达到 `MESSAGE_TRIGGER_COUNT`(例如 10、20 条消息)时。
|
||||||
|
2. **分析**:插件将最近的对话 + 现有规则发送给 LLM。
|
||||||
|
3. **综合**:LLM 将新见解与旧规则合并,移除过时的规则。
|
||||||
|
4. **更新**:新的规则集替换文件夹系统提示词中的 `<!-- OWUI_PROJECT_RULES_START -->` 块。
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
- 此插件会修改文件夹的 `system_prompt`。
|
||||||
|
- 它使用特定标记 `<!-- OWUI_PROJECT_RULES_START -->` 来定位内容。如果您希望插件继续管理该部分,请勿手动删除这些标记。
|
||||||
|
|
||||||
|
## 🗺️ 路线图
|
||||||
|
|
||||||
|
查看 [ROADMAP.md](./ROADMAP.md) 了解未来计划,包括“项目知识”收集功能。
|
||||||
10
plugins/filters/folder-memory/ROADMAP.md
Normal file
10
plugins/filters/folder-memory/ROADMAP.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Roadmap
|
||||||
|
|
||||||
|
## Future Features
|
||||||
|
|
||||||
|
### 🧠 Project Knowledge (Planned)
|
||||||
|
In future versions, we plan to introduce "Project Knowledge" collection. Unlike "Rules" which are strict instructions, "Knowledge" will capture reusable information, consensus, and context that helps the LLM understand the project better.
|
||||||
|
|
||||||
|
- **Knowledge Extraction**: Automatically extract reusable knowledge (terminology, style guides, business logic) from conversations.
|
||||||
|
- **Long-term Memory**: Use the entire folder's chat history as a corpus for knowledge generation.
|
||||||
|
- **Context Injection**: Inject summarized knowledge into the system prompt alongside rules.
|
||||||
BIN
plugins/filters/folder-memory/folder-memory-demo.png
Normal file
BIN
plugins/filters/folder-memory/folder-memory-demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 459 KiB |
483
plugins/filters/folder-memory/folder_memory.py
Normal file
483
plugins/filters/folder-memory/folder_memory.py
Normal file
@@ -0,0 +1,483 @@
|
|||||||
|
"""
|
||||||
|
title: 📂 Folder Memory
|
||||||
|
author: Fu-Jie
|
||||||
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
|
funding_url: https://github.com/open-webui
|
||||||
|
version: 0.1.0
|
||||||
|
description: Automatically extracts project rules from conversations and injects them into the folder's system prompt.
|
||||||
|
requirements:
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, Dict, List
|
||||||
|
from fastapi import Request
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from open_webui.utils.chat import generate_chat_completion
|
||||||
|
from open_webui.models.users import Users
|
||||||
|
from open_webui.models.folders import Folders, FolderUpdateForm
|
||||||
|
from open_webui.models.chats import Chats
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Markers for rule injection
|
||||||
|
RULES_BLOCK_START = "<!-- OWUI_PROJECT_RULES_START -->"
|
||||||
|
RULES_BLOCK_END = "<!-- OWUI_PROJECT_RULES_END -->"
|
||||||
|
|
||||||
|
# System Prompt for Rule Generation
|
||||||
|
SYSTEM_PROMPT_RULE_GENERATOR = """
|
||||||
|
You are a project rule extractor. Your task is to extract "Project Rules" from the conversation and merge them with existing rules.
|
||||||
|
|
||||||
|
### Input
|
||||||
|
1. **Existing Rules**: Current rules in the folder system prompt.
|
||||||
|
2. **Conversation**: Recent chat history.
|
||||||
|
|
||||||
|
### Goal
|
||||||
|
Synthesize a concise list of rules that apply to this project/folder.
|
||||||
|
- **Remove** rules that are no longer relevant or were one-off instructions.
|
||||||
|
- **Add** new consistent requirements found in the conversation.
|
||||||
|
- **Merge** similar rules.
|
||||||
|
- **Format**: Concise bullet points (Markdown).
|
||||||
|
|
||||||
|
### Output Format
|
||||||
|
ONLY output the rules list as Markdown bullet points. Do not include any intro/outro text.
|
||||||
|
Example:
|
||||||
|
- Always use Python 3.11 for type hinting.
|
||||||
|
- Docstrings must follow Google style.
|
||||||
|
- Commit messages should be in English.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Filter:
|
||||||
|
class Valves(BaseModel):
|
||||||
|
PRIORITY: int = Field(
|
||||||
|
default=20, description="Priority level for the filter operations."
|
||||||
|
)
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False, description="Show debug logs in console."
|
||||||
|
)
|
||||||
|
MESSAGE_TRIGGER_COUNT: int = Field(
|
||||||
|
default=10, description="Analyze rules after every N messages in a chat."
|
||||||
|
)
|
||||||
|
MODEL_ID: str = Field(
|
||||||
|
default="",
|
||||||
|
description="Model used for rule extraction. If empty, uses the current chat model.",
|
||||||
|
)
|
||||||
|
RULES_BLOCK_TITLE: str = Field(
|
||||||
|
default="## 📂 Project Rules",
|
||||||
|
description="Title displayed above the rules block.",
|
||||||
|
)
|
||||||
|
UPDATE_ROOT_FOLDER: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="If enabled, finds and updates the root folder rules instead of the current subfolder.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.valves = self.Valves()
|
||||||
|
|
||||||
|
# ==================== Helper Methods ====================
|
||||||
|
|
||||||
|
def _get_user_context(self, __user__: Optional[dict]) -> Dict[str, str]:
|
||||||
|
"""Safely extracts user context information."""
|
||||||
|
if isinstance(__user__, (list, tuple)):
|
||||||
|
user_data = __user__[0] if __user__ else {}
|
||||||
|
elif isinstance(__user__, dict):
|
||||||
|
user_data = __user__
|
||||||
|
else:
|
||||||
|
user_data = {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"user_id": user_data.get("id", ""),
|
||||||
|
"user_name": user_data.get("name", "User"),
|
||||||
|
"user_language": user_data.get("language", "en-US"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_chat_context(
|
||||||
|
self, body: dict, __metadata__: Optional[dict] = None
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
"""Unified extraction of chat context information (chat_id, message_id)."""
|
||||||
|
chat_id = ""
|
||||||
|
message_id = ""
|
||||||
|
|
||||||
|
if isinstance(body, dict):
|
||||||
|
chat_id = body.get("chat_id", "")
|
||||||
|
message_id = body.get("id", "")
|
||||||
|
|
||||||
|
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", "")
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, __event_emitter__, title: str, data: dict):
|
||||||
|
if self.valves.SHOW_DEBUG_LOG and __event_emitter__:
|
||||||
|
try:
|
||||||
|
# Flat log format as requested
|
||||||
|
js_code = f"""
|
||||||
|
console.log("[Folder Memory] {title}", {json.dumps(data, ensure_ascii=False)});
|
||||||
|
"""
|
||||||
|
await __event_emitter__({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error emitting log: {e}")
|
||||||
|
|
||||||
|
async def _emit_status(
|
||||||
|
self, __event_emitter__, description: str, done: bool = False
|
||||||
|
):
|
||||||
|
if __event_emitter__:
|
||||||
|
await __event_emitter__(
|
||||||
|
{"type": "status", "data": {"description": description, "done": done}}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_folder_id(self, body: dict) -> Optional[str]:
|
||||||
|
# 1. Try retrieving folder_id specifically from metadata
|
||||||
|
if "metadata" in body and isinstance(body["metadata"], dict):
|
||||||
|
if "folder_id" in body["metadata"]:
|
||||||
|
return body["metadata"]["folder_id"]
|
||||||
|
|
||||||
|
# 2. Check regular body chat object if available
|
||||||
|
if "chat" in body and isinstance(body["chat"], dict):
|
||||||
|
if "folder_id" in body["chat"]:
|
||||||
|
return body["chat"]["folder_id"]
|
||||||
|
|
||||||
|
# 3. Try fallback via Chat ID (Most reliable)
|
||||||
|
chat_id = body.get("chat_id")
|
||||||
|
if not chat_id:
|
||||||
|
if "metadata" in body and isinstance(body["metadata"], dict):
|
||||||
|
chat_id = body["metadata"].get("chat_id")
|
||||||
|
|
||||||
|
if chat_id:
|
||||||
|
try:
|
||||||
|
chat = Chats.get_chat_by_id(chat_id)
|
||||||
|
if chat and chat.folder_id:
|
||||||
|
return chat.folder_id
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to fetch chat {chat_id}: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _extract_existing_rules(self, system_prompt: str) -> str:
|
||||||
|
pattern = re.compile(
|
||||||
|
re.escape(RULES_BLOCK_START) + r"([\s\S]*?)" + re.escape(RULES_BLOCK_END)
|
||||||
|
)
|
||||||
|
match = pattern.search(system_prompt)
|
||||||
|
if match:
|
||||||
|
# Remove title if it's inside the block
|
||||||
|
content = match.group(1).strip()
|
||||||
|
# Simple cleanup of the title if user formatted it inside
|
||||||
|
title_pat = re.compile(r"^#+\s+.*$", re.MULTILINE)
|
||||||
|
return title_pat.sub("", content).strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _inject_rules(self, system_prompt: str, new_rules: str, title: str) -> str:
|
||||||
|
new_block_content = f"\n{title}\n\n{new_rules}\n"
|
||||||
|
new_block = f"{RULES_BLOCK_START}{new_block_content}{RULES_BLOCK_END}"
|
||||||
|
|
||||||
|
system_prompt = system_prompt or ""
|
||||||
|
pattern = re.compile(
|
||||||
|
re.escape(RULES_BLOCK_START) + r"[\s\S]*?" + re.escape(RULES_BLOCK_END)
|
||||||
|
)
|
||||||
|
|
||||||
|
if pattern.search(system_prompt):
|
||||||
|
return pattern.sub(new_block, system_prompt).strip()
|
||||||
|
else:
|
||||||
|
# Append if not found
|
||||||
|
if system_prompt:
|
||||||
|
return f"{system_prompt}\n\n{new_block}"
|
||||||
|
else:
|
||||||
|
return new_block
|
||||||
|
|
||||||
|
async def _generate_new_rules(
|
||||||
|
self,
|
||||||
|
current_rules: str,
|
||||||
|
messages: List[Dict],
|
||||||
|
user_id: str,
|
||||||
|
__request__: Request,
|
||||||
|
) -> str:
|
||||||
|
# Prepare context
|
||||||
|
conversation_text = "\n".join(
|
||||||
|
[
|
||||||
|
f"{msg['role'].upper()}: {msg['content']}"
|
||||||
|
for msg in messages[-20:] # Analyze last 20 messages context
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt = f"""
|
||||||
|
Existing Rules:
|
||||||
|
{current_rules if current_rules else "None"}
|
||||||
|
|
||||||
|
Conversation Excerpt:
|
||||||
|
{conversation_text}
|
||||||
|
|
||||||
|
Please output the updated Project Rules:
|
||||||
|
"""
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"model": self.valves.MODEL_ID,
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": SYSTEM_PROMPT_RULE_GENERATOR},
|
||||||
|
{"role": "user", "content": prompt},
|
||||||
|
],
|
||||||
|
"stream": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# We need a user object for permission checks in generate_chat_completion
|
||||||
|
user = Users.get_user_by_id(user_id)
|
||||||
|
if not user:
|
||||||
|
return current_rules
|
||||||
|
|
||||||
|
completion = await generate_chat_completion(__request__, payload, user)
|
||||||
|
if "choices" in completion and len(completion["choices"]) > 0:
|
||||||
|
content = completion["choices"][0]["message"]["content"].strip()
|
||||||
|
# Basic validation: ensure it looks like a list
|
||||||
|
if (
|
||||||
|
content.startswith("-")
|
||||||
|
or content.startswith("*")
|
||||||
|
or content.startswith("1.")
|
||||||
|
):
|
||||||
|
return content
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Rule generation failed: {e}")
|
||||||
|
|
||||||
|
return current_rules
|
||||||
|
|
||||||
|
async def _process_rules_update(
|
||||||
|
self,
|
||||||
|
folder_id: str,
|
||||||
|
body: dict,
|
||||||
|
user_id: str,
|
||||||
|
__request__: Request,
|
||||||
|
__event_emitter__,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"Start Processing",
|
||||||
|
{"step": "start", "initial_folder_id": folder_id, "user_id": user_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 1. Fetch Folder Data (ORM)
|
||||||
|
initial_folder = Folders.get_folder_by_id_and_user_id(folder_id, user_id)
|
||||||
|
if not initial_folder:
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"Error: Initial folder not found",
|
||||||
|
{
|
||||||
|
"step": "fetch_initial_folder",
|
||||||
|
"initial_folder_id": folder_id,
|
||||||
|
"user_id": user_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Subfolder handling logic
|
||||||
|
target_folder = initial_folder
|
||||||
|
if self.valves.UPDATE_ROOT_FOLDER:
|
||||||
|
# Traverse up until a folder with no parent_id is found
|
||||||
|
while target_folder and getattr(target_folder, "parent_id", None):
|
||||||
|
try:
|
||||||
|
parent = Folders.get_folder_by_id_and_user_id(
|
||||||
|
target_folder.parent_id, user_id
|
||||||
|
)
|
||||||
|
if parent:
|
||||||
|
target_folder = parent
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"Warning: Failed to traverse parent folder",
|
||||||
|
{"step": "traverse_root", "error": str(e)},
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
target_folder_id = target_folder.id
|
||||||
|
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"Target Folder Resolved",
|
||||||
|
{
|
||||||
|
"step": "target_resolved",
|
||||||
|
"target_folder_id": target_folder_id,
|
||||||
|
"target_folder_name": target_folder.name,
|
||||||
|
"is_root_update": target_folder_id != folder_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
existing_data = target_folder.data if target_folder.data else {}
|
||||||
|
existing_sys_prompt = existing_data.get("system_prompt", "")
|
||||||
|
|
||||||
|
# 2. Extract Existing Rules
|
||||||
|
current_rules_content = self._extract_existing_rules(existing_sys_prompt)
|
||||||
|
|
||||||
|
# 3. Generate New Rules
|
||||||
|
await self._emit_status(
|
||||||
|
__event_emitter__, "Analyzing project rules...", done=False
|
||||||
|
)
|
||||||
|
|
||||||
|
messages = body.get("messages", [])
|
||||||
|
new_rules_content = await self._generate_new_rules(
|
||||||
|
current_rules_content, messages, user_id, __request__
|
||||||
|
)
|
||||||
|
|
||||||
|
rules_changed = new_rules_content != current_rules_content
|
||||||
|
|
||||||
|
# 4. If no change, skip
|
||||||
|
if not rules_changed:
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"No Changes",
|
||||||
|
{
|
||||||
|
"step": "check_changes",
|
||||||
|
"reason": "content_identical_or_generation_failed",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await self._emit_status(
|
||||||
|
__event_emitter__,
|
||||||
|
"Rule analysis complete: No new content.",
|
||||||
|
done=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 5. Inject Rules into System Prompt
|
||||||
|
updated_sys_prompt = existing_sys_prompt
|
||||||
|
if rules_changed:
|
||||||
|
updated_sys_prompt = self._inject_rules(
|
||||||
|
updated_sys_prompt,
|
||||||
|
new_rules_content,
|
||||||
|
self.valves.RULES_BLOCK_TITLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"Ready to Update DB",
|
||||||
|
{"step": "pre_db_update", "target_folder_id": target_folder_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 6. Update Folder (ORM) - Only update 'data' field
|
||||||
|
existing_data["system_prompt"] = updated_sys_prompt
|
||||||
|
|
||||||
|
updated_folder = Folders.update_folder_by_id_and_user_id(
|
||||||
|
target_folder_id,
|
||||||
|
user_id,
|
||||||
|
FolderUpdateForm(data=existing_data),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not updated_folder:
|
||||||
|
raise Exception("Update folder failed (ORM returned None)")
|
||||||
|
|
||||||
|
await self._emit_status(
|
||||||
|
__event_emitter__, "Rule analysis complete: Rules updated.", done=True
|
||||||
|
)
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"Rule Generation Process & Change Details",
|
||||||
|
{
|
||||||
|
"step": "success",
|
||||||
|
"folder_id": target_folder_id,
|
||||||
|
"target_is_root": target_folder_id != folder_id,
|
||||||
|
"model_used": self.valves.MODEL_ID,
|
||||||
|
"analyzed_messages_count": len(messages),
|
||||||
|
"old_rules_length": len(current_rules_content),
|
||||||
|
"new_rules_length": len(new_rules_content),
|
||||||
|
"changes_digest": {
|
||||||
|
"old_rules_preview": (
|
||||||
|
current_rules_content[:100] + "..."
|
||||||
|
if current_rules_content
|
||||||
|
else "None"
|
||||||
|
),
|
||||||
|
"new_rules_preview": (
|
||||||
|
new_rules_content[:100] + "..."
|
||||||
|
if new_rules_content
|
||||||
|
else "None"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Async rule processing error: {e}")
|
||||||
|
await self._emit_status(
|
||||||
|
__event_emitter__, "Failed to update rules.", done=True
|
||||||
|
)
|
||||||
|
# Emit error to console for debugging
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"Execution Error",
|
||||||
|
{"error": str(e), "folder_id": folder_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
# ==================== Filter Hooks ====================
|
||||||
|
|
||||||
|
async def inlet(
|
||||||
|
self, body: dict, __user__: Optional[dict] = None, __event_emitter__=None
|
||||||
|
) -> dict:
|
||||||
|
return body
|
||||||
|
|
||||||
|
async def outlet(
|
||||||
|
self,
|
||||||
|
body: dict,
|
||||||
|
__user__: Optional[dict] = None,
|
||||||
|
__event_emitter__=None,
|
||||||
|
__request__: Optional[Request] = None,
|
||||||
|
) -> dict:
|
||||||
|
user_ctx = self._get_user_context(__user__)
|
||||||
|
chat_ctx = self._get_chat_context(body)
|
||||||
|
|
||||||
|
messages = body.get("messages", [])
|
||||||
|
if not messages:
|
||||||
|
return body
|
||||||
|
|
||||||
|
# Trigger logic: Message Count threshold
|
||||||
|
if len(messages) % self.valves.MESSAGE_TRIGGER_COUNT != 0:
|
||||||
|
return body
|
||||||
|
|
||||||
|
folder_id = self._get_folder_id(body)
|
||||||
|
if not folder_id:
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"Skipping Analysis",
|
||||||
|
{
|
||||||
|
"reason": "Chat does not belong to any folder",
|
||||||
|
"chat_id": chat_ctx.get("chat_id"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return body
|
||||||
|
|
||||||
|
# User Info
|
||||||
|
user_id = user_ctx.get("user_id")
|
||||||
|
if not user_id:
|
||||||
|
return body
|
||||||
|
|
||||||
|
# Async Task
|
||||||
|
if self.valves.MODEL_ID == "":
|
||||||
|
self.valves.MODEL_ID = body.get("model", "")
|
||||||
|
|
||||||
|
asyncio.create_task(
|
||||||
|
self._process_rules_update(
|
||||||
|
folder_id, body, user_id, __request__, __event_emitter__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return body
|
||||||
470
plugins/filters/folder-memory/folder_memory_cn.py
Normal file
470
plugins/filters/folder-memory/folder_memory_cn.py
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
"""
|
||||||
|
title: 📂 文件夹记忆 (Folder Memory)
|
||||||
|
author: Fu-Jie
|
||||||
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
|
funding_url: https://github.com/open-webui
|
||||||
|
version: 0.1.0
|
||||||
|
description: 自动从对话中提取项目规则,并将其注入到文件夹的系统提示词中。
|
||||||
|
requirements:
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, Dict, List
|
||||||
|
from fastapi import Request
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from open_webui.utils.chat import generate_chat_completion
|
||||||
|
from open_webui.models.users import Users
|
||||||
|
from open_webui.models.folders import Folders, FolderUpdateForm
|
||||||
|
from open_webui.models.chats import Chats
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 规则注入标记
|
||||||
|
RULES_BLOCK_START = "<!-- OWUI_PROJECT_RULES_START -->"
|
||||||
|
RULES_BLOCK_END = "<!-- OWUI_PROJECT_RULES_END -->"
|
||||||
|
|
||||||
|
# 规则生成系统提示词
|
||||||
|
SYSTEM_PROMPT_RULE_GENERATOR = """
|
||||||
|
你是一个项目规则提取器。你的任务是从对话中提取“项目规则”,并与现有规则合并。
|
||||||
|
|
||||||
|
### 输入
|
||||||
|
1. **现有规则 (Existing Rules)**:当前文件夹系统提示词中的规则。
|
||||||
|
2. **对话片段 (Conversation)**:最近的聊天记录。
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
综合生成一份适用于当前项目/文件夹的简洁规则列表。
|
||||||
|
- **移除** 不再相关或仅是一次性指令的规则。
|
||||||
|
- **添加** 对话中发现的新的、一致性的要求。
|
||||||
|
- **合并** 相似的规则。
|
||||||
|
- **格式**:简洁的 Markdown 项目符号列表。
|
||||||
|
|
||||||
|
### 输出格式
|
||||||
|
仅输出 Markdown 项目符号列表形式的规则。不要包含任何开头或结尾的说明文字。
|
||||||
|
示例:
|
||||||
|
- 始终使用 Python 3.11 进行类型提示。
|
||||||
|
- 文档字符串必须遵循 Google 风格。
|
||||||
|
- 提交信息必须使用英文。
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Filter:
|
||||||
|
class Valves(BaseModel):
|
||||||
|
PRIORITY: int = Field(default=20, description="过滤器操作的优先级。")
|
||||||
|
SHOW_DEBUG_LOG: bool = Field(
|
||||||
|
default=False, description="在控制台显示调试日志。"
|
||||||
|
)
|
||||||
|
MESSAGE_TRIGGER_COUNT: int = Field(
|
||||||
|
default=10, description="每隔 N 条消息分析一次规则。"
|
||||||
|
)
|
||||||
|
MODEL_ID: str = Field(
|
||||||
|
default="", description="用于提取规则的模型 ID。为空则使用当前对话模型。"
|
||||||
|
)
|
||||||
|
RULES_BLOCK_TITLE: str = Field(
|
||||||
|
default="## 📂 项目规则", description="显示在规则块上方的标题。"
|
||||||
|
)
|
||||||
|
UPDATE_ROOT_FOLDER: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="如果启用,将向上查找并更新根文件夹的规则,而不是当前子文件夹。",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.valves = self.Valves()
|
||||||
|
|
||||||
|
# ==================== 辅助方法 ====================
|
||||||
|
|
||||||
|
def _get_user_context(self, __user__: Optional[dict]) -> Dict[str, str]:
|
||||||
|
"""安全提取用户上下文信息。"""
|
||||||
|
if isinstance(__user__, (list, tuple)):
|
||||||
|
user_data = __user__[0] if __user__ else {}
|
||||||
|
elif isinstance(__user__, dict):
|
||||||
|
user_data = __user__
|
||||||
|
else:
|
||||||
|
user_data = {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"user_id": user_data.get("id", ""),
|
||||||
|
"user_name": user_data.get("name", "User"),
|
||||||
|
"user_language": user_data.get("language", "zh-CN"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_chat_context(
|
||||||
|
self, body: dict, __metadata__: Optional[dict] = None
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
"""统一提取聊天上下文信息 (chat_id, message_id)。"""
|
||||||
|
chat_id = ""
|
||||||
|
message_id = ""
|
||||||
|
|
||||||
|
if isinstance(body, dict):
|
||||||
|
chat_id = body.get("chat_id", "")
|
||||||
|
message_id = body.get("id", "")
|
||||||
|
|
||||||
|
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", "")
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _emit_debug_log(self, __event_emitter__, title: str, data: dict):
|
||||||
|
if self.valves.SHOW_DEBUG_LOG and __event_emitter__:
|
||||||
|
try:
|
||||||
|
# 按照用户要求的格式输出展平的日志
|
||||||
|
js_code = f"""
|
||||||
|
console.log("[Folder Memory] {title}", {json.dumps(data, ensure_ascii=False)});
|
||||||
|
"""
|
||||||
|
await __event_emitter__({"type": "execute", "data": {"code": js_code}})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"发出日志错误: {e}")
|
||||||
|
|
||||||
|
async def _emit_status(
|
||||||
|
self, __event_emitter__, description: str, done: bool = False
|
||||||
|
):
|
||||||
|
if __event_emitter__:
|
||||||
|
await __event_emitter__(
|
||||||
|
{"type": "status", "data": {"description": description, "done": done}}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_folder_id(self, body: dict) -> Optional[str]:
|
||||||
|
# 1. 尝试从 metadata 获取 folder_id
|
||||||
|
if "metadata" in body and isinstance(body["metadata"], dict):
|
||||||
|
if "folder_id" in body["metadata"]:
|
||||||
|
return body["metadata"]["folder_id"]
|
||||||
|
|
||||||
|
# 2. 检查 chat 对象
|
||||||
|
if "chat" in body and isinstance(body["chat"], dict):
|
||||||
|
if "folder_id" in body["chat"]:
|
||||||
|
return body["chat"]["folder_id"]
|
||||||
|
|
||||||
|
# 3. 尝试通过 Chat ID 查找 (最可靠的方法)
|
||||||
|
chat_id = body.get("chat_id")
|
||||||
|
if not chat_id:
|
||||||
|
if "metadata" in body and isinstance(body["metadata"], dict):
|
||||||
|
chat_id = body["metadata"].get("chat_id")
|
||||||
|
|
||||||
|
if chat_id:
|
||||||
|
try:
|
||||||
|
chat = Chats.get_chat_by_id(chat_id)
|
||||||
|
if chat and chat.folder_id:
|
||||||
|
return chat.folder_id
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取聊天信息失败 chat_id={chat_id}: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _extract_existing_rules(self, system_prompt: str) -> str:
|
||||||
|
pattern = re.compile(
|
||||||
|
re.escape(RULES_BLOCK_START) + r"([\s\S]*?)" + re.escape(RULES_BLOCK_END)
|
||||||
|
)
|
||||||
|
match = pattern.search(system_prompt)
|
||||||
|
if match:
|
||||||
|
# 如果标题在块内,将其移除以便纯净合并
|
||||||
|
content = match.group(1).strip()
|
||||||
|
title_pat = re.compile(r"^#+\s+.*$", re.MULTILINE)
|
||||||
|
return title_pat.sub("", content).strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _inject_rules(self, system_prompt: str, new_rules: str, title: str) -> str:
|
||||||
|
new_block_content = f"\n{title}\n\n{new_rules}\n"
|
||||||
|
new_block = f"{RULES_BLOCK_START}{new_block_content}{RULES_BLOCK_END}"
|
||||||
|
|
||||||
|
system_prompt = system_prompt or ""
|
||||||
|
pattern = re.compile(
|
||||||
|
re.escape(RULES_BLOCK_START) + r"[\s\S]*?" + re.escape(RULES_BLOCK_END)
|
||||||
|
)
|
||||||
|
|
||||||
|
if pattern.search(system_prompt):
|
||||||
|
# 替换现有块
|
||||||
|
return pattern.sub(new_block, system_prompt).strip()
|
||||||
|
else:
|
||||||
|
# 追加到末尾
|
||||||
|
if system_prompt:
|
||||||
|
return f"{system_prompt}\n\n{new_block}"
|
||||||
|
else:
|
||||||
|
return new_block
|
||||||
|
|
||||||
|
async def _generate_new_rules(
|
||||||
|
self,
|
||||||
|
current_rules: str,
|
||||||
|
messages: List[Dict],
|
||||||
|
user_id: str,
|
||||||
|
__request__: Request,
|
||||||
|
) -> str:
|
||||||
|
# 准备上下文
|
||||||
|
conversation_text = "\n".join(
|
||||||
|
[
|
||||||
|
f"{msg['role'].upper()}: {msg['content']}"
|
||||||
|
for msg in messages[-20:] # 分析最近 20 条消息上下文
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt = f"""
|
||||||
|
Existing Rules (现有规则):
|
||||||
|
{current_rules if current_rules else "无"}
|
||||||
|
|
||||||
|
Conversation Excerpt (对话片段):
|
||||||
|
{conversation_text}
|
||||||
|
|
||||||
|
Please output the updated Project Rules (请输出更新后的项目规则):
|
||||||
|
"""
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"model": self.valves.MODEL_ID,
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": SYSTEM_PROMPT_RULE_GENERATOR},
|
||||||
|
{"role": "user", "content": prompt},
|
||||||
|
],
|
||||||
|
"stream": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 需要用户对象进行权限检查
|
||||||
|
user = Users.get_user_by_id(user_id)
|
||||||
|
if not user:
|
||||||
|
return current_rules
|
||||||
|
|
||||||
|
completion = await generate_chat_completion(__request__, payload, user)
|
||||||
|
if "choices" in completion and len(completion["choices"]) > 0:
|
||||||
|
content = completion["choices"][0]["message"]["content"].strip()
|
||||||
|
# 简单验证:确保看起来像个列表
|
||||||
|
if (
|
||||||
|
content.startswith("-")
|
||||||
|
or content.startswith("*")
|
||||||
|
or content.startswith("1.")
|
||||||
|
):
|
||||||
|
return content
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"规则生成失败: {e}")
|
||||||
|
|
||||||
|
return current_rules
|
||||||
|
|
||||||
|
async def _process_rules_update(
|
||||||
|
self,
|
||||||
|
folder_id: str,
|
||||||
|
body: dict,
|
||||||
|
user_id: str,
|
||||||
|
__request__: Request,
|
||||||
|
__event_emitter__,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"开始处理",
|
||||||
|
{"step": "start", "initial_folder_id": folder_id, "user_id": user_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 1. 获取文件夹数据 (ORM)
|
||||||
|
initial_folder = Folders.get_folder_by_id_and_user_id(folder_id, user_id)
|
||||||
|
if not initial_folder:
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"错误:未找到初始文件夹",
|
||||||
|
{
|
||||||
|
"step": "fetch_initial_folder",
|
||||||
|
"initial_folder_id": folder_id,
|
||||||
|
"user_id": user_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 处理子文件夹逻辑:决定是更新当前文件夹还是根文件夹
|
||||||
|
target_folder = initial_folder
|
||||||
|
if self.valves.UPDATE_ROOT_FOLDER:
|
||||||
|
# 向上遍历直到找到没有 parent_id 的根文件夹
|
||||||
|
while target_folder and getattr(target_folder, "parent_id", None):
|
||||||
|
try:
|
||||||
|
parent = Folders.get_folder_by_id_and_user_id(
|
||||||
|
target_folder.parent_id, user_id
|
||||||
|
)
|
||||||
|
if parent:
|
||||||
|
target_folder = parent
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"警告:向上查找父文件夹失败",
|
||||||
|
{"step": "traverse_root", "error": str(e)},
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
target_folder_id = target_folder.id
|
||||||
|
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"定目标文件夹",
|
||||||
|
{
|
||||||
|
"step": "target_resolved",
|
||||||
|
"target_folder_id": target_folder_id,
|
||||||
|
"target_folder_name": target_folder.name,
|
||||||
|
"is_root_update": target_folder_id != folder_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
existing_data = target_folder.data if target_folder.data else {}
|
||||||
|
existing_sys_prompt = existing_data.get("system_prompt", "")
|
||||||
|
|
||||||
|
# 2. 提取现有规则
|
||||||
|
current_rules_content = self._extract_existing_rules(existing_sys_prompt)
|
||||||
|
|
||||||
|
# 3. 生成新规则
|
||||||
|
await self._emit_status(
|
||||||
|
__event_emitter__, "正在分析项目规则...", done=False
|
||||||
|
)
|
||||||
|
|
||||||
|
messages = body.get("messages", [])
|
||||||
|
new_rules_content = await self._generate_new_rules(
|
||||||
|
current_rules_content, messages, user_id, __request__
|
||||||
|
)
|
||||||
|
|
||||||
|
rules_changed = new_rules_content != current_rules_content
|
||||||
|
|
||||||
|
# 如果生成结果无变更
|
||||||
|
if not rules_changed:
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"无变更",
|
||||||
|
{
|
||||||
|
"step": "check_changes",
|
||||||
|
"reason": "content_identical_or_generation_failed",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await self._emit_status(
|
||||||
|
__event_emitter__, "规则分析完成:无新增内容。", done=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 5. 注入规则到 System Prompt
|
||||||
|
updated_sys_prompt = existing_sys_prompt
|
||||||
|
if rules_changed:
|
||||||
|
updated_sys_prompt = self._inject_rules(
|
||||||
|
updated_sys_prompt,
|
||||||
|
new_rules_content,
|
||||||
|
self.valves.RULES_BLOCK_TITLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"准备更新数据库",
|
||||||
|
{"step": "pre_db_update", "target_folder_id": target_folder_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 6. 更新文件夹 (ORM) - 仅更新 'data' 字段
|
||||||
|
existing_data["system_prompt"] = updated_sys_prompt
|
||||||
|
|
||||||
|
updated_folder = Folders.update_folder_by_id_and_user_id(
|
||||||
|
target_folder_id,
|
||||||
|
user_id,
|
||||||
|
FolderUpdateForm(data=existing_data),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not updated_folder:
|
||||||
|
raise Exception("Update folder failed (ORM returned None)")
|
||||||
|
|
||||||
|
await self._emit_status(
|
||||||
|
__event_emitter__, "规则分析完成:规则已更新。", done=True
|
||||||
|
)
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"规则生成过程和变更详情",
|
||||||
|
{
|
||||||
|
"step": "success",
|
||||||
|
"folder_id": target_folder_id,
|
||||||
|
"target_is_root": target_folder_id != folder_id,
|
||||||
|
"model_used": self.valves.MODEL_ID,
|
||||||
|
"analyzed_messages_count": len(messages),
|
||||||
|
"old_rules_length": len(current_rules_content),
|
||||||
|
"new_rules_length": len(new_rules_content),
|
||||||
|
"changes_digest": {
|
||||||
|
"old_rules_preview": (
|
||||||
|
current_rules_content[:100] + "..."
|
||||||
|
if current_rules_content
|
||||||
|
else "None"
|
||||||
|
),
|
||||||
|
"new_rules_preview": (
|
||||||
|
new_rules_content[:100] + "..."
|
||||||
|
if new_rules_content
|
||||||
|
else "None"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"异步规则处理错误: {e}")
|
||||||
|
await self._emit_status(__event_emitter__, "更新规则失败。", done=True)
|
||||||
|
# 在控制台也输出错误信息,方便调试
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__, "执行出错", {"error": str(e), "folder_id": folder_id}
|
||||||
|
)
|
||||||
|
|
||||||
|
# ==================== Filter Hooks ====================
|
||||||
|
|
||||||
|
async def inlet(
|
||||||
|
self, body: dict, __user__: Optional[dict] = None, __event_emitter__=None
|
||||||
|
) -> dict:
|
||||||
|
return body
|
||||||
|
|
||||||
|
async def outlet(
|
||||||
|
self,
|
||||||
|
body: dict,
|
||||||
|
__user__: Optional[dict] = None,
|
||||||
|
__event_emitter__=None,
|
||||||
|
__request__: Optional[Request] = None,
|
||||||
|
) -> dict:
|
||||||
|
user_ctx = self._get_user_context(__user__)
|
||||||
|
chat_ctx = self._get_chat_context(body)
|
||||||
|
|
||||||
|
messages = body.get("messages", [])
|
||||||
|
if not messages:
|
||||||
|
return body
|
||||||
|
|
||||||
|
# 触发逻辑:消息计数阈值
|
||||||
|
if len(messages) % self.valves.MESSAGE_TRIGGER_COUNT != 0:
|
||||||
|
return body
|
||||||
|
|
||||||
|
folder_id = self._get_folder_id(body)
|
||||||
|
if not folder_id:
|
||||||
|
await self._emit_debug_log(
|
||||||
|
__event_emitter__,
|
||||||
|
"跳过分析",
|
||||||
|
{"reason": "对话不属于任何文件夹", "chat_id": chat_ctx.get("chat_id")},
|
||||||
|
)
|
||||||
|
return body
|
||||||
|
|
||||||
|
# 用户信息
|
||||||
|
user_id = user_ctx.get("user_id")
|
||||||
|
if not user_id:
|
||||||
|
return body
|
||||||
|
|
||||||
|
# 异步任务
|
||||||
|
if self.valves.MODEL_ID == "":
|
||||||
|
self.valves.MODEL_ID = body.get("model", "")
|
||||||
|
|
||||||
|
asyncio.create_task(
|
||||||
|
self._process_rules_update(
|
||||||
|
folder_id, body, user_id, __request__, __event_emitter__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return body
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,61 +1,96 @@
|
|||||||
# Markdown Normalizer Filter
|
# Markdown Normalizer Filter
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui)
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.2.4 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||||
**Version:** 1.1.2
|
|
||||||
|
|
||||||
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.
|
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
|
## Features
|
||||||
|
|
||||||
* **Mermaid Syntax Fix**: Automatically fixes common Mermaid syntax errors, such as unquoted node labels (including multi-line labels and citations) and unclosed subgraphs. **New in v1.1.2**: Comprehensive protection for edge labels (text on connecting lines) across all link types (solid, dotted, thick).
|
* **Details Tag Normalization**: Ensures proper spacing for `<details>` tags (used for thought chains). Adds a blank line after `</details>` and ensures a newline after self-closing `<details />` tags to prevent rendering issues.
|
||||||
* **Frontend Console Debugging**: Supports printing structured debug logs directly to the browser console (F12) for easier troubleshooting.
|
* **Emphasis Spacing Fix**: Fixes extra spaces inside emphasis markers (e.g., `** text **` -> `**text**`) which can cause rendering failures. Includes safeguards to protect math expressions (e.g., `2 * 3 * 4`) and list variables.
|
||||||
* **Code Block Formatting**: Fixes broken code block prefixes, suffixes, and indentation.
|
* **Mermaid Syntax Fix**: Automatically fixes common Mermaid syntax errors, such as unquoted node labels (including multi-line labels and citations) and unclosed subgraphs. **New in v1.1.2**: Comprehensive protection for edge labels (text on connecting lines) across all link types (solid, dotted, thick).
|
||||||
* **LaTeX Normalization**: Standardizes LaTeX formula delimiters (`\[` -> `$$`, `\(` -> `$`).
|
* **Frontend Console Debugging**: Supports printing structured debug logs directly to the browser console (F12) for easier troubleshooting.
|
||||||
* **Thought Tag Normalization**: Unifies thought tags (`<think>`, `<thinking>` -> `<thought>`).
|
* **Code Block Formatting**: Fixes broken code block prefixes, suffixes, and indentation.
|
||||||
* **Escape Character Fix**: Cleans up excessive escape characters (`\\n`, `\\t`).
|
* **LaTeX Normalization**: Standardizes LaTeX formula delimiters (`\[` -> `$$`, `\(` -> `$`).
|
||||||
* **List Formatting**: Ensures proper newlines in list items.
|
* **Thought Tag Normalization**: Unifies thought tags (`<think>`, `<thinking>` -> `<thought>`).
|
||||||
* **Heading Fix**: Adds missing spaces in headings (`#Heading` -> `# Heading`).
|
* **Escape Character Fix**: Cleans up excessive escape characters (`\\n`, `\\t`).
|
||||||
* **Table Fix**: Adds missing closing pipes in tables.
|
* **List Formatting**: Ensures proper newlines in list items.
|
||||||
* **XML Cleanup**: Removes leftover XML artifacts.
|
* **Heading Fix**: Adds missing spaces in headings (`#Heading` -> `# Heading`).
|
||||||
|
* **Table Fix**: Adds missing closing pipes in tables.
|
||||||
|
* **XML Cleanup**: Removes leftover XML artifacts.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Install the plugin in Open WebUI.
|
1. Install the plugin in Open WebUI.
|
||||||
2. Enable the filter globally or for specific models.
|
2. Enable the filter globally or for specific models.
|
||||||
3. Configure the enabled fixes in the **Valves** settings.
|
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).
|
4. (Optional) **Show Debug Log** is enabled by default in Valves. This prints structured logs to the browser console (F12).
|
||||||
> [!WARNING]
|
> [!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.
|
> 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)
|
## Configuration (Valves)
|
||||||
|
|
||||||
* `priority`: Filter priority (default: 50).
|
* `priority`: Filter priority (default: 50).
|
||||||
* `enable_escape_fix`: Fix excessive escape characters.
|
* `enable_escape_fix`: Fix excessive escape characters.
|
||||||
* `enable_thought_tag_fix`: Normalize thought tags.
|
* `enable_thought_tag_fix`: Normalize thought tags.
|
||||||
* `enable_code_block_fix`: Fix code block formatting.
|
* `enable_details_tag_fix`: Normalize details tags (default: True).
|
||||||
* `enable_latex_fix`: Normalize LaTeX formulas.
|
* `enable_code_block_fix`: Fix code block formatting.
|
||||||
* `enable_list_fix`: Fix list item newlines (Experimental).
|
* `enable_latex_fix`: Normalize LaTeX formulas.
|
||||||
* `enable_unclosed_block_fix`: Auto-close unclosed code blocks.
|
* `enable_list_fix`: Fix list item newlines (Experimental).
|
||||||
* `enable_fullwidth_symbol_fix`: Fix full-width symbols in code blocks.
|
* `enable_unclosed_block_fix`: Auto-close unclosed code blocks.
|
||||||
* `enable_mermaid_fix`: Fix Mermaid syntax errors.
|
* `enable_fullwidth_symbol_fix`: Fix full-width symbols in code blocks.
|
||||||
* `enable_heading_fix`: Fix missing space in headings.
|
* `enable_mermaid_fix`: Fix Mermaid syntax errors.
|
||||||
* `enable_table_fix`: Fix missing closing pipe in tables.
|
* `enable_heading_fix`: Fix missing space in headings.
|
||||||
* `enable_xml_tag_cleanup`: Cleanup leftover XML tags.
|
* `enable_table_fix`: Fix missing closing pipe in tables.
|
||||||
* `show_status`: Show status notification when fixes are applied.
|
* `enable_xml_tag_cleanup`: Cleanup leftover XML tags.
|
||||||
* `show_debug_log`: Print debug logs to browser console.
|
* `enable_emphasis_spacing_fix`: Fix extra spaces in emphasis (default: False).
|
||||||
|
* `show_status`: Show status notification when fixes are applied.
|
||||||
|
* `show_debug_log`: Print debug logs to browser console.
|
||||||
|
|
||||||
|
## Troubleshooting ❓
|
||||||
|
|
||||||
|
* **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### v1.2.4
|
||||||
|
|
||||||
|
* **Documentation Updates**: Synchronized version numbers across all documentation and code files.
|
||||||
|
|
||||||
|
### v1.2.3
|
||||||
|
|
||||||
|
* **List Marker Protection Enhancement**: Fixed a bug where list markers (`*`) followed by plain text and emphasis were having their spaces incorrectly stripped (e.g., `* U16 forward` became `*U16 forward`).
|
||||||
|
* **Placeholder Support**: Confirmed that 4 or more underscores (e.g., `____`) are correctly treated as placeholders and not modified by the emphasis fix.
|
||||||
|
|
||||||
|
### v1.2.2
|
||||||
|
|
||||||
|
* **Code Block Indentation Fix**: Fixed an issue where code blocks nested inside lists were having their indentation incorrectly stripped. Now preserves proper indentation for nested code blocks.
|
||||||
|
* **Underscore Emphasis Support**: Extended emphasis spacing fix to support `__` (double underscore for bold) and `___` (triple underscore for bold+italic) syntax.
|
||||||
|
* **List Marker Protection**: Fixed a bug where list markers (`*`) followed by emphasis markers (`**`) were incorrectly merged (e.g., `* **Yes**` became `***Yes**`). Added safeguard to prevent this.
|
||||||
|
* **Test Suite**: Added comprehensive pytest test suite with 56 test cases covering all major features.
|
||||||
|
|
||||||
|
### v1.2.1
|
||||||
|
|
||||||
|
* **Emphasis Spacing Fix**: Added a new fix for extra spaces inside emphasis markers (e.g., `** text **` -> `**text**`).
|
||||||
|
* Uses a recursive approach to handle nested emphasis (e.g., `**bold _italic _**`).
|
||||||
|
* Includes safeguards to prevent modifying math expressions (e.g., `2 * 3 * 4`) or list variables.
|
||||||
|
* Controlled by the `enable_emphasis_spacing_fix` valve (default: True).
|
||||||
|
|
||||||
|
### v1.2.0
|
||||||
|
|
||||||
|
* **Details Tag Support**: Added normalization for `<details>` tags.
|
||||||
|
* Ensures a blank line is added after `</details>` closing tags to separate thought content from the main response.
|
||||||
|
* Ensures a newline is added after self-closing `<details ... />` tags to prevent them from interfering with subsequent Markdown headings (e.g., fixing `<details/>#Heading`).
|
||||||
|
* Includes safeguard to prevent modification of `<details>` tags inside code blocks.
|
||||||
|
|
||||||
### v1.1.2
|
### v1.1.2
|
||||||
* **Mermaid Edge Label Protection**: Implemented comprehensive protection for edge labels (text on connecting lines) to prevent them from being incorrectly modified. Now supports all Mermaid link types including solid (`--`), dotted (`-.`), and thick (`==`) lines with or without arrows.
|
|
||||||
* **Bug Fixes**: Fixed an issue where lines without arrows (e.g., `A -- text --- B`) were not correctly protected.
|
* **Mermaid Edge Label Protection**: Implemented comprehensive protection for edge labels (text on connecting lines) to prevent them from being incorrectly modified. Now supports all Mermaid link types including solid (`--`), dotted (`-.`), and thick (`==`) lines with or without arrows.
|
||||||
|
* **Bug Fixes**: Fixed an issue where lines without arrows (e.g., `A -- text --- B`) were not correctly protected.
|
||||||
|
|
||||||
### v1.1.0
|
### 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
|
* **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.
|
||||||
MIT
|
* **Full-width Symbol Cleanup**: Fixed duplicate keys and incorrect quote mapping in `FULLWIDTH_MAP`.
|
||||||
|
* **Bug Fixes**: Fixed missing `Dict` import in Python files.
|
||||||
|
|||||||
@@ -1,61 +1,96 @@
|
|||||||
# Markdown 格式化过滤器 (Markdown Normalizer)
|
# Markdown 格式化过滤器 (Markdown Normalizer)
|
||||||
|
|
||||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui)
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.2.4 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||||
**版本:** 1.1.2
|
|
||||||
|
|
||||||
这是一个用于 Open WebUI 的内容格式化过滤器,旨在修复 LLM 输出中常见的 Markdown 格式问题。它能确保代码块、LaTeX 公式、Mermaid 图表和其他 Markdown 元素被正确渲染。
|
这是一个用于 Open WebUI 的内容格式化过滤器,旨在修复 LLM 输出中常见的 Markdown 格式问题。它能确保代码块、LaTeX 公式、Mermaid 图表和其他 Markdown 元素被正确渲染。
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
* **Mermaid 语法修复**: 自动修复常见的 Mermaid 语法错误,如未加引号的节点标签(支持多行标签和引用标记)和未闭合的子图 (Subgraph)。**v1.1.2 新增**: 全面保护各种类型的连线标签(实线、虚线、粗线),防止被误修改。
|
* **Details 标签规范化**: 确保 `<details>` 标签(常用于思维链)有正确的间距。在 `</details>` 后添加空行,并在自闭合 `<details />` 标签后添加换行,防止渲染问题。
|
||||||
* **前端控制台调试**: 支持将结构化的调试日志直接打印到浏览器控制台 (F12),方便排查问题。
|
* **强调空格修复**: 修复强调标记内部的多余空格(例如 `** 文本 **` -> `**文本**`),这会导致 Markdown 渲染失败。包含保护机制,防止误修改数学表达式(如 `2 * 3 * 4`)或列表变量。
|
||||||
* **代码块格式化**: 修复破损的代码块前缀、后缀和缩进问题。
|
* **Mermaid 语法修复**: 自动修复常见的 Mermaid 语法错误,如未加引号的节点标签(支持多行标签和引用标记)和未闭合的子图 (Subgraph)。**v1.1.2 新增**: 全面保护各种类型的连线标签(实线、虚线、粗线),防止被误修改。
|
||||||
* **LaTeX 规范化**: 标准化 LaTeX 公式定界符 (`\[` -> `$$`, `\(` -> `$`)。
|
* **前端控制台调试**: 支持将结构化的调试日志直接打印到浏览器控制台 (F12),方便排查问题。
|
||||||
* **思维标签规范化**: 统一思维链标签 (`<think>`, `<thinking>` -> `<thought>`)。
|
* **代码块格式化**: 修复破损的代码块前缀、后缀和缩进问题。
|
||||||
* **转义字符修复**: 清理过度的转义字符 (`\\n`, `\\t`)。
|
* **LaTeX 规范化**: 标准化 LaTeX 公式定界符 (`\[` -> `$$`, `\(` -> `$`)。
|
||||||
* **列表格式化**: 确保列表项有正确的换行。
|
* **思维标签规范化**: 统一思维链标签 (`<think>`, `<thinking>` -> `<thought>`)。
|
||||||
* **标题修复**: 修复标题中缺失的空格 (`#标题` -> `# 标题`)。
|
* **转义字符修复**: 清理过度的转义字符 (`\\n`, `\\t`)。
|
||||||
* **表格修复**: 修复表格中缺失的闭合管道符。
|
* **列表格式化**: 确保列表项有正确的换行。
|
||||||
* **XML 清理**: 移除残留的 XML 标签。
|
* **标题修复**: 修复标题中缺失的空格 (`#标题` -> `# 标题`)。
|
||||||
|
* **表格修复**: 修复表格中缺失的闭合管道符。
|
||||||
|
* **XML 清理**: 移除残留的 XML 标签。
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
1. 在 Open WebUI 中安装此插件。
|
1. 在 Open WebUI 中安装此插件。
|
||||||
2. 全局启用或为特定模型启用此过滤器。
|
2. 全局启用或为特定模型启用此过滤器。
|
||||||
3. 在 **Valves** 设置中配置需要启用的修复项。
|
3. 在 **Valves** 设置中配置需要启用的修复项。
|
||||||
4. (可选) **显示调试日志 (Show Debug Log)** 在 Valves 中默认开启。这会将结构化的日志打印到浏览器控制台 (F12)。
|
4. (可选) **显示调试日志 (Show Debug Log)** 在 Valves 中默认开启。这会将结构化的日志打印到浏览器控制台 (F12)。
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> 由于这是初版,可能会出现“负向修复”的情况(例如破坏了原本正确的格式)。如果您遇到问题,请务必查看控制台日志,复制“原始 (Original)”与“规范化 (Normalized)”的内容对比,并提交 Issue 反馈。
|
> 由于这是初版,可能会出现“负向修复”的情况(例如破坏了原本正确的格式)。如果您遇到问题,请务必查看控制台日志,复制“原始 (Original)”与“规范化 (Normalized)”的内容对比,并提交 Issue 反馈。
|
||||||
|
|
||||||
## 配置项 (Valves)
|
## 配置项 (Valves)
|
||||||
|
|
||||||
* `priority`: 过滤器优先级 (默认: 50)。
|
* `priority`: 过滤器优先级 (默认: 50)。
|
||||||
* `enable_escape_fix`: 修复过度的转义字符。
|
* `enable_escape_fix`: 修复过度的转义字符。
|
||||||
* `enable_thought_tag_fix`: 规范化思维标签。
|
* `enable_thought_tag_fix`: 规范化思维标签。
|
||||||
* `enable_code_block_fix`: 修复代码块格式。
|
* `enable_details_tag_fix`: 规范化 Details 标签 (默认: True)。
|
||||||
* `enable_latex_fix`: 规范化 LaTeX 公式。
|
* `enable_code_block_fix`: 修复代码块格式。
|
||||||
* `enable_list_fix`: 修复列表项换行 (实验性)。
|
* `enable_latex_fix`: 规范化 LaTeX 公式。
|
||||||
* `enable_unclosed_block_fix`: 自动闭合未闭合的代码块。
|
* `enable_list_fix`: 修复列表项换行 (实验性)。
|
||||||
* `enable_fullwidth_symbol_fix`: 修复代码块中的全角符号。
|
* `enable_unclosed_block_fix`: 自动闭合未闭合的代码块。
|
||||||
* `enable_mermaid_fix`: 修复 Mermaid 语法错误。
|
* `enable_fullwidth_symbol_fix`: 修复代码块中的全角符号。
|
||||||
* `enable_heading_fix`: 修复标题中缺失的空格。
|
* `enable_mermaid_fix`: 修复 Mermaid 语法错误。
|
||||||
* `enable_table_fix`: 修复表格中缺失的闭合管道符。
|
* `enable_heading_fix`: 修复标题中缺失的空格。
|
||||||
* `enable_xml_tag_cleanup`: 清理残留的 XML 标签。
|
* `enable_table_fix`: 修复表格中缺失的闭合管道符。
|
||||||
* `show_status`: 应用修复时显示状态通知。
|
* `enable_xml_tag_cleanup`: 清理残留的 XML 标签。
|
||||||
* `show_debug_log`: 在浏览器控制台打印调试日志。
|
* `enable_emphasis_spacing_fix`: 修复强调语法中的多余空格 (默认: True)。
|
||||||
|
* `show_status`: 应用修复时显示状态通知。
|
||||||
|
* `show_debug_log`: 在浏览器控制台打印调试日志。
|
||||||
|
|
||||||
|
## 故障排除 (Troubleshooting) ❓
|
||||||
|
|
||||||
|
* **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
|
### v1.2.4
|
||||||
|
|
||||||
|
* **文档更新**: 同步了所有文档和代码文件的版本号。
|
||||||
|
|
||||||
|
### v1.2.3
|
||||||
|
|
||||||
|
* **列表标记保护增强**: 修复了列表标记 (`*`) 后跟普通文本和强调标记时,空格被错误剥离的问题(例如 `* U16 前锋` 变成 `*U16 前锋`)。
|
||||||
|
* **占位符支持**: 确认 4 个或更多下划线(如 `____`)会被正确视为占位符,不会被强调修复逻辑修改。
|
||||||
|
|
||||||
|
### v1.2.2
|
||||||
|
|
||||||
|
* **代码块缩进修复**: 修复了列表中嵌套代码块的缩进被错误剥离的问题。现在会正确保留嵌套代码块的缩进。
|
||||||
|
* **下划线强调语法支持**: 扩展强调空格修复以支持 `__` (双下划线加粗) 和 `___` (三下划线加粗斜体) 语法。
|
||||||
|
* **列表标记保护**: 修复了列表标记 (`*`) 后跟强调标记 (`**`) 被错误合并的 Bug(例如 `* **是**` 变成 `***是**`)。添加了保护逻辑防止此问题。
|
||||||
|
* **测试套件**: 新增完整的 pytest 测试套件,包含 56 个测试用例,覆盖所有主要功能。
|
||||||
|
|
||||||
|
### v1.2.1
|
||||||
|
|
||||||
|
* **强调空格修复**: 新增了对强调标记内部多余空格的修复(例如 `** 文本 **` -> `**文本**`)。
|
||||||
|
* 采用递归方法处理嵌套强调(例如 `**加粗 _斜体 _**`)。
|
||||||
|
* 包含保护机制,防止误修改数学表达式(如 `2 * 3 * 4`)或列表变量。
|
||||||
|
* 通过 `enable_emphasis_spacing_fix` 开关控制(默认:开启)。
|
||||||
|
|
||||||
|
### v1.2.0
|
||||||
|
|
||||||
|
* **Details 标签支持**: 新增了对 `<details>` 标签的规范化支持。
|
||||||
|
* 确保在 `</details>` 闭合标签后添加空行,将思维内容与正文分隔开。
|
||||||
|
* 确保在自闭合 `<details ... />` 标签后添加换行,防止其干扰后续的 Markdown 标题(例如修复 `<details/>#标题`)。
|
||||||
|
* 包含保护机制,防止修改代码块内部的 `<details>` 标签。
|
||||||
|
|
||||||
### v1.1.2
|
### v1.1.2
|
||||||
* **Mermaid 连线标签保护**: 实现了全面的连线标签保护机制,防止连接线上的文字被误修改。现在支持所有 Mermaid 连线类型,包括实线 (`--`)、虚线 (`-.`) 和粗线 (`==`),无论是否带有箭头。
|
|
||||||
* **Bug 修复**: 修复了无箭头连线(如 `A -- text --- B`)未被正确保护的问题。
|
* **Mermaid 连线标签保护**: 实现了全面的连线标签保护机制,防止连接线上的文字被误修改。现在支持所有 Mermaid 连线类型,包括实线 (`--`)、虚线 (`-.`) 和粗线 (`==`),无论是否带有箭头。
|
||||||
|
* **Bug 修复**: 修复了无箭头连线(如 `A -- text --- B`)未被正确保护的问题。
|
||||||
|
|
||||||
### v1.1.0
|
### v1.1.0
|
||||||
* **Mermaid 修复优化**: 改进了正则表达式以处理节点标签中的嵌套括号(如 `ID("标签 (文本)")`),并避免误匹配连接线上的文字。
|
|
||||||
* **HTML 保护机制优化**: 优化了 `_contains_html` 检测,允许 `<br/>`, `<b>`, `<i>` 等常见标签,确保包含这些标签的 Mermaid 图表能被正常规范化。
|
|
||||||
* **全角符号清理**: 修复了 `FULLWIDTH_MAP` 中的重复键名和错误的引号映射。
|
|
||||||
* **Bug 修复**: 修复了 Python 文件中缺失的 `Dict` 类型导入。
|
|
||||||
|
|
||||||
## 许可证
|
* **Mermaid 修复优化**: 改进了正则表达式以处理节点标签中的嵌套括号(如 `ID("标签 (文本)")`),并避免误匹配连接线上的文字。
|
||||||
|
* **HTML 保护机制优化**: 优化了 `_contains_html` 检测,允许 `<br/>`, `<b>`, `<i>` 等常见标签,确保包含这些标签的 Mermaid 图表能被正常规范化。
|
||||||
MIT
|
* **全角符号清理**: 修复了 `FULLWIDTH_MAP` 中的重复键名和错误的引号映射。
|
||||||
|
* **Bug 修复**: 修复了 Python 文件中缺失的 `Dict` 类型导入。
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ title: Markdown Normalizer
|
|||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/open-webui
|
funding_url: https://github.com/open-webui
|
||||||
version: 1.1.2
|
version: 1.2.4
|
||||||
|
openwebui_id: baaa8732-9348-40b7-8359-7e009660e23c
|
||||||
description: A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting.
|
description: A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ class NormalizerConfig:
|
|||||||
False # Apply escape fix inside code blocks (default: False for safety)
|
False # Apply escape fix inside code blocks (default: False for safety)
|
||||||
)
|
)
|
||||||
enable_thought_tag_fix: bool = True # Normalize thought tags
|
enable_thought_tag_fix: bool = True # Normalize thought tags
|
||||||
|
enable_details_tag_fix: bool = True # Normalize <details> tags (like thought tags)
|
||||||
enable_code_block_fix: bool = True # Fix code block formatting
|
enable_code_block_fix: bool = True # Fix code block formatting
|
||||||
enable_latex_fix: bool = True # Fix LaTeX formula formatting
|
enable_latex_fix: bool = True # Fix LaTeX formula formatting
|
||||||
enable_list_fix: bool = (
|
enable_list_fix: bool = (
|
||||||
@@ -41,6 +43,7 @@ class NormalizerConfig:
|
|||||||
)
|
)
|
||||||
enable_table_fix: bool = True # Fix missing closing pipe in tables
|
enable_table_fix: bool = True # Fix missing closing pipe in tables
|
||||||
enable_xml_tag_cleanup: bool = True # Cleanup leftover XML tags
|
enable_xml_tag_cleanup: bool = True # Cleanup leftover XML tags
|
||||||
|
enable_emphasis_spacing_fix: bool = False # Fix spaces inside **emphasis**
|
||||||
|
|
||||||
# Custom cleaner functions (for advanced extension)
|
# Custom cleaner functions (for advanced extension)
|
||||||
custom_cleaners: List[Callable[[str], str]] = field(default_factory=list)
|
custom_cleaners: List[Callable[[str], str]] = field(default_factory=list)
|
||||||
@@ -51,8 +54,8 @@ class ContentNormalizer:
|
|||||||
|
|
||||||
# --- 1. Pre-compiled Regex Patterns (Performance Optimization) ---
|
# --- 1. Pre-compiled Regex Patterns (Performance Optimization) ---
|
||||||
_PATTERNS = {
|
_PATTERNS = {
|
||||||
# Code block prefix: if ``` is not at start of line or file
|
# Code block prefix: if ``` is not at start of line (ignoring whitespace)
|
||||||
"code_block_prefix": re.compile(r"(?<!^)(?<!\n)(```)", re.MULTILINE),
|
"code_block_prefix": re.compile(r"(\S[ \t]*)(```)"),
|
||||||
# Code block suffix: ```lang followed by non-whitespace (no newline)
|
# Code block suffix: ```lang followed by non-whitespace (no newline)
|
||||||
"code_block_suffix": re.compile(r"(```[\w\+\-\.]*)[ \t]+([^\n\r])"),
|
"code_block_suffix": re.compile(r"(```[\w\+\-\.]*)[ \t]+([^\n\r])"),
|
||||||
# Code block indent: whitespace at start of line + ```
|
# Code block indent: whitespace at start of line + ```
|
||||||
@@ -62,6 +65,12 @@ class ContentNormalizer:
|
|||||||
r"</(thought|think|thinking)>[ \t]*\n*", re.IGNORECASE
|
r"</(thought|think|thinking)>[ \t]*\n*", re.IGNORECASE
|
||||||
),
|
),
|
||||||
"thought_start": re.compile(r"<(thought|think|thinking)>", re.IGNORECASE),
|
"thought_start": re.compile(r"<(thought|think|thinking)>", re.IGNORECASE),
|
||||||
|
# Details tag: </details> followed by optional whitespace/newlines
|
||||||
|
"details_end": re.compile(r"</details>[ \t]*\n*", re.IGNORECASE),
|
||||||
|
# Self-closing details tag: <details ... /> followed by optional whitespace (but NOT already having newline)
|
||||||
|
"details_self_closing": re.compile(
|
||||||
|
r"(<details[^>]*/\s*>)(?!\n)", re.IGNORECASE
|
||||||
|
),
|
||||||
# LaTeX block: \[ ... \]
|
# LaTeX block: \[ ... \]
|
||||||
"latex_bracket_block": re.compile(r"\\\[(.+?)\\\]", re.DOTALL),
|
"latex_bracket_block": re.compile(r"\\\[(.+?)\\\]", re.DOTALL),
|
||||||
# LaTeX inline: \( ... \)
|
# LaTeX inline: \( ... \)
|
||||||
@@ -100,6 +109,14 @@ class ContentNormalizer:
|
|||||||
"heading_space": re.compile(r"^(#+)([^ \n#])", re.MULTILINE),
|
"heading_space": re.compile(r"^(#+)([^ \n#])", re.MULTILINE),
|
||||||
# Table: | col1 | col2 -> | col1 | col2 |
|
# Table: | col1 | col2 -> | col1 | col2 |
|
||||||
"table_pipe": re.compile(r"^(\|.*[^|\n])$", re.MULTILINE),
|
"table_pipe": re.compile(r"^(\|.*[^|\n])$", re.MULTILINE),
|
||||||
|
# Emphasis spacing: ** text ** -> **text**, __ text __ -> __text__
|
||||||
|
# Matches emphasis blocks within a single line. We use a recursive approach
|
||||||
|
# in _fix_emphasis_spacing to handle nesting and spaces correctly.
|
||||||
|
# NOTE: We use [^\n] instead of . to prevent cross-line matching.
|
||||||
|
# Supports: * (italic), ** (bold), *** (bold+italic), _ (italic), __ (bold), ___ (bold+italic)
|
||||||
|
"emphasis_spacing": re.compile(
|
||||||
|
r"(?<!\*|_)(\*{1,3}|_{1,3})(?P<inner>[^\n]*?)(\1)(?!\*|_)"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, config: Optional[NormalizerConfig] = None):
|
def __init__(self, config: Optional[NormalizerConfig] = None):
|
||||||
@@ -129,7 +146,14 @@ class ContentNormalizer:
|
|||||||
if content != original:
|
if content != original:
|
||||||
self.applied_fixes.append("Normalize Thought Tags")
|
self.applied_fixes.append("Normalize Thought Tags")
|
||||||
|
|
||||||
# 3. Code block formatting fix
|
# 3. Details tag normalization (must be before heading fix)
|
||||||
|
if self.config.enable_details_tag_fix:
|
||||||
|
original = content
|
||||||
|
content = self._fix_details_tags(content)
|
||||||
|
if content != original:
|
||||||
|
self.applied_fixes.append("Normalize Details Tags")
|
||||||
|
|
||||||
|
# 4. Code block formatting fix
|
||||||
if self.config.enable_code_block_fix:
|
if self.config.enable_code_block_fix:
|
||||||
original = content
|
original = content
|
||||||
content = self._fix_code_blocks(content)
|
content = self._fix_code_blocks(content)
|
||||||
@@ -192,6 +216,13 @@ class ContentNormalizer:
|
|||||||
if content != original:
|
if content != original:
|
||||||
self.applied_fixes.append("Cleanup XML Tags")
|
self.applied_fixes.append("Cleanup XML Tags")
|
||||||
|
|
||||||
|
# 12. Emphasis spacing fix
|
||||||
|
if self.config.enable_emphasis_spacing_fix:
|
||||||
|
original = content
|
||||||
|
content = self._fix_emphasis_spacing(content)
|
||||||
|
if content != original:
|
||||||
|
self.applied_fixes.append("Fix Emphasis Spacing")
|
||||||
|
|
||||||
# 9. Custom cleaners
|
# 9. Custom cleaners
|
||||||
for cleaner in self.config.custom_cleaners:
|
for cleaner in self.config.custom_cleaners:
|
||||||
original = content
|
original = content
|
||||||
@@ -248,10 +279,26 @@ class ContentNormalizer:
|
|||||||
# 2. Standardize end tag and ensure newlines: </think> -> </thought>\n\n
|
# 2. Standardize end tag and ensure newlines: </think> -> </thought>\n\n
|
||||||
return self._PATTERNS["thought_end"].sub("</thought>\n\n", content)
|
return self._PATTERNS["thought_end"].sub("</thought>\n\n", content)
|
||||||
|
|
||||||
|
def _fix_details_tags(self, content: str) -> str:
|
||||||
|
"""Normalize <details> tags: ensure proper spacing after closing tags
|
||||||
|
|
||||||
|
Handles two cases:
|
||||||
|
1. </details> followed by content -> ensure double newline
|
||||||
|
2. <details .../> (self-closing) followed by content -> ensure newline
|
||||||
|
|
||||||
|
Note: Only applies outside of code blocks to avoid breaking code examples.
|
||||||
|
"""
|
||||||
|
parts = content.split("```")
|
||||||
|
for i in range(0, len(parts), 2): # Even indices are markdown text
|
||||||
|
# 1. Ensure double newline after </details>
|
||||||
|
parts[i] = self._PATTERNS["details_end"].sub("</details>\n\n", parts[i])
|
||||||
|
# 2. Ensure newline after self-closing <details ... />
|
||||||
|
parts[i] = self._PATTERNS["details_self_closing"].sub(r"\1\n", parts[i])
|
||||||
|
|
||||||
|
return "```".join(parts)
|
||||||
|
|
||||||
def _fix_code_blocks(self, content: str) -> str:
|
def _fix_code_blocks(self, content: str) -> str:
|
||||||
"""Fix code block formatting (prefixes, suffixes, indentation)"""
|
"""Fix code block formatting (prefixes, suffixes, indentation)"""
|
||||||
# Remove indentation before code blocks
|
|
||||||
content = self._PATTERNS["code_block_indent"].sub(r"\1", content)
|
|
||||||
# Ensure newline before ```
|
# Ensure newline before ```
|
||||||
content = self._PATTERNS["code_block_prefix"].sub(r"\n\1", content)
|
content = self._PATTERNS["code_block_prefix"].sub(r"\n\1", content)
|
||||||
# Ensure newline after ```lang
|
# Ensure newline after ```lang
|
||||||
@@ -410,6 +457,61 @@ class ContentNormalizer:
|
|||||||
"""Remove leftover XML tags"""
|
"""Remove leftover XML tags"""
|
||||||
return self._PATTERNS["xml_artifacts"].sub("", content)
|
return self._PATTERNS["xml_artifacts"].sub("", content)
|
||||||
|
|
||||||
|
def _fix_emphasis_spacing(self, content: str) -> str:
|
||||||
|
"""Fix spaces inside **emphasis** or _emphasis_
|
||||||
|
Example: ** text ** -> **text**, **text ** -> **text**, ** text** -> **text**
|
||||||
|
"""
|
||||||
|
|
||||||
|
def replacer(match):
|
||||||
|
symbol = match.group(1)
|
||||||
|
inner = match.group("inner")
|
||||||
|
|
||||||
|
# Recursive step: Fix emphasis spacing INSIDE the current block first
|
||||||
|
# This ensures that ** _ italic _ ** becomes ** _italic_ ** before we strip outer spaces.
|
||||||
|
inner = self._PATTERNS["emphasis_spacing"].sub(replacer, inner)
|
||||||
|
|
||||||
|
# If no leading/trailing whitespace, nothing to fix at this level
|
||||||
|
stripped_inner = inner.strip()
|
||||||
|
if stripped_inner == inner:
|
||||||
|
return f"{symbol}{inner}{symbol}"
|
||||||
|
|
||||||
|
# Safeguard: If inner content is just whitespace, don't touch it
|
||||||
|
if not stripped_inner:
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
# Safeguard: If it looks like a math expression or list of variables (e.g. " * 3 * " or " _ b _ ")
|
||||||
|
# If the symbol is surrounded by spaces in the original text, it's likely an operator.
|
||||||
|
if inner.startswith(" ") and inner.endswith(" "):
|
||||||
|
# If it's single '*' or '_', and both sides have spaces, it's almost certainly an operator.
|
||||||
|
if symbol in ["*", "_"]:
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
# Safeguard: List marker protection
|
||||||
|
# If symbol is single '*' and inner content starts with whitespace followed by emphasis markers,
|
||||||
|
# this is likely a list item like "* **bold**" - don't merge them.
|
||||||
|
# Pattern: "* **text**" should NOT become "***text**"
|
||||||
|
if symbol == "*" and inner.lstrip().startswith(("*", "_")):
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
# Extended list marker protection:
|
||||||
|
# If symbol is single '*' and inner starts with multiple spaces (list indentation pattern),
|
||||||
|
# this is likely a list item like "* text" - don't strip the spaces.
|
||||||
|
# Pattern: "* U16 forward **Kuang**" should NOT become "*U16 forward **Kuang**"
|
||||||
|
if symbol == "*" and inner.startswith(" "):
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
return f"{symbol}{stripped_inner}{symbol}"
|
||||||
|
|
||||||
|
parts = content.split("```")
|
||||||
|
for i in range(0, len(parts), 2): # Even indices are markdown text
|
||||||
|
# We use a while loop to handle overlapping or multiple occurrences at the top level
|
||||||
|
while True:
|
||||||
|
new_part = self._PATTERNS["emphasis_spacing"].sub(replacer, parts[i])
|
||||||
|
if new_part == parts[i]:
|
||||||
|
break
|
||||||
|
parts[i] = new_part
|
||||||
|
return "```".join(parts)
|
||||||
|
|
||||||
|
|
||||||
class Filter:
|
class Filter:
|
||||||
class Valves(BaseModel):
|
class Valves(BaseModel):
|
||||||
@@ -427,6 +529,10 @@ class Filter:
|
|||||||
enable_thought_tag_fix: bool = Field(
|
enable_thought_tag_fix: bool = Field(
|
||||||
default=True, description="Normalize </thought> tags"
|
default=True, description="Normalize </thought> tags"
|
||||||
)
|
)
|
||||||
|
enable_details_tag_fix: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="Normalize <details> tags (add blank line after </details> and handle self-closing tags)",
|
||||||
|
)
|
||||||
enable_code_block_fix: bool = Field(
|
enable_code_block_fix: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
description="Fix code block formatting (indentation, newlines)",
|
description="Fix code block formatting (indentation, newlines)",
|
||||||
@@ -457,6 +563,10 @@ class Filter:
|
|||||||
enable_xml_tag_cleanup: bool = Field(
|
enable_xml_tag_cleanup: bool = Field(
|
||||||
default=True, description="Cleanup leftover XML tags"
|
default=True, description="Cleanup leftover XML tags"
|
||||||
)
|
)
|
||||||
|
enable_emphasis_spacing_fix: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="Fix spaces inside **emphasis** (e.g. ** text ** -> **text**)",
|
||||||
|
)
|
||||||
show_status: bool = Field(
|
show_status: bool = Field(
|
||||||
default=True, description="Show status notification when fixes are applied"
|
default=True, description="Show status notification when fixes are applied"
|
||||||
)
|
)
|
||||||
@@ -585,11 +695,21 @@ class Filter:
|
|||||||
if self._contains_html(content):
|
if self._contains_html(content):
|
||||||
return body
|
return body
|
||||||
|
|
||||||
|
# Skip if content contains tool output markers (native function calling)
|
||||||
|
# Pattern: """...""" or tool_call_id or <details type="tool_calls"...>
|
||||||
|
if (
|
||||||
|
'"""' in content
|
||||||
|
or "tool_call_id" in content
|
||||||
|
or '<details type="tool_calls"' in content
|
||||||
|
):
|
||||||
|
return body
|
||||||
|
|
||||||
# Configure normalizer based on valves
|
# Configure normalizer based on valves
|
||||||
config = NormalizerConfig(
|
config = NormalizerConfig(
|
||||||
enable_escape_fix=self.valves.enable_escape_fix,
|
enable_escape_fix=self.valves.enable_escape_fix,
|
||||||
enable_escape_fix_in_code_blocks=self.valves.enable_escape_fix_in_code_blocks,
|
enable_escape_fix_in_code_blocks=self.valves.enable_escape_fix_in_code_blocks,
|
||||||
enable_thought_tag_fix=self.valves.enable_thought_tag_fix,
|
enable_thought_tag_fix=self.valves.enable_thought_tag_fix,
|
||||||
|
enable_details_tag_fix=self.valves.enable_details_tag_fix,
|
||||||
enable_code_block_fix=self.valves.enable_code_block_fix,
|
enable_code_block_fix=self.valves.enable_code_block_fix,
|
||||||
enable_latex_fix=self.valves.enable_latex_fix,
|
enable_latex_fix=self.valves.enable_latex_fix,
|
||||||
enable_list_fix=self.valves.enable_list_fix,
|
enable_list_fix=self.valves.enable_list_fix,
|
||||||
@@ -599,6 +719,7 @@ class Filter:
|
|||||||
enable_heading_fix=self.valves.enable_heading_fix,
|
enable_heading_fix=self.valves.enable_heading_fix,
|
||||||
enable_table_fix=self.valves.enable_table_fix,
|
enable_table_fix=self.valves.enable_table_fix,
|
||||||
enable_xml_tag_cleanup=self.valves.enable_xml_tag_cleanup,
|
enable_xml_tag_cleanup=self.valves.enable_xml_tag_cleanup,
|
||||||
|
enable_emphasis_spacing_fix=self.valves.enable_emphasis_spacing_fix,
|
||||||
)
|
)
|
||||||
|
|
||||||
normalizer = ContentNormalizer(config)
|
normalizer = ContentNormalizer(config)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: Markdown 格式修复器 (Markdown Normalizer)
|
|||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
funding_url: https://github.com/open-webui
|
funding_url: https://github.com/open-webui
|
||||||
version: 1.1.2
|
version: 1.2.4
|
||||||
description: 内容规范化过滤器,修复 LLM 输出中常见的 Markdown 格式问题,如损坏的代码块、LaTeX 公式、Mermaid 图表和列表格式。
|
description: 内容规范化过滤器,修复 LLM 输出中常见的 Markdown 格式问题,如损坏的代码块、LaTeX 公式、Mermaid 图表和列表格式。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -24,7 +24,11 @@ class NormalizerConfig:
|
|||||||
"""配置类,用于启用/禁用特定的规范化规则"""
|
"""配置类,用于启用/禁用特定的规范化规则"""
|
||||||
|
|
||||||
enable_escape_fix: bool = True # 修复过度的转义字符
|
enable_escape_fix: bool = True # 修复过度的转义字符
|
||||||
|
enable_escape_fix_in_code_blocks: bool = (
|
||||||
|
False # 在代码块内部应用转义修复 (默认:关闭,以确保安全)
|
||||||
|
)
|
||||||
enable_thought_tag_fix: bool = True # 规范化思维链标签
|
enable_thought_tag_fix: bool = True # 规范化思维链标签
|
||||||
|
enable_details_tag_fix: bool = True # 规范化 <details> 标签(类似思维链标签)
|
||||||
enable_code_block_fix: bool = True # 修复代码块格式
|
enable_code_block_fix: bool = True # 修复代码块格式
|
||||||
enable_latex_fix: bool = True # 修复 LaTeX 公式格式
|
enable_latex_fix: bool = True # 修复 LaTeX 公式格式
|
||||||
enable_list_fix: bool = False # 修复列表项换行 (默认关闭,因为可能过于激进)
|
enable_list_fix: bool = False # 修复列表项换行 (默认关闭,因为可能过于激进)
|
||||||
@@ -34,6 +38,7 @@ class NormalizerConfig:
|
|||||||
enable_heading_fix: bool = True # 修复标题中缺失的空格 (#Header -> # Header)
|
enable_heading_fix: bool = True # 修复标题中缺失的空格 (#Header -> # Header)
|
||||||
enable_table_fix: bool = True # 修复表格中缺失的闭合管道符
|
enable_table_fix: bool = True # 修复表格中缺失的闭合管道符
|
||||||
enable_xml_tag_cleanup: bool = True # 清理残留的 XML 标签
|
enable_xml_tag_cleanup: bool = True # 清理残留的 XML 标签
|
||||||
|
enable_emphasis_spacing_fix: bool = False # 修复 **强调内容** 中的多余空格
|
||||||
|
|
||||||
# 自定义清理函数 (用于高级扩展)
|
# 自定义清理函数 (用于高级扩展)
|
||||||
custom_cleaners: List[Callable[[str], str]] = field(default_factory=list)
|
custom_cleaners: List[Callable[[str], str]] = field(default_factory=list)
|
||||||
@@ -44,8 +49,8 @@ class ContentNormalizer:
|
|||||||
|
|
||||||
# --- 1. Pre-compiled Regex Patterns (Performance Optimization) ---
|
# --- 1. Pre-compiled Regex Patterns (Performance Optimization) ---
|
||||||
_PATTERNS = {
|
_PATTERNS = {
|
||||||
# Code block prefix: if ``` is not at start of line or file
|
# Code block prefix: if ``` is not at start of line (ignoring whitespace)
|
||||||
"code_block_prefix": re.compile(r"(?<!^)(?<!\n)(```)", re.MULTILINE),
|
"code_block_prefix": re.compile(r"(\S[ \t]*)(```)"),
|
||||||
# Code block suffix: ```lang followed by non-whitespace (no newline)
|
# Code block suffix: ```lang followed by non-whitespace (no newline)
|
||||||
"code_block_suffix": re.compile(r"(```[\w\+\-\.]*)[ \t]+([^\n\r])"),
|
"code_block_suffix": re.compile(r"(```[\w\+\-\.]*)[ \t]+([^\n\r])"),
|
||||||
# Code block indent: whitespace at start of line + ```
|
# Code block indent: whitespace at start of line + ```
|
||||||
@@ -55,6 +60,12 @@ class ContentNormalizer:
|
|||||||
r"</(thought|think|thinking)>[ \t]*\n*", re.IGNORECASE
|
r"</(thought|think|thinking)>[ \t]*\n*", re.IGNORECASE
|
||||||
),
|
),
|
||||||
"thought_start": re.compile(r"<(thought|think|thinking)>", re.IGNORECASE),
|
"thought_start": re.compile(r"<(thought|think|thinking)>", re.IGNORECASE),
|
||||||
|
# Details tag: </details> followed by optional whitespace/newlines
|
||||||
|
"details_end": re.compile(r"</details>[ \t]*\n*", re.IGNORECASE),
|
||||||
|
# Self-closing details tag: <details ... /> followed by optional whitespace (but NOT already having newline)
|
||||||
|
"details_self_closing": re.compile(
|
||||||
|
r"(<details[^>]*/\s*>)(?!\n)", re.IGNORECASE
|
||||||
|
),
|
||||||
# LaTeX block: \[ ... \]
|
# LaTeX block: \[ ... \]
|
||||||
"latex_bracket_block": re.compile(r"\\\[(.+?)\\\]", re.DOTALL),
|
"latex_bracket_block": re.compile(r"\\\[(.+?)\\\]", re.DOTALL),
|
||||||
# LaTeX inline: \( ... \)
|
# LaTeX inline: \( ... \)
|
||||||
@@ -93,6 +104,14 @@ class ContentNormalizer:
|
|||||||
"heading_space": re.compile(r"^(#+)([^ \n#])", re.MULTILINE),
|
"heading_space": re.compile(r"^(#+)([^ \n#])", re.MULTILINE),
|
||||||
# Table: | col1 | col2 -> | col1 | col2 |
|
# Table: | col1 | col2 -> | col1 | col2 |
|
||||||
"table_pipe": re.compile(r"^(\|.*[^|\n])$", re.MULTILINE),
|
"table_pipe": re.compile(r"^(\|.*[^|\n])$", re.MULTILINE),
|
||||||
|
# Emphasis spacing: ** text ** -> **text**, __ text __ -> __text__
|
||||||
|
# Matches emphasis blocks within a single line. We use a recursive approach
|
||||||
|
# in _fix_emphasis_spacing to handle nesting and spaces correctly.
|
||||||
|
# NOTE: We use [^\n] instead of . to prevent cross-line matching.
|
||||||
|
# Supports: * (italic), ** (bold), *** (bold+italic), _ (italic), __ (bold), ___ (bold+italic)
|
||||||
|
"emphasis_spacing": re.compile(
|
||||||
|
r"(?<!\*|_)(\*{1,3}|_{1,3})(?P<inner>[^\n]*?)(\1)(?!\*|_)"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, config: Optional[NormalizerConfig] = None):
|
def __init__(self, config: Optional[NormalizerConfig] = None):
|
||||||
@@ -122,7 +141,14 @@ class ContentNormalizer:
|
|||||||
if content != original:
|
if content != original:
|
||||||
self.applied_fixes.append("Normalize Thought Tags")
|
self.applied_fixes.append("Normalize Thought Tags")
|
||||||
|
|
||||||
# 3. Code block formatting fix
|
# 3. Details tag normalization (must be before heading fix)
|
||||||
|
if self.config.enable_details_tag_fix:
|
||||||
|
original = content
|
||||||
|
content = self._fix_details_tags(content)
|
||||||
|
if content != original:
|
||||||
|
self.applied_fixes.append("Normalize Details Tags")
|
||||||
|
|
||||||
|
# 4. Code block formatting fix
|
||||||
if self.config.enable_code_block_fix:
|
if self.config.enable_code_block_fix:
|
||||||
original = content
|
original = content
|
||||||
content = self._fix_code_blocks(content)
|
content = self._fix_code_blocks(content)
|
||||||
@@ -185,6 +211,13 @@ class ContentNormalizer:
|
|||||||
if content != original:
|
if content != original:
|
||||||
self.applied_fixes.append("Cleanup XML Tags")
|
self.applied_fixes.append("Cleanup XML Tags")
|
||||||
|
|
||||||
|
# 12. Emphasis spacing fix
|
||||||
|
if self.config.enable_emphasis_spacing_fix:
|
||||||
|
original = content
|
||||||
|
content = self._fix_emphasis_spacing(content)
|
||||||
|
if content != original:
|
||||||
|
self.applied_fixes.append("Fix Emphasis Spacing")
|
||||||
|
|
||||||
# 9. Custom cleaners
|
# 9. Custom cleaners
|
||||||
for cleaner in self.config.custom_cleaners:
|
for cleaner in self.config.custom_cleaners:
|
||||||
original = content
|
original = content
|
||||||
@@ -209,12 +242,27 @@ class ContentNormalizer:
|
|||||||
return content
|
return content
|
||||||
|
|
||||||
def _fix_escape_characters(self, content: str) -> str:
|
def _fix_escape_characters(self, content: str) -> str:
|
||||||
"""Fix excessive escape characters"""
|
"""修复过度的转义字符
|
||||||
content = content.replace("\\r\\n", "\n")
|
|
||||||
content = content.replace("\\n", "\n")
|
如果 enable_escape_fix_in_code_blocks 为 False (默认),此方法将仅修复代码块外部的转义字符,
|
||||||
content = content.replace("\\t", "\t")
|
以避免破坏有效的代码示例 (例如,带有 \\n 的 JSON 字符串、正则表达式模式等)。
|
||||||
content = content.replace("\\\\", "\\")
|
"""
|
||||||
return content
|
if self.config.enable_escape_fix_in_code_blocks:
|
||||||
|
# 全局应用 (原始行为)
|
||||||
|
content = content.replace("\\r\\n", "\n")
|
||||||
|
content = content.replace("\\n", "\n")
|
||||||
|
content = content.replace("\\t", "\t")
|
||||||
|
content = content.replace("\\\\", "\\")
|
||||||
|
return content
|
||||||
|
else:
|
||||||
|
# 仅在代码块外部应用 (安全模式)
|
||||||
|
parts = content.split("```")
|
||||||
|
for i in range(0, len(parts), 2): # 偶数索引是 Markdown 文本 (非代码)
|
||||||
|
parts[i] = parts[i].replace("\\r\\n", "\n")
|
||||||
|
parts[i] = parts[i].replace("\\n", "\n")
|
||||||
|
parts[i] = parts[i].replace("\\t", "\t")
|
||||||
|
parts[i] = parts[i].replace("\\\\", "\\")
|
||||||
|
return "```".join(parts)
|
||||||
|
|
||||||
def _fix_thought_tags(self, content: str) -> str:
|
def _fix_thought_tags(self, content: str) -> str:
|
||||||
"""Normalize thought tags: unify naming and fix spacing"""
|
"""Normalize thought tags: unify naming and fix spacing"""
|
||||||
@@ -223,10 +271,26 @@ class ContentNormalizer:
|
|||||||
# 2. Standardize end tag and ensure newlines: </think> -> </thought>\n\n
|
# 2. Standardize end tag and ensure newlines: </think> -> </thought>\n\n
|
||||||
return self._PATTERNS["thought_end"].sub("</thought>\n\n", content)
|
return self._PATTERNS["thought_end"].sub("</thought>\n\n", content)
|
||||||
|
|
||||||
|
def _fix_details_tags(self, content: str) -> str:
|
||||||
|
"""规范化 <details> 标签:确保闭合标签后的正确间距
|
||||||
|
|
||||||
|
处理两种情况:
|
||||||
|
1. </details> 后跟内容 -> 确保有双换行
|
||||||
|
2. <details .../> (自闭合) 后跟内容 -> 确保有换行
|
||||||
|
|
||||||
|
注意:仅在代码块外部应用,以避免破坏代码示例。
|
||||||
|
"""
|
||||||
|
parts = content.split("```")
|
||||||
|
for i in range(0, len(parts), 2): # 偶数索引是 Markdown 文本
|
||||||
|
# 1. 确保 </details> 后有双换行
|
||||||
|
parts[i] = self._PATTERNS["details_end"].sub("</details>\n\n", parts[i])
|
||||||
|
# 2. 确保自闭合 <details ... /> 后有换行
|
||||||
|
parts[i] = self._PATTERNS["details_self_closing"].sub(r"\1\n", parts[i])
|
||||||
|
|
||||||
|
return "```".join(parts)
|
||||||
|
|
||||||
def _fix_code_blocks(self, content: str) -> str:
|
def _fix_code_blocks(self, content: str) -> str:
|
||||||
"""Fix code block formatting (prefixes, suffixes, indentation)"""
|
"""Fix code block formatting (prefixes, suffixes, indentation)"""
|
||||||
# Remove indentation before code blocks
|
|
||||||
content = self._PATTERNS["code_block_indent"].sub(r"\1", content)
|
|
||||||
# Ensure newline before ```
|
# Ensure newline before ```
|
||||||
content = self._PATTERNS["code_block_prefix"].sub(r"\n\1", content)
|
content = self._PATTERNS["code_block_prefix"].sub(r"\n\1", content)
|
||||||
# Ensure newline after ```lang
|
# Ensure newline after ```lang
|
||||||
@@ -390,6 +454,61 @@ class ContentNormalizer:
|
|||||||
"""Remove leftover XML tags"""
|
"""Remove leftover XML tags"""
|
||||||
return self._PATTERNS["xml_artifacts"].sub("", content)
|
return self._PATTERNS["xml_artifacts"].sub("", content)
|
||||||
|
|
||||||
|
def _fix_emphasis_spacing(self, content: str) -> str:
|
||||||
|
"""Fix spaces inside **emphasis** or _emphasis_
|
||||||
|
Example: ** text ** -> **text**, **text ** -> **text**, ** text** -> **text**
|
||||||
|
"""
|
||||||
|
|
||||||
|
def replacer(match):
|
||||||
|
symbol = match.group(1)
|
||||||
|
inner = match.group("inner")
|
||||||
|
|
||||||
|
# Recursive step: Fix emphasis spacing INSIDE the current block first
|
||||||
|
# This ensures that ** _ italic _ ** becomes ** _italic_ ** before we strip outer spaces.
|
||||||
|
inner = self._PATTERNS["emphasis_spacing"].sub(replacer, inner)
|
||||||
|
|
||||||
|
# If no leading/trailing whitespace, nothing to fix at this level
|
||||||
|
stripped_inner = inner.strip()
|
||||||
|
if stripped_inner == inner:
|
||||||
|
return f"{symbol}{inner}{symbol}"
|
||||||
|
|
||||||
|
# Safeguard: If inner content is just whitespace, don't touch it
|
||||||
|
if not stripped_inner:
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
# Safeguard: If it looks like a math expression or list of variables (e.g. " * 3 * " or " _ b _ ")
|
||||||
|
# If the symbol is surrounded by spaces in the original text, it's likely an operator.
|
||||||
|
if inner.startswith(" ") and inner.endswith(" "):
|
||||||
|
# If it's single '*' or '_', and both sides have spaces, it's almost certainly an operator.
|
||||||
|
if symbol in ["*", "_"]:
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
# Safeguard: List marker protection
|
||||||
|
# If symbol is single '*' and inner content starts with whitespace followed by emphasis markers,
|
||||||
|
# this is likely a list item like "* **bold**" - don't merge them.
|
||||||
|
# Pattern: "* **text**" should NOT become "***text**"
|
||||||
|
if symbol == "*" and inner.lstrip().startswith(("*", "_")):
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
# Extended list marker protection:
|
||||||
|
# If symbol is single '*' and inner starts with multiple spaces (list indentation pattern),
|
||||||
|
# this is likely a list item like "* text" - don't strip the spaces.
|
||||||
|
# Pattern: "* U16 forward **Kuang**" should NOT become "*U16 forward **Kuang**"
|
||||||
|
if symbol == "*" and inner.startswith(" "):
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
return f"{symbol}{stripped_inner}{symbol}"
|
||||||
|
|
||||||
|
parts = content.split("```")
|
||||||
|
for i in range(0, len(parts), 2): # Even indices are markdown text
|
||||||
|
# We use a while loop to handle overlapping or multiple occurrences at the top level
|
||||||
|
while True:
|
||||||
|
new_part = self._PATTERNS["emphasis_spacing"].sub(replacer, parts[i])
|
||||||
|
if new_part == parts[i]:
|
||||||
|
break
|
||||||
|
parts[i] = new_part
|
||||||
|
return "```".join(parts)
|
||||||
|
|
||||||
|
|
||||||
class Filter:
|
class Filter:
|
||||||
class Valves(BaseModel):
|
class Valves(BaseModel):
|
||||||
@@ -400,9 +519,17 @@ class Filter:
|
|||||||
enable_escape_fix: bool = Field(
|
enable_escape_fix: bool = Field(
|
||||||
default=True, description="修复过度的转义字符 (\\n, \\t 等)"
|
default=True, description="修复过度的转义字符 (\\n, \\t 等)"
|
||||||
)
|
)
|
||||||
|
enable_escape_fix_in_code_blocks: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="在代码块内部应用转义修复 (⚠️ 警告:可能会破坏有效的代码,如 JSON 字符串或正则模式。默认:关闭,以确保安全)",
|
||||||
|
)
|
||||||
enable_thought_tag_fix: bool = Field(
|
enable_thought_tag_fix: bool = Field(
|
||||||
default=True, description="规范化思维链标签 (<think> -> <thought>)"
|
default=True, description="规范化思维链标签 (<think> -> <thought>)"
|
||||||
)
|
)
|
||||||
|
enable_details_tag_fix: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="规范化 <details> 标签 (在 </details> 后添加空行,处理自闭合标签)",
|
||||||
|
)
|
||||||
enable_code_block_fix: bool = Field(
|
enable_code_block_fix: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
description="修复代码块格式 (缩进、换行)",
|
description="修复代码块格式 (缩进、换行)",
|
||||||
@@ -433,6 +560,10 @@ class Filter:
|
|||||||
enable_xml_tag_cleanup: bool = Field(
|
enable_xml_tag_cleanup: bool = Field(
|
||||||
default=True, description="清理残留的 XML 标签"
|
default=True, description="清理残留的 XML 标签"
|
||||||
)
|
)
|
||||||
|
enable_emphasis_spacing_fix: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="修复强调语法中的多余空格 (例如 ** 文本 ** -> **文本**)",
|
||||||
|
)
|
||||||
show_status: bool = Field(default=True, description="应用修复时显示状态通知")
|
show_status: bool = Field(default=True, description="应用修复时显示状态通知")
|
||||||
show_debug_log: bool = Field(
|
show_debug_log: bool = Field(
|
||||||
default=True, description="在浏览器控制台打印调试日志 (F12)"
|
default=True, description="在浏览器控制台打印调试日志 (F12)"
|
||||||
@@ -494,6 +625,7 @@ class Filter:
|
|||||||
fix_map = {
|
fix_map = {
|
||||||
"Fix Escape Chars": "转义字符",
|
"Fix Escape Chars": "转义字符",
|
||||||
"Normalize Thought Tags": "思维标签",
|
"Normalize Thought Tags": "思维标签",
|
||||||
|
"Normalize Details Tags": "Details标签",
|
||||||
"Fix Code Blocks": "代码块",
|
"Fix Code Blocks": "代码块",
|
||||||
"Normalize LaTeX": "LaTeX公式",
|
"Normalize LaTeX": "LaTeX公式",
|
||||||
"Fix List Format": "列表格式",
|
"Fix List Format": "列表格式",
|
||||||
@@ -503,6 +635,7 @@ class Filter:
|
|||||||
"Fix Headings": "标题格式",
|
"Fix Headings": "标题格式",
|
||||||
"Fix Tables": "表格格式",
|
"Fix Tables": "表格格式",
|
||||||
"Cleanup XML Tags": "XML清理",
|
"Cleanup XML Tags": "XML清理",
|
||||||
|
"Fix Emphasis Spacing": "强调空格",
|
||||||
"Custom Cleaner": "自定义清理",
|
"Custom Cleaner": "自定义清理",
|
||||||
}
|
}
|
||||||
translated_fixes = [fix_map.get(fix, fix) for fix in applied_fixes]
|
translated_fixes = [fix_map.get(fix, fix) for fix in applied_fixes]
|
||||||
@@ -571,14 +704,25 @@ class Filter:
|
|||||||
content = last.get("content", "") or ""
|
content = last.get("content", "") or ""
|
||||||
|
|
||||||
if last.get("role") == "assistant" and isinstance(content, str):
|
if last.get("role") == "assistant" and isinstance(content, str):
|
||||||
# Skip if content looks like HTML to avoid breaking it
|
# 如果内容看起来像 HTML,则跳过以避免破坏它
|
||||||
if self._contains_html(content):
|
if self._contains_html(content):
|
||||||
return body
|
return body
|
||||||
|
|
||||||
# Configure normalizer based on valves
|
# 如果内容包含工具输出标记 (原生函数调用),则跳过
|
||||||
|
# 模式:"""...""" 或 tool_call_id 或 <details type="tool_calls"...>
|
||||||
|
if (
|
||||||
|
'"""' in content
|
||||||
|
or "tool_call_id" in content
|
||||||
|
or '<details type="tool_calls"' in content
|
||||||
|
):
|
||||||
|
return body
|
||||||
|
|
||||||
|
# 根据 Valves 配置 Normalizer
|
||||||
config = NormalizerConfig(
|
config = NormalizerConfig(
|
||||||
enable_escape_fix=self.valves.enable_escape_fix,
|
enable_escape_fix=self.valves.enable_escape_fix,
|
||||||
|
enable_escape_fix_in_code_blocks=self.valves.enable_escape_fix_in_code_blocks,
|
||||||
enable_thought_tag_fix=self.valves.enable_thought_tag_fix,
|
enable_thought_tag_fix=self.valves.enable_thought_tag_fix,
|
||||||
|
enable_details_tag_fix=self.valves.enable_details_tag_fix,
|
||||||
enable_code_block_fix=self.valves.enable_code_block_fix,
|
enable_code_block_fix=self.valves.enable_code_block_fix,
|
||||||
enable_latex_fix=self.valves.enable_latex_fix,
|
enable_latex_fix=self.valves.enable_latex_fix,
|
||||||
enable_list_fix=self.valves.enable_list_fix,
|
enable_list_fix=self.valves.enable_list_fix,
|
||||||
@@ -588,6 +732,7 @@ class Filter:
|
|||||||
enable_heading_fix=self.valves.enable_heading_fix,
|
enable_heading_fix=self.valves.enable_heading_fix,
|
||||||
enable_table_fix=self.valves.enable_table_fix,
|
enable_table_fix=self.valves.enable_table_fix,
|
||||||
enable_xml_tag_cleanup=self.valves.enable_xml_tag_cleanup,
|
enable_xml_tag_cleanup=self.valves.enable_xml_tag_cleanup,
|
||||||
|
enable_emphasis_spacing_fix=self.valves.enable_emphasis_spacing_fix,
|
||||||
)
|
)
|
||||||
|
|
||||||
normalizer = ContentNormalizer(config)
|
normalizer = ContentNormalizer(config)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class TestMarkdownNormalizer(unittest.TestCase):
|
|||||||
self.config = NormalizerConfig(
|
self.config = NormalizerConfig(
|
||||||
enable_escape_fix=True,
|
enable_escape_fix=True,
|
||||||
enable_thought_tag_fix=True,
|
enable_thought_tag_fix=True,
|
||||||
|
enable_details_tag_fix=True,
|
||||||
enable_code_block_fix=True,
|
enable_code_block_fix=True,
|
||||||
enable_latex_fix=True,
|
enable_latex_fix=True,
|
||||||
enable_list_fix=True,
|
enable_list_fix=True,
|
||||||
@@ -21,6 +22,7 @@ class TestMarkdownNormalizer(unittest.TestCase):
|
|||||||
enable_fullwidth_symbol_fix=True,
|
enable_fullwidth_symbol_fix=True,
|
||||||
enable_mermaid_fix=True,
|
enable_mermaid_fix=True,
|
||||||
enable_xml_tag_cleanup=True,
|
enable_xml_tag_cleanup=True,
|
||||||
|
enable_heading_fix=True,
|
||||||
)
|
)
|
||||||
self.normalizer = ContentNormalizer(self.config)
|
self.normalizer = ContentNormalizer(self.config)
|
||||||
|
|
||||||
@@ -42,6 +44,32 @@ class TestMarkdownNormalizer(unittest.TestCase):
|
|||||||
self.normalizer.normalize(input_text_deepseek), expected_deepseek
|
self.normalizer.normalize(input_text_deepseek), expected_deepseek
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_details_tag_fix(self):
|
||||||
|
# Case 1: </details> followed by content without blank line
|
||||||
|
input_text = (
|
||||||
|
"<details><summary>Thought</summary>\n> Thinking\n</details>Next paragraph"
|
||||||
|
)
|
||||||
|
expected = "<details><summary>Thought</summary>\n> Thinking\n</details>\n\nNext paragraph"
|
||||||
|
self.assertEqual(self.normalizer.normalize(input_text), expected)
|
||||||
|
|
||||||
|
# Case 2: Self-closing <details /> followed by heading
|
||||||
|
input_text_self_closing = '<details id="__DETAIL_0__"/>#Heading'
|
||||||
|
result = self.normalizer.normalize(input_text_self_closing)
|
||||||
|
self.assertIn("# Heading", result) # Heading should be fixed
|
||||||
|
self.assertIn(
|
||||||
|
'<details id="__DETAIL_0__"/>\n', result
|
||||||
|
) # Should have newline after
|
||||||
|
|
||||||
|
# Case 3: </details> already has proper spacing (should not add extra)
|
||||||
|
input_already_good = "</details>\n\nNext"
|
||||||
|
self.assertEqual(
|
||||||
|
self.normalizer.normalize(input_already_good), input_already_good
|
||||||
|
)
|
||||||
|
|
||||||
|
# Case 4: Details tag inside code block (should NOT be modified)
|
||||||
|
input_code_block = "```html\n<details>\n</details>\n```"
|
||||||
|
self.assertEqual(self.normalizer.normalize(input_code_block), input_code_block)
|
||||||
|
|
||||||
def test_code_block_fix(self):
|
def test_code_block_fix(self):
|
||||||
# Case 1: Indentation
|
# Case 1: Indentation
|
||||||
self.assertEqual(self.normalizer._fix_code_blocks(" ```python"), "```python")
|
self.assertEqual(self.normalizer._fix_code_blocks(" ```python"), "```python")
|
||||||
|
|||||||
37
plugins/filters/markdown_normalizer/test_side_effects.py
Normal file
37
plugins/filters/markdown_normalizer/test_side_effects.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from markdown_normalizer import ContentNormalizer, NormalizerConfig
|
||||||
|
|
||||||
|
|
||||||
|
def test_side_effects():
|
||||||
|
normalizer = ContentNormalizer(NormalizerConfig(enable_details_tag_fix=True))
|
||||||
|
|
||||||
|
# Scenario 1: HTML code block
|
||||||
|
code_block = """```html
|
||||||
|
<details>
|
||||||
|
<summary>Click</summary>
|
||||||
|
Content
|
||||||
|
</details>
|
||||||
|
```"""
|
||||||
|
|
||||||
|
# Scenario 2: Python string
|
||||||
|
python_code = """```python
|
||||||
|
html = "</details>"
|
||||||
|
print(html)
|
||||||
|
```"""
|
||||||
|
|
||||||
|
print("--- Scenario 1: HTML Code Block ---")
|
||||||
|
res1 = normalizer.normalize(code_block)
|
||||||
|
print(repr(res1))
|
||||||
|
if "</details>\n\n" in res1 and "```" in res1:
|
||||||
|
print("WARNING: Modified inside HTML code block")
|
||||||
|
|
||||||
|
print("\n--- Scenario 2: Python String ---")
|
||||||
|
res2 = normalizer.normalize(python_code)
|
||||||
|
print(repr(res2))
|
||||||
|
if 'html = "</details>\n\n"' in res2:
|
||||||
|
print("CRITICAL: Broke Python string literal")
|
||||||
|
else:
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_side_effects()
|
||||||
1
plugins/filters/markdown_normalizer/tests/__init__.py
Normal file
1
plugins/filters/markdown_normalizer/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Markdown Normalizer Test Suite
|
||||||
75
plugins/filters/markdown_normalizer/tests/conftest.py
Normal file
75
plugins/filters/markdown_normalizer/tests/conftest.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
"""
|
||||||
|
Shared fixtures for Markdown Normalizer tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Add the parent directory to sys.path for imports
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from markdown_normalizer import ContentNormalizer, NormalizerConfig
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def normalizer():
|
||||||
|
"""Default normalizer with all fixes enabled."""
|
||||||
|
config = NormalizerConfig(
|
||||||
|
enable_escape_fix=True,
|
||||||
|
enable_thought_tag_fix=True,
|
||||||
|
enable_details_tag_fix=True,
|
||||||
|
enable_code_block_fix=True,
|
||||||
|
enable_latex_fix=True,
|
||||||
|
enable_list_fix=False, # Experimental, keep off by default
|
||||||
|
enable_unclosed_block_fix=True,
|
||||||
|
enable_fullwidth_symbol_fix=False,
|
||||||
|
enable_mermaid_fix=True,
|
||||||
|
enable_heading_fix=True,
|
||||||
|
enable_table_fix=True,
|
||||||
|
enable_xml_tag_cleanup=True,
|
||||||
|
enable_emphasis_spacing_fix=True,
|
||||||
|
)
|
||||||
|
return ContentNormalizer(config)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def emphasis_only_normalizer():
|
||||||
|
"""Normalizer with only emphasis spacing fix enabled."""
|
||||||
|
config = NormalizerConfig(
|
||||||
|
enable_escape_fix=False,
|
||||||
|
enable_thought_tag_fix=False,
|
||||||
|
enable_details_tag_fix=False,
|
||||||
|
enable_code_block_fix=False,
|
||||||
|
enable_latex_fix=False,
|
||||||
|
enable_list_fix=False,
|
||||||
|
enable_unclosed_block_fix=False,
|
||||||
|
enable_fullwidth_symbol_fix=False,
|
||||||
|
enable_mermaid_fix=False,
|
||||||
|
enable_heading_fix=False,
|
||||||
|
enable_table_fix=False,
|
||||||
|
enable_xml_tag_cleanup=False,
|
||||||
|
enable_emphasis_spacing_fix=True,
|
||||||
|
)
|
||||||
|
return ContentNormalizer(config)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mermaid_only_normalizer():
|
||||||
|
"""Normalizer with only Mermaid fix enabled."""
|
||||||
|
config = NormalizerConfig(
|
||||||
|
enable_escape_fix=False,
|
||||||
|
enable_thought_tag_fix=False,
|
||||||
|
enable_details_tag_fix=False,
|
||||||
|
enable_code_block_fix=False,
|
||||||
|
enable_latex_fix=False,
|
||||||
|
enable_list_fix=False,
|
||||||
|
enable_unclosed_block_fix=False,
|
||||||
|
enable_fullwidth_symbol_fix=False,
|
||||||
|
enable_mermaid_fix=True,
|
||||||
|
enable_heading_fix=False,
|
||||||
|
enable_table_fix=False,
|
||||||
|
enable_xml_tag_cleanup=False,
|
||||||
|
enable_emphasis_spacing_fix=False,
|
||||||
|
)
|
||||||
|
return ContentNormalizer(config)
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
Tests for code block formatting fixes.
|
||||||
|
Covers: prefix, suffix, indentation preservation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
class TestCodeBlockFix:
|
||||||
|
"""Test code block formatting normalization."""
|
||||||
|
|
||||||
|
def test_code_block_indentation_preserved(self, normalizer):
|
||||||
|
"""Indented code blocks (e.g., in lists) should preserve indentation."""
|
||||||
|
input_str = """
|
||||||
|
* List item 1
|
||||||
|
```python
|
||||||
|
def foo():
|
||||||
|
print("bar")
|
||||||
|
```
|
||||||
|
* List item 2
|
||||||
|
"""
|
||||||
|
# Indentation should be preserved
|
||||||
|
assert " ```python" in normalizer.normalize(input_str)
|
||||||
|
|
||||||
|
def test_inline_code_block_prefix(self, normalizer):
|
||||||
|
"""Code block that follows text on same line should be modified."""
|
||||||
|
input_str = "text```python\ncode\n```"
|
||||||
|
result = normalizer.normalize(input_str)
|
||||||
|
# Just verify the code block markers are present
|
||||||
|
assert "```" in result
|
||||||
|
|
||||||
|
def test_code_block_suffix_fix(self, normalizer):
|
||||||
|
"""Code block with content on same line after lang should be fixed."""
|
||||||
|
input_str = "```python code\nmore code\n```"
|
||||||
|
result = normalizer.normalize(input_str)
|
||||||
|
# Content should be on new line
|
||||||
|
assert "```python\n" in result or "```python " in result
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnclosedCodeBlock:
|
||||||
|
"""Test auto-closing of unclosed code blocks."""
|
||||||
|
|
||||||
|
def test_unclosed_code_block_is_closed(self, normalizer):
|
||||||
|
"""Unclosed code blocks should be automatically closed."""
|
||||||
|
input_str = "```python\ncode here"
|
||||||
|
result = normalizer.normalize(input_str)
|
||||||
|
# Should have closing ```
|
||||||
|
assert result.endswith("```") or result.count("```") == 2
|
||||||
|
|
||||||
|
def test_balanced_code_blocks_unchanged(self, normalizer):
|
||||||
|
"""Already balanced code blocks should not get extra closing."""
|
||||||
|
input_str = "```python\ncode\n```"
|
||||||
|
result = normalizer.normalize(input_str)
|
||||||
|
assert result.count("```") == 2
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
"""
|
||||||
|
Tests for details tag normalization.
|
||||||
|
Covers: </details> spacing, self-closing tags.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
class TestDetailsTagFix:
|
||||||
|
"""Test details tag normalization."""
|
||||||
|
|
||||||
|
def test_details_end_gets_newlines(self, normalizer):
|
||||||
|
"""</details> should be followed by double newline."""
|
||||||
|
input_str = "</details>Content after"
|
||||||
|
result = normalizer.normalize(input_str)
|
||||||
|
assert "</details>\n\n" in result
|
||||||
|
|
||||||
|
def test_self_closing_details_gets_newline(self, normalizer):
|
||||||
|
"""Self-closing <details .../> should get newline after."""
|
||||||
|
input_str = "<details open />## Heading"
|
||||||
|
result = normalizer.normalize(input_str)
|
||||||
|
# Should have newline between tag and heading
|
||||||
|
assert "/>\n" in result or "/> \n" in result
|
||||||
|
|
||||||
|
def test_details_in_code_block_unchanged(self, normalizer):
|
||||||
|
"""Details tags inside code blocks should not be modified."""
|
||||||
|
input_str = "```html\n<details>content</details>more\n```"
|
||||||
|
result = normalizer.normalize(input_str)
|
||||||
|
# Content inside code block should be unchanged
|
||||||
|
assert "</details>more" in result
|
||||||
|
|
||||||
|
|
||||||
|
class TestThoughtTagFix:
|
||||||
|
"""Test thought tag normalization."""
|
||||||
|
|
||||||
|
def test_think_tag_normalized(self, normalizer):
|
||||||
|
"""<think> should be normalized to <thought>."""
|
||||||
|
input_str = "<think>content</think>"
|
||||||
|
result = normalizer.normalize(input_str)
|
||||||
|
assert "<thought>" in result
|
||||||
|
assert "</thought>" in result
|
||||||
|
|
||||||
|
def test_thinking_tag_normalized(self, normalizer):
|
||||||
|
"""<thinking> should be normalized to <thought>."""
|
||||||
|
input_str = "<thinking>content</thinking>"
|
||||||
|
result = normalizer.normalize(input_str)
|
||||||
|
assert "<thought>" in result
|
||||||
|
assert "</thought>" in result
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user