Compare commits
294 Commits
v2026.01.1
...
v2026.02.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e17818bf6c | ||
|
|
c5c9fd9d57 | ||
|
|
5c9875d390 | ||
|
|
9185f88d40 | ||
|
|
1fcad993ea | ||
|
|
1594ea3a20 | ||
|
|
ee681ddad9 | ||
|
|
881597fd51 | ||
|
|
5772b1c65f | ||
|
|
3d4b4b96e8 | ||
|
|
409d2f663f | ||
|
|
de9948a5b0 | ||
|
|
0a51b4cfca | ||
|
|
f8eb096300 | ||
|
|
f6369a1591 | ||
|
|
81634f57fa | ||
|
|
70dc62aaab | ||
|
|
8762c05e04 | ||
|
|
db52ac1041 | ||
|
|
795ac34cd9 | ||
|
|
89a12a4fe8 | ||
|
|
f882997337 | ||
|
|
8e2c1b467e | ||
|
|
db2433afa1 | ||
|
|
fd125c9dea | ||
|
|
0a726aacb6 | ||
|
|
bf82e093d4 | ||
|
|
53ae7ef003 | ||
|
|
c2c7e3b2d3 | ||
|
|
26df5f5144 | ||
|
|
aebeb3677c | ||
|
|
076c1d62c6 | ||
|
|
21cf6ecc1d | ||
|
|
e6bdab54c9 | ||
|
|
2232e5adb3 | ||
|
|
d17089972a | ||
|
|
a7991147be | ||
|
|
be6eb35567 | ||
|
|
90bc295871 | ||
|
|
310ad5d1d3 | ||
|
|
00ce724430 | ||
|
|
43b68b3ff0 | ||
|
|
b6c1335335 | ||
|
|
8b0b33015f | ||
|
|
11ee9086b2 | ||
|
|
3578ffc543 | ||
|
|
b8531f1979 | ||
|
|
8607afd4c3 | ||
|
|
41d9963d35 | ||
|
|
bd5f3d3f7c | ||
|
|
dda8262bc0 | ||
|
|
0271013ec2 | ||
|
|
e1f210d600 | ||
|
|
eac8f6f355 | ||
|
|
3998b93034 | ||
|
|
ae04e95e13 | ||
|
|
12d638e134 | ||
|
|
4a9eb8ed3d | ||
|
|
af127bbfd5 | ||
|
|
db7bb6250a | ||
|
|
24b029e617 | ||
|
|
ff63ab3118 | ||
|
|
07e7e74fe1 | ||
|
|
55456775c1 | ||
|
|
05bb2e4644 | ||
|
|
83fa20ed08 | ||
|
|
ec69524357 | ||
|
|
829361da63 | ||
|
|
af05ecec6a | ||
|
|
1e08ae7d10 | ||
|
|
b24233ee07 | ||
|
|
e5d1550986 | ||
|
|
7f5deb603e | ||
|
|
55b2a28f79 | ||
|
|
db9bcb2c31 | ||
|
|
ad2773e8f1 | ||
|
|
7d4da3be8a | ||
|
|
28166728a4 | ||
|
|
4dfca903c4 | ||
|
|
b619d3f402 | ||
|
|
1e68f985fb | ||
|
|
596b571887 | ||
|
|
c4d36c32a0 | ||
|
|
6adbcd8d42 | ||
|
|
89c039fe33 | ||
|
|
3a73ccfaa7 | ||
|
|
7eff265e1c | ||
|
|
989b45fc16 | ||
|
|
163d8ce8bd | ||
|
|
4e32e1a1da | ||
|
|
070e9f2456 | ||
|
|
219ba83df3 | ||
|
|
e412aeb93d | ||
|
|
38102ca0c4 | ||
|
|
6ab69fba1c | ||
|
|
e0c0f69dc8 | ||
|
|
7921b14dae | ||
|
|
30cde9e871 | ||
|
|
ac50cd249a | ||
|
|
927db6dbaa | ||
|
|
376c398ac7 | ||
|
|
a167a3cf83 | ||
|
|
c51e7dfdf7 | ||
|
|
1d4d13b34b | ||
|
|
18e8775f38 | ||
|
|
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 | ||
|
|
e2be1b25b1 | ||
|
|
700a7fc27a | ||
|
|
06cc48bab1 | ||
|
|
498e433ed3 | ||
|
|
4e915ea7a9 | ||
|
|
825ea07f4b | ||
|
|
1a731c181b | ||
|
|
c59ba5e501 | ||
|
|
e21e3e2ffa | ||
|
|
d2abaa138e | ||
|
|
3843ae5bc7 | ||
|
|
02c7a87c63 | ||
|
|
1e59025535 | ||
|
|
46195791b6 | ||
|
|
85b6bcece1 | ||
|
|
fece7d9898 | ||
|
|
d41822911c | ||
|
|
7b1180a1c8 | ||
|
|
6d5c3f1415 | ||
|
|
f8157f92fc | ||
|
|
fa2e9f5344 | ||
|
|
9c37955cf2 | ||
|
|
261f74efe8 | ||
|
|
83727bdab1 | ||
|
|
3b1a8d795f | ||
|
|
f650c64ffe | ||
|
|
6000c880de | ||
|
|
048fbb26d7 | ||
|
|
a88eda62cc | ||
|
|
957fb2dfb7 | ||
|
|
d2be5109ad | ||
|
|
80fdc52598 | ||
|
|
2b90ead3cf | ||
|
|
2aa5d77586 | ||
|
|
2b1b1ef939 | ||
|
|
4e21e06617 | ||
|
|
82ce1cef29 | ||
|
|
533eace74e | ||
|
|
83b3dcda65 | ||
|
|
e89373e0ed | ||
|
|
4b66a2bb1c | ||
|
|
59ba23da63 | ||
|
|
f8a89e222c | ||
|
|
096568f3e6 | ||
|
|
e10e12ebc9 | ||
|
|
c4df5eba47 | ||
|
|
0da3d3d881 | ||
|
|
6f4a62d1bc | ||
|
|
5d71c2a4d3 | ||
|
|
097707c168 | ||
|
|
8f4cfceb50 | ||
|
|
4ab5fab7d0 | ||
|
|
0e293be8bc | ||
|
|
182c12f81a | ||
|
|
1337a90911 | ||
|
|
2f0a347ab3 | ||
|
|
4eda286512 | ||
|
|
0fead8158d | ||
|
|
031bef563a | ||
|
|
04c3fd2bf9 | ||
|
|
cbbf6118b5 | ||
|
|
4c529369ce | ||
|
|
797dea0d77 | ||
|
|
a91aee31de | ||
|
|
8511b7df80 | ||
|
|
afd1e7a444 |
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)
|
||||
@@ -11,6 +11,7 @@ This workflow outlines the standard process for developing, documenting, and rel
|
||||
Reference: `.github/copilot-instructions.md`
|
||||
|
||||
### Bilingual Requirement
|
||||
|
||||
Every plugin **MUST** have bilingual versions for both code and documentation:
|
||||
|
||||
- **Code**:
|
||||
@@ -21,17 +22,21 @@ Every plugin **MUST** have bilingual versions for both code and documentation:
|
||||
- Chinese: `plugins/{type}/{name}/README_CN.md`
|
||||
|
||||
### Code Structure
|
||||
|
||||
- **Docstring**: Must include `title`, `author`, `version`, `description`, etc.
|
||||
- **Valves**: Use `pydantic` for configuration.
|
||||
- **Database**: Re-use `open_webui.internal.db` shared connection.
|
||||
- **User Context**: Use `_get_user_context` helper method.
|
||||
- **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`.
|
||||
- Use Event API for immediate UI updates
|
||||
- Use Chat Persistence API for database storage
|
||||
- Always update both `messages[]` and `history.messages`
|
||||
|
||||
### Commit Messages
|
||||
- **Language**: **English ONLY**. Do not use Chinese in commit messages.
|
||||
### Commit Messages & Release Notes
|
||||
|
||||
- **Language**: **English ONLY**. Do not use Chinese in commit messages or release notes.
|
||||
- **Format**: Conventional Commits (e.g., `feat:`, `fix:`, `docs:`).
|
||||
|
||||
## 2. Documentation Updates
|
||||
@@ -39,10 +44,16 @@ Every plugin **MUST** have bilingual versions for both code and documentation:
|
||||
When adding or updating a plugin, you **MUST** update the following documentation files to maintain consistency:
|
||||
|
||||
### Plugin Directory
|
||||
- `README.md`: Update version, description, and usage. **Explicitly describe new features in a prominent position at the beginning.**
|
||||
- `README_CN.md`: Update version, description, and usage. **Explicitly describe new features in a prominent position at the beginning.**
|
||||
|
||||
- `README.md`: Update version, description, and usage.
|
||||
- **Key Capabilities**: **MUST** include ALL core functionalities and features. Do not only list new features in "What's New".
|
||||
- **What's New**: Explicitly describe only the latest changes/updates in a prominent position at the beginning. This section is dynamic and changes with versions.
|
||||
- `README_CN.md`: Update version, description, and usage.
|
||||
- **核心功能 (Key Capabilities)**: **必须**包含所有核心功能和特性,不能只在 "What's New" 中列出。
|
||||
- **最新更新 (What's New)**: 在开头显眼位置明确描述最新的更改/更新。此部分是动态的,随版本变化。
|
||||
|
||||
### Global Documentation (`docs/`)
|
||||
|
||||
- **Index Pages**:
|
||||
- `docs/plugins/{type}/index.md`: Add/Update list item with **correct version**.
|
||||
- `docs/plugins/{type}/index.zh.md`: Add/Update list item with **correct version**.
|
||||
@@ -51,6 +62,7 @@ When adding or updating a plugin, you **MUST** update the following documentatio
|
||||
- `docs/plugins/{type}/{name}.zh.md`: Ensure content matches README_CN.
|
||||
|
||||
### Root README
|
||||
|
||||
- `README.md`: Add to "Featured Plugins" if applicable.
|
||||
- `README_CN.md`: Add to "Featured Plugins" if applicable.
|
||||
|
||||
@@ -59,6 +71,7 @@ When adding or updating a plugin, you **MUST** update the following documentatio
|
||||
Reference: `.github/workflows/release.yml`
|
||||
|
||||
### Version Bumping
|
||||
|
||||
- **Rule**: Version bump is required **ONLY when the user explicitly requests a release**. Regular code changes do NOT require version bumps.
|
||||
- **Format**: Semantic Versioning (e.g., `1.0.0` -> `1.0.1`).
|
||||
- **When to Bump**: Only update the version when:
|
||||
@@ -76,19 +89,25 @@ Reference: `.github/workflows/release.yml`
|
||||
8. Docs Detail CN (`docs/.../{name}.zh.md`)
|
||||
|
||||
### Automated Release Process
|
||||
1. **Trigger**: Push to `main` branch with changes in `plugins/**/*.py`.
|
||||
2. **Detection**: `scripts/extract_plugin_versions.py` detects changed plugins and compares versions.
|
||||
3. **Release**:
|
||||
|
||||
1. **Trigger**: Push to `main` branch with changes in `plugins/**/*.py`.
|
||||
2. **Detection**: `scripts/extract_plugin_versions.py` detects changed plugins and compares versions.
|
||||
3. **Release**:
|
||||
- Generates release notes based on changes.
|
||||
- Creates a GitHub Release tag (e.g., `v2024.01.01-1`).
|
||||
- Uploads individual `.py` files of **changed plugins only** as assets.
|
||||
4. **Market Publishing**:
|
||||
4. **Market Publishing**:
|
||||
- Workflow: `.github/workflows/publish_plugin.yml`
|
||||
- Trigger: Release published.
|
||||
- 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.
|
||||
- **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
|
||||
|
||||
- Workflow: `.github/workflows/plugin-version-check.yml`
|
||||
- Checks if plugin files are modified.
|
||||
- **Fails** if version number is not updated.
|
||||
@@ -108,5 +127,3 @@ Before committing:
|
||||
## 5. Git Operations (Agent Rules)
|
||||
|
||||
Strictly follow the rules defined in `.github/copilot-instructions.md` → **Git Operations (Agent Rules)** section.
|
||||
|
||||
|
||||
|
||||
56
.all-contributorsrc
Normal file
56
.all-contributorsrc
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"imageSize": 100,
|
||||
"commit": false,
|
||||
"commitType": "docs",
|
||||
"commitConvention": "angular",
|
||||
"contributors": [
|
||||
{
|
||||
"login": "rbb-dev",
|
||||
"name": "rbb-dev",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/37469229?v=4",
|
||||
"profile": "https://github.com/rbb-dev",
|
||||
"contributions": [
|
||||
"ideas",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dhaern",
|
||||
"name": "Raxxoor",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7317522?v=4",
|
||||
"profile": "https://trade.xyz/?ref=BZ1RJRXWO",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "i-iooi-i",
|
||||
"name": "ZOLO",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1827701?v=4",
|
||||
"profile": "https://github.com/i-iooi-i",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"skipCi": true,
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"projectName": "awesome-openwebui",
|
||||
"projectOwner": "Fu-Jie"
|
||||
}
|
||||
2124
.github/copilot-instructions.md
vendored
2124
.github/copilot-instructions.md
vendored
File diff suppressed because it is too large
Load Diff
101
.github/workflows/community-stats.yml
vendored
101
.github/workflows/community-stats.yml
vendored
@@ -1,5 +1,9 @@
|
||||
# OpenWebUI 社区统计报告自动生成
|
||||
# 只在统计数据变化时 commit,避免频繁提交
|
||||
# 智能检测:只在有意义的变更时才 commit
|
||||
# - 新增插件 (total_posts)
|
||||
# - 插件版本变更 (version)
|
||||
# - 积分增加 (total_points)
|
||||
# - 粉丝增加 (followers)
|
||||
|
||||
name: Community Stats
|
||||
|
||||
@@ -31,48 +35,95 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install requests python-dotenv
|
||||
|
||||
- name: Get previous stats
|
||||
id: prev_stats
|
||||
|
||||
- name: Capture existing stats (before update)
|
||||
id: old_stats
|
||||
run: |
|
||||
# 获取当前的 points 用于比较
|
||||
if [ -f docs/community-stats.json ]; then
|
||||
OLD_POINTS=$(jq -r '.user.total_points' docs/community-stats.json 2>/dev/null || echo "0")
|
||||
echo "old_points=$OLD_POINTS" >> $GITHUB_OUTPUT
|
||||
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 "old_points=0" >> $GITHUB_OUTPUT
|
||||
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
|
||||
env:
|
||||
OPENWEBUI_API_KEY: ${{ secrets.OPENWEBUI_API_KEY }}
|
||||
OPENWEBUI_USER_ID: ${{ secrets.OPENWEBUI_USER_ID }}
|
||||
run: |
|
||||
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: |
|
||||
# 获取新的 points
|
||||
NEW_POINTS=$(jq -r '.user.total_points' docs/community-stats.json 2>/dev/null || echo "0")
|
||||
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 }}"
|
||||
|
||||
echo "📊 Previous points: ${{ steps.prev_stats.outputs.old_points }}"
|
||||
echo "📊 Current points: $NEW_POINTS"
|
||||
SHOULD_COMMIT="false"
|
||||
CHANGE_REASON=""
|
||||
|
||||
# 只在 points 变化时才 commit
|
||||
if [ "$NEW_POINTS" != "${{ steps.prev_stats.outputs.old_points }}" ]; then
|
||||
echo "changed=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ Points changed (${{ steps.prev_stats.outputs.old_points }} → $NEW_POINTS), will commit"
|
||||
else
|
||||
echo "changed=false" >> $GITHUB_OUTPUT
|
||||
echo "⏭️ Points unchanged, skipping commit"
|
||||
# 检查新增插件
|
||||
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
|
||||
if: steps.check_changes.outputs.changed == 'true'
|
||||
if: steps.check_changes.outputs.should_commit == 'true'
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
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 diff --staged --quiet || git commit -m "chore: update community stats $(date +'%Y-%m-%d')"
|
||||
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 - ${{ steps.check_changes.outputs.change_reason }}"
|
||||
git push
|
||||
|
||||
1
.github/workflows/publish_plugin.yml
vendored
1
.github/workflows/publish_plugin.yml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- 'plugins/**/*.py'
|
||||
- '!plugins/debug/**'
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
58
.github/workflows/release.yml
vendored
58
.github/workflows/release.yml
vendored
@@ -246,6 +246,52 @@ jobs:
|
||||
echo "=== Collected Files ==="
|
||||
find release_plugins -name "*.py" -type f | head -20
|
||||
|
||||
- name: Update plugin icon URLs
|
||||
run: |
|
||||
echo "Updating icon_url in plugins to use absolute GitHub URLs..."
|
||||
# Base URL for raw content using the release tag
|
||||
REPO_URL="https://raw.githubusercontent.com/${{ github.repository }}/${{ steps.version.outputs.version }}"
|
||||
|
||||
find release_plugins -name "*.py" | while read -r file; do
|
||||
# $file is like release_plugins/plugins/actions/infographic/infographic.py
|
||||
# Remove release_plugins/ prefix to get the path in the repo
|
||||
src_file="${file#release_plugins/}"
|
||||
src_dir=$(dirname "$src_file")
|
||||
base_name=$(basename "$src_file" .py)
|
||||
|
||||
# Check if a corresponding png exists in the source repository
|
||||
png_file="${src_dir}/${base_name}.png"
|
||||
|
||||
if [ -f "$png_file" ]; then
|
||||
echo "Found icon for $src_file: $png_file"
|
||||
TARGET_ICON_URL="${REPO_URL}/${png_file}"
|
||||
|
||||
# Use python for safe replacement
|
||||
python3 -c "
|
||||
import sys
|
||||
import re
|
||||
|
||||
file_path = '$file'
|
||||
icon_url = '$TARGET_ICON_URL'
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace icon_url: ... with new url
|
||||
# Matches 'icon_url: ...' and replaces it
|
||||
new_content = re.sub(r'^icon_url:.*$', f'icon_url: {icon_url}', content, flags=re.MULTILINE)
|
||||
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
print(f'Successfully updated icon_url in {file_path}')
|
||||
except Exception as e:
|
||||
print(f'Error updating {file_path}: {e}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Debug Filenames
|
||||
run: |
|
||||
python3 -c "import sys; print(f'Filesystem encoding: {sys.getfilesystemencoding()}')"
|
||||
@@ -278,13 +324,13 @@ jobs:
|
||||
|
||||
- name: Generate release notes
|
||||
id: notes
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
TITLE: ${{ github.event.inputs.release_title }}
|
||||
NOTES: ${{ github.event.inputs.release_notes }}
|
||||
DETECTED_CHANGES: ${{ needs.check-changes.outputs.release_notes }}
|
||||
COMMITS: ${{ steps.commits.outputs.commits }}
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
TITLE="${{ github.event.inputs.release_title }}"
|
||||
NOTES="${{ github.event.inputs.release_notes }}"
|
||||
DETECTED_CHANGES="${{ needs.check-changes.outputs.release_notes }}"
|
||||
COMMITS="${{ steps.commits.outputs.commits }}"
|
||||
|
||||
echo "# ${VERSION} Release / 发布" > release_notes.md
|
||||
echo "" >> release_notes.md
|
||||
|
||||
|
||||
70
README.md
70
README.md
@@ -1,4 +1,7 @@
|
||||
# OpenWebUI Extras
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
English | [中文](./README_CN.md)
|
||||
|
||||
@@ -7,28 +10,28 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
|
||||
<!-- STATS_START -->
|
||||
## 📊 Community Stats
|
||||
|
||||
> 🕐 Auto-updated: 2026-01-12 01:06
|
||||
> 🕐 Auto-updated: 2026-02-09 11:10
|
||||
|
||||
| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |
|
||||
|:---:|:---:|:---:|:---:|
|
||||
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **82** | **86** | **22** |
|
||||
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **210** | **259** | **43** |
|
||||
|
||||
| 📝 Posts | ⬇️ Downloads | 👁️ Views | 👍 Upvotes | 💾 Saves |
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
| **14** | **1161** | **12713** | **76** | **73** |
|
||||
| **20** | **3909** | **45507** | **221** | **264** |
|
||||
|
||||
### 🔥 Top 6 Popular Plugins
|
||||
|
||||
> 🕐 Auto-updated: 2026-01-12 01:06
|
||||
> 🕐 Auto-updated: 2026-02-09 11:10
|
||||
|
||||
| Rank | Plugin | Downloads | Views | Updated |
|
||||
|:---:|------|:---:|:---:|:---:|
|
||||
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 368 | 3352 | 2026-01-07 |
|
||||
| 🥈 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 184 | 590 | 2026-01-07 |
|
||||
| 🥉 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 136 | 1482 | 2026-01-11 |
|
||||
| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 136 | 1510 | 2026-01-11 |
|
||||
| 5️⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 100 | 1833 | 2026-01-07 |
|
||||
| 6️⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 96 | 880 | 2026-01-07 |
|
||||
| Rank | Plugin | Version | Downloads | Views | Updated |
|
||||
|:---:|------|:---:|:---:|:---:|:---:|
|
||||
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.2 | 916 | 8070 | 2026-01-28 |
|
||||
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.5.0 | 665 | 6205 | 2026-01-30 |
|
||||
| 🥉 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.4 | 365 | 2841 | 2026-02-07 |
|
||||
| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 350 | 3580 | 2026-01-28 |
|
||||
| 5️⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 334 | 1569 | 2026-01-29 |
|
||||
| 6️⃣ | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | 1.2.4 | 310 | 4433 | 2026-01-29 |
|
||||
|
||||
*See full stats in [Community Stats Report](./docs/community-stats.md)*
|
||||
<!-- STATS_END -->
|
||||
@@ -40,6 +43,7 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
|
||||
Located in the `plugins/` directory, containing Python-based enhancements:
|
||||
|
||||
#### Actions
|
||||
|
||||
- **Smart Mind Map** (`smart-mind-map`): Generates interactive mind maps from text.
|
||||
- **Smart Infographic** (`infographic`): Transforms text into professional infographics using AntV.
|
||||
- **Flash Card** (`flash-card`): Quickly generates beautiful flashcards for learning.
|
||||
@@ -48,18 +52,18 @@ Located in the `plugins/` directory, containing Python-based enhancements:
|
||||
- **Export to Word** (`export_to_docx`): Exports chat history to Word documents.
|
||||
|
||||
#### Filters
|
||||
|
||||
- **Async Context Compression** (`async-context-compression`): Optimizes token usage via context compression.
|
||||
- **Context Enhancement** (`context_enhancement_filter`): Enhances chat context.
|
||||
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Companion filter for Gemini Manifold.
|
||||
- **Gemini Multimodal Filter** (`web_gemini_multimodel_filter`): Provides multimodal capabilities (PDF, Office, Video) for any model via Gemini.
|
||||
- **Folder Memory** (`folder-memory`): Automatically extracts project rules from conversations and injects them into the folder's system prompt.
|
||||
- **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
|
||||
- **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
|
||||
|
||||
- **MoE Prompt Refiner** (`moe_prompt_refiner`): Refines prompts for Mixture of Experts (MoE) summary requests to generate high-quality comprehensive reports.
|
||||
|
||||
### 🎯 Prompts
|
||||
@@ -69,6 +73,12 @@ Located in the `prompts/` directory, containing fine-tuned System Prompts:
|
||||
- **Coding**: Programming assistance prompts.
|
||||
- **Marketing**: Marketing and copywriting prompts.
|
||||
|
||||
## 🛠️ Extensions
|
||||
|
||||
Standalone frontend extensions to supercharge your Open WebUI:
|
||||
|
||||
- **[Open WebUI Prompt Plus](https://github.com/Fu-Jie/open-webui-prompt-plus)**: An all-in-one prompt management suite featuring AI-powered prompt generation, spotlight-style quick search, and advanced category organization.
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
Located in the `docs/en/` directory:
|
||||
@@ -104,8 +114,34 @@ This project is a collection of resources and does not require a Python environm
|
||||
### Contributing
|
||||
|
||||
If you have great prompts or plugins to share:
|
||||
|
||||
1. Fork this repository.
|
||||
2. Add your files to the appropriate `prompts/` or `plugins/` directory.
|
||||
3. Submit a Pull Request.
|
||||
|
||||
[Contributing](./CONTRIBUTING.md)
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rbb-dev"><img src="https://avatars.githubusercontent.com/u/37469229?v=4?s=100" width="100px;" alt="rbb-dev"/><br /><sub><b>rbb-dev</b></sub></a><br /><a href="#ideas-rbb-dev" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Fu-Jie/awesome-openwebui/commits?author=rbb-dev" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://trade.xyz/?ref=BZ1RJRXWO"><img src="https://avatars.githubusercontent.com/u/7317522?v=4?s=100" width="100px;" alt="Raxxoor"/><br /><sub><b>Raxxoor</b></sub></a><br /><a href="https://github.com/Fu-Jie/awesome-openwebui/issues?q=author%3Adhaern" title="Bug reports">🐛</a> <a href="#ideas-dhaern" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/i-iooi-i"><img src="https://avatars.githubusercontent.com/u/1827701?v=4?s=100" width="100px;" alt="ZOLO"/><br /><sub><b>ZOLO</b></sub></a><br /><a href="https://github.com/Fu-Jie/awesome-openwebui/issues?q=author%3Ai-iooi-i" title="Bug reports">🐛</a> <a href="#ideas-i-iooi-i" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
37
README_CN.md
37
README_CN.md
@@ -7,28 +7,28 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
<!-- STATS_START -->
|
||||
## 📊 社区统计
|
||||
|
||||
> 🕐 自动更新于 2026-01-12 01:06
|
||||
> 🕐 自动更新于 2026-02-09 11:10
|
||||
|
||||
| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |
|
||||
|:---:|:---:|:---:|:---:|
|
||||
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **82** | **86** | **22** |
|
||||
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **210** | **259** | **43** |
|
||||
|
||||
| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
| **14** | **1161** | **12713** | **76** | **73** |
|
||||
| **20** | **3909** | **45507** | **221** | **264** |
|
||||
|
||||
### 🔥 热门插件 Top 6
|
||||
|
||||
> 🕐 自动更新于 2026-01-12 01:06
|
||||
> 🕐 自动更新于 2026-02-09 11:10
|
||||
|
||||
| 排名 | 插件 | 下载 | 浏览 | 更新日期 |
|
||||
|:---:|------|:---:|:---:|:---:|
|
||||
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 368 | 3352 | 2026-01-07 |
|
||||
| 🥈 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 184 | 590 | 2026-01-07 |
|
||||
| 🥉 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 136 | 1482 | 2026-01-11 |
|
||||
| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 136 | 1510 | 2026-01-11 |
|
||||
| 5️⃣ | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | 100 | 1833 | 2026-01-07 |
|
||||
| 6️⃣ | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 96 | 880 | 2026-01-07 |
|
||||
| 排名 | 插件 | 版本 | 下载 | 浏览 | 更新日期 |
|
||||
|:---:|------|:---:|:---:|:---:|:---:|
|
||||
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.2 | 916 | 8070 | 2026-01-28 |
|
||||
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.5.0 | 665 | 6205 | 2026-01-30 |
|
||||
| 🥉 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.4 | 365 | 2841 | 2026-02-07 |
|
||||
| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 350 | 3580 | 2026-01-28 |
|
||||
| 5️⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 334 | 1569 | 2026-01-29 |
|
||||
| 6️⃣ | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | 1.2.4 | 310 | 4433 | 2026-01-29 |
|
||||
|
||||
*完整统计请查看 [社区统计报告](./docs/community-stats.zh.md)*
|
||||
<!-- STATS_END -->
|
||||
@@ -40,6 +40,7 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
位于 `plugins/` 目录,包含各类 Python 编写的功能增强插件:
|
||||
|
||||
#### Actions (交互增强)
|
||||
|
||||
- **Smart Mind Map** (`smart-mind-map`): 智能分析文本并生成交互式思维导图。
|
||||
- **Smart Infographic** (`infographic`): 基于 AntV 的智能信息图生成工具。
|
||||
- **Flash Card** (`flash-card`): 快速生成精美的学习记忆卡片。
|
||||
@@ -48,17 +49,22 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
- **Export to Word** (`export_to_docx`): 将对话内容导出为 Word 文档。
|
||||
|
||||
#### Filters (消息处理)
|
||||
|
||||
- **Async Context Compression** (`async-context-compression`): 异步上下文压缩,优化 Token 使用。
|
||||
- **Context Enhancement** (`context_enhancement_filter`): 上下文增强过滤器。
|
||||
- **Folder Memory** (`folder-memory`): 自动从对话中提取项目规则并注入到文件夹系统提示词中。
|
||||
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Gemini Manifold 配套增强。
|
||||
- **Gemini Multimodal Filter** (`web_gemini_multimodel_filter`): 为任意模型提供多模态能力(PDF、Office、视频等),支持智能路由和字幕精修。
|
||||
- **Markdown Normalizer** (`markdown_normalizer`): 修复 LLM 输出中常见的 Markdown 格式问题。
|
||||
- **Multi-Model Context Merger** (`multi_model_context_merger`): 自动合并并注入多模型回答的上下文。
|
||||
|
||||
#### Pipes (模型管道)
|
||||
|
||||
- **GitHub Copilot SDK** (`github-copilot-sdk`): GitHub Copilot SDK 官方集成。支持动态模型、多轮对话、流式输出、图片输入及无限会话。
|
||||
- **Gemini Manifold** (`gemini_mainfold`): 集成 Gemini 模型的管道。
|
||||
|
||||
#### Pipelines (工作流管道)
|
||||
|
||||
- **MoE Prompt Refiner** (`moe_prompt_refiner`): 优化多模型 (MoE) 汇总请求的提示词,生成高质量的综合报告。
|
||||
|
||||
### 🎯 提示词 (Prompts)
|
||||
@@ -70,6 +76,12 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
|
||||
每个提示词都独立保存为 Markdown 文件,可直接在 OpenWebUI 中使用。
|
||||
|
||||
## 🛠️ 扩展 (Extensions)
|
||||
|
||||
Open WebUI 的前端增强扩展:
|
||||
|
||||
- **[Open WebUI Prompt Plus](https://github.com/Fu-Jie/open-webui-prompt-plus)**: 一站式提示词管理套件,支持 AI 提示词生成、Spotlight 风格快速搜索及高级分类管理。
|
||||
|
||||
## 📖 开发文档
|
||||
|
||||
位于 `docs/zh/` 目录:
|
||||
@@ -106,6 +118,7 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
### 贡献代码
|
||||
|
||||
如果你有优质的提示词或插件想要分享:
|
||||
|
||||
1. Fork 本仓库。
|
||||
2. 将你的文件添加到对应的 `prompts/` 或 `plugins/` 目录。
|
||||
3. 提交 Pull Request。
|
||||
|
||||
53
docs/PLUGIN_README_TEMPLATE.md
Normal file
53
docs/PLUGIN_README_TEMPLATE.md
Normal file
@@ -0,0 +1,53 @@
|
||||
<!--
|
||||
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 only the latest update 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. |
|
||||
|
||||
## ⭐ Support
|
||||
|
||||
If this plugin has been useful, a star on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) is a big motivation for me. Thank you for the support.
|
||||
|
||||
## 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)
|
||||
|
||||
## Changelog
|
||||
|
||||
See the full history on GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
7
docs/badges/downloads.json
Normal file
7
docs/badges/downloads.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "downloads",
|
||||
"message": "3.9k",
|
||||
"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": "210",
|
||||
"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": "20",
|
||||
"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": "259",
|
||||
"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": "221",
|
||||
"color": "brightgreen"
|
||||
}
|
||||
@@ -1,33 +1,82 @@
|
||||
{
|
||||
"total_posts": 14,
|
||||
"total_downloads": 1161,
|
||||
"total_views": 12713,
|
||||
"total_upvotes": 76,
|
||||
"total_posts": 20,
|
||||
"total_downloads": 3909,
|
||||
"total_views": 45507,
|
||||
"total_upvotes": 221,
|
||||
"total_downvotes": 2,
|
||||
"total_saves": 73,
|
||||
"total_comments": 18,
|
||||
"total_saves": 264,
|
||||
"total_comments": 51,
|
||||
"by_type": {
|
||||
"action": 11,
|
||||
"filter": 2,
|
||||
"unknown": 1
|
||||
"pipe": 1,
|
||||
"action": 14,
|
||||
"unknown": 4,
|
||||
"filter": 1
|
||||
},
|
||||
"posts": [
|
||||
{
|
||||
"title": "Smart Mind Map",
|
||||
"slug": "turn_any_text_into_beautiful_mind_maps_3094c59a",
|
||||
"type": "action",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
|
||||
"downloads": 368,
|
||||
"views": 3352,
|
||||
"upvotes": 11,
|
||||
"saves": 22,
|
||||
"comments": 11,
|
||||
"downloads": 916,
|
||||
"views": 8070,
|
||||
"upvotes": 22,
|
||||
"saves": 50,
|
||||
"comments": 13,
|
||||
"created_at": "2025-12-30",
|
||||
"updated_at": "2026-01-07",
|
||||
"updated_at": "2026-01-28",
|
||||
"url": "https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a"
|
||||
},
|
||||
{
|
||||
"title": "Smart Infographic",
|
||||
"slug": "smart_infographic_ad6f0c7f",
|
||||
"type": "action",
|
||||
"version": "1.5.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.",
|
||||
"downloads": 665,
|
||||
"views": 6205,
|
||||
"upvotes": 24,
|
||||
"saves": 34,
|
||||
"comments": 10,
|
||||
"created_at": "2025-12-28",
|
||||
"updated_at": "2026-01-30",
|
||||
"url": "https://openwebui.com/posts/smart_infographic_ad6f0c7f"
|
||||
},
|
||||
{
|
||||
"title": "Export to Word Enhanced",
|
||||
"slug": "export_to_word_enhanced_formatting_fca6a315",
|
||||
"type": "action",
|
||||
"version": "0.4.4",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Export current conversation from Markdown to Word (.docx) with Mermaid diagrams rendered client-side (Mermaid.js, SVG+PNG), LaTeX math, real hyperlinks, improved tables, syntax highlighting, and blockquote support.",
|
||||
"downloads": 365,
|
||||
"views": 2841,
|
||||
"upvotes": 14,
|
||||
"saves": 26,
|
||||
"comments": 3,
|
||||
"created_at": "2026-01-03",
|
||||
"updated_at": "2026-02-07",
|
||||
"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": 350,
|
||||
"views": 3580,
|
||||
"upvotes": 14,
|
||||
"saves": 33,
|
||||
"comments": 0,
|
||||
"created_at": "2025-11-08",
|
||||
"updated_at": "2026-01-28",
|
||||
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
|
||||
},
|
||||
{
|
||||
"title": "Export to Excel",
|
||||
"slug": "export_mulit_table_to_excel_244b8f9d",
|
||||
@@ -35,46 +84,30 @@
|
||||
"version": "0.3.7",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
|
||||
"downloads": 184,
|
||||
"views": 590,
|
||||
"upvotes": 3,
|
||||
"saves": 4,
|
||||
"downloads": 334,
|
||||
"views": 1569,
|
||||
"upvotes": 7,
|
||||
"saves": 6,
|
||||
"comments": 0,
|
||||
"created_at": "2025-05-30",
|
||||
"updated_at": "2026-01-07",
|
||||
"updated_at": "2026-01-29",
|
||||
"url": "https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d"
|
||||
},
|
||||
{
|
||||
"title": "📊 Smart Infographic (AntV)",
|
||||
"slug": "smart_infographic_ad6f0c7f",
|
||||
"title": "Markdown Normalizer",
|
||||
"slug": "markdown_normalizer_baaa8732",
|
||||
"type": "action",
|
||||
"version": "1.4.9",
|
||||
"version": "1.2.4",
|
||||
"author": "Fu-Jie",
|
||||
"description": "AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.",
|
||||
"downloads": 136,
|
||||
"views": 1482,
|
||||
"upvotes": 8,
|
||||
"saves": 9,
|
||||
"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": "filter",
|
||||
"version": "1.1.2",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
|
||||
"downloads": 136,
|
||||
"views": 1510,
|
||||
"upvotes": 6,
|
||||
"saves": 10,
|
||||
"comments": 0,
|
||||
"created_at": "2025-11-08",
|
||||
"updated_at": "2026-01-11",
|
||||
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
|
||||
"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": 310,
|
||||
"views": 4433,
|
||||
"upvotes": 17,
|
||||
"saves": 28,
|
||||
"comments": 5,
|
||||
"created_at": "2026-01-12",
|
||||
"updated_at": "2026-01-29",
|
||||
"url": "https://openwebui.com/posts/markdown_normalizer_baaa8732"
|
||||
},
|
||||
{
|
||||
"title": "Flash Card",
|
||||
@@ -83,62 +116,30 @@
|
||||
"version": "0.2.4",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
|
||||
"downloads": 100,
|
||||
"views": 1833,
|
||||
"upvotes": 8,
|
||||
"saves": 6,
|
||||
"downloads": 217,
|
||||
"views": 3302,
|
||||
"upvotes": 13,
|
||||
"saves": 14,
|
||||
"comments": 2,
|
||||
"created_at": "2025-12-30",
|
||||
"updated_at": "2026-01-07",
|
||||
"updated_at": "2026-01-28",
|
||||
"url": "https://openwebui.com/posts/flash_card_65a2ea8f"
|
||||
},
|
||||
{
|
||||
"title": "Export to Word (Enhanced)",
|
||||
"slug": "export_to_word_enhanced_formatting_fca6a315",
|
||||
"type": "action",
|
||||
"version": "0.4.3",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Export current conversation from Markdown to Word (.docx) with Mermaid diagrams rendered client-side (Mermaid.js, SVG+PNG), LaTeX math, real hyperlinks, improved tables, syntax highlighting, and blockquote support.",
|
||||
"downloads": 96,
|
||||
"views": 880,
|
||||
"upvotes": 6,
|
||||
"saves": 9,
|
||||
"title": "AI Task Instruction Generator",
|
||||
"slug": "ai_task_instruction_generator_9bab8b37",
|
||||
"type": "unknown",
|
||||
"version": "",
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 178,
|
||||
"views": 2475,
|
||||
"upvotes": 8,
|
||||
"saves": 4,
|
||||
"comments": 0,
|
||||
"created_at": "2026-01-03",
|
||||
"updated_at": "2026-01-07",
|
||||
"url": "https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315"
|
||||
},
|
||||
{
|
||||
"title": "📊 智能信息图 (AntV Infographic)",
|
||||
"slug": "智能信息图_e04a48ff",
|
||||
"type": "action",
|
||||
"version": "1.4.9",
|
||||
"author": "Fu-Jie",
|
||||
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
|
||||
"downloads": 35,
|
||||
"views": 511,
|
||||
"upvotes": 3,
|
||||
"saves": 0,
|
||||
"comments": 0,
|
||||
"created_at": "2025-12-28",
|
||||
"updated_at": "2026-01-11",
|
||||
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
|
||||
},
|
||||
{
|
||||
"title": "导出为 Word (增强版)",
|
||||
"slug": "导出为_word_支持公式流程图表格和代码块_8a6306c0",
|
||||
"type": "action",
|
||||
"version": "0.4.3",
|
||||
"author": "Fu-Jie",
|
||||
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
|
||||
"downloads": 35,
|
||||
"views": 998,
|
||||
"upvotes": 9,
|
||||
"saves": 2,
|
||||
"comments": 1,
|
||||
"created_at": "2026-01-04",
|
||||
"updated_at": "2026-01-07",
|
||||
"url": "https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0"
|
||||
"created_at": "2026-01-28",
|
||||
"updated_at": "2026-01-28",
|
||||
"url": "https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37"
|
||||
},
|
||||
{
|
||||
"title": "Deep Dive",
|
||||
@@ -147,31 +148,111 @@
|
||||
"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": 30,
|
||||
"views": 307,
|
||||
"upvotes": 3,
|
||||
"saves": 4,
|
||||
"downloads": 144,
|
||||
"views": 1218,
|
||||
"upvotes": 6,
|
||||
"saves": 11,
|
||||
"comments": 0,
|
||||
"created_at": "2026-01-08",
|
||||
"updated_at": "2026-01-08",
|
||||
"url": "https://openwebui.com/posts/deep_dive_c0b846e4"
|
||||
},
|
||||
{
|
||||
"title": "导出为Word增强版",
|
||||
"slug": "导出为_word_支持公式流程图表格和代码块_8a6306c0",
|
||||
"type": "action",
|
||||
"version": "0.4.4",
|
||||
"author": "Fu-Jie",
|
||||
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
|
||||
"downloads": 122,
|
||||
"views": 2149,
|
||||
"upvotes": 13,
|
||||
"saves": 6,
|
||||
"comments": 4,
|
||||
"created_at": "2026-01-04",
|
||||
"updated_at": "2026-02-07",
|
||||
"url": "https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0"
|
||||
},
|
||||
{
|
||||
"title": "GitHub Copilot Official SDK Pipe",
|
||||
"slug": "github_copilot_official_sdk_pipe_ce96f7b4",
|
||||
"type": "pipe",
|
||||
"version": "0.5.1",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, and frontend debug logging.",
|
||||
"downloads": 71,
|
||||
"views": 2069,
|
||||
"upvotes": 13,
|
||||
"saves": 6,
|
||||
"comments": 4,
|
||||
"created_at": "2026-01-26",
|
||||
"updated_at": "2026-02-08",
|
||||
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
|
||||
},
|
||||
{
|
||||
"title": "智能信息图",
|
||||
"slug": "智能信息图_e04a48ff",
|
||||
"type": "action",
|
||||
"version": "1.5.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
|
||||
"downloads": 58,
|
||||
"views": 1031,
|
||||
"upvotes": 10,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
"created_at": "2025-12-28",
|
||||
"updated_at": "2026-01-29",
|
||||
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
|
||||
},
|
||||
{
|
||||
"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": 58,
|
||||
"views": 1247,
|
||||
"upvotes": 6,
|
||||
"saves": 8,
|
||||
"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": "思维导图",
|
||||
"slug": "智能生成交互式思维导图帮助用户可视化知识_8d4b097b",
|
||||
"type": "action",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"author": "Fu-Jie",
|
||||
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
|
||||
"downloads": 18,
|
||||
"views": 330,
|
||||
"upvotes": 2,
|
||||
"saves": 1,
|
||||
"downloads": 37,
|
||||
"views": 573,
|
||||
"upvotes": 6,
|
||||
"saves": 2,
|
||||
"comments": 0,
|
||||
"created_at": "2025-12-31",
|
||||
"updated_at": "2026-01-07",
|
||||
"updated_at": "2026-01-28",
|
||||
"url": "https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b"
|
||||
},
|
||||
{
|
||||
"title": "异步上下文压缩",
|
||||
"slug": "异步上下文压缩_5c0617cb",
|
||||
"type": "action",
|
||||
"version": "1.2.2",
|
||||
"author": "Fu-Jie",
|
||||
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
|
||||
"downloads": 33,
|
||||
"views": 646,
|
||||
"upvotes": 7,
|
||||
"saves": 5,
|
||||
"comments": 0,
|
||||
"created_at": "2025-11-08",
|
||||
"updated_at": "2026-01-28",
|
||||
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
|
||||
},
|
||||
{
|
||||
"title": "闪记卡 (Flash Card)",
|
||||
"slug": "闪记卡生成插件_4a31eac3",
|
||||
@@ -179,31 +260,15 @@
|
||||
"version": "0.2.4",
|
||||
"author": "Fu-Jie",
|
||||
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
|
||||
"downloads": 12,
|
||||
"views": 361,
|
||||
"upvotes": 4,
|
||||
"downloads": 27,
|
||||
"views": 670,
|
||||
"upvotes": 8,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
"created_at": "2025-12-30",
|
||||
"updated_at": "2026-01-07",
|
||||
"updated_at": "2026-01-28",
|
||||
"url": "https://openwebui.com/posts/闪记卡生成插件_4a31eac3"
|
||||
},
|
||||
{
|
||||
"title": "异步上下文压缩",
|
||||
"slug": "异步上下文压缩_5c0617cb",
|
||||
"type": "filter",
|
||||
"version": "1.1.2",
|
||||
"author": "Fu-Jie",
|
||||
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
|
||||
"downloads": 8,
|
||||
"views": 211,
|
||||
"upvotes": 4,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
"created_at": "2025-11-08",
|
||||
"updated_at": "2026-01-11",
|
||||
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
|
||||
},
|
||||
{
|
||||
"title": "精读",
|
||||
"slug": "精读_99830b0f",
|
||||
@@ -211,15 +276,47 @@
|
||||
"version": "1.0.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
|
||||
"downloads": 3,
|
||||
"views": 112,
|
||||
"upvotes": 2,
|
||||
"downloads": 24,
|
||||
"views": 419,
|
||||
"upvotes": 5,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
"created_at": "2026-01-08",
|
||||
"updated_at": "2026-01-08",
|
||||
"url": "https://openwebui.com/posts/精读_99830b0f"
|
||||
},
|
||||
{
|
||||
"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": 1460,
|
||||
"upvotes": 12,
|
||||
"saves": 18,
|
||||
"comments": 8,
|
||||
"created_at": "2026-01-25",
|
||||
"updated_at": "2026-01-28",
|
||||
"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": 139,
|
||||
"upvotes": 2,
|
||||
"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",
|
||||
"slug": "debug_open_webui_plugins_in_your_browser_81bf7960",
|
||||
@@ -228,9 +325,9 @@
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 236,
|
||||
"upvotes": 7,
|
||||
"saves": 3,
|
||||
"views": 1411,
|
||||
"upvotes": 14,
|
||||
"saves": 10,
|
||||
"comments": 2,
|
||||
"created_at": "2026-01-10",
|
||||
"updated_at": "2026-01-10",
|
||||
@@ -242,11 +339,11 @@
|
||||
"name": "Fu-Jie",
|
||||
"profile_url": "https://openwebui.com/u/Fu-Jie",
|
||||
"profile_image": "https://community.s3.openwebui.com/uploads/users/b15d1348-4347-42b4-b815-e053342d6cb0/profile_d9510745-4bd4-4f8f-a997-4a21847d9300.webp",
|
||||
"followers": 82,
|
||||
"following": 2,
|
||||
"total_points": 86,
|
||||
"post_points": 74,
|
||||
"comment_points": 12,
|
||||
"contributions": 22
|
||||
"followers": 210,
|
||||
"following": 4,
|
||||
"total_points": 259,
|
||||
"post_points": 219,
|
||||
"comment_points": 40,
|
||||
"contributions": 43
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,46 @@
|
||||
# 📊 OpenWebUI Community Stats Report
|
||||
|
||||
> 📅 Updated: 2026-01-12 01:06
|
||||
> 📅 Updated: 2026-02-09 11:10
|
||||
|
||||
## 📈 Overview
|
||||
|
||||
| Metric | Value |
|
||||
|------|------|
|
||||
| 📝 Total Posts | 14 |
|
||||
| ⬇️ Total Downloads | 1161 |
|
||||
| 👁️ Total Views | 12713 |
|
||||
| 👍 Total Upvotes | 76 |
|
||||
| 💾 Total Saves | 73 |
|
||||
| 💬 Total Comments | 18 |
|
||||
| 📝 Total Posts | 20 |
|
||||
| ⬇️ Total Downloads | 3909 |
|
||||
| 👁️ Total Views | 45507 |
|
||||
| 👍 Total Upvotes | 221 |
|
||||
| 💾 Total Saves | 264 |
|
||||
| 💬 Total Comments | 51 |
|
||||
|
||||
## 📂 By Type
|
||||
|
||||
- **action**: 11
|
||||
- **filter**: 2
|
||||
- **unknown**: 1
|
||||
- **pipe**: 1
|
||||
- **action**: 14
|
||||
- **unknown**: 4
|
||||
- **filter**: 1
|
||||
|
||||
## 📋 Posts List
|
||||
|
||||
| Rank | Title | Type | Version | Downloads | Views | Upvotes | Saves | Updated |
|
||||
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 368 | 3352 | 11 | 22 | 2026-01-07 |
|
||||
| 2 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 184 | 590 | 3 | 4 | 2026-01-07 |
|
||||
| 3 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 136 | 1482 | 8 | 9 | 2026-01-11 |
|
||||
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | 1.1.2 | 136 | 1510 | 6 | 10 | 2026-01-11 |
|
||||
| 5 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 100 | 1833 | 8 | 6 | 2026-01-07 |
|
||||
| 6 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 96 | 880 | 6 | 9 | 2026-01-07 |
|
||||
| 7 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 35 | 511 | 3 | 0 | 2026-01-11 |
|
||||
| 8 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 35 | 998 | 9 | 2 | 2026-01-07 |
|
||||
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 30 | 307 | 3 | 4 | 2026-01-08 |
|
||||
| 10 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 18 | 330 | 2 | 1 | 2026-01-07 |
|
||||
| 11 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 12 | 361 | 4 | 1 | 2026-01-07 |
|
||||
| 12 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | filter | 1.1.2 | 8 | 211 | 4 | 1 | 2026-01-11 |
|
||||
| 13 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 3 | 112 | 2 | 1 | 2026-01-08 |
|
||||
| 14 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 236 | 7 | 3 | 2026-01-10 |
|
||||
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.2 | 916 | 8070 | 22 | 50 | 2026-01-28 |
|
||||
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.5.0 | 665 | 6205 | 24 | 34 | 2026-01-30 |
|
||||
| 3 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.4 | 365 | 2841 | 14 | 26 | 2026-02-07 |
|
||||
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 350 | 3580 | 14 | 33 | 2026-01-28 |
|
||||
| 5 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 334 | 1569 | 7 | 6 | 2026-01-29 |
|
||||
| 6 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 310 | 4433 | 17 | 28 | 2026-01-29 |
|
||||
| 7 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 217 | 3302 | 13 | 14 | 2026-01-28 |
|
||||
| 8 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | unknown | | 178 | 2475 | 8 | 4 | 2026-01-28 |
|
||||
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 144 | 1218 | 6 | 11 | 2026-01-08 |
|
||||
| 10 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.4 | 122 | 2149 | 13 | 6 | 2026-02-07 |
|
||||
| 11 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | 0.5.1 | 71 | 2069 | 13 | 6 | 2026-02-08 |
|
||||
| 12 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.5.0 | 58 | 1031 | 10 | 1 | 2026-01-29 |
|
||||
| 13 | [📂 Folder Memory – Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 58 | 1247 | 6 | 8 | 2026-01-20 |
|
||||
| 14 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.2 | 37 | 573 | 6 | 2 | 2026-01-28 |
|
||||
| 15 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 33 | 646 | 7 | 5 | 2026-01-28 |
|
||||
| 16 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 27 | 670 | 8 | 1 | 2026-01-28 |
|
||||
| 17 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 24 | 419 | 5 | 1 | 2026-01-08 |
|
||||
| 18 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 1460 | 12 | 18 | 2026-01-28 |
|
||||
| 19 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 139 | 2 | 0 | 2026-01-14 |
|
||||
| 20 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1411 | 14 | 10 | 2026-01-10 |
|
||||
|
||||
@@ -1,39 +1,46 @@
|
||||
# 📊 OpenWebUI 社区统计报告
|
||||
|
||||
> 📅 更新时间: 2026-01-12 01:06
|
||||
> 📅 更新时间: 2026-02-09 11:10
|
||||
|
||||
## 📈 总览
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| 📝 发布数量 | 14 |
|
||||
| ⬇️ 总下载量 | 1161 |
|
||||
| 👁️ 总浏览量 | 12713 |
|
||||
| 👍 总点赞数 | 76 |
|
||||
| 💾 总收藏数 | 73 |
|
||||
| 💬 总评论数 | 18 |
|
||||
| 📝 发布数量 | 20 |
|
||||
| ⬇️ 总下载量 | 3909 |
|
||||
| 👁️ 总浏览量 | 45507 |
|
||||
| 👍 总点赞数 | 221 |
|
||||
| 💾 总收藏数 | 264 |
|
||||
| 💬 总评论数 | 51 |
|
||||
|
||||
## 📂 按类型分类
|
||||
|
||||
- **action**: 11
|
||||
- **filter**: 2
|
||||
- **unknown**: 1
|
||||
- **pipe**: 1
|
||||
- **action**: 14
|
||||
- **unknown**: 4
|
||||
- **filter**: 1
|
||||
|
||||
## 📋 发布列表
|
||||
|
||||
| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |
|
||||
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.1 | 368 | 3352 | 11 | 22 | 2026-01-07 |
|
||||
| 2 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 184 | 590 | 3 | 4 | 2026-01-07 |
|
||||
| 3 | [📊 Smart Infographic (AntV)](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.4.9 | 136 | 1482 | 8 | 9 | 2026-01-11 |
|
||||
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | 1.1.2 | 136 | 1510 | 6 | 10 | 2026-01-11 |
|
||||
| 5 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 100 | 1833 | 8 | 6 | 2026-01-07 |
|
||||
| 6 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 96 | 880 | 6 | 9 | 2026-01-07 |
|
||||
| 7 | [📊 智能信息图 (AntV Infographic)](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.4.9 | 35 | 511 | 3 | 0 | 2026-01-11 |
|
||||
| 8 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 35 | 998 | 9 | 2 | 2026-01-07 |
|
||||
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 30 | 307 | 3 | 4 | 2026-01-08 |
|
||||
| 10 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.1 | 18 | 330 | 2 | 1 | 2026-01-07 |
|
||||
| 11 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 12 | 361 | 4 | 1 | 2026-01-07 |
|
||||
| 12 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | filter | 1.1.2 | 8 | 211 | 4 | 1 | 2026-01-11 |
|
||||
| 13 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 3 | 112 | 2 | 1 | 2026-01-08 |
|
||||
| 14 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 236 | 7 | 3 | 2026-01-10 |
|
||||
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.2 | 916 | 8070 | 22 | 50 | 2026-01-28 |
|
||||
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.5.0 | 665 | 6205 | 24 | 34 | 2026-01-30 |
|
||||
| 3 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.4 | 365 | 2841 | 14 | 26 | 2026-02-07 |
|
||||
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 350 | 3580 | 14 | 33 | 2026-01-28 |
|
||||
| 5 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 334 | 1569 | 7 | 6 | 2026-01-29 |
|
||||
| 6 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 310 | 4433 | 17 | 28 | 2026-01-29 |
|
||||
| 7 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 217 | 3302 | 13 | 14 | 2026-01-28 |
|
||||
| 8 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | unknown | | 178 | 2475 | 8 | 4 | 2026-01-28 |
|
||||
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 144 | 1218 | 6 | 11 | 2026-01-08 |
|
||||
| 10 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.4 | 122 | 2149 | 13 | 6 | 2026-02-07 |
|
||||
| 11 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | 0.5.1 | 71 | 2069 | 13 | 6 | 2026-02-08 |
|
||||
| 12 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.5.0 | 58 | 1031 | 10 | 1 | 2026-01-29 |
|
||||
| 13 | [📂 Folder Memory – Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 58 | 1247 | 6 | 8 | 2026-01-20 |
|
||||
| 14 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.2 | 37 | 573 | 6 | 2 | 2026-01-28 |
|
||||
| 15 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 33 | 646 | 7 | 5 | 2026-01-28 |
|
||||
| 16 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 27 | 670 | 8 | 1 | 2026-01-28 |
|
||||
| 17 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 24 | 419 | 5 | 1 | 2026-01-08 |
|
||||
| 18 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 1460 | 12 | 18 | 2026-01-28 |
|
||||
| 19 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 139 | 2 | 0 | 2026-01-14 |
|
||||
| 20 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1411 | 14 | 10 | 2026-01-10 |
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
## 📚 Table of Contents
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
---
|
||||
@@ -351,8 +351,7 @@ async def action(self, body, __event_call__, __metadata__, ...):
|
||||
|
||||
#### Reference Implementations
|
||||
|
||||
- `plugins/actions/js-render-poc/infographic_markdown.py` - AntV Infographic + Data URL
|
||||
- `plugins/actions/js-render-poc/js_render_poc.py` - Basic proof of concept
|
||||
- `plugins/actions/infographic/infographic.py` - Production-ready implementation using AntV + Data URL
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
|
||||
## 📚 目录
|
||||
|
||||
1. [插件开发快速入门](#1-插件开发快速入门)
|
||||
2. [核心概念与 SDK 详解](#2-核心概念与-sdk-详解)
|
||||
3. [插件类型深度解析](#3-插件类型深度解析)
|
||||
* [Action (动作)](#31-action-动作)
|
||||
* [Filter (过滤器)](#32-filter-过滤器)
|
||||
* [Pipe (管道)](#33-pipe-管道)
|
||||
4. [高级开发模式](#4-高级开发模式)
|
||||
5. [最佳实践与设计原则](#5-最佳实践与设计原则)
|
||||
6. [故障排查](#6-故障排查)
|
||||
1. [插件开发快速入门](#1-quick-start)
|
||||
2. [核心概念与 SDK 详解](#2-core-concepts-sdk-details)
|
||||
3. [插件类型深度解析](#3-plugin-types)
|
||||
* [Action (动作)](#31-action)
|
||||
* [Filter (过滤器)](#32-filter)
|
||||
* [Pipe (管道)](#33-pipe)
|
||||
4. [高级开发模式](#4-advanced-patterns)
|
||||
5. [最佳实践与设计原则](#5-best-practices)
|
||||
6. [故障排查](#6-troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## 1. 插件开发快速入门
|
||||
## 1. 插件开发快速入门 {: #1-quick-start }
|
||||
|
||||
### 1.1 什么是 OpenWebUI 插件?
|
||||
|
||||
@@ -64,7 +64,7 @@ class Action:
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心概念与 SDK 详解
|
||||
## 2. 核心概念与 SDK 详解 {: #2-core-concepts-sdk-details }
|
||||
|
||||
### 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}})
|
||||
```
|
||||
|
||||
### 3.2 Filter (过滤器)
|
||||
### 3.2 Filter (过滤器) {: #32-filter }
|
||||
|
||||
**定位**:中间件,拦截并修改请求/响应。
|
||||
|
||||
@@ -155,7 +155,7 @@ async def inlet(self, body, __metadata__):
|
||||
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 协同
|
||||
利用 `__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/js-render-poc/js_render_poc.py` - 基础概念验证
|
||||
- `plugins/actions/infographic/infographic.py` - 基于 AntV + Data URL 的生产级实现
|
||||
|
||||
## 5. 最佳实践与设计原则
|
||||
## 5. 最佳实践与设计原则 {: #5-best-practices }
|
||||
|
||||
### 5.1 命名与定位
|
||||
* **简短有力**:如 "闪记卡", "精读"。避免 "文本分析助手" 这种泛词。
|
||||
@@ -344,7 +343,7 @@ except Exception as e:
|
||||
|
||||
---
|
||||
|
||||
## 6. 故障排查
|
||||
## 6. 故障排查 {: #6-troubleshooting }
|
||||
|
||||
* **HTML 不显示?** 确保包裹在 ` ```html ... ``` ` 代码块中。
|
||||
* **数据库报错?** 检查是否在 `async` 函数中直接调用了同步的 DB 方法,请使用 `asyncio.to_thread`。
|
||||
|
||||
22
docs/extensions/index.md
Normal file
22
docs/extensions/index.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Extensions
|
||||
---
|
||||
|
||||
# Extensions
|
||||
|
||||
Standalone frontend extensions to supercharge your Open WebUI.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Open WebUI Prompt Plus
|
||||
|
||||
**An all-in-one prompt management suite for power users.**
|
||||
|
||||
[Open WebUI Prompt Plus](https://github.com/Fu-Jie/open-webui-prompt-plus) elevates your experience with:
|
||||
|
||||
- **🤖 AI-Powered Prompt Generator**: Turn natural language into structured prompts.
|
||||
- **⚡ Quick Insert Panel**: Spotlight-style search (`Cmd/Ctrl + Shift + P`).
|
||||
- **📂 Advanced Management**: Dynamic categories, favorites, and usage stats.
|
||||
- **📝 Native Variable Support**: Visual form rendering for template variables.
|
||||
|
||||
[:fontawesome-brands-github: View on GitHub](https://github.com/Fu-Jie/open-webui-prompt-plus){ .md-button .md-button--primary }
|
||||
22
docs/extensions/index.zh.md
Normal file
22
docs/extensions/index.zh.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: 扩展 (Extensions)
|
||||
---
|
||||
|
||||
# 扩展 (Extensions)
|
||||
|
||||
Open WebUI 的独立前端增强扩展。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Open WebUI Prompt Plus
|
||||
|
||||
**专为高级用户打造的一站式提示词管理套件。**
|
||||
|
||||
[Open WebUI Prompt Plus](https://github.com/Fu-Jie/open-webui-prompt-plus) 通过以下功能提升您的体验:
|
||||
|
||||
- **🤖 AI 提示词生成器**: 将自然语言转化为结构化提示词。
|
||||
- **⚡ 快速插入面板**: Spotlight 风格的快速搜索 (`Cmd/Ctrl + Shift + P`)。
|
||||
- **📂 高级管理**: 动态分类、收藏夹及使用统计。
|
||||
- **📝 原生变量支持**: 模板变量的可视化表单渲染。
|
||||
|
||||
[:fontawesome-brands-github: 查看 GitHub](https://github.com/Fu-Jie/open-webui-prompt-plus){ .md-button .md-button--primary }
|
||||
@@ -349,6 +349,53 @@ await __event_emitter__(
|
||||
)
|
||||
```
|
||||
|
||||
#### Advanced Use Case: Retrieving Frontend Data
|
||||
|
||||
One of the most powerful capabilities of the `execute` event type is the ability to fetch data from the browser environment (JavaScript) and return it to your Python backend. This allows plugins to access information like:
|
||||
|
||||
- `localStorage` items (user preferences, tokens)
|
||||
- `navigator` properties (language, geolocation, platform)
|
||||
- `document` properties (cookies, URL parameters)
|
||||
|
||||
**How it works:**
|
||||
The JavaScript code you provide in the `"code"` field is executed in the browser. If your JS code includes a `return` statement, that value is sent back to Python as the result of `await __event_call__`.
|
||||
|
||||
**Example: Getting the User's UI Language**
|
||||
|
||||
```python
|
||||
try:
|
||||
# Execute JS on the frontend to get language settings
|
||||
response = await __event_call__(
|
||||
{
|
||||
"type": "execute",
|
||||
"data": {
|
||||
# This JS code runs in the browser.
|
||||
# The 'return' value is sent back to Python.
|
||||
"code": """
|
||||
return (
|
||||
localStorage.getItem('locale') ||
|
||||
localStorage.getItem('language') ||
|
||||
navigator.language ||
|
||||
'en-US'
|
||||
);
|
||||
""",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# 'response' will contain the string returned by JS (e.g., "en-US", "zh-CN")
|
||||
# Note: Wrap in try-except to handle potential timeouts or JS errors
|
||||
logger.info(f"Frontend Language: {response}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get frontend data: {e}")
|
||||
```
|
||||
|
||||
**Key capabilities unlocked:**
|
||||
- **Context Awareness:** Adapt responses based on user time zone or language.
|
||||
- **Client-Side Storage:** Use `localStorage` to persist simple plugin settings without a database.
|
||||
- **Hardware Access:** Request geolocation or clipboard access (requires user permission).
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ When & Where to Use Events
|
||||
@@ -421,4 +468,4 @@ Refer to this document for common event types and structures, and explore Open W
|
||||
|
||||
---
|
||||
|
||||
**Happy event-driven coding in Open WebUI! 🚀**
|
||||
**Happy event-driven coding in Open WebUI! 🚀**
|
||||
|
||||
@@ -25,7 +25,7 @@ hide:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-puzzle:{ .lg .middle } **Plugin Center**
|
||||
- :material-puzzle:{ .lg .middle } **Plugin Center**
|
||||
|
||||
---
|
||||
|
||||
@@ -33,7 +33,7 @@ hide:
|
||||
|
||||
[:octicons-arrow-right-24: Explore Plugins](plugins/index.md)
|
||||
|
||||
- :material-message-text:{ .lg .middle } **Prompt Library**
|
||||
- :material-message-text:{ .lg .middle } **Prompt Library**
|
||||
|
||||
---
|
||||
|
||||
@@ -41,7 +41,7 @@ hide:
|
||||
|
||||
[:octicons-arrow-right-24: Browse Prompts](prompts/index.md)
|
||||
|
||||
- :material-tools:{ .lg .middle } **Enhancements**
|
||||
- :material-tools:{ .lg .middle } **Enhancements**
|
||||
|
||||
---
|
||||
|
||||
@@ -49,7 +49,15 @@ hide:
|
||||
|
||||
[:octicons-arrow-right-24: View Guides](enhancements/index.md)
|
||||
|
||||
- :material-book-open-page-variant:{ .lg .middle } **Development**
|
||||
- :material-rocket-launch:{ .lg .middle } **Extensions**
|
||||
|
||||
---
|
||||
|
||||
Standalone frontend extensions to supercharge your Open WebUI interface.
|
||||
|
||||
[:octicons-arrow-right-24: Browse Extensions](extensions/index.md)
|
||||
|
||||
- :material-book-open-page-variant:{ .lg .middle } **Development**
|
||||
|
||||
---
|
||||
|
||||
@@ -65,7 +73,7 @@ hide:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-brain:{ .lg .middle } **Smart Mind Map**
|
||||
- :material-brain:{ .lg .middle } **Smart Mind Map**
|
||||
|
||||
---
|
||||
|
||||
@@ -73,15 +81,15 @@ hide:
|
||||
|
||||
[: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**
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ hide:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-puzzle:{ .lg .middle } **插件中心**
|
||||
- :material-puzzle:{ .lg .middle } **插件中心**
|
||||
|
||||
---
|
||||
|
||||
@@ -33,7 +33,7 @@ hide:
|
||||
|
||||
[:octicons-arrow-right-24: 探索插件](plugins/index.md)
|
||||
|
||||
- :material-message-text:{ .lg .middle } **提示词库**
|
||||
- :material-message-text:{ .lg .middle } **提示词库**
|
||||
|
||||
---
|
||||
|
||||
@@ -41,7 +41,7 @@ hide:
|
||||
|
||||
[:octicons-arrow-right-24: 浏览提示词](prompts/index.md)
|
||||
|
||||
- :material-tools:{ .lg .middle } **增强功能**
|
||||
- :material-tools:{ .lg .middle } **增强功能**
|
||||
|
||||
---
|
||||
|
||||
@@ -49,7 +49,15 @@ hide:
|
||||
|
||||
[:octicons-arrow-right-24: 查看指南](enhancements/index.md)
|
||||
|
||||
- :material-book-open-page-variant:{ .lg .middle } **开发指南**
|
||||
- :material-rocket-launch:{ .lg .middle } **扩展 (Extensions)**
|
||||
|
||||
---
|
||||
|
||||
独立的 OpenWebUI 前端增强扩展,全面提升交互体验。
|
||||
|
||||
[:octicons-arrow-right-24: 浏览扩展](extensions/index.md)
|
||||
|
||||
- :material-book-open-page-variant:{ .lg .middle } **开发指南**
|
||||
|
||||
---
|
||||
|
||||
@@ -65,7 +73,7 @@ hide:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-brain:{ .lg .middle } **智能思维导图**
|
||||
- :material-brain:{ .lg .middle } **智能思维导图**
|
||||
|
||||
---
|
||||
|
||||
@@ -73,15 +81,15 @@ hide:
|
||||
|
||||
[: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,7 +1,7 @@
|
||||
# Export to Word
|
||||
|
||||
<span class="category-badge action">Action</span>
|
||||
<span class="version-badge">v0.4.3</span>
|
||||
<span class="version-badge">v0.4.4</span>
|
||||
|
||||
Export conversation to Word (.docx) with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.
|
||||
|
||||
@@ -53,11 +53,17 @@ You can configure the following settings via the **Valves** button in the plugin
|
||||
| `MATH_ENABLE` | Enable LaTeX math block conversion. | `True` |
|
||||
| `MATH_INLINE_DOLLAR_ENABLE` | Enable inline `$ ... $` math conversion. | `True` |
|
||||
|
||||
## 🔥 What's New in v0.4.3
|
||||
## 🔥 What's New in v0.4.4
|
||||
|
||||
- 🧹 **Content Cleanup**: Enhanced stripping of `<details>` blocks (often used for tool calls/thinking process) to ensure a clean final document.
|
||||
- 📄 **Standard Document Formatting**: Applied professional document formatting standards for titles and headings (centered title, bold, optimized font sizes and spacing), including GB/T compliance for Chinese content.
|
||||
- 🔠 **Font Rendering Fix**: Fixed an issue where CJK characters would fallback to MS Gothic in Word; now correctly uses the configured Asian font (e.g., SimSun).
|
||||
- ⚙️ **Title Alignment**: Added `TITLE_ALIGNMENT` valve to configure document title alignment (left, center, right).
|
||||
|
||||
### User-Level Configuration (UserValves)
|
||||
|
||||
Users can override the following settings in their personal settings:
|
||||
|
||||
- `TITLE_SOURCE`
|
||||
- `UI_LANGUAGE`
|
||||
- `FONT_LATIN`, `FONT_ASIAN`, `FONT_CODE`
|
||||
@@ -120,4 +126,4 @@ Users can override the following settings in their personal settings:
|
||||
## Source Code
|
||||
|
||||
[:fontawesome-brands-github: View on GitHub](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/export_to_docx){ .md-button }
|
||||
**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) | **Version:** 0.4.4 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Export to Word(导出为 Word)
|
||||
|
||||
<span class="category-badge action">Action</span>
|
||||
<span class="version-badge">v0.4.3</span>
|
||||
<span class="version-badge">v0.4.4</span>
|
||||
|
||||
将当前对话导出为完美格式的 Word 文档,支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用资料**以及**增强表格**渲染。
|
||||
|
||||
@@ -53,9 +53,17 @@ Export to Word 插件会把聊天消息从 Markdown 转成精致的 Word 文档
|
||||
| `启用数学公式` | 启用 LaTeX 数学公式块转换。 | `True` |
|
||||
| `启用行内公式` | 启用行内 `$ ... $` 数学公式转换。 | `True` |
|
||||
|
||||
## 🔥 v0.4.4 更新内容
|
||||
|
||||
- 🧹 **内容清理加强**: 增强了对 `<details>` 块(通常包含工具调用或思考过程)的清理,确保最终文档整洁。
|
||||
- 📄 **文档格式标准化**: 采用了专业的文档排版标准(兼容中文 GB/T 规范),标题居中加粗,各级标题使用标准字号和间距。
|
||||
- 🔠 **字体渲染修复**: 修复了 CJK 字符在 Word 中回退到 MS Gothic 的问题;现在正确使用配置的中文字体(例如宋体)。
|
||||
- ⚙️ **标题对齐配置**: 新增 `标题对齐方式` Valve,支持配置文档标题的对齐方式(左对齐、居中、右对齐)。
|
||||
|
||||
### 用户级配置 (UserValves)
|
||||
|
||||
用户可以在个人设置中覆盖以下配置:
|
||||
|
||||
- `文档标题来源`
|
||||
- `界面语言`
|
||||
- `英文字体`, `中文字体`, `代码字体`
|
||||
@@ -117,4 +125,5 @@ Export to Word 插件会把聊天消息从 Markdown 转成精致的 Word 文档
|
||||
|
||||
## 源码
|
||||
|
||||
[:fontawes**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)/tree/main/plugins/actions/export_to_docx){ .md-button }
|
||||
[:fontawesome-brands-github: View on GitHub](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/export_to_docx){ .md-button }
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.4.4 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Knowledge Card
|
||||
# Flash Card
|
||||
|
||||
<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
|
||||
|
||||
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**
|
||||
3. Enable the plugin
|
||||
|
||||
@@ -85,4 +85,4 @@ The Knowledge Card plugin (also known as Flash Card / 闪记卡) transforms cont
|
||||
|
||||
## 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="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**
|
||||
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 }
|
||||
@@ -17,17 +17,17 @@ Actions are interactive plugins that:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-brain:{ .lg .middle } **Smart Mind Map**
|
||||
- :material-brain:{ .lg .middle } **Smart Mind Map**
|
||||
|
||||
---
|
||||
|
||||
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)
|
||||
|
||||
- :material-chart-bar:{ .lg .middle } **Smart Infographic**
|
||||
- :material-chart-bar:{ .lg .middle } **Smart Infographic**
|
||||
|
||||
---
|
||||
|
||||
@@ -37,17 +37,17 @@ Actions are interactive plugins that:
|
||||
|
||||
[: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**
|
||||
|
||||
---
|
||||
|
||||
@@ -57,17 +57,17 @@ Actions are interactive plugins that:
|
||||
|
||||
[:octicons-arrow-right-24: Documentation](export-to-excel.md)
|
||||
|
||||
- :material-file-word-box:{ .lg .middle } **Export to Word (Enhanced Formatting)**
|
||||
- :material-file-word-box:{ .lg .middle } **Export to Word (Enhanced Formatting)**
|
||||
|
||||
---
|
||||
|
||||
Export the current conversation to a formatted Word doc with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.
|
||||
|
||||
**Version:** 0.4.2
|
||||
|
||||
**Version:** 0.4.4
|
||||
|
||||
[:octicons-arrow-right-24: Documentation](export-to-word.md)
|
||||
|
||||
- :material-brain:{ .lg .middle } **Deep Dive**
|
||||
- :material-brain:{ .lg .middle } **Deep Dive**
|
||||
|
||||
---
|
||||
|
||||
@@ -77,16 +77,6 @@ Actions are interactive plugins that:
|
||||
|
||||
[: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>
|
||||
|
||||
---
|
||||
|
||||
@@ -17,7 +17,7 @@ Actions 是交互式插件,能够:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-brain:{ .lg .middle } **Smart Mind Map**
|
||||
- :material-brain:{ .lg .middle } **Smart Mind Map**
|
||||
|
||||
---
|
||||
|
||||
@@ -27,7 +27,7 @@ Actions 是交互式插件,能够:
|
||||
|
||||
[:octicons-arrow-right-24: 查看文档](smart-mind-map.md)
|
||||
|
||||
- :material-chart-bar:{ .lg .middle } **Smart Infographic**
|
||||
- :material-chart-bar:{ .lg .middle } **Smart Infographic**
|
||||
|
||||
---
|
||||
|
||||
@@ -37,17 +37,17 @@ Actions 是交互式插件,能够:
|
||||
|
||||
[: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**
|
||||
|
||||
---
|
||||
|
||||
@@ -57,17 +57,17 @@ Actions 是交互式插件,能够:
|
||||
|
||||
[:octicons-arrow-right-24: 查看文档](export-to-excel.md)
|
||||
|
||||
- :material-file-word-box:{ .lg .middle } **Word 导出 (格式增强)**
|
||||
- :material-file-word-box:{ .lg .middle } **Word 导出 (格式增强)**
|
||||
|
||||
---
|
||||
|
||||
将当前对话导出为完美格式的 Word 文档,支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用资料**以及**增强表格**渲染。
|
||||
|
||||
**版本:** 0.4.2
|
||||
**版本:** 0.4.4
|
||||
|
||||
[:octicons-arrow-right-24: 查看文档](export-to-word.md)
|
||||
|
||||
- :material-brain:{ .lg .middle } **精读 (Deep Dive)**
|
||||
- :material-brain:{ .lg .middle } **精读 (Deep Dive)**
|
||||
|
||||
---
|
||||
|
||||
@@ -77,16 +77,6 @@ Actions 是交互式插件,能够:
|
||||
|
||||
[: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>
|
||||
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Smart Mind Map
|
||||
|
||||
<span class="category-badge action">Action</span>
|
||||
<span class="version-badge">v0.8.0</span>
|
||||
<span class="version-badge">v0.9.2</span>
|
||||
|
||||
Intelligently analyzes text content and generates interactive mind maps for better visualization and understanding.
|
||||
|
||||
@@ -17,13 +17,13 @@ The Smart Mind Map plugin transforms text content into beautiful, interactive mi
|
||||
- :material-gesture-swipe: **Rich Controls**: Zoom, reset view, expand level selector (All/2/3) and fullscreen
|
||||
- :material-palette: **Theme Aware**: Auto-detects OpenWebUI light/dark theme with manual toggle
|
||||
- :material-download: **One-Click Export**: Download high-res PNG, copy SVG, or copy Markdown source
|
||||
- :material-translate: **Multi-language**: Adapts output language to the user context
|
||||
- :material-translate: **Multi-language**: Matches output language to the input text
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
3. Enable the plugin, and optionally allow iframe same-origin access so theme auto-detection works
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Smart Mind Map(智能思维导图)
|
||||
|
||||
<span class="category-badge action">Action</span>
|
||||
<span class="version-badge">v0.8.0</span>
|
||||
<span class="version-badge">v0.9.2</span>
|
||||
|
||||
智能分析文本内容,生成交互式思维导图,帮助你更直观地理解信息结构。
|
||||
|
||||
@@ -17,13 +17,13 @@ Smart Mind Map 会将文本转换成漂亮的交互式思维导图。插件会
|
||||
- :material-gesture-swipe: **丰富控制**:缩放/重置、展开层级(全部/2/3 级)与全屏
|
||||
- :material-palette: **主题感知**:自动检测 OpenWebUI 亮/暗色主题并支持手动切换
|
||||
- :material-download: **一键导出**:下载高分辨率 PNG、复制 SVG 或 Markdown
|
||||
- :material-translate: **多语言**:根据用户语言自动输出
|
||||
- :material-translate: **多语言**:输出语言与输入文本一致
|
||||
|
||||
---
|
||||
|
||||
## 安装
|
||||
|
||||
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)
|
||||
3. 启用插件,并可在设置中允许 iframe same-origin 以启用主题自动检测
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Async Context Compression
|
||||
|
||||
<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.
|
||||
|
||||
@@ -34,6 +34,12 @@ This is especially useful for:
|
||||
- :material-check-all: **Open WebUI v0.7.x Compatibility**: Dynamic DB session handling
|
||||
- :material-account-convert: **Improved Compatibility**: Summary role changed to `assistant`
|
||||
- :material-shield-check: **Enhanced Stability**: Resolved race conditions in state management
|
||||
- :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 |
|
||||
|--------|------|---------|-------------|
|
||||
| `token_threshold` | integer | `4000` | Trigger compression above this token count |
|
||||
| `preserve_recent` | integer | `5` | Number of recent messages to keep uncompressed |
|
||||
| `summary_model` | string | `"auto"` | Model to use for summarization |
|
||||
| `compression_ratio` | float | `0.3` | Target compression ratio |
|
||||
| `compression_threshold_tokens` | integer | `64000` | Trigger compression above this token count |
|
||||
| `max_context_tokens` | integer | `128000` | Hard limit for context |
|
||||
| `keep_first` | integer | `1` | Always keep the first N messages |
|
||||
| `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(异步上下文压缩)
|
||||
|
||||
<span class="category-badge filter">Filter</span>
|
||||
<span class="version-badge">v1.1.3</span>
|
||||
<span class="version-badge">v1.2.2</span>
|
||||
|
||||
通过智能摘要减少长对话的 token 消耗,同时保持对话连贯。
|
||||
|
||||
@@ -34,6 +34,12 @@ Async Context Compression 过滤器通过以下方式帮助管理长对话的 to
|
||||
- :material-check-all: **Open WebUI v0.7.x 兼容性**:动态数据库会话处理
|
||||
- :material-account-convert: **兼容性提升**:摘要角色改为 `assistant`
|
||||
- :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 数触发压缩 |
|
||||
| `preserve_recent` | integer | `5` | 保留不压缩的最近消息数量 |
|
||||
| `summary_model` | string | `"auto"` | 用于摘要的模型 |
|
||||
| `compression_ratio` | float | `0.3` | 目标压缩比例 |
|
||||
| `compression_threshold_tokens` | integer | `64000` | 超过该 token 数触发压缩 |
|
||||
| `max_context_tokens` | integer | `128000` | 上下文硬性上限 |
|
||||
| `keep_first` | integer | `1` | 始终保留的前 N 条消息 |
|
||||
| `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 }
|
||||
53
docs/plugins/filters/github-copilot-sdk-files-filter.md
Normal file
53
docs/plugins/filters/github-copilot-sdk-files-filter.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# GitHub Copilot SDK Files Filter (v0.1.2)
|
||||
|
||||
This is a dedicated **companion filter plugin** designed specifically for the [GitHub Copilot SDK Pipe](../pipes/github-copilot-sdk.md).
|
||||
|
||||
Its core mission is to **protect user-uploaded files from being "pre-processed" by the OpenWebUI core system, ensuring that the Copilot Agent receives the raw files for autonomous analysis.**
|
||||
|
||||
## 🎯 Why is this needed?
|
||||
|
||||
In OpenWebUI's default workflow, when you upload a file (e.g., PDF, Excel, Python script), OpenWebUI automatically initiates a **RAG (Retrieval-Augmented Generation)** process: parsing the file, vectorizing it, extracting text, and injecting it into the prompt.
|
||||
|
||||
While useful for standard models, this is often disruptive for a **Copilot SDK Agent**:
|
||||
|
||||
1. **Agent Needs Raw Files**: The Agent may need to run Python code to read an Excel file or analyze a full directory structure, not chopped-up text fragments.
|
||||
2. **Context Pollution**: Large amounts of text injected by RAG consume tokens and can confuse the Agent about "where the file is."
|
||||
3. **Conflicts**: If you have other multimodal plugins installed (like Gemini Filter), they might compete for file processing rights.
|
||||
|
||||
**This plugin acts as a "bodyguard" to solve these issues.**
|
||||
|
||||
## 🚀 How it Works
|
||||
|
||||
When you select a Copilot model (name containing `copilot_sdk`) in OpenWebUI and send a file:
|
||||
|
||||
1. **Intercept**: This plugin runs with high priority (Priority 0), before RAG and other filters.
|
||||
2. **Relocate**: Detecting a Copilot model, it moves the `files` list from the request to a secure custom field `copilot_files`.
|
||||
3. **Hide**: It clears the original `files` field.
|
||||
4. **Status Update**: It emits a status message "Managed X files for Copilot (RAG Bypassed)" to the UI.
|
||||
5. **Pass**: The OpenWebUI core sees an empty `files` list and **does not trigger RAG**.
|
||||
6. **Deliver**: The subsequent [Copilot SDK Pipe](../pipes/github-copilot-sdk.md) plugin checks `copilot_files`, retrieves file information, and automatically copies them into the Agent's isolated workspace.
|
||||
|
||||
## 📦 Installation & Configuration
|
||||
|
||||
### 1. Installation
|
||||
|
||||
Import this plugin on the OpenWebUI **Functions** page.
|
||||
|
||||
### 2. Enable
|
||||
|
||||
Ensure this Filter is enabled globally or in chat settings.
|
||||
|
||||
### 3. Configuration (Valves)
|
||||
|
||||
Default settings work for most users unless you have specific needs:
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| :--- | :--- | :--- |
|
||||
| **priority** | Execution priority of the filter. **Must be lower than OpenWebUI RAG priority**. | `0` |
|
||||
| **target_model_keyword** | Keyword to identify Copilot models. Only models containing this keyword will trigger file interception. | `copilot_sdk` |
|
||||
|
||||
## ⚠️ Important Notes
|
||||
|
||||
* **Must be used with Copilot SDK Pipe**: If you install this plugin without the main Pipe plugin, uploaded files will simply "disappear" (as no subsequent plugin will look for them in `copilot_files`).
|
||||
* **Gemini Filter Compatibility**: This plugin is fully compatible with the Gemini Multimodal Filter. As long as the priority is set correctly (This Plugin < Gemini Plugin), they can coexist without interference.
|
||||
* **Physical File Path**: Ensure the `OPENWEBUI_UPLOAD_PATH` is correctly set in the Pipe plugin Valves for the actual file transport to work.
|
||||
53
docs/plugins/filters/github-copilot-sdk-files-filter.zh.md
Normal file
53
docs/plugins/filters/github-copilot-sdk-files-filter.zh.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# GitHub Copilot SDK 文件过滤器 (v0.1.2)
|
||||
|
||||
这是一个专门为 [GitHub Copilot SDK Pipe](../pipes/github-copilot-sdk.zh.md) 设计的**伴侣过滤器插件**。
|
||||
|
||||
它的核心使命是:**保护用户上传的文件不被 OpenWebUI 核心系统“抢先处理”,确保 Copilot Agent 能够接收到原始文件并进行自主分析。**
|
||||
|
||||
## 🎯 为什么需要它?
|
||||
|
||||
在 OpenWebUI 的默认流程中,当你上传一个文件(如 PDF、Excel、Python 脚本)时,OpenWebUI 会自动启动 **RAG(检索增强生成)** 流程:解析文件、向量化、提取文本并注入到提示词中。
|
||||
|
||||
虽然这对普通模型很有用,但对于 **Copilot SDK Agent** 来说,这往往是干扰:
|
||||
|
||||
1. **Agent 需要原始文件**:Agent 可能需要运行 Python 代码读取 Excel,或者分析完整的代码结构,而不是被切碎的文本片段。
|
||||
2. **上下文污染**:RAG 注入的大量文本会消耗 Token,且容易让 Agent 混淆“文件在哪里”。
|
||||
3. **冲突**:如果你同时安装了其他多模态插件(如 Gemini Filter),它们可能会争夺文件的处理权。
|
||||
|
||||
**本插件就是为了解决这个问题而生的“保镖”。**
|
||||
|
||||
## 🚀 功能原理
|
||||
|
||||
当你在 OpenWebUI 中选择了一个 Copilot 模型(名称包含 `copilot_sdk`)并发送文件时:
|
||||
|
||||
1. **拦截 (Intercept)**:本插件会以极高的优先级(Priority 0)运行,先于 RAG 和其他过滤器。
|
||||
2. **搬运 (Relocate)**:它检测到模型是 Copilot,便将请求中的 `files`(文件列表)移动到一个安全的自定义字段 `copilot_files` 中。
|
||||
3. **隐身 (Hide)**:它清空原始的 `files` 字段。
|
||||
4. **状态更新**: 它会在 UI 中发出“已为 Copilot 管理 X 个文件 (已绕过 RAG)”的状态消息。
|
||||
5. **放行 (Pass)**:OpenWebUI 核心看到 `files` 为空,便**不会触发 RAG**。
|
||||
6. **交付 (Deliver)**:后续的 [Copilot SDK Pipe](../pipes/github-copilot-sdk.zh.md) 插件会检查 `copilot_files`,从中获取文件信息,并自动将其复制到 Agent 的独立工作区中。
|
||||
|
||||
## 📦 安装与配置
|
||||
|
||||
### 1. 安装
|
||||
|
||||
在 OpenWebUI 的 **Functions** 页面导入此插件。
|
||||
|
||||
### 2. 启用
|
||||
|
||||
确保在全局或对话设置中启用了此 Filter。
|
||||
|
||||
### 3. 配置 (Valves)
|
||||
|
||||
通常不需要修改默认配置,除非你有特殊需求:
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
| :--- | :--- | :--- |
|
||||
| **priority** | 过滤器的执行优先级。**必须小于 OpenWebUI RAG 的优先级**。 | `0` |
|
||||
| **target_model_keyword** | 用于识别 Copilot 模型的关键词。只有包含此关键词的模型才会触发 file 拦截。 | `copilot_sdk` |
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
* **必须配合 Copilot SDK Pipe 使用**:如果你没有安装主 Pipe 插件,本插件将导致上传的文件“凭空消失”(因为没有后续插件去 `copilot_files` 里找它们)。
|
||||
* **Gemini Filter 兼容性**:本插件已完美兼容 Gemini 多模态过滤器。只要优先级设置正确(本插件 < Gemini 插件),它们可以共存互不干扰。
|
||||
* **物理文件路径**: 确保在 Pipe 插件的 Valves 中正确设置了 `OPENWEBUI_UPLOAD_PATH`,以便文件自动搬运功能正常工作。
|
||||
@@ -16,17 +16,17 @@ Filters act as middleware in the message pipeline:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
|
||||
- :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
|
||||
|
||||
---
|
||||
|
||||
Reduces token consumption in long conversations through intelligent summarization while maintaining coherence.
|
||||
|
||||
**Version:** 1.1.3
|
||||
**Version:** 1.2.2
|
||||
|
||||
[:octicons-arrow-right-24: Documentation](async-context-compression.md)
|
||||
|
||||
- :material-text-box-plus:{ .lg .middle } **Context Enhancement**
|
||||
- :material-text-box-plus:{ .lg .middle } **Context Enhancement**
|
||||
|
||||
---
|
||||
|
||||
@@ -36,26 +36,56 @@ Filters act as middleware in the message pipeline:
|
||||
|
||||
[: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**
|
||||
|
||||
---
|
||||
|
||||
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)
|
||||
|
||||
- :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)
|
||||
|
||||
- :material-file-shield:{ .lg .middle } **Copilot SDK Files Filter**
|
||||
|
||||
---
|
||||
|
||||
A specialized filter to bypass OpenWebUI's default RAG for GitHub Copilot SDK models. It ensures the Agent receives raw files for autonomous analysis.
|
||||
|
||||
**Version:** 0.1.2
|
||||
|
||||
[:octicons-arrow-right-24: Documentation](github-copilot-sdk-files-filter.md)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
@@ -16,17 +16,17 @@ Filter 充当消息管线中的中间件:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
|
||||
- :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
|
||||
|
||||
---
|
||||
|
||||
通过智能总结减少长对话的 token 消耗,同时保持连贯性。
|
||||
|
||||
**版本:** 1.1.3
|
||||
**版本:** 1.2.2
|
||||
|
||||
[:octicons-arrow-right-24: 查看文档](async-context-compression.md)
|
||||
|
||||
- :material-text-box-plus:{ .lg .middle } **Context Enhancement**
|
||||
- :material-text-box-plus:{ .lg .middle } **Context Enhancement**
|
||||
|
||||
---
|
||||
|
||||
@@ -36,26 +36,36 @@ Filter 充当消息管线中的中间件:
|
||||
|
||||
[: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**
|
||||
|
||||
---
|
||||
|
||||
修复 LLM 输出中常见的 Markdown 格式问题,包括 Mermaid 语法、代码块和 LaTeX 公式。
|
||||
|
||||
**版本:** 1.0.1
|
||||
**版本:** 1.2.4
|
||||
|
||||
[:octicons-arrow-right-24: 查看文档](markdown_normalizer.zh.md)
|
||||
|
||||
- :material-file-shield:{ .lg .middle } **Copilot SDK 文件过滤器**
|
||||
|
||||
---
|
||||
|
||||
专门用于绕过 OpenWebUI 默认 RAG 机制的过滤器,针对 GitHub Copilot SDK 模型。确保 Agent 能够接收到原始文件进行自主分析。
|
||||
|
||||
**版本:** 0.1.2
|
||||
|
||||
[:octicons-arrow-right-24: 查看文档](github-copilot-sdk-files-filter.zh.md)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
@@ -1,45 +1,97 @@
|
||||
# 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
|
||||
|
||||
* **Mermaid Syntax Fix**: Automatically fixes common Mermaid syntax errors, such as unquoted node labels (including multi-line labels and citations) and unclosed subgraphs, ensuring diagrams render correctly.
|
||||
* **Frontend Console Debugging**: Supports printing structured debug logs directly to the browser console (F12) for easier troubleshooting.
|
||||
* **Code Block Formatting**: Fixes broken code block prefixes, suffixes, and indentation.
|
||||
* **LaTeX Normalization**: Standardizes LaTeX formula delimiters (`\[` -> `$$`, `\(` -> `$`).
|
||||
* **Thought Tag Normalization**: Unifies thought tags (`<think>`, `<thinking>` -> `<thought>`).
|
||||
* **Escape Character Fix**: Cleans up excessive escape characters (`\\n`, `\\t`).
|
||||
* **List Formatting**: Ensures proper newlines in list items.
|
||||
* **Heading Fix**: Adds missing spaces in headings (`#Heading` -> `# Heading`).
|
||||
* **Table Fix**: Adds missing closing pipes in tables.
|
||||
* **XML Cleanup**: Removes leftover XML artifacts.
|
||||
* **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.
|
||||
* **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.
|
||||
* **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).
|
||||
* **Frontend Console Debugging**: Supports printing structured debug logs directly to the browser console (F12) for easier troubleshooting.
|
||||
* **Code Block Formatting**: Fixes broken code block prefixes, suffixes, and indentation.
|
||||
* **LaTeX Normalization**: Standardizes LaTeX formula delimiters (`\[` -> `$$`, `\(` -> `$`).
|
||||
* **Thought Tag Normalization**: Unifies thought tags (`<think>`, `<thinking>` -> `<thought>`).
|
||||
* **Escape Character Fix**: Cleans up excessive escape characters (`\\n`, `\\t`).
|
||||
* **List Formatting**: Ensures proper newlines in list items.
|
||||
* **Heading Fix**: Adds missing spaces in headings (`#Heading` -> `# Heading`).
|
||||
* **Table Fix**: Adds missing closing pipes in tables.
|
||||
* **XML Cleanup**: Removes leftover XML artifacts.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Install the plugin in Open WebUI.
|
||||
2. Enable the filter globally or for specific models.
|
||||
3. Configure the enabled fixes in the **Valves** settings.
|
||||
4. (Optional) **Show Debug Log** is enabled by default in Valves. This prints structured logs to the browser console (F12).
|
||||
1. Install the plugin in Open WebUI.
|
||||
2. Enable the filter globally or for specific models.
|
||||
3. Configure the enabled fixes in the **Valves** settings.
|
||||
4. (Optional) **Show Debug Log** is enabled by default in Valves. This prints structured logs to the browser console (F12).
|
||||
> [!WARNING]
|
||||
> As this is an initial version, some "negative fixes" might occur (e.g., breaking valid Markdown). If you encounter issues, please check the console logs, copy the "Original" vs "Normalized" content, and submit an issue.
|
||||
|
||||
## Configuration (Valves)
|
||||
|
||||
* `priority`: Filter priority (default: 50).
|
||||
* `enable_escape_fix`: Fix excessive escape characters.
|
||||
* `enable_thought_tag_fix`: Normalize thought tags.
|
||||
* `enable_code_block_fix`: Fix code block formatting.
|
||||
* `enable_latex_fix`: Normalize LaTeX formulas.
|
||||
* `enable_list_fix`: Fix list item newlines (Experimental).
|
||||
* `enable_unclosed_block_fix`: Auto-close unclosed code blocks.
|
||||
* `enable_fullwidth_symbol_fix`: Fix full-width symbols in code blocks.
|
||||
* `enable_mermaid_fix`: Fix Mermaid syntax errors.
|
||||
* `enable_heading_fix`: Fix missing space in headings.
|
||||
* `enable_table_fix`: Fix missing closing pipe in tables.
|
||||
* `enable_xml_tag_cleanup`: Cleanup leftover XML tags.
|
||||
* `show_status`: Show status notification when fixes are applied.
|
||||
* `show_debug_log`: Print debug logs to browser console.
|
||||
* `priority`: Filter priority (default: 50).
|
||||
* `enable_escape_fix`: Fix excessive escape characters.
|
||||
* `enable_thought_tag_fix`: Normalize thought tags.
|
||||
* `enable_details_tag_fix`: Normalize details tags (default: True).
|
||||
* `enable_code_block_fix`: Fix code block formatting.
|
||||
* `enable_latex_fix`: Normalize LaTeX formulas.
|
||||
* `enable_list_fix`: Fix list item newlines (Experimental).
|
||||
* `enable_unclosed_block_fix`: Auto-close unclosed code blocks.
|
||||
* `enable_fullwidth_symbol_fix`: Fix full-width symbols in code blocks.
|
||||
* `enable_mermaid_fix`: Fix Mermaid syntax errors.
|
||||
* `enable_heading_fix`: Fix missing space in headings.
|
||||
* `enable_table_fix`: Fix missing closing pipe in tables.
|
||||
* `enable_xml_tag_cleanup`: Cleanup leftover XML tags.
|
||||
* `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
|
||||
|
||||
|
||||
@@ -1,45 +1,97 @@
|
||||
# Markdown 格式化过滤器 (Markdown Normalizer)
|
||||
|
||||
这是一个用于 Open WebUI 的生产级内容格式化过滤器,旨在修复 LLM 输出中常见的 Markdown 格式问题。它能确保代码块、LaTeX 公式、Mermaid 图表和其他 Markdown 元素被正确渲染。
|
||||
这是一个用于 Open WebUI 的内容格式化过滤器,旨在修复 LLM 输出中常见的 Markdown 格式问题。它能确保代码块、LaTeX 公式、Mermaid 图表和其他 Markdown 元素被正确渲染。
|
||||
|
||||
## 功能特性
|
||||
|
||||
* **Mermaid 语法修复**: 自动修复常见的 Mermaid 语法错误,如未加引号的节点标签(支持多行标签和引用标记)和未闭合的子图 (Subgraph),确保图表能正确渲染。
|
||||
* **前端控制台调试**: 支持将结构化的调试日志直接打印到浏览器控制台 (F12),方便排查问题。
|
||||
* **代码块格式化**: 修复破损的代码块前缀、后缀和缩进问题。
|
||||
* **LaTeX 规范化**: 标准化 LaTeX 公式定界符 (`\[` -> `$$`, `\(` -> `$`)。
|
||||
* **思维标签规范化**: 统一思维链标签 (`<think>`, `<thinking>` -> `<thought>`)。
|
||||
* **转义字符修复**: 清理过度的转义字符 (`\\n`, `\\t`)。
|
||||
* **列表格式化**: 确保列表项有正确的换行。
|
||||
* **标题修复**: 修复标题中缺失的空格 (`#标题` -> `# 标题`)。
|
||||
* **表格修复**: 修复表格中缺失的闭合管道符。
|
||||
* **XML 清理**: 移除残留的 XML 标签。
|
||||
* **Details 标签规范化**: 确保 `<details>` 标签(常用于思维链)有正确的间距。在 `</details>` 后添加空行,并在自闭合 `<details />` 标签后添加换行,防止渲染问题。
|
||||
* **强调空格修复**: 修复强调标记内部的多余空格(例如 `** 文本 **` -> `**文本**`),这会导致 Markdown 渲染失败。包含保护机制,防止误修改数学表达式(如 `2 * 3 * 4`)或列表变量。
|
||||
* **Mermaid 语法修复**: 自动修复常见的 Mermaid 语法错误,如未加引号的节点标签(支持多行标签和引用标记)和未闭合的子图 (Subgraph)。**v1.1.2 新增**: 全面保护各种类型的连线标签(实线、虚线、粗线),防止被误修改。
|
||||
* **前端控制台调试**: 支持将结构化的调试日志直接打印到浏览器控制台 (F12),方便排查问题。
|
||||
* **代码块格式化**: 修复破损的代码块前缀、后缀和缩进问题。
|
||||
* **LaTeX 规范化**: 标准化 LaTeX 公式定界符 (`\[` -> `$$`, `\(` -> `$`)。
|
||||
* **思维标签规范化**: 统一思维链标签 (`<think>`, `<thinking>` -> `<thought>`)。
|
||||
* **转义字符修复**: 清理过度的转义字符 (`\\n`, `\\t`)。
|
||||
* **列表格式化**: 确保列表项有正确的换行。
|
||||
* **标题修复**: 修复标题中缺失的空格 (`#标题` -> `# 标题`)。
|
||||
* **表格修复**: 修复表格中缺失的闭合管道符。
|
||||
* **XML 清理**: 移除残留的 XML 标签。
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 在 Open WebUI 中安装此插件。
|
||||
2. 全局启用或为特定模型启用此过滤器。
|
||||
3. 在 **Valves** 设置中配置需要启用的修复项。
|
||||
4. (可选) **显示调试日志 (Show Debug Log)** 在 Valves 中默认开启。这会将结构化的日志打印到浏览器控制台 (F12)。
|
||||
1. 在 Open WebUI 中安装此插件。
|
||||
2. 全局启用或为特定模型启用此过滤器。
|
||||
3. 在 **Valves** 设置中配置需要启用的修复项。
|
||||
4. (可选) **显示调试日志 (Show Debug Log)** 在 Valves 中默认开启。这会将结构化的日志打印到浏览器控制台 (F12)。
|
||||
> [!WARNING]
|
||||
> 由于这是初版,可能会出现“负向修复”的情况(例如破坏了原本正确的格式)。如果您遇到问题,请务必查看控制台日志,复制“原始 (Original)”与“规范化 (Normalized)”的内容对比,并提交 Issue 反馈。
|
||||
|
||||
## 配置项 (Valves)
|
||||
|
||||
* `priority`: 过滤器优先级 (默认: 50)。
|
||||
* `enable_escape_fix`: 修复过度的转义字符。
|
||||
* `enable_thought_tag_fix`: 规范化思维标签。
|
||||
* `enable_code_block_fix`: 修复代码块格式。
|
||||
* `enable_latex_fix`: 规范化 LaTeX 公式。
|
||||
* `enable_list_fix`: 修复列表项换行 (实验性)。
|
||||
* `enable_unclosed_block_fix`: 自动闭合未闭合的代码块。
|
||||
* `enable_fullwidth_symbol_fix`: 修复代码块中的全角符号。
|
||||
* `enable_mermaid_fix`: 修复 Mermaid 语法错误。
|
||||
* `enable_heading_fix`: 修复标题中缺失的空格。
|
||||
* `enable_table_fix`: 修复表格中缺失的闭合管道符。
|
||||
* `enable_xml_tag_cleanup`: 清理残留的 XML 标签。
|
||||
* `show_status`: 应用修复时显示状态通知。
|
||||
* `show_debug_log`: 在浏览器控制台打印调试日志。
|
||||
* `priority`: 过滤器优先级 (默认: 50)。
|
||||
* `enable_escape_fix`: 修复过度的转义字符。
|
||||
* `enable_thought_tag_fix`: 规范化思维标签。
|
||||
* `enable_details_tag_fix`: 规范化 Details 标签 (默认: True)。
|
||||
* `enable_code_block_fix`: 修复代码块格式。
|
||||
* `enable_latex_fix`: 规范化 LaTeX 公式。
|
||||
* `enable_list_fix`: 修复列表项换行 (实验性)。
|
||||
* `enable_unclosed_block_fix`: 自动闭合未闭合的代码块。
|
||||
* `enable_fullwidth_symbol_fix`: 修复代码块中的全角符号。
|
||||
* `enable_mermaid_fix`: 修复 Mermaid 语法错误。
|
||||
* `enable_heading_fix`: 修复标题中缺失的空格。
|
||||
* `enable_table_fix`: 修复表格中缺失的闭合管道符。
|
||||
* `enable_xml_tag_cleanup`: 清理残留的 XML 标签。
|
||||
* `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 |
|
||||
|--------|------|-------------|---------|
|
||||
| [Smart Mind Map](actions/smart-mind-map.md) | Action | Generate interactive mind maps from text | 0.8.0 |
|
||||
| [Smart Infographic](actions/smart-infographic.md) | Action | Transform text into professional infographics | 1.0.0 |
|
||||
| [Knowledge Card](actions/knowledge-card.md) | Action | Create beautiful learning flashcards | 0.2.0 |
|
||||
| [Export to Excel](actions/export-to-excel.md) | Action | Export chat history to Excel files | 1.0.0 |
|
||||
| [Export to Word](actions/export-to-word.md) | Action | Export chat content to Word (.docx) with formatting | 0.1.0 |
|
||||
| [Async Context Compression](filters/async-context-compression.md) | Filter | Intelligent context compression | 1.0.0 |
|
||||
| [Context Enhancement](filters/context-enhancement.md) | Filter | Enhance chat context | 1.0.0 |
|
||||
| [Gemini Manifold Companion](filters/gemini-manifold-companion.md) | Filter | Companion for Gemini Manifold | 1.0.0 |
|
||||
| [Gemini Manifold](pipes/gemini-manifold.md) | Pipe | Gemini model integration | 1.0.0 |
|
||||
| [Smart Mind Map](actions/smart-mind-map.md) | Action | Generate interactive mind maps from text | 0.9.2 |
|
||||
| [Smart Infographic](actions/smart-infographic.md) | Action | Transform text into professional infographics | 1.4.9 |
|
||||
| [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 | 0.3.7 |
|
||||
| [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.1.3 |
|
||||
| [Context Enhancement](filters/context-enhancement.md) | Filter | Enhance chat context | 0.3.0 |
|
||||
| [Multi-Model Context Merger](filters/multi-model-context-merger.md) | Filter | Merge context from multiple models | 0.1.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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -48,15 +48,15 @@ OpenWebUI 支持四种类型的插件,每种都有不同的用途:
|
||||
|
||||
| 插件 | 类型 | 描述 | 版本 |
|
||||
|--------|------|-------------|---------|
|
||||
| [Smart Mind Map(智能思维导图)](actions/smart-mind-map.md) | Action | 从文本生成交互式思维导图 | 0.8.0 |
|
||||
| [Smart Infographic(智能信息图)](actions/smart-infographic.md) | Action | 将文本转成专业信息图 | 1.0.0 |
|
||||
| [Knowledge Card(知识卡片)](actions/knowledge-card.md) | Action | 生成精美学习卡片 | 0.2.0 |
|
||||
| [Export to Excel(导出到 Excel)](actions/export-to-excel.md) | Action | 导出聊天记录为 Excel | 1.0.0 |
|
||||
| [Export to Word(导出为 Word)](actions/export-to-word.md) | Action | 将聊天内容导出为 Word (.docx) 并保留格式 | 0.1.0 |
|
||||
| [Async Context Compression(异步上下文压缩)](filters/async-context-compression.md) | Filter | 智能上下文压缩 | 1.0.0 |
|
||||
| [Context Enhancement(上下文增强)](filters/context-enhancement.md) | Filter | 提升对话上下文 | 1.0.0 |
|
||||
| [Gemini Manifold Companion](filters/gemini-manifold-companion.md) | Filter | Gemini Manifold 伴侣 | 1.0.0 |
|
||||
| [Gemini Manifold](pipes/gemini-manifold.md) | Pipe | Gemini 模型集成 | 1.0.0 |
|
||||
| [Smart Mind Map(智能思维导图)](actions/smart-mind-map.md) | Action | 从文本生成交互式思维导图 | 0.9.2 |
|
||||
| [Smart Infographic(智能信息图)](actions/smart-infographic.md) | Action | 将文本转成专业信息图 | 1.4.9 |
|
||||
| [Flash Card(闪记卡)](actions/flash-card.md) | Action | 生成精美学习卡片 | 0.2.4 |
|
||||
| [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.4.3 |
|
||||
| [Async Context Compression(异步上下文压缩)](filters/async-context-compression.md) | Filter | 智能上下文压缩 | 1.1.3 |
|
||||
| [Context Enhancement(上下文增强)](filters/context-enhancement.md) | Filter | 提升对话上下文 | 0.3.0 |
|
||||
| [Multi-Model Context Merger(多模型上下文合并)](filters/multi-model-context-merger.md) | Filter | 合并多个模型的上下文 | 0.1.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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -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 }
|
||||
80
docs/plugins/pipes/github-copilot-sdk-deep-dive.md
Normal file
80
docs/plugins/pipes/github-copilot-sdk-deep-dive.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# GitHub Copilot SDK Plugin Deep Dive
|
||||
|
||||
**Version:** 0.6.0 | **Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Status:** Production-Ready
|
||||
|
||||
The GitHub Copilot SDK plugin is far more than a simple API proxy; it is a highly integrated **Intelligent Agent Runtime Environment**. This document explores the core features, implementation details, technical architecture, and security design that define its capabilities.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 1. Feature Catalog
|
||||
|
||||
The plugin implements advanced capabilities that go far beyond standard API calls:
|
||||
|
||||
- **✅ Physical Workspace Management**: Automatically creates isolated directories for each chat session to manage uploaded files and Agent-generated scripts.
|
||||
- **✅ Real-time TODO Sync**: Mounts to the OpenWebUI database to persist Agent plans directly into a UI progress bar, ensuring transparency for long-running tasks.
|
||||
- **✅ Cross-Ecosystem Tool Bridging**: Translates OpenWebUI Search, Python interpreters, and custom MCP tools into native Copilot capabilities.
|
||||
- **✅ Intelligent File Transport**: Ensures the Agent can access raw files (Excel, PDF, Code) like a local developer through physical file duplication.
|
||||
- **✅ Chain of Thought Streaming**: Full support for rendering the model's reasoning/thinking process in real-time.
|
||||
- **✅ BYOK with Plugin Power**: Connect external OpenAI/Anthropic models while retaining all the plugin's workspace and tool management features.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 2. Use Cases: Beyond Basic Chat
|
||||
|
||||
With these features, the plugin excels in complex, real-world scenarios:
|
||||
|
||||
### 📁 Autonomous Repository Maintenance (Agentic DevOps)
|
||||
>
|
||||
> **Action**: Upload a Zip archive containing a codebase with bugs.
|
||||
> **Utility**: The Agent extracts the archive, uses `bash` to navigate and search, applies fixes via the `edit` tool, and runs tests to verify the solution—all within its isolated sandbox.
|
||||
|
||||
### 📊 Deep Data Analysis (Data Scientist Agent)
|
||||
>
|
||||
> **Action**: Upload multiple heavy Excel spreadsheets.
|
||||
> **Utility**: By bypassing RAG (via the filter), the Agent loads raw files directly into its Python interpreter, performs cross-table calculations, and generates analytical charts presented via Artifacts.
|
||||
|
||||
### 📝 Strategic Task Management
|
||||
>
|
||||
> **Action**: "Develop a full architecture for a new mobile app."
|
||||
> **Utility**: The plugin captures the Agent's breakdown of 20+ sub-tasks. The persistent progress bar reflects ongoing progress (e.g., "Designing Schema", "Drafting API"), providing clarity during long marathons.
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ 3. Technical Architecture
|
||||
|
||||
### 3.1 Three-Layer Sandbox Isolation
|
||||
|
||||
To prevent data leakage in multi-user environments, the plugin enforces a strict physical directory structure:
|
||||
`/app/backend/data/copilot_workspace/{user_id}/{chat_id}/`
|
||||
|
||||
- **Constraint**: Code execution and file storage are confined to the session-specific folder.
|
||||
- **Persistence**: Data remains valid across container restarts due to volume mounting.
|
||||
|
||||
### 3.2 Dynamic Tool Bridging
|
||||
|
||||
How does Copilot "learn" to use OpenWebUI tools?
|
||||
|
||||
1. **Introspection**: Analyzes docstrings and type hints of OpenWebUI tools.
|
||||
2. **Schema Generation**: Dynamically creates JSON descriptions compliant with the GitHub Copilot specification.
|
||||
3. **Routing**: Handles parameter validation, identity injection, and result forwarding between systems.
|
||||
|
||||
### 3.3 Event-Driven TODO Hub
|
||||
|
||||
The plugin captures internal SDK events to power the UI progress bar:
|
||||
|
||||
- **Interceptor**: Listens for `tool.execution_complete` events for the `update_todo` tool.
|
||||
- **Storage**: Syncs project metrics directly to the `chat_todos` table in the OpenWebUI database (SQLite/PostgreSQL).
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 4. Runtime Performance
|
||||
|
||||
- **Anti-Shake Logic**: Environment checks happen only once every 24 hours per process, preventing redundant system calls.
|
||||
- **Tool Caching**: Persists tool definitions across sessions to reduce overhead, improving initial response times by up to 40%.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 5. Development Best Practices
|
||||
|
||||
1. **Use the Filter**: Always pair with `github_copilot_sdk_files_filter` to ensure files reach the Agent in their original binary form.
|
||||
2. **File-First Execution**: Encourage the Agent to "write code to file and execute" rather than relying on direct interactive shell input for complex logic.
|
||||
80
docs/plugins/pipes/github-copilot-sdk-deep-dive.zh.md
Normal file
80
docs/plugins/pipes/github-copilot-sdk-deep-dive.zh.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# GitHub Copilot SDK 插件深度解析 (Deep Dive)
|
||||
|
||||
**版本:** 0.6.0 | **作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **状态:** 生产级
|
||||
|
||||
GitHub Copilot SDK 插件不仅仅是一个 API 转发器,它是一个高度集成的 **智能 Agent 运行环境**。本文将从功能实现、应用场景、技术架构及安全设计四个维度,深入解析这一插件的强大之处。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 1. 核心功能清单 (Feature Catalog)
|
||||
|
||||
插件通过深度集成实现了以下超越普通 API 调用的能力:
|
||||
|
||||
- **✅ 物理级工作区管理**: 自动为每个对话创建独立的物理目录,管理用户上传和 Agent 生成的所有文件。
|
||||
- **✅ 实时 TODO 状态同步**: 通过数据库挂载,实时将 Agent 的计划提取到 UI 进度条中,解决长任务透明度问题。
|
||||
- **✅ 跨生态工具桥接**: 自动将 OpenWebUI 的搜索、Python 运行环境和自定义 MCP 工具转化为 Copilot 原生工具。
|
||||
- **✅ 智能文件搬运**: 物理级文件副本传输,确保 Agent 可以像本地开发者一样访问 Excel、PDF 和代码仓库。
|
||||
- **✅ 思考过程可见性**: 完整模拟 GitHub Copilot 的思维链 (Thinking Process) 流式展示。
|
||||
- **✅ BYOK 二次鉴权**: 支持在 Copilot 框架内接入外部 OpenAI/Anthropic 模型,同时享受插件的所有增强功能。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 2. 这个插件能用来干什么? (Use Cases)
|
||||
|
||||
基于上述功能,该插件可以胜任以下复杂场景:
|
||||
|
||||
### 📁 场景 A:全自动代码仓库维护 (Agentic DevOps)
|
||||
>
|
||||
> **操作**: 上传一个包含 Bug 的 Zip 压缩包。
|
||||
> **用途**: Agent 会自动解密、解压,使用 `bash` 定位问题,调用 `edit` 修改代码,最后运行测试脚本。这一切都在隔离沙箱中完成,你只需要审核最终的补丁。
|
||||
|
||||
### 📊 场景 B:深度财务数据审计 (Data Analyst Agent)
|
||||
>
|
||||
> **操作**: 上传一年的 Excel 财务报表。
|
||||
> **用途**: 绕过传统 RAG 的文本切片限制,Agent 直接通过 Python 脚本加载原始表格,进行跨表计算和逻辑校验,并生成可视化图表。
|
||||
|
||||
### 📝 场景 C:复杂长任务进度追踪 (Project Manager)
|
||||
>
|
||||
> **操作**: 输入“请基于以下需求文档编写一个完整的 Web 后端方案”。
|
||||
> **用途**: 插件捕捉 Agent 拆解的 20+ 个子任务。顶部的实时进度条会告诉你它正在“设计数据库”还是“编写认证逻辑”,确保你对黑盒任务了如指掌。
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ 3. 技术架构设计 (Technical Architecture)
|
||||
|
||||
### 3.1 三层物理安全隔离 (Workspace Isolation)
|
||||
|
||||
为了确保多用户环境下的数据安全,插件强制执行以下物理路径:
|
||||
`/app/backend/data/copilot_workspace/{user_id}/{chat_id}/`
|
||||
|
||||
- **隔离性**: 进程内代码执行被严格约束在 `chat_id` 目录下。
|
||||
- **持久性**: 即使容器重启,挂载路径下的工作成果依然保留。
|
||||
|
||||
### 3.2 零配置工具桥接 (Dynamic Tool Bridging)
|
||||
|
||||
插件如何让 Copilot “学会”使用 OpenWebUI 的工具?
|
||||
|
||||
1. **内省 (Introspection)**: 实时读取工具的 `docstring` 和 `type hints`。
|
||||
2. **动态转换**: 生成符合 GitHub Copilot 规范的工具描述符。
|
||||
3. **双向路由**: 处理参数校验、身份注入(如认证头)以及结果回传。
|
||||
|
||||
### 3.3 数据库集成与事件驱动
|
||||
|
||||
插件通过监听 `NDJSON` 事件流,实现状态同步:
|
||||
|
||||
- **监听器**: 实时过滤 `tool.execution_complete` 事件。
|
||||
- **持久层**: 使用 OpenWebUI 核心相同的 `SQLAlchemy` 引擎操作 `chat_todos` 表。
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 4. 性能优化 (Performance)
|
||||
|
||||
- **环境检查防抖**: 全局类变量保护,版本核对周期为 24 小时,避免高并发下的 I/O 争抢。
|
||||
- **工具定义缓存**: 仅在变更时刷新工具元数据,首包响应速度(TTFB)提升约 40%。
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 5. 开发建议 (Best Practices)
|
||||
|
||||
1. **协同工作**: 必须安装 `github_copilot_sdk_files_filter` 以确保文件以“二进制原文”而非“RAG 切片”传递。
|
||||
2. **Python 范式**: 鼓励 Agent 采取“写文件 -> 运行文件”的模式,而非交互式 Shell 输出,以获得更好的执行稳定性。
|
||||
119
docs/plugins/pipes/github-copilot-sdk.md
Normal file
119
docs/plugins/pipes/github-copilot-sdk.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# GitHub Copilot SDK Pipe for OpenWebUI
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.6.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 integrates the official [GitHub Copilot SDK](https://github.com/github/copilot-sdk). It enables you to use **GitHub Copilot models** (e.g., `gpt-5.2-codex`, `claude-sonnet-4.5`,`gemini-3-pro`, `gpt-5-mini`) **AND** your own models via **BYOK** (OpenAI, Anthropic) directly within OpenWebUI, providing a unified agentic experience with **strict User & Chat-level Workspace Isolation**.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Essential Companion**
|
||||
> To unlock file handling and data analysis capabilities, you must install the [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6).
|
||||
|
||||
> [!TIP]
|
||||
> **No Subscription Required for BYOK**
|
||||
> If you are using your own API keys (BYOK mode with OpenAI/Anthropic), **you do NOT need a GitHub Copilot subscription.** A subscription is only required to access GitHub's official models.
|
||||
|
||||
---
|
||||
|
||||
## ✨ v0.6.0 Updates (What's New)
|
||||
|
||||
- **👥 User & Chat Management**: Physical management architecture (`user_id/chat_id`) for absolute resource independence.
|
||||
- **🤖 Empowering Agent Autonomy**: Automatic synchronization of raw files to the workspace, enabling direct Python-based analysis of Excel/CSV.
|
||||
- **🔧 OpenAPI & External Tool Fixes**: Full support for tools mounted via OpenAPI servers.
|
||||
- **📊 Cost Control**: Enhanced **Billing Multiplier Limits** (`MAX_MULTIPLIER`, e.g., set to 0 for free models only) and **Model Keyword Filtering** (`EXCLUDE_KEYWORDS`) for precise cost management.
|
||||
- **🧠 Persistent TODO Lists**: Database-backed task tracking that persists across sessions.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Key Capabilities
|
||||
|
||||
- **🔑 Flexible Auth & BYOK**: Official Copilot subscriptions (PAT) or Bring Your Own Key (OpenAI/Anthropic).
|
||||
- **🔌 Universal Tool Protocol**: Native support for **MCP (Model Context Protocol)**, OpenAPI, and OpenWebUI built-in tools.
|
||||
- **🛡️ Sandbox Workspace Isolation**: Strict per-session sandboxing for data privacy and security.
|
||||
- **♾️ Infinite Session Management**: Smart context window management with automatic compaction for indefinite conversation capability.
|
||||
- **🧠 Deep Database Integration**: Real-time persistence of TOD·O lists for long-running workflows.
|
||||
- **🌊 Advanced Streaming**: Full support for thinking process/Chain of Thought visualization.
|
||||
- **🖼️ Intelligent Multimodal**: Vision capabilities and raw file analysis support.
|
||||
- **⚡ Interactive Artifacts**: Automatically renders HTML/JS apps generated by the agent directly in the chat interface.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Core Configuration (Valves)
|
||||
|
||||
### 1. Administrator Settings (Base)
|
||||
|
||||
Administrators define the default behavior for all users in the function settings.
|
||||
|
||||
| Valve | Default | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `GH_TOKEN` | `""` | Global GitHub Token (Requires 'Copilot Requests' permission). |
|
||||
| `ENABLE_OPENWEBUI_TOOLS` | `True` | Enable OpenWebUI Tools (includes defined Tools and Built-in Tools). |
|
||||
| `ENABLE_OPENAPI_SERVER` | `True` | Enable OpenAPI Tool Server connection. |
|
||||
| `ENABLE_MCP_SERVER` | `True` | Enable Direct MCP Client connection (Recommended). |
|
||||
| `REASONING_EFFORT` | `medium` | Reasoning effort level: low, medium, high. |
|
||||
| `SHOW_THINKING` | `True` | Show model reasoning/thinking process. |
|
||||
| `INFINITE_SESSION` | `True` | Enable Infinite Sessions (automatic context compaction). |
|
||||
| `MAX_MULTIPLIER` | `1.0` | Max allowed billing multiplier (0x for free models only). |
|
||||
| `EXCLUDE_KEYWORDS` | `""` | Exclude models containing these keywords (comma separated). |
|
||||
| `TIMEOUT` | `300` | Timeout for each stream chunk (seconds). |
|
||||
| `BYOK_TYPE` | `openai` | BYOK Provider Type: `openai`, `anthropic`. |
|
||||
| `BYOK_BASE_URL` | `""` | BYOK Base URL (e.g., <https://api.openai.com/v1>). |
|
||||
| `BYOK_MODELS` | `""` | BYOK Model List (comma separated). Leave empty to fetch from API. |
|
||||
| `CUSTOM_ENV_VARS` | `""` | Custom environment variables (JSON format). |
|
||||
| `DEBUG` | `False` | Enable this to see detailed logs in your browser console. |
|
||||
|
||||
### 2. User Settings (Individual Overrides)
|
||||
|
||||
Standard users can override these settings in their individual Profile/Function settings.
|
||||
|
||||
| Valve | Description |
|
||||
| :--- | :--- |
|
||||
| `GH_TOKEN` | Use your personal GitHub Token. |
|
||||
| `REASONING_EFFORT` | Individual reasoning effort preference. |
|
||||
| `SHOW_THINKING` | Show model reasoning/thinking process. |
|
||||
| `MAX_MULTIPLIER` | Maximum allowed billing multiplier override. |
|
||||
| `EXCLUDE_KEYWORDS` | Exclude models containing these keywords. |
|
||||
| `BYOK_API_KEY` | Use your personal OpenAI/Anthropic API Key. |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Use Cases (What can you do?)
|
||||
|
||||
- **📁 Fully Autonomous DevOps**: Agent analyzes code, runs tests, and applies patches within its isolated sandbox.
|
||||
- **📊 Deep Data Auditing**: Directly process raw Excel/CSV data via Python (bypassing RAG) and generate visual reports.
|
||||
- **📝 Long-Task Management**: Automatically decomposes complex requests and persists TOD·O progress across sessions.
|
||||
|
||||
---
|
||||
|
||||
## ⭐ Support
|
||||
|
||||
If this plugin has been useful, a **Star** on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) would be a great motivation for me. Thank you!
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Installation & Configuration
|
||||
|
||||
### 1) Import Function
|
||||
|
||||
1. Open OpenWebUI, go to **Workspace** -> **Functions**.
|
||||
2. Click **+** (Create Function), paste the content of `github_copilot_sdk.py`.
|
||||
3. Save and ensure it is enabled.
|
||||
|
||||
### 2) Get Token
|
||||
|
||||
1. Visit [GitHub Token Settings](https://github.com/settings/tokens?type=beta).
|
||||
2. Create **Fine-grained token**, granting **Account permissions** -> **Copilot Requests** access.
|
||||
3. Paste the generated Token into the `GH_TOKEN` field in Valves.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Troubleshooting & Dependencies
|
||||
|
||||
- **Agent ignores files?**: Ensure the Files Filter is enabled, otherwise RAG will interfere with raw binaries.
|
||||
- **No progress bar?**: The bar only appears when the Agent uses the `update_todo` tool.
|
||||
- **Dependencies**: This Pipe automatically installs `github-copilot-sdk` (Python) and `github-copilot-cli` (Binary).
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
See the full history on GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
119
docs/plugins/pipes/github-copilot-sdk.zh.md
Normal file
119
docs/plugins/pipes/github-copilot-sdk.zh.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# GitHub Copilot SDK 官方管道
|
||||
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.6.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||
|
||||
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,深度集成了 **GitHub Copilot SDK**。它不仅支持 **GitHub Copilot 官方模型**(如 `gpt-5.2-codex`, `claude-sonnet-4.5`, `gemini-3-pro`, `gpt-5-mini`),还支持 **BYOK (自带 Key)** 模式对接自定义服务商(OpenAI, Anthropic),并具备**严格的用户与会话级工作区隔离**能力,提供统一且安全的 Agent 交互体验。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **核心伴侣组件**
|
||||
> 如需启用文件处理与数据分析能力,请务必安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
|
||||
|
||||
> [!TIP]
|
||||
> **BYOK 模式无需订阅**
|
||||
> 如果您使用自带的 API Key (BYOK 模式对接 OpenAI/Anthropic),**您不需要 GitHub Copilot 官方订阅**。只有在访问 GitHub 官方模型时才需要订阅。
|
||||
|
||||
---
|
||||
|
||||
## ✨ 0.6.0 更新内容 (What's New)
|
||||
|
||||
- **👥 多用户与会话管理**: 采用 `user_id/chat_id` 的物理隔离架构,确保资源独立与稳健管理。
|
||||
- **🤖 赋予 Agent 文件自主权**: 自动将上传的文件同步至物理工作区,支持 Python 直接分析 Excel/CSV。
|
||||
- **🔧 OpenAPI & 外部工具修复**: 完美支持通过 OpenAPI 服务器挂载的工具调用。
|
||||
- **📊 计费与成本控制**: 增强的**计费倍率限制** (`MAX_MULTIPLIER`,例如设为 0 即仅限免费模型) 和**模型关键词过滤** (`EXCLUDE_KEYWORDS`),实现更精准的成本管控。
|
||||
- **🧠 数据库持久化 TODO**: 任务进度跨会话保存,Agent 拥有更持久的任务 memory。
|
||||
|
||||
---
|
||||
|
||||
## ✨ 核心能力 (Key Capabilities)
|
||||
|
||||
- **🔑 灵活鉴权与 BYOK**: 支持 GitHub Copilot 官方订阅 (PAT) 或自带 Key (OpenAI/Anthropic)。
|
||||
- **🔌 通用工具协议**: 原生支持 **MCP (Model Context Protocol)**、OpenAPI 以及 OpenWebUI 内置工具。
|
||||
- **🛡️ 物理级工作区隔离**: 强制执行严格的用户特定沙箱,确保数据隐私与文件安全。
|
||||
- **♾️ 无限会话管理**: 智能上下文窗口管理与自动压缩算法,支持无限时长的对话交互。
|
||||
- **🧠 深度数据库集成**: 实时持久化 TOD·O 列表到 UI 进度条。
|
||||
- **🌊 深度推理展示**: 完整支持模型思考过程 (Thinking Process) 的流式渲染。
|
||||
- **🖼️ 智能多模态**: 完整支持图像识别与附件上传分析。
|
||||
- **⚡ 交互式伪影 (Artifacts)**: 自动渲染 Agent 生成的 HTML/JS 应用程序,直接在聊天界面交互。
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 核心配置参数 (Valves)
|
||||
|
||||
### 1. 管理员配置 (基础设置)
|
||||
|
||||
管理员可在函数设置中定义全局默认行为。
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| `GH_TOKEN` | `""` | 全局 GitHub Token (需具备 'Copilot Requests' 权限)。 |
|
||||
| `ENABLE_OPENWEBUI_TOOLS` | `True` | 启用 OpenWebUI 工具 (包括定义工具和内置工具)。 |
|
||||
| `ENABLE_OPENAPI_SERVER` | `True` | 启用 OpenAPI 工具服务器连接。 |
|
||||
| `ENABLE_MCP_SERVER` | `True` | 启用直接 MCP 客户端连接 (推荐)。 |
|
||||
| `REASONING_EFFORT` | `medium` | 推理强度:low, medium, high。 |
|
||||
| `SHOW_THINKING` | `True` | 显示模型推理/思考过程。 |
|
||||
| `INFINITE_SESSION` | `True` | 启用无限会话 (自动上下文压缩)。 |
|
||||
| `MAX_MULTIPLIER` | `1.0` | 最大允许的模型计费倍率 (0x 为仅限免费模型)。 |
|
||||
| `EXCLUDE_KEYWORDS` | `""` | 排除包含这些关键字的模型 (逗号分隔)。 |
|
||||
| `TIMEOUT` | `300` | 每个流数据块的超时时间 (秒)。 |
|
||||
| `BYOK_TYPE` | `openai` | BYOK 服务商类型:`openai`, `anthropic`。 |
|
||||
| `BYOK_BASE_URL` | `""` | BYOK 基础 URL (例如: <https://api.openai.com/v1>)。 |
|
||||
| `BYOK_MODELS` | `""` | BYOK 模型列表 (逗号分隔)。留空则从 API 获取。 |
|
||||
| `CUSTOM_ENV_VARS` | `""` | 自定义环境变量 (JSON 格式)。 |
|
||||
| `DEBUG` | `False` | 开启此项以在前端控制台输出详细调试日志。 |
|
||||
|
||||
### 2. 用户配置 (个人覆盖)
|
||||
|
||||
普通用户可在各自的个人设置中根据需要覆盖以下参数。
|
||||
|
||||
| 参数 | 说明 |
|
||||
| :--- | :--- |
|
||||
| `GH_TOKEN` | 使用个人的 GitHub Token。 |
|
||||
| `REASONING_EFFORT` | 个人偏好的推理强度。 |
|
||||
| `SHOW_THINKING` | 显示模型推理/思考过程。 |
|
||||
| `MAX_MULTIPLIER` | 最大允许的模型计费倍率覆盖。 |
|
||||
| `EXCLUDE_KEYWORDS` | 排除包含这些关键字的模型。 |
|
||||
| `BYOK_API_KEY` | 使用个人的 OpenAI/Anthropic API Key。 |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 典型应用场景 (Use Cases)
|
||||
|
||||
- **📁 全自主仓库维护**: Agent 在隔离工作区内自动分析代码、运行测试并应用补丁。
|
||||
- **📊 深度财务数据审计**: 直接通过 Python 加载 Excel/CSV 原始数据(绕过 RAG),生成图表并实时预览。
|
||||
- **📝 长任务项目管理**: 自动拆解复杂任务并持久化 TOD·O 进度,跨会话跟踪执行状态。
|
||||
|
||||
---
|
||||
|
||||
## ⭐ 支持与交流 (Support)
|
||||
|
||||
如果这个插件对您有所帮助,请在 [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 项目上点个 **Star** 💫,这是对我最大的鼓励。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 安装与配置 (Installation)
|
||||
|
||||
### 1) 导入函数
|
||||
|
||||
1. 打开 OpenWebUI,前往 **工作区** -> **函数**。
|
||||
2. 点击 **+** (创建函数),完整粘贴 `github_copilot_sdk_cn.py` 的内容。
|
||||
3. 点击保存并确保已启用。
|
||||
|
||||
### 2) 获取 Token (Get Token)
|
||||
|
||||
1. 访问 [GitHub Token 设置](https://github.com/settings/tokens?type=beta)。
|
||||
2. 创建 **Fine-grained token**,授予 **Account permissions** -> **Copilot Requests** 访问权限。
|
||||
3. 将生成的 Token 填入插件的 `GH_TOKEN` 配置项中。
|
||||
|
||||
---
|
||||
|
||||
## 📋 常见问题与依赖 (Troubleshooting)
|
||||
|
||||
- **Agent 无法识别文件?**: 请确保已安装并启用了 Files Filter 插件,否则原始文件会被 RAG 干扰。
|
||||
- **看不到 TODO 进度条?**: 进度条仅在 Agent 使用 `update_todo` 工具(通常是处理复杂任务)时出现。
|
||||
- **依赖安装**: 本管道会自动尝试安装 `github-copilot-sdk` (Python 包) 和 `github-copilot-cli` (官方二进制)。
|
||||
|
||||
---
|
||||
|
||||
## 更新日志 (Changelog)
|
||||
|
||||
完整历史记录请见 GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
@@ -15,19 +15,7 @@ Pipes allow you to:
|
||||
|
||||
## Available Pipe Plugins
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :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>
|
||||
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.6.0) - Official GitHub Copilot SDK integration. Features **Workspace Isolation**, **Database Persistence**, **Zero-config OpenWebUI Tool Bridge**, **BYOK** support, and **dynamic MCP discovery**. Supports streaming, multimodal, and infinite sessions.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -15,19 +15,7 @@ Pipes 可以用于:
|
||||
|
||||
## 可用的 Pipe 插件
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-google:{ .lg .middle } **Gemini Manifold**
|
||||
|
||||
---
|
||||
|
||||
面向 Google Gemini 的集成流水线,支持完整流式返回。
|
||||
|
||||
**版本:** 1.0.0
|
||||
|
||||
[:octicons-arrow-right-24: 查看文档](gemini-manifold.md)
|
||||
|
||||
</div>
|
||||
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.6.0) - GitHub Copilot SDK 官方集成。具备**工作区安全隔离**、**数据库持久化**、**零配置工具桥接**与**BYOK (自带 Key) 支持**。支持流式输出、打字机思考过程及无限会话。[查看深度架构解析](github-copilot-sdk-deep-dive.zh.md)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
12
mkdocs.yml
12
mkdocs.yml
@@ -97,14 +97,14 @@ plugins:
|
||||
Documentation Guide: 文档编写指南
|
||||
Smart Mind Map: 智能思维导图
|
||||
Smart Infographic: 智能信息图
|
||||
Knowledge Card: 知识卡片
|
||||
Flash Card: 闪记卡
|
||||
Export to Excel: 导出到 Excel
|
||||
Export to Word: 导出为 Word
|
||||
Summary: 摘要
|
||||
Async Context Compression: 异步上下文压缩
|
||||
Context Enhancement: 上下文增强
|
||||
Gemini Manifold Companion: Gemini Manifold 伴侣
|
||||
Gemini Manifold: Gemini Manifold
|
||||
Multi-Model Context Merger: 多模型上下文合并
|
||||
Web Gemini Multimodal Filter: Web Gemini 多模态过滤器
|
||||
MoE Prompt Refiner: MoE 提示词优化器
|
||||
- minify:
|
||||
minify_html: true
|
||||
@@ -184,17 +184,17 @@ nav:
|
||||
- plugins/actions/index.md
|
||||
- Smart Mind Map: plugins/actions/smart-mind-map.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 Word: plugins/actions/export-to-word.md
|
||||
- Filters:
|
||||
- plugins/filters/index.md
|
||||
- Async Context Compression: plugins/filters/async-context-compression.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:
|
||||
- plugins/pipes/index.md
|
||||
- Gemini Manifold: plugins/pipes/gemini-manifold.md
|
||||
- Pipelines:
|
||||
- plugins/pipelines/index.md
|
||||
- MoE Prompt Refiner: plugins/pipelines/moe-prompt-refiner.md
|
||||
|
||||
@@ -124,10 +124,6 @@ Each plugin should include:
|
||||
Fu-Jie
|
||||
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.
|
||||
|
||||
@@ -124,10 +124,6 @@ plugins/
|
||||
Fu-Jie
|
||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
---
|
||||
|
||||
> **注意**:有关每种插件类型的详细信息,请参阅每个插件类型目录中的相应 README 文件。
|
||||
|
||||
@@ -230,7 +230,3 @@ except Exception as e:
|
||||
|
||||
Fu-Jie
|
||||
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
|
||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 🌊 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.
|
||||
|
||||
@@ -39,6 +39,10 @@ A comprehensive thinking lens that dives deep into any content - from context to
|
||||
| **Clear Previous HTML (CLEAR_PREVIOUS_HTML)** | `True` | Whether to clear previous plugin results. |
|
||||
| **Message Count (MESSAGE_COUNT)** | `1` | Number of recent messages to analyze. |
|
||||
|
||||
## ⭐ Support
|
||||
|
||||
If this plugin has been useful, a star on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) is a big motivation for me. Thank you for the support.
|
||||
|
||||
## 🌗 Theme Support
|
||||
|
||||
The plugin automatically detects and adapts to OpenWebUI's theme settings:
|
||||
@@ -48,7 +52,7 @@ The plugin automatically detects and adapts to OpenWebUI's theme settings:
|
||||
2. Parent document `html/body` class or `data-theme` attribute
|
||||
3. System preference via `prefers-color-scheme: dark`
|
||||
|
||||
- **Requirements**: For best results, enable **iframe Sandbox Allow Same Origin** in OpenWebUI:
|
||||
- **Requirements**: For best results, enable **iframe Sandbox Allow Same Origin** in OpenWebUI:
|
||||
- Go to **Settings** → **Interface** → **Artifacts** → Check **iframe Sandbox Allow Same Origin**
|
||||
|
||||
## 🎨 Visual Preview
|
||||
@@ -81,3 +85,14 @@ The plugin generates a structured thinking timeline:
|
||||
|
||||
- `deep_dive.py` - English 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)
|
||||
|
||||
## Changelog
|
||||
|
||||
See the full history on GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
@@ -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
|
||||
|
||||
全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
| **清除旧 HTML (CLEAR_PREVIOUS_HTML)** | `True` | 是否清除之前的插件结果。 |
|
||||
| **消息数量 (MESSAGE_COUNT)** | `1` | 要分析的最近消息数量。 |
|
||||
|
||||
## ⭐ 支持
|
||||
|
||||
如果这个插件对你有帮助,欢迎到 [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 点个 Star,这将是我持续改进的动力,感谢支持。
|
||||
|
||||
## 🌗 主题支持
|
||||
|
||||
插件会自动检测并适配 OpenWebUI 的主题设置:
|
||||
@@ -81,3 +85,14 @@
|
||||
|
||||
- `deep_dive.py` - 英文版 (Deep Dive)
|
||||
- `deep_dive_cn.py` - 中文版 (精读)
|
||||
|
||||
## 故障排除 (Troubleshooting) ❓
|
||||
|
||||
- **插件不工作?**: 请检查是否在模型设置中启用了该过滤器/动作。
|
||||
- **调试日志**: 在 Valves 中启用 `SHOW_STATUS` 以查看进度更新。
|
||||
- **错误信息**: 如果看到错误,请复制完整的错误信息并报告。
|
||||
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||
|
||||
## 更新日志
|
||||
|
||||
完整历史请查看 GitHub 项目: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
title: Deep Dive
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie
|
||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 1.0.0
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg==
|
||||
requirements: markdown
|
||||
@@ -466,6 +466,10 @@ class Action:
|
||||
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.",
|
||||
)
|
||||
MODEL_ID: str = Field(
|
||||
default="",
|
||||
description="LLM Model ID for analysis. Empty = use current model.",
|
||||
@@ -501,6 +505,42 @@ class Action:
|
||||
"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]:
|
||||
"""Parse LLM output and convert to styled HTML."""
|
||||
# Extract sections using flexible regex
|
||||
@@ -700,6 +740,26 @@ class Action:
|
||||
{"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:
|
||||
"""Removes existing plugin-generated HTML."""
|
||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
title: 精读
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie
|
||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 1.0.0
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg==
|
||||
requirements: markdown
|
||||
@@ -466,6 +466,10 @@ class Action:
|
||||
default=True,
|
||||
description="是否显示操作状态更新。",
|
||||
)
|
||||
SHOW_DEBUG_LOG: bool = Field(
|
||||
default=False,
|
||||
description="是否在浏览器控制台打印调试日志。",
|
||||
)
|
||||
MODEL_ID: str = Field(
|
||||
default="",
|
||||
description="用于分析的 LLM 模型 ID。留空则使用当前模型。",
|
||||
@@ -501,6 +505,42 @@ class Action:
|
||||
"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]:
|
||||
"""解析 LLM 输出并转换为样式化 HTML。"""
|
||||
# 使用灵活的正则提取各部分
|
||||
@@ -694,6 +734,26 @@ class Action:
|
||||
{"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:
|
||||
"""移除已有的插件生成的 HTML。"""
|
||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# 📝 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.4 | **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**.
|
||||
|
||||
## 🔥 What's New in v0.4.3
|
||||
## 🔥 What's New in v0.4.4
|
||||
|
||||
- ✨ **S3 Object Storage Support**: Direct access to images stored in S3/MinIO via boto3, bypassing API layer for faster exports.
|
||||
- 🔧 **Multi-level File Fallback**: 6-level fallback mechanism for file retrieval (DB → S3 → Local → URL → API → Attributes).
|
||||
- 🛡️ **Improved Error Handling**: Better logging and error messages for file retrieval failures.
|
||||
- 🧹 **Content Cleanup**: Enhanced stripping of `<details>` blocks (often used for tool calls/thinking process) to ensure a clean final document.
|
||||
- 📄 **Standard Document Formatting**: Applied professional document formatting standards for titles and headings (centered title, bold, optimized font sizes and spacing), including GB/T compliance for Chinese content.
|
||||
- 🔠 **Font Rendering Fix**: Fixed an issue where CJK characters would fallback to MS Gothic in Word; now correctly uses the configured Asian font (e.g., SimSun).
|
||||
- ⚙️ **Title Alignment**: Added `TITLE_ALIGNMENT` valve to configure document title alignment (left, center, right).
|
||||
|
||||
## ✨ Key Features
|
||||
|
||||
@@ -44,6 +45,10 @@ Export conversation to Word (.docx) with **syntax highlighting**, **native math
|
||||
| **Mermaid PNG Scale** | `3.0` | Resolution multiplier for Mermaid images |
|
||||
| **Math Enable** | `True` | Enable LaTeX math conversion |
|
||||
|
||||
## ⭐ Support
|
||||
|
||||
If this plugin has been useful, a star on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) is a big motivation for me. Thank you for the support.
|
||||
|
||||
## 🛠️ Supported Markdown Syntax
|
||||
|
||||
| Syntax | Word Result |
|
||||
@@ -71,18 +76,13 @@ Export conversation to Word (.docx) with **syntax highlighting**, **native math
|
||||
- `latex2mathml` - LaTeX to MathML conversion
|
||||
- `mathml2omml` - MathML to Office Math (OMML) conversion
|
||||
|
||||
## 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)
|
||||
|
||||
## 📝 Changelog
|
||||
|
||||
### v0.4.3
|
||||
- **S3 Object Storage**: Direct S3/MinIO access via boto3 for faster image retrieval.
|
||||
- **6-Level Fallback**: Robust file retrieval: DB → S3 → Local → URL → API → Attributes.
|
||||
- **Better Logging**: Improved error messages for debugging file access issues.
|
||||
|
||||
### v0.4.1
|
||||
- **Chinese Parameter Names**: Localized configuration names for Chinese version.
|
||||
|
||||
### v0.4.0
|
||||
- **Multi-language Support**: UI language switching (English/Chinese).
|
||||
- **Font & Style Configuration**: Customizable fonts and table colors.
|
||||
- **Mermaid Enhancements**: Hybrid SVG+PNG rendering, background color config.
|
||||
- **Performance**: Real-time progress updates for large exports.
|
||||
See the full history on GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# 📝 导出为 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.4 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||
|
||||
将对话导出为 Word (.docx),支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用参考**和**增强表格格式**。
|
||||
|
||||
## 🔥 v0.4.3 更新内容
|
||||
## 🔥 v0.4.4 更新内容
|
||||
|
||||
- ✨ **S3 对象存储支持**: 通过 boto3 直连 S3/MinIO,绕过 API 层,导出速度更快。
|
||||
- 🔧 **多级文件回退**: 6 级文件获取机制(数据库 → S3 → 本地 → URL → API → 属性)。
|
||||
- 🛡️ **错误处理优化**: 更完善的日志记录和错误提示,便于调试文件访问问题。
|
||||
- 🧹 **内容清理加强**: 增强了对 `<details>` 块(通常包含工具调用或思考过程)的清理,确保最终文档整洁。
|
||||
- 📄 **文档格式标准化**: 采用了专业的文档排版标准(兼容中文 GB/T 规范),标题居中加粗,各级标题使用标准字号和间距。
|
||||
- 🔠 **字体渲染修复**: 修复了 CJK 字符在 Word 中回退到 MS Gothic 的问题;现在正确使用配置的中文字体(例如宋体)。
|
||||
- ⚙️ **标题对齐配置**: 新增 `标题对齐方式` Valve,支持配置文档标题的对齐方式(左对齐、居中、右对齐)。
|
||||
|
||||
## ✨ 核心特性
|
||||
|
||||
@@ -44,6 +45,10 @@
|
||||
| **Mermaid_PNG缩放比例** | `3.0` | Mermaid 图片分辨率倍数 |
|
||||
| **启用数学公式** | `True` | 启用 LaTeX 公式转换 |
|
||||
|
||||
## ⭐ 支持
|
||||
|
||||
如果这个插件对你有帮助,欢迎到 [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 点个 Star,这将是我持续改进的动力,感谢支持。
|
||||
|
||||
## 🛠️ 支持的 Markdown 语法
|
||||
|
||||
| 语法 | Word 效果 |
|
||||
@@ -71,18 +76,13 @@
|
||||
- `latex2mathml` - LaTeX 转 MathML
|
||||
- `mathml2omml` - MathML 转 Office Math (OMML)
|
||||
|
||||
## 故障排除 (Troubleshooting) ❓
|
||||
|
||||
- **插件不工作?**: 请检查是否在模型设置中启用了该过滤器/动作。
|
||||
- **调试日志**: 请查看浏览器控制台 (F12) 获取详细日志(如果可用)。
|
||||
- **错误信息**: 如果看到错误,请复制完整的错误信息并报告。
|
||||
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### v0.4.3
|
||||
- **S3 对象存储**: 通过 boto3 直连 S3/MinIO,图片获取速度更快。
|
||||
- **6 级回退机制**: 稳健的文件获取:数据库 → S3 → 本地 → URL → API → 属性。
|
||||
- **日志优化**: 改进错误提示,便于调试文件访问问题。
|
||||
|
||||
### v0.4.1
|
||||
- **中文参数名**: 配置项名称和描述全部汉化。
|
||||
|
||||
### v0.4.0
|
||||
- **多语言支持**: 界面语言切换(中文/英文)。
|
||||
- **字体与样式配置**: 支持自定义中英文字体、代码字体以及表格颜色。
|
||||
- **Mermaid 增强**: 混合 SVG+PNG 渲染,支持背景色配置。
|
||||
- **性能优化**: 导出大型文档时提供实时进度反馈。
|
||||
完整历史请查看 GitHub 项目: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""
|
||||
title: Export to Word (Enhanced)
|
||||
title: Export to Word Enhanced
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie
|
||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
version: 0.4.3
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.4.4
|
||||
openwebui_id: fca6a315-2a45-42cc-8c96-55cbc85f87f2
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
|
||||
requirements: python-docx, Pygments, latex2mathml, mathml2omml
|
||||
@@ -101,9 +101,8 @@ _TRANSPARENT_1PX_PNG = base64.b64decode(
|
||||
_ASVG_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main"
|
||||
nsmap.setdefault("asvg", _ASVG_NS)
|
||||
|
||||
_REASONING_DETAILS_RE = re.compile(
|
||||
r"<details\b[^>]*\btype\s*=\s*(?:\"reasoning\"|'reasoning'|reasoning)[^>]*>.*?</details\s*>",
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
_ALL_DETAILS_RE = re.compile(
|
||||
r"<details\b[^>]*>.*?</details\s*>", re.IGNORECASE | re.DOTALL
|
||||
)
|
||||
_THINK_RE = re.compile(r"<think\b[^>]*>.*?</think\s*>", re.IGNORECASE | re.DOTALL)
|
||||
_ANALYSIS_RE = re.compile(
|
||||
@@ -150,6 +149,14 @@ class Action:
|
||||
default="chat_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(
|
||||
default=20,
|
||||
@@ -170,6 +177,12 @@ class Action:
|
||||
description="Font for code blocks and inline code (e.g., 'Consolas', 'Courier New', 'Monaco')",
|
||||
)
|
||||
|
||||
# Title alignment
|
||||
TITLE_ALIGNMENT: str = Field(
|
||||
default="center",
|
||||
description="Title alignment: 'left', 'center', or 'right'",
|
||||
)
|
||||
|
||||
# Table styling
|
||||
TABLE_HEADER_COLOR: str = Field(
|
||||
default="F2F2F2",
|
||||
@@ -234,60 +247,60 @@ class Action:
|
||||
)
|
||||
|
||||
class UserValves(BaseModel):
|
||||
TITLE_SOURCE: str = Field(
|
||||
default="chat_title",
|
||||
TITLE_SOURCE: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
|
||||
)
|
||||
UI_LANGUAGE: str = Field(
|
||||
default="en",
|
||||
UI_LANGUAGE: Optional[str] = Field(
|
||||
default=None,
|
||||
description="UI language for export messages. Options: 'en' (English), 'zh' (Chinese)",
|
||||
)
|
||||
FONT_LATIN: str = Field(
|
||||
default="Times New Roman",
|
||||
FONT_LATIN: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Font for Latin characters (e.g., 'Times New Roman', 'Calibri', 'Arial')",
|
||||
)
|
||||
FONT_ASIAN: str = Field(
|
||||
default="SimSun",
|
||||
FONT_ASIAN: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Font for Asian characters (e.g., 'SimSun', 'Microsoft YaHei', 'PingFang SC')",
|
||||
)
|
||||
FONT_CODE: str = Field(
|
||||
default="Consolas",
|
||||
FONT_CODE: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Font for code blocks and inline code (e.g., 'Consolas', 'Courier New', 'Monaco')",
|
||||
)
|
||||
TABLE_HEADER_COLOR: str = Field(
|
||||
default="F2F2F2",
|
||||
TABLE_HEADER_COLOR: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Table header background color (hex, without #)",
|
||||
)
|
||||
TABLE_ZEBRA_COLOR: str = Field(
|
||||
default="FBFBFB",
|
||||
TABLE_ZEBRA_COLOR: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Table zebra stripe background color for alternate rows (hex, without #)",
|
||||
)
|
||||
MERMAID_PNG_SCALE: float = Field(
|
||||
default=3.0,
|
||||
MERMAID_PNG_SCALE: Optional[float] = Field(
|
||||
default=None,
|
||||
description="PNG render resolution multiplier (higher = clearer, larger file)",
|
||||
)
|
||||
MERMAID_DISPLAY_SCALE: float = Field(
|
||||
default=1.0,
|
||||
MERMAID_DISPLAY_SCALE: Optional[float] = Field(
|
||||
default=None,
|
||||
description="Diagram width relative to available page width (<=1 recommended)",
|
||||
)
|
||||
MERMAID_OPTIMIZE_LAYOUT: bool = Field(
|
||||
default=False,
|
||||
MERMAID_OPTIMIZE_LAYOUT: Optional[bool] = Field(
|
||||
default=None,
|
||||
description="Optimize Mermaid layout: convert LR to TD for graph/flowchart",
|
||||
)
|
||||
MERMAID_BACKGROUND: str = Field(
|
||||
default="",
|
||||
MERMAID_BACKGROUND: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Mermaid background color. Empty = transparent (recommended for Word dark mode). Used only for optional PNG fill.",
|
||||
)
|
||||
MERMAID_CAPTIONS_ENABLE: bool = Field(
|
||||
default=True,
|
||||
MERMAID_CAPTIONS_ENABLE: Optional[bool] = Field(
|
||||
default=None,
|
||||
description="Add figure captions under Mermaid images/charts",
|
||||
)
|
||||
MATH_ENABLE: bool = Field(
|
||||
default=True,
|
||||
MATH_ENABLE: Optional[bool] = Field(
|
||||
default=None,
|
||||
description="Enable LaTeX math block conversion (\\\\[...\\\\] and $$...$$) into Word equations",
|
||||
)
|
||||
MATH_INLINE_DOLLAR_ENABLE: bool = Field(
|
||||
default=True,
|
||||
MATH_INLINE_DOLLAR_ENABLE: Optional[bool] = Field(
|
||||
default=None,
|
||||
description="Enable inline $...$ math conversion into Word equations (conservative parsing to reduce false positives)",
|
||||
)
|
||||
|
||||
@@ -320,10 +333,100 @@ class Action:
|
||||
return msg
|
||||
return msg
|
||||
|
||||
async def _send_notification(self, emitter: Callable, type: str, content: str):
|
||||
await emitter(
|
||||
{"type": "notification", "data": {"type": type, "content": content}}
|
||||
)
|
||||
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> 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", "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(
|
||||
self,
|
||||
@@ -351,13 +454,21 @@ class Action:
|
||||
user_id = __user__.get("id", "unknown_user")
|
||||
|
||||
# Apply UserValves if present
|
||||
if __user__ and "valves" in __user__:
|
||||
# Update self.valves with user-specific values
|
||||
# Note: This assumes per-request instantiation or that we are okay with modifying the singleton.
|
||||
# Given the plugin architecture, we'll update it for this execution.
|
||||
for key, value in __user__["valves"].model_dump().items():
|
||||
if hasattr(self.valves, key):
|
||||
setattr(self.valves, key, value)
|
||||
if __user__:
|
||||
# Robustly parse UserValves whether it's a dict or Pydantic model
|
||||
raw_valves = __user__.get("valves", {})
|
||||
if isinstance(raw_valves, self.UserValves):
|
||||
user_valves = raw_valves
|
||||
elif isinstance(raw_valves, dict):
|
||||
user_valves = self.UserValves(**raw_valves)
|
||||
else:
|
||||
user_valves = None
|
||||
|
||||
if user_valves:
|
||||
for key, value in user_valves.model_dump(exclude_unset=True).items():
|
||||
# Only override if the value is not None (and explicitly set)
|
||||
if hasattr(self.valves, key) and value is not None:
|
||||
setattr(self.valves, key, value)
|
||||
|
||||
# Get user language from Valves configuration
|
||||
self._user_lang = self._get_lang_key(self.valves.UI_LANGUAGE)
|
||||
@@ -394,17 +505,49 @@ class Action:
|
||||
try:
|
||||
message_content = last_assistant_message["content"]
|
||||
if isinstance(message_content, str):
|
||||
if __event_emitter__ and self.valves.SHOW_DEBUG_LOG:
|
||||
debug_data = {}
|
||||
for name, regex in [
|
||||
("Details Block", _ALL_DETAILS_RE),
|
||||
("Think Block", _THINK_RE),
|
||||
("Analysis Block", _ANALYSIS_RE),
|
||||
]:
|
||||
matches = regex.findall(message_content)
|
||||
if matches:
|
||||
debug_data[name] = [
|
||||
(m[:200] + "...") if len(m) > 200 else m
|
||||
for m in matches
|
||||
]
|
||||
if debug_data:
|
||||
await self._emit_debug_log(
|
||||
__event_emitter__,
|
||||
"Context Stripping Analysis",
|
||||
debug_data,
|
||||
)
|
||||
|
||||
# Log font configuration
|
||||
await self._emit_debug_log(
|
||||
__event_emitter__,
|
||||
"Font Configuration",
|
||||
{
|
||||
"Latin Font": self.valves.FONT_LATIN,
|
||||
"Asian Font": self.valves.FONT_ASIAN,
|
||||
"Code Font": self.valves.FONT_CODE,
|
||||
},
|
||||
)
|
||||
|
||||
message_content = self._strip_reasoning_blocks(message_content)
|
||||
|
||||
if not message_content or not message_content.strip():
|
||||
await self._send_notification(
|
||||
__event_emitter__, "error", self._get_msg("error_no_content")
|
||||
await self._emit_notification(
|
||||
__event_emitter__, self._get_msg("error_no_content"), "error"
|
||||
)
|
||||
return
|
||||
|
||||
# Generate filename
|
||||
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
|
||||
chat_title = ""
|
||||
@@ -873,10 +1016,10 @@ class Action:
|
||||
}
|
||||
)
|
||||
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
__event_emitter__,
|
||||
"success",
|
||||
self._get_msg("success", filename=filename),
|
||||
"success",
|
||||
)
|
||||
|
||||
return {"message": "Download triggered"}
|
||||
@@ -892,10 +1035,10 @@ class Action:
|
||||
},
|
||||
}
|
||||
)
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
__event_emitter__,
|
||||
"error",
|
||||
self._get_msg("error_export", error=str(e)),
|
||||
"error",
|
||||
)
|
||||
|
||||
async def generate_title_using_ai(
|
||||
@@ -1008,30 +1151,7 @@ class Action:
|
||||
if not isinstance(name, str):
|
||||
return ""
|
||||
|
||||
def _is_emoji_codepoint(codepoint: int) -> bool:
|
||||
# Common emoji ranges + flag regional indicators.
|
||||
return (
|
||||
0x1F000 <= codepoint <= 0x1FAFF
|
||||
or 0x1F1E6 <= codepoint <= 0x1F1FF
|
||||
or 0x2600 <= codepoint <= 0x26FF
|
||||
or 0x2700 <= codepoint <= 0x27BF
|
||||
or 0x2300 <= codepoint <= 0x23FF
|
||||
or 0x2B00 <= codepoint <= 0x2BFF
|
||||
)
|
||||
|
||||
def _is_emoji_modifier(codepoint: int) -> bool:
|
||||
# VS15/VS16, ZWJ, keycap, skin tones, and tag characters used in some emoji sequences.
|
||||
return (
|
||||
codepoint in (0x200D, 0xFE0E, 0xFE0F, 0x20E3)
|
||||
or 0x1F3FB <= codepoint <= 0x1F3FF
|
||||
or 0xE0020 <= codepoint <= 0xE007F
|
||||
)
|
||||
|
||||
without_emoji = "".join(
|
||||
ch
|
||||
for ch in name
|
||||
if not (_is_emoji_codepoint(ord(ch)) or _is_emoji_modifier(ord(ch)))
|
||||
)
|
||||
without_emoji = self._remove_emojis(name)
|
||||
cleaned = re.sub(r'[\\/*?:"<>|]', "", without_emoji)
|
||||
cleaned = re.sub(r"\s+", " ", cleaned).strip().strip(".")
|
||||
return cleaned[:50].strip()
|
||||
@@ -1399,7 +1519,10 @@ class Action:
|
||||
|
||||
# If there is no h1 in content, prepend chat title as h1 when provided
|
||||
if top_heading and not has_h1:
|
||||
self.add_heading(doc, top_heading, 1)
|
||||
# Remove emojis from title for a professional look
|
||||
clean_title = self._remove_emojis(top_heading)
|
||||
# Use Title style (level 0) for the main document title
|
||||
self.add_heading(doc, clean_title, 0)
|
||||
|
||||
lines = markdown_text.split("\n")
|
||||
i = 0
|
||||
@@ -1659,7 +1782,7 @@ class Action:
|
||||
cur = text
|
||||
for _ in range(10):
|
||||
prev = cur
|
||||
cur = _REASONING_DETAILS_RE.sub("", cur)
|
||||
cur = _ALL_DETAILS_RE.sub("", cur)
|
||||
cur = _THINK_RE.sub("", cur)
|
||||
cur = _ANALYSIS_RE.sub("", cur)
|
||||
if cur == prev:
|
||||
@@ -2143,14 +2266,155 @@ class Action:
|
||||
font = style.font
|
||||
font.name = self.valves.FONT_LATIN
|
||||
font.size = Pt(11)
|
||||
# Set Asian font
|
||||
style._element.rPr.rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
|
||||
|
||||
# Ensure rPr element exists
|
||||
rPr = style._element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
|
||||
# Set Latin and Asian fonts explicitly
|
||||
rFonts.set(qn("w:ascii"), self.valves.FONT_LATIN)
|
||||
rFonts.set(qn("w:hAnsi"), self.valves.FONT_LATIN)
|
||||
rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
|
||||
|
||||
# Set language to zh-CN to prevent MS Gothic fallback (Japanese font)
|
||||
# Even for English interface, we want to prioritize Chinese glyphs over Japanese for CJK
|
||||
lang = rPr.find(qn("w:lang"))
|
||||
if lang is None:
|
||||
lang = OxmlElement("w:lang")
|
||||
rPr.append(lang)
|
||||
lang.set(qn("w:val"), "en-US")
|
||||
lang.set(qn("w:eastAsia"), "zh-CN")
|
||||
|
||||
logger.info(
|
||||
f"[Font Config] Latin: {self.valves.FONT_LATIN}, Asian: {self.valves.FONT_ASIAN}"
|
||||
)
|
||||
|
||||
# Set paragraph format
|
||||
paragraph_format = style.paragraph_format
|
||||
paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
|
||||
paragraph_format.space_after = Pt(6)
|
||||
|
||||
# Configure Title style (used for document title)
|
||||
# Standard format: 22pt (二号), bold, centered, 24pt space after
|
||||
if "Title" in doc.styles:
|
||||
title_style = doc.styles["Title"]
|
||||
title_font = title_style.font
|
||||
title_font.name = self.valves.FONT_LATIN
|
||||
title_font.size = Pt(22) # 二号字体
|
||||
title_font.bold = True
|
||||
title_font.color.rgb = RGBColor(0, 0, 0)
|
||||
|
||||
# Set paragraph format: alignment based on configuration
|
||||
title_pf = title_style.paragraph_format
|
||||
alignment_map = {
|
||||
"left": WD_ALIGN_PARAGRAPH.LEFT,
|
||||
"center": WD_ALIGN_PARAGRAPH.CENTER,
|
||||
"right": WD_ALIGN_PARAGRAPH.RIGHT,
|
||||
}
|
||||
title_pf.alignment = alignment_map.get(
|
||||
self.valves.TITLE_ALIGNMENT.lower(), WD_ALIGN_PARAGRAPH.CENTER
|
||||
)
|
||||
title_pf.space_before = Pt(0)
|
||||
title_pf.space_after = Pt(24)
|
||||
|
||||
t_rPr = title_style._element.get_or_add_rPr()
|
||||
t_rFonts = t_rPr.get_or_add_rFonts()
|
||||
t_rFonts.set(qn("w:ascii"), self.valves.FONT_LATIN)
|
||||
t_rFonts.set(qn("w:hAnsi"), self.valves.FONT_LATIN)
|
||||
t_rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
|
||||
|
||||
# Set language to zh-CN
|
||||
t_lang = t_rPr.find(qn("w:lang"))
|
||||
if t_lang is None:
|
||||
t_lang = OxmlElement("w:lang")
|
||||
t_rPr.append(t_lang)
|
||||
t_lang.set(qn("w:val"), "en-US")
|
||||
t_lang.set(qn("w:eastAsia"), "zh-CN")
|
||||
|
||||
# Standard heading sizes based on Chinese document standards:
|
||||
# Heading 1: 16pt (三号), bold, space before 24pt, space after 12pt
|
||||
# Heading 2: 15pt (小三), bold, space before 18pt, space after 6pt
|
||||
# Heading 3: 14pt (四号), bold, space before 12pt, space after 6pt
|
||||
# Heading 4-9: 12pt (小四), bold, gradually reduced spacing
|
||||
heading_formats = {
|
||||
1: {"size": 16, "space_before": 24, "space_after": 12},
|
||||
2: {"size": 15, "space_before": 18, "space_after": 6},
|
||||
3: {"size": 14, "space_before": 12, "space_after": 6},
|
||||
4: {"size": 12, "space_before": 12, "space_after": 6},
|
||||
5: {"size": 12, "space_before": 6, "space_after": 6},
|
||||
6: {"size": 11, "space_before": 6, "space_after": 3},
|
||||
7: {"size": 11, "space_before": 6, "space_after": 3},
|
||||
8: {"size": 10.5, "space_before": 6, "space_after": 3},
|
||||
9: {"size": 10.5, "space_before": 6, "space_after": 3},
|
||||
}
|
||||
|
||||
# Apply font settings to Heading 1-9
|
||||
for i in range(1, 10):
|
||||
style_id = f"Heading {i}"
|
||||
if style_id in doc.styles:
|
||||
heading_style = doc.styles[style_id]
|
||||
heading_font = heading_style.font
|
||||
heading_font.name = self.valves.FONT_LATIN
|
||||
heading_font.color.rgb = RGBColor(0, 0, 0)
|
||||
|
||||
# Apply standard formatting
|
||||
fmt = heading_formats.get(
|
||||
i, {"size": 11, "space_before": 6, "space_after": 3}
|
||||
)
|
||||
heading_font.size = Pt(fmt["size"])
|
||||
heading_font.bold = True
|
||||
|
||||
heading_pf = heading_style.paragraph_format
|
||||
heading_pf.space_before = Pt(fmt["space_before"])
|
||||
heading_pf.space_after = Pt(fmt["space_after"])
|
||||
|
||||
# Ensure rPr exists
|
||||
h_rPr = heading_style._element.get_or_add_rPr()
|
||||
h_rFonts = h_rPr.get_or_add_rFonts()
|
||||
|
||||
# Set fonts
|
||||
h_rFonts.set(qn("w:ascii"), self.valves.FONT_LATIN)
|
||||
h_rFonts.set(qn("w:hAnsi"), self.valves.FONT_LATIN)
|
||||
h_rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
|
||||
|
||||
# Set language to zh-CN
|
||||
h_lang = h_rPr.find(qn("w:lang"))
|
||||
if h_lang is None:
|
||||
h_lang = OxmlElement("w:lang")
|
||||
h_rPr.append(h_lang)
|
||||
h_lang.set(qn("w:val"), "en-US")
|
||||
h_lang.set(qn("w:eastAsia"), "zh-CN")
|
||||
|
||||
def _remove_emojis(self, text: str) -> str:
|
||||
"""Remove emojis from text based on unicode ranges."""
|
||||
if not isinstance(text, str):
|
||||
return ""
|
||||
|
||||
def _is_emoji_codepoint(codepoint: int) -> bool:
|
||||
# Common emoji ranges + flag regional indicators.
|
||||
return (
|
||||
0x1F000 <= codepoint <= 0x1FAFF
|
||||
or 0x1F1E6 <= codepoint <= 0x1F1FF
|
||||
or 0x2600 <= codepoint <= 0x26FF
|
||||
or 0x2700 <= codepoint <= 0x27BF
|
||||
or 0x2300 <= codepoint <= 0x23FF
|
||||
or 0x2B00 <= codepoint <= 0x2BFF
|
||||
)
|
||||
|
||||
def _is_emoji_modifier(codepoint: int) -> bool:
|
||||
# VS15/VS16, ZWJ, keycap, skin tones, and tag characters used in some emoji sequences.
|
||||
return (
|
||||
codepoint in (0x200D, 0xFE0E, 0xFE0F, 0x20E3)
|
||||
or 0x1F3FB <= codepoint <= 0x1F3FF
|
||||
or 0xE0020 <= codepoint <= 0xE007F
|
||||
)
|
||||
|
||||
return "".join(
|
||||
ch
|
||||
for ch in text
|
||||
if not (_is_emoji_codepoint(ord(ch)) or _is_emoji_modifier(ord(ch)))
|
||||
)
|
||||
|
||||
def add_heading(self, doc: Document, text: str, level: int):
|
||||
"""Add heading"""
|
||||
# Word heading levels start from 0, Markdown from 1
|
||||
@@ -2186,6 +2450,12 @@ class Action:
|
||||
if strike:
|
||||
run.font.strike = True
|
||||
|
||||
# Explicitly set East Asian font to prevent MS Gothic fallback
|
||||
# Word may not inherit w:eastAsia from style, causing Japanese font fallback for CJK
|
||||
rPr = run._element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
|
||||
|
||||
def _add_inline_code(self, paragraph, s: str):
|
||||
if s == "":
|
||||
return
|
||||
@@ -2585,7 +2855,11 @@ class Action:
|
||||
):
|
||||
u = self._normalize_url(url)
|
||||
if not u:
|
||||
paragraph.add_run(display_text or text)
|
||||
run = paragraph.add_run(display_text or text)
|
||||
# Set East Asian font to prevent MS Gothic fallback
|
||||
rPr = run._element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
|
||||
return
|
||||
|
||||
part = getattr(paragraph, "part", None)
|
||||
@@ -2594,6 +2868,10 @@ class Action:
|
||||
run = paragraph.add_run(display_text or text)
|
||||
run.font.color.rgb = RGBColor(0, 0, 255)
|
||||
run.font.underline = True
|
||||
# Set East Asian font to prevent MS Gothic fallback
|
||||
rPr = run._element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
|
||||
return
|
||||
|
||||
r_id = part.relate_to(u, RT.HYPERLINK, is_external=True)
|
||||
@@ -2607,6 +2885,11 @@ class Action:
|
||||
rStyle.set(qn("w:val"), "Hyperlink")
|
||||
rPr.append(rStyle)
|
||||
|
||||
# Set East Asian font to prevent MS Gothic fallback
|
||||
rFonts = OxmlElement("w:rFonts")
|
||||
rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
|
||||
rPr.append(rFonts)
|
||||
|
||||
color = OxmlElement("w:color")
|
||||
color.set(qn("w:val"), "0000FF")
|
||||
rPr.append(color)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""
|
||||
title: 导出为 Word (增强版)
|
||||
title: 导出为Word增强版
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie
|
||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
version: 0.4.3
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.4.4
|
||||
openwebui_id: 8a6306c0-d005-4e46-aaae-8db3532c9ed5
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
|
||||
requirements: python-docx, Pygments, latex2mathml, mathml2omml
|
||||
@@ -101,9 +101,8 @@ _TRANSPARENT_1PX_PNG = base64.b64decode(
|
||||
_ASVG_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main"
|
||||
nsmap.setdefault("asvg", _ASVG_NS)
|
||||
|
||||
_REASONING_DETAILS_RE = re.compile(
|
||||
r"<details\b[^>]*\btype\s*=\s*(?:\"reasoning\"|'reasoning'|reasoning)[^>]*>.*?</details\s*>",
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
_ALL_DETAILS_RE = re.compile(
|
||||
r"<details\b[^>]*>.*?</details\s*>", re.IGNORECASE | re.DOTALL
|
||||
)
|
||||
_THINK_RE = re.compile(r"<think\b[^>]*>.*?</think\s*>", re.IGNORECASE | re.DOTALL)
|
||||
_ANALYSIS_RE = re.compile(
|
||||
@@ -150,6 +149,14 @@ class Action:
|
||||
default="chat_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(
|
||||
default=20,
|
||||
@@ -170,6 +177,12 @@ class Action:
|
||||
description="Font for code blocks and inline code (e.g., 'Consolas', 'Courier New', 'Monaco')",
|
||||
)
|
||||
|
||||
# Title alignment
|
||||
标题对齐方式: str = Field(
|
||||
default="center",
|
||||
description="标题对齐方式: 'left' (左对齐), 'center' (居中), 或 'right' (右对齐)",
|
||||
)
|
||||
|
||||
# Table styling
|
||||
表头背景色: str = Field(
|
||||
default="F2F2F2",
|
||||
@@ -234,60 +247,60 @@ class Action:
|
||||
)
|
||||
|
||||
class UserValves(BaseModel):
|
||||
文档标题来源: str = Field(
|
||||
default="chat_title",
|
||||
文档标题来源: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
|
||||
)
|
||||
界面语言: str = Field(
|
||||
default="zh",
|
||||
界面语言: Optional[str] = Field(
|
||||
default=None,
|
||||
description="UI language for export messages. Options: 'en' (English), 'zh' (Chinese)",
|
||||
)
|
||||
英文字体: str = Field(
|
||||
default="Calibri",
|
||||
英文字体: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Font for Latin characters (e.g., 'Times New Roman', '', 'Arial')",
|
||||
)
|
||||
中文字体: str = Field(
|
||||
default="SimSun",
|
||||
中文字体: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Font for Asian characters (e.g., 'SimSun', 'Microsoft YaHei', 'PingFang SC')",
|
||||
)
|
||||
代码字体: str = Field(
|
||||
default="Consolas",
|
||||
代码字体: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Font for code blocks and inline code (e.g., 'Consolas', 'Courier New', 'Monaco')",
|
||||
)
|
||||
表头背景色: str = Field(
|
||||
default="F2F2F2",
|
||||
表头背景色: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Table header background color (hex, without #)",
|
||||
)
|
||||
表格隔行背景色: str = Field(
|
||||
default="FBFBFB",
|
||||
表格隔行背景色: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Table zebra stripe background color for alternate rows (hex, without #)",
|
||||
)
|
||||
Mermaid_PNG缩放比例: float = Field(
|
||||
default=3.0,
|
||||
Mermaid_PNG缩放比例: Optional[float] = Field(
|
||||
default=None,
|
||||
description="PNG render resolution multiplier (higher = clearer, larger file)",
|
||||
)
|
||||
Mermaid显示比例: float = Field(
|
||||
default=1.0,
|
||||
Mermaid显示比例: Optional[float] = Field(
|
||||
default=None,
|
||||
description="Diagram width relative to available page width (<=1 recommended)",
|
||||
)
|
||||
Mermaid布局优化: bool = Field(
|
||||
default=False,
|
||||
Mermaid布局优化: Optional[bool] = Field(
|
||||
default=None,
|
||||
description="Optimize Mermaid layout: convert LR to TD for graph/flowchart",
|
||||
)
|
||||
Mermaid背景色: str = Field(
|
||||
default="",
|
||||
Mermaid背景色: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Mermaid background color. Empty = transparent (recommended for Word dark mode). Used only for optional PNG fill.",
|
||||
)
|
||||
启用Mermaid图注: bool = Field(
|
||||
default=True,
|
||||
启用Mermaid图注: Optional[bool] = Field(
|
||||
default=None,
|
||||
description="Add figure captions under Mermaid images/charts",
|
||||
)
|
||||
启用数学公式: bool = Field(
|
||||
default=True,
|
||||
启用数学公式: Optional[bool] = Field(
|
||||
default=None,
|
||||
description="Enable LaTeX math block conversion (\\\\[...\\\\] and $$...$$) into Word equations",
|
||||
)
|
||||
启用行内公式: bool = Field(
|
||||
default=True,
|
||||
启用行内公式: Optional[bool] = Field(
|
||||
default=None,
|
||||
description="Enable inline $...$ math conversion into Word equations (conservative parsing to reduce false positives)",
|
||||
)
|
||||
|
||||
@@ -320,10 +333,100 @@ class Action:
|
||||
return msg
|
||||
return msg
|
||||
|
||||
async def _send_notification(self, emitter: Callable, type: str, content: str):
|
||||
await emitter(
|
||||
{"type": "notification", "data": {"type": type, "content": content}}
|
||||
)
|
||||
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> 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", "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(
|
||||
self,
|
||||
@@ -351,11 +454,21 @@ class Action:
|
||||
user_id = __user__.get("id", "unknown_user")
|
||||
|
||||
# Apply UserValves if present
|
||||
if __user__ and "valves" in __user__:
|
||||
# Update self.valves with user-specific values
|
||||
for key, value in __user__["valves"].model_dump().items():
|
||||
if hasattr(self.valves, key):
|
||||
setattr(self.valves, key, value)
|
||||
if __user__:
|
||||
# Robustly parse UserValves whether it's a dict or Pydantic model
|
||||
raw_valves = __user__.get("valves", {})
|
||||
if isinstance(raw_valves, self.UserValves):
|
||||
user_valves = raw_valves
|
||||
elif isinstance(raw_valves, dict):
|
||||
user_valves = self.UserValves(**raw_valves)
|
||||
else:
|
||||
user_valves = None
|
||||
|
||||
if user_valves:
|
||||
for key, value in user_valves.model_dump(exclude_unset=True).items():
|
||||
# Only override if the value is not None (and explicitly set)
|
||||
if hasattr(self.valves, key) and value is not None:
|
||||
setattr(self.valves, key, value)
|
||||
|
||||
# Get user language from Valves configuration
|
||||
self._user_lang = self._get_lang_key(self.valves.界面语言)
|
||||
@@ -392,17 +505,49 @@ class Action:
|
||||
try:
|
||||
message_content = last_assistant_message["content"]
|
||||
if isinstance(message_content, str):
|
||||
if __event_emitter__ and self.valves.SHOW_DEBUG_LOG:
|
||||
debug_data = {}
|
||||
for name, regex in [
|
||||
("Details Block (详情块)", _ALL_DETAILS_RE),
|
||||
("Think Block (思考块)", _THINK_RE),
|
||||
("Analysis Block (分析块)", _ANALYSIS_RE),
|
||||
]:
|
||||
matches = regex.findall(message_content)
|
||||
if matches:
|
||||
debug_data[name] = [
|
||||
(m[:200] + "...") if len(m) > 200 else m
|
||||
for m in matches
|
||||
]
|
||||
if debug_data:
|
||||
await self._emit_debug_log(
|
||||
__event_emitter__,
|
||||
"上下文内容清理分析 (Context Stripping Analysis)",
|
||||
debug_data,
|
||||
)
|
||||
|
||||
# Log font configuration
|
||||
await self._emit_debug_log(
|
||||
__event_emitter__,
|
||||
"字体配置 (Font Configuration)",
|
||||
{
|
||||
"英文字体 (Latin Font)": self.valves.英文字体,
|
||||
"中文字体 (Asian Font)": self.valves.中文字体,
|
||||
"代码字体 (Code Font)": self.valves.代码字体,
|
||||
},
|
||||
)
|
||||
|
||||
message_content = self._strip_reasoning_blocks(message_content)
|
||||
|
||||
if not message_content or not message_content.strip():
|
||||
await self._send_notification(
|
||||
__event_emitter__, "error", self._get_msg("error_no_content")
|
||||
await self._emit_notification(
|
||||
__event_emitter__, self._get_msg("error_no_content"), "error"
|
||||
)
|
||||
return
|
||||
|
||||
# Generate filename
|
||||
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
|
||||
chat_title = ""
|
||||
@@ -871,10 +1016,10 @@ class Action:
|
||||
}
|
||||
)
|
||||
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
__event_emitter__,
|
||||
"success",
|
||||
self._get_msg("success", filename=filename),
|
||||
"success",
|
||||
)
|
||||
|
||||
return {"message": "Download triggered"}
|
||||
@@ -890,10 +1035,10 @@ class Action:
|
||||
},
|
||||
}
|
||||
)
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
__event_emitter__,
|
||||
"error",
|
||||
self._get_msg("error_export", error=str(e)),
|
||||
"error",
|
||||
)
|
||||
|
||||
async def generate_title_using_ai(
|
||||
@@ -1002,34 +1147,11 @@ class Action:
|
||||
return title.strip() if isinstance(title, str) else ""
|
||||
|
||||
def clean_filename(self, name: str) -> str:
|
||||
"""Clean illegal characters from filename and strip emoji."""
|
||||
"""清理文件名中的非法字符并移除 Emoji"""
|
||||
if not isinstance(name, str):
|
||||
return ""
|
||||
|
||||
def _is_emoji_codepoint(codepoint: int) -> bool:
|
||||
# Common emoji ranges + flag regional indicators.
|
||||
return (
|
||||
0x1F000 <= codepoint <= 0x1FAFF
|
||||
or 0x1F1E6 <= codepoint <= 0x1F1FF
|
||||
or 0x2600 <= codepoint <= 0x26FF
|
||||
or 0x2700 <= codepoint <= 0x27BF
|
||||
or 0x2300 <= codepoint <= 0x23FF
|
||||
or 0x2B00 <= codepoint <= 0x2BFF
|
||||
)
|
||||
|
||||
def _is_emoji_modifier(codepoint: int) -> bool:
|
||||
# VS15/VS16, ZWJ, keycap, skin tones, and tag characters used in some emoji sequences.
|
||||
return (
|
||||
codepoint in (0x200D, 0xFE0E, 0xFE0F, 0x20E3)
|
||||
or 0x1F3FB <= codepoint <= 0x1F3FF
|
||||
or 0xE0020 <= codepoint <= 0xE007F
|
||||
)
|
||||
|
||||
without_emoji = "".join(
|
||||
ch
|
||||
for ch in name
|
||||
if not (_is_emoji_codepoint(ord(ch)) or _is_emoji_modifier(ord(ch)))
|
||||
)
|
||||
without_emoji = self._remove_emojis(name)
|
||||
cleaned = re.sub(r'[\\/*?:"<>|]', "", without_emoji)
|
||||
cleaned = re.sub(r"\s+", " ", cleaned).strip().strip(".")
|
||||
return cleaned[:50].strip()
|
||||
@@ -1397,7 +1519,10 @@ class Action:
|
||||
|
||||
# If there is no h1 in content, prepend chat title as h1 when provided
|
||||
if top_heading and not has_h1:
|
||||
self.add_heading(doc, top_heading, 1)
|
||||
# Remove emojis from title for a professional look
|
||||
clean_title = self._remove_emojis(top_heading)
|
||||
# Use Title style (level 0) for the main document title
|
||||
self.add_heading(doc, clean_title, 0)
|
||||
|
||||
lines = markdown_text.split("\n")
|
||||
i = 0
|
||||
@@ -1657,7 +1782,7 @@ class Action:
|
||||
cur = text
|
||||
for _ in range(10):
|
||||
prev = cur
|
||||
cur = _REASONING_DETAILS_RE.sub("", cur)
|
||||
cur = _ALL_DETAILS_RE.sub("", cur)
|
||||
cur = _THINK_RE.sub("", cur)
|
||||
cur = _ANALYSIS_RE.sub("", cur)
|
||||
if cur == prev:
|
||||
@@ -2141,14 +2266,154 @@ class Action:
|
||||
font = style.font
|
||||
font.name = self.valves.英文字体
|
||||
font.size = Pt(11)
|
||||
# Set Asian font
|
||||
style._element.rPr.rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
|
||||
|
||||
# Ensure rPr element exists
|
||||
rPr = style._element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
|
||||
# Set Latin and Asian fonts explicitly
|
||||
rFonts.set(qn("w:ascii"), self.valves.英文字体)
|
||||
rFonts.set(qn("w:hAnsi"), self.valves.英文字体)
|
||||
rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
|
||||
|
||||
# Set language to zh-CN to prevent MS Gothic fallback (Japanese font)
|
||||
lang = rPr.find(qn("w:lang"))
|
||||
if lang is None:
|
||||
lang = OxmlElement("w:lang")
|
||||
rPr.append(lang)
|
||||
lang.set(qn("w:val"), "en-US")
|
||||
lang.set(qn("w:eastAsia"), "zh-CN")
|
||||
|
||||
logger.info(
|
||||
f"[Font Config] Latin: {self.valves.英文字体}, Asian: {self.valves.中文字体}"
|
||||
)
|
||||
|
||||
# Set paragraph format
|
||||
paragraph_format = style.paragraph_format
|
||||
paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
|
||||
paragraph_format.space_after = Pt(6)
|
||||
|
||||
# 配置 Title 样式 (用于文档标题)
|
||||
# 标准格式: 22pt (二号), 加粗, 居中, 段后 24pt
|
||||
if "Title" in doc.styles:
|
||||
title_style = doc.styles["Title"]
|
||||
title_font = title_style.font
|
||||
title_font.name = self.valves.英文字体
|
||||
title_font.size = Pt(22) # 二号字体
|
||||
title_font.bold = True
|
||||
title_font.color.rgb = RGBColor(0, 0, 0)
|
||||
|
||||
# 段落格式: 根据配置设置对齐方式和间距
|
||||
title_pf = title_style.paragraph_format
|
||||
alignment_map = {
|
||||
"left": WD_ALIGN_PARAGRAPH.LEFT,
|
||||
"center": WD_ALIGN_PARAGRAPH.CENTER,
|
||||
"right": WD_ALIGN_PARAGRAPH.RIGHT,
|
||||
}
|
||||
title_pf.alignment = alignment_map.get(
|
||||
self.valves.标题对齐方式.lower(), WD_ALIGN_PARAGRAPH.CENTER
|
||||
)
|
||||
title_pf.space_before = Pt(0)
|
||||
title_pf.space_after = Pt(24)
|
||||
|
||||
t_rPr = title_style._element.get_or_add_rPr()
|
||||
t_rFonts = t_rPr.get_or_add_rFonts()
|
||||
t_rFonts.set(qn("w:ascii"), self.valves.英文字体)
|
||||
t_rFonts.set(qn("w:hAnsi"), self.valves.英文字体)
|
||||
t_rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
|
||||
|
||||
# Set language for Title
|
||||
t_lang = t_rPr.find(qn("w:lang"))
|
||||
if t_lang is None:
|
||||
t_lang = OxmlElement("w:lang")
|
||||
t_rPr.append(t_lang)
|
||||
t_lang.set(qn("w:val"), "en-US")
|
||||
t_lang.set(qn("w:eastAsia"), "zh-CN")
|
||||
|
||||
# 标准标题字号 (基于中文文档规范):
|
||||
# Heading 1: 16pt (三号), 加粗, 段前 24pt, 段后 12pt
|
||||
# Heading 2: 15pt (小三), 加粗, 段前 18pt, 段后 6pt
|
||||
# Heading 3: 14pt (四号), 加粗, 段前 12pt, 段后 6pt
|
||||
# Heading 4-9: 12pt (小四), 加粗, 逐级减小间距
|
||||
heading_formats = {
|
||||
1: {"size": 16, "space_before": 24, "space_after": 12},
|
||||
2: {"size": 15, "space_before": 18, "space_after": 6},
|
||||
3: {"size": 14, "space_before": 12, "space_after": 6},
|
||||
4: {"size": 12, "space_before": 12, "space_after": 6},
|
||||
5: {"size": 12, "space_before": 6, "space_after": 6},
|
||||
6: {"size": 11, "space_before": 6, "space_after": 3},
|
||||
7: {"size": 11, "space_before": 6, "space_after": 3},
|
||||
8: {"size": 10.5, "space_before": 6, "space_after": 3},
|
||||
9: {"size": 10.5, "space_before": 6, "space_after": 3},
|
||||
}
|
||||
|
||||
# Apply font settings to Heading 1-9
|
||||
for i in range(1, 10):
|
||||
style_id = f"Heading {i}"
|
||||
if style_id in doc.styles:
|
||||
heading_style = doc.styles[style_id]
|
||||
heading_font = heading_style.font
|
||||
heading_font.name = self.valves.英文字体
|
||||
heading_font.color.rgb = RGBColor(0, 0, 0)
|
||||
|
||||
# 应用标准格式
|
||||
fmt = heading_formats.get(
|
||||
i, {"size": 11, "space_before": 6, "space_after": 3}
|
||||
)
|
||||
heading_font.size = Pt(fmt["size"])
|
||||
heading_font.bold = True
|
||||
|
||||
heading_pf = heading_style.paragraph_format
|
||||
heading_pf.space_before = Pt(fmt["space_before"])
|
||||
heading_pf.space_after = Pt(fmt["space_after"])
|
||||
|
||||
# Ensure rPr exists
|
||||
h_rPr = heading_style._element.get_or_add_rPr()
|
||||
h_rFonts = h_rPr.get_or_add_rFonts()
|
||||
|
||||
# Set fonts
|
||||
h_rFonts.set(qn("w:ascii"), self.valves.英文字体)
|
||||
h_rFonts.set(qn("w:hAnsi"), self.valves.英文字体)
|
||||
h_rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
|
||||
|
||||
# Set language for Heading
|
||||
h_lang = h_rPr.find(qn("w:lang"))
|
||||
if h_lang is None:
|
||||
h_lang = OxmlElement("w:lang")
|
||||
h_rPr.append(h_lang)
|
||||
h_lang.set(qn("w:val"), "en-US")
|
||||
h_lang.set(qn("w:eastAsia"), "zh-CN")
|
||||
|
||||
def _remove_emojis(self, text: str) -> str:
|
||||
"""从文本中移除 Emoji (基于 Unicode 范围)"""
|
||||
if not isinstance(text, str):
|
||||
return ""
|
||||
|
||||
def _is_emoji_codepoint(codepoint: int) -> bool:
|
||||
# Common emoji ranges + flag regional indicators.
|
||||
return (
|
||||
0x1F000 <= codepoint <= 0x1FAFF
|
||||
or 0x1F1E6 <= codepoint <= 0x1F1FF
|
||||
or 0x2600 <= codepoint <= 0x26FF
|
||||
or 0x2700 <= codepoint <= 0x27BF
|
||||
or 0x2300 <= codepoint <= 0x23FF
|
||||
or 0x2B00 <= codepoint <= 0x2BFF
|
||||
)
|
||||
|
||||
def _is_emoji_modifier(codepoint: int) -> bool:
|
||||
# VS15/VS16, ZWJ, keycap, skin tones, and tag characters used in some emoji sequences.
|
||||
return (
|
||||
codepoint in (0x200D, 0xFE0E, 0xFE0F, 0x20E3)
|
||||
or 0x1F3FB <= codepoint <= 0x1F3FF
|
||||
or 0xE0020 <= codepoint <= 0xE007F
|
||||
)
|
||||
|
||||
return "".join(
|
||||
ch
|
||||
for ch in text
|
||||
if not (_is_emoji_codepoint(ord(ch)) or _is_emoji_modifier(ord(ch)))
|
||||
)
|
||||
|
||||
def add_heading(self, doc: Document, text: str, level: int):
|
||||
"""Add heading"""
|
||||
# Word heading levels start from 0, Markdown from 1
|
||||
@@ -2184,6 +2449,12 @@ class Action:
|
||||
if strike:
|
||||
run.font.strike = True
|
||||
|
||||
# Explicitly set East Asian font to prevent MS Gothic fallback
|
||||
# Word may not inherit w:eastAsia from style, causing Japanese font fallback for CJK
|
||||
rPr = run._element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
|
||||
|
||||
def _add_inline_code(self, paragraph, s: str):
|
||||
if s == "":
|
||||
return
|
||||
@@ -2579,7 +2850,11 @@ class Action:
|
||||
):
|
||||
u = self._normalize_url(url)
|
||||
if not u:
|
||||
paragraph.add_run(display_text or text)
|
||||
run = paragraph.add_run(display_text or text)
|
||||
# Set East Asian font to prevent MS Gothic fallback
|
||||
rPr = run._element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
|
||||
return
|
||||
|
||||
part = getattr(paragraph, "part", None)
|
||||
@@ -2588,6 +2863,10 @@ class Action:
|
||||
run = paragraph.add_run(display_text or text)
|
||||
run.font.color.rgb = RGBColor(0, 0, 255)
|
||||
run.font.underline = True
|
||||
# Set East Asian font to prevent MS Gothic fallback
|
||||
rPr = run._element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
|
||||
return
|
||||
|
||||
r_id = part.relate_to(u, RT.HYPERLINK, is_external=True)
|
||||
@@ -2601,6 +2880,11 @@ class Action:
|
||||
rStyle.set(qn("w:val"), "Hyperlink")
|
||||
rPr.append(rStyle)
|
||||
|
||||
# Set East Asian font to prevent MS Gothic fallback
|
||||
rFonts = OxmlElement("w:rFonts")
|
||||
rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
|
||||
rPr.append(rFonts)
|
||||
|
||||
color = OxmlElement("w:color")
|
||||
color.set(qn("w:val"), "0000FF")
|
||||
rPr.append(color)
|
||||
|
||||
@@ -1,4 +1,59 @@
|
||||
# Export to Excel
|
||||
# 📊 Export to Excel
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.3.6 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||
|
||||
Export chat history to an Excel (.xlsx) file directly from the chat interface.
|
||||
|
||||
## 🔥 What's New in v0.3.6
|
||||
|
||||
- **OpenWebUI-Style Theme**: Modern dark header (#1f2937) with light gray zebra striping for better readability.
|
||||
- **Zebra Striping**: Alternating row colors (#ffffff / #f3f4f6) for improved visual scanning.
|
||||
- **Smart Data Type Conversion**: Automatically converts columns to numeric or datetime types with fallback to string.
|
||||
- **Full Cell Bold/Italic**: Supports full cell Markdown bold (`**text**`) and italic (`*text*`) formatting in Excel.
|
||||
- **Partial Markdown Cleanup**: Removes partial Markdown formatting symbols for cleaner Excel output.
|
||||
- **Export Scope**: Added `EXPORT_SCOPE` to choose between the last message or all messages.
|
||||
- **Smart Sheet Naming**: Names sheets based on Markdown headers, AI titles, or message index.
|
||||
- **Multiple Tables Support**: Improved handling of multiple tables across messages.
|
||||
- **Smart Filename Generation**: Supports filenames based on chat title, AI summary, or Markdown headers.
|
||||
- **Configuration Options**: Added `TITLE_SOURCE` to control filename strategy.
|
||||
- **AI Title Generation**: Added `MODEL_ID` to use AI for filename generation with progress notifications.
|
||||
|
||||
## ✨ Core Features
|
||||
|
||||
- 🚀 **One-Click Export**: Adds an “Export to Excel” action button to the chat.
|
||||
- 🧠 **Automatic Header Extraction**: Intelligently identifies table headers from chat content.
|
||||
- 📊 **Multi-Table Support**: Handles multiple tables within a single chat session.
|
||||
|
||||
## 🚀 How to Use
|
||||
|
||||
1. **Install**: Search for “Export to Excel” in the Open WebUI Community and install.
|
||||
2. **Trigger**: In any chat, click the “Export to Excel” action button.
|
||||
3. **Download**: The .xlsx file will be automatically downloaded.
|
||||
|
||||
## ⚙️ Configuration (Valves)
|
||||
|
||||
| Parameter | Default | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `TITLE_SOURCE` | `chat_title` | Filename source: `chat_title`, `ai_generated`, or `markdown_title`. |
|
||||
| `EXPORT_SCOPE` | `last_message` | Export scope: `last_message` or `all_messages`. |
|
||||
| `MODEL_ID` | `""` | Model ID for AI title generation. Empty uses current chat model. |
|
||||
| `SHOW_STATUS` | `True` | Show operation status updates. |
|
||||
| `SHOW_DEBUG_LOG` | `False` | Print debug logs in the browser console (F12). |
|
||||
|
||||
## ⭐ Support
|
||||
|
||||
If this plugin has been useful, a star on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) is a big motivation for me. Thank you for the support.
|
||||
|
||||
## Troubleshooting ❓
|
||||
|
||||
- **Plugin not working?**: Check if the filter/action is enabled in the model settings.
|
||||
- **Debug Logs**: Enable `SHOW_STATUS` and check the browser console (F12) if needed.
|
||||
- **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)
|
||||
|
||||
## Changelog
|
||||
|
||||
See the full history on GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)# Export to Excel
|
||||
|
||||
This plugin allows you to export your chat history to an Excel (.xlsx) file directly from the chat interface.
|
||||
|
||||
|
||||
@@ -1,4 +1,59 @@
|
||||
# 导出为 Excel
|
||||
# 📊 导出为 Excel
|
||||
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.3.6 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||
|
||||
将对话历史直接导出为 Excel (.xlsx) 文件。
|
||||
|
||||
## 🔥 最新更新 v0.3.6
|
||||
|
||||
- **OpenWebUI 风格主题**:现代深灰表头(#1f2937)与浅灰斑马纹,提升可读性。
|
||||
- **斑马纹效果**:隔行变色(#ffffff / #f3f4f6),方便视觉扫描。
|
||||
- **智能数据类型转换**:自动将列转换为数字或日期类型,无法转换时保持字符串。
|
||||
- **全单元格粗体/斜体**:支持全单元格 Markdown 粗体与斜体格式。
|
||||
- **部分 Markdown 清理**:移除部分 Markdown 格式符号,输出更整洁。
|
||||
- **导出范围**:新增 `EXPORT_SCOPE`,可选择导出最后一条或所有消息。
|
||||
- **智能 Sheet 命名**:按 Markdown 标题、AI 标题或消息索引命名。
|
||||
- **多表格支持**:优化了多表格处理能力。
|
||||
- **智能文件名生成**:支持对话标题 / AI 总结 / Markdown 标题命名。
|
||||
- **配置选项**:新增 `TITLE_SOURCE` 控制文件名策略。
|
||||
- **AI 标题生成**:新增 `MODEL_ID`,支持 AI 标题生成与进度提示。
|
||||
|
||||
## ✨ 核心特性
|
||||
|
||||
- 🚀 **一键导出**:在聊天界面添加“导出为 Excel”按钮。
|
||||
- 🧠 **自动表头提取**:智能识别聊天内容中的表格标题。
|
||||
- 📊 **多表支持**:支持单次对话中的多个表格。
|
||||
|
||||
## 🚀 使用方法
|
||||
|
||||
1. **安装**:在 Open WebUI 社区搜索“导出为 Excel”并安装。
|
||||
2. **触发**:在任意对话中,点击“导出为 Excel”动作按钮。
|
||||
3. **下载**:.xlsx 文件将自动下载到你的设备。
|
||||
|
||||
## ⚙️ 配置参数 (Valves)
|
||||
|
||||
| 参数 | 默认值 | 描述 |
|
||||
| :--- | :--- | :--- |
|
||||
| `TITLE_SOURCE` | `chat_title` | 文件名来源:`chat_title`、`ai_generated`、`markdown_title`。 |
|
||||
| `EXPORT_SCOPE` | `last_message` | 导出范围:`last_message` 或 `all_messages`。 |
|
||||
| `MODEL_ID` | `""` | AI 标题生成的模型 ID。为空则使用当前对话模型。 |
|
||||
| `SHOW_STATUS` | `True` | 是否显示操作状态更新。 |
|
||||
| `SHOW_DEBUG_LOG` | `False` | 是否在浏览器控制台输出调试日志 (F12)。 |
|
||||
|
||||
## ⭐ 支持
|
||||
|
||||
如果这个插件对你有帮助,欢迎到 [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 点个 Star,这将是我持续改进的动力,感谢支持。
|
||||
|
||||
## 故障排除 (Troubleshooting) ❓
|
||||
|
||||
- **插件不工作?**: 请检查是否在模型设置中启用了该过滤器/动作。
|
||||
- **调试日志**: 如需排查,启用 `SHOW_STATUS` 并查看浏览器控制台 (F12)。
|
||||
- **错误信息**: 如果看到错误,请复制完整的错误信息并报告。
|
||||
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||
|
||||
## 更新日志
|
||||
|
||||
完整历史请查看 GitHub 项目: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)# 导出为 Excel
|
||||
|
||||
此插件允许你直接从聊天界面将对话历史导出为 Excel (.xlsx) 文件。
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
title: Export to Excel
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie
|
||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.3.7
|
||||
openwebui_id: 244b8f9d-7459-47d6-84d3-c7ae8e3ec710
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
|
||||
@@ -32,6 +32,10 @@ class Action:
|
||||
default="chat_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(
|
||||
default="last_message",
|
||||
description="Export Scope: 'last_message' (Last Message Only), 'all_messages' (All Messages)",
|
||||
@@ -40,14 +44,57 @@ class Action:
|
||||
default="",
|
||||
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):
|
||||
self.valves = self.Valves()
|
||||
|
||||
async def _send_notification(self, emitter: Callable, type: str, content: str):
|
||||
await emitter(
|
||||
{"type": "notification", "data": {"type": type, "content": content}}
|
||||
)
|
||||
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(
|
||||
self,
|
||||
@@ -190,17 +237,18 @@ class Action:
|
||||
# Notify user about the number of tables found
|
||||
table_count = len(all_tables)
|
||||
if self.valves.EXPORT_SCOPE == "all_messages":
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
__event_emitter__,
|
||||
"info",
|
||||
f"Found {table_count} table(s) in all messages.",
|
||||
"info",
|
||||
)
|
||||
# Wait a moment for user to see the notification before download dialog
|
||||
await asyncio.sleep(1.5)
|
||||
# Generate Workbook Title (Filename)
|
||||
# Use the title of the chat, or the first header of the first message with tables
|
||||
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 = ""
|
||||
if chat_id:
|
||||
chat_title = await self.fetch_chat_title(chat_id, user_id)
|
||||
@@ -330,8 +378,8 @@ class Action:
|
||||
},
|
||||
}
|
||||
)
|
||||
await self._send_notification(
|
||||
__event_emitter__, "error", "No tables found to export!"
|
||||
await self._emit_notification(
|
||||
__event_emitter__, "No tables found to export!", "error"
|
||||
)
|
||||
raise e
|
||||
except Exception as e:
|
||||
@@ -345,8 +393,8 @@ class Action:
|
||||
},
|
||||
}
|
||||
)
|
||||
await self._send_notification(
|
||||
__event_emitter__, "error", "No tables found to export!"
|
||||
await self._emit_notification(
|
||||
__event_emitter__, "No tables found to export!", "error"
|
||||
)
|
||||
|
||||
async def generate_title_using_ai(
|
||||
@@ -389,20 +437,20 @@ class Action:
|
||||
async def notification_task():
|
||||
# Send initial notification immediately
|
||||
if event_emitter:
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
event_emitter,
|
||||
"info",
|
||||
"AI is generating a filename for your Excel file...",
|
||||
"info",
|
||||
)
|
||||
|
||||
# Subsequent notifications every 5 seconds
|
||||
while True:
|
||||
await asyncio.sleep(5)
|
||||
if event_emitter:
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
event_emitter,
|
||||
"info",
|
||||
"Still generating filename, please be patient...",
|
||||
"info",
|
||||
)
|
||||
|
||||
# Run tasks concurrently
|
||||
@@ -432,10 +480,10 @@ class Action:
|
||||
except Exception as e:
|
||||
print(f"Error generating title: {e}")
|
||||
if event_emitter:
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
event_emitter,
|
||||
"warning",
|
||||
f"AI title generation failed, using default title. Error: {str(e)}",
|
||||
"warning",
|
||||
)
|
||||
|
||||
return ""
|
||||
@@ -450,24 +498,56 @@ class Action:
|
||||
return match.group(1).strip()
|
||||
return ""
|
||||
|
||||
def extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||
"""Extract chat_id from body or metadata"""
|
||||
if isinstance(body, dict):
|
||||
chat_id = body.get("chat_id") or body.get("id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> 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 = {}
|
||||
|
||||
for key in ("chat", "conversation"):
|
||||
nested = body.get(key)
|
||||
if isinstance(nested, dict):
|
||||
nested_id = nested.get("id") or nested.get("chat_id")
|
||||
if isinstance(nested_id, str) and nested_id.strip():
|
||||
return nested_id.strip()
|
||||
if isinstance(metadata, dict):
|
||||
chat_id = metadata.get("chat_id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
return ""
|
||||
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 fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
|
||||
"""Fetch chat title from database by chat_id"""
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
title: 导出为 Excel
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie
|
||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.3.7
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
|
||||
description: 从聊天消息中提取表格并导出为 Excel (.xlsx) 文件,支持智能格式化。
|
||||
@@ -31,6 +31,10 @@ class Action:
|
||||
default="chat_title",
|
||||
description="标题来源: 'chat_title' (对话标题), 'ai_generated' (AI生成), 'markdown_title' (Markdown标题)",
|
||||
)
|
||||
SHOW_STATUS: bool = Field(
|
||||
default=True,
|
||||
description="是否显示操作状态更新。",
|
||||
)
|
||||
EXPORT_SCOPE: Literal["last_message", "all_messages"] = Field(
|
||||
default="last_message",
|
||||
description="导出范围: 'last_message' (仅最后一条消息), 'all_messages' (所有消息)",
|
||||
@@ -39,14 +43,57 @@ class Action:
|
||||
default="",
|
||||
description="AI 标题生成模型 ID。留空则使用当前对话模型。",
|
||||
)
|
||||
SHOW_DEBUG_LOG: bool = Field(
|
||||
default=False,
|
||||
description="是否在浏览器控制台打印调试日志。",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
async def _send_notification(self, emitter: Callable, type: str, content: str):
|
||||
await emitter(
|
||||
{"type": "notification", "data": {"type": type, "content": content}}
|
||||
)
|
||||
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(
|
||||
self,
|
||||
@@ -180,17 +227,18 @@ class Action:
|
||||
# 通知用户提取到的表格数量
|
||||
table_count = len(all_tables)
|
||||
if self.valves.EXPORT_SCOPE == "all_messages":
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
__event_emitter__,
|
||||
"info",
|
||||
f"从所有消息中提取到 {table_count} 个表格。",
|
||||
"info",
|
||||
)
|
||||
# 等待片刻让用户看到通知,再触发下载
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
# Generate Workbook Title (Filename)
|
||||
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 = ""
|
||||
if chat_id:
|
||||
chat_title = await self.fetch_chat_title(chat_id, user_id)
|
||||
@@ -318,8 +366,8 @@ class Action:
|
||||
},
|
||||
}
|
||||
)
|
||||
await self._send_notification(
|
||||
__event_emitter__, "error", "未找到可导出的表格!"
|
||||
await self._emit_notification(
|
||||
__event_emitter__, "未找到可导出的表格!", "error"
|
||||
)
|
||||
raise e
|
||||
except Exception as e:
|
||||
@@ -333,8 +381,8 @@ class Action:
|
||||
},
|
||||
}
|
||||
)
|
||||
await self._send_notification(
|
||||
__event_emitter__, "error", "未找到可导出的表格!"
|
||||
await self._emit_notification(
|
||||
__event_emitter__, "未找到可导出的表格!", "error"
|
||||
)
|
||||
|
||||
async def generate_title_using_ai(
|
||||
@@ -377,20 +425,20 @@ class Action:
|
||||
async def notification_task():
|
||||
# 立即发送首次通知
|
||||
if event_emitter:
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
event_emitter,
|
||||
"info",
|
||||
"AI 正在为您生成文件名,请稍候...",
|
||||
"info",
|
||||
)
|
||||
|
||||
# 之后每5秒通知一次
|
||||
while True:
|
||||
await asyncio.sleep(5)
|
||||
if event_emitter:
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
event_emitter,
|
||||
"info",
|
||||
"文件名生成中,请耐心等待...",
|
||||
"info",
|
||||
)
|
||||
|
||||
# 并发运行任务
|
||||
@@ -420,10 +468,10 @@ class Action:
|
||||
except Exception as e:
|
||||
print(f"生成标题时出错: {e}")
|
||||
if event_emitter:
|
||||
await self._send_notification(
|
||||
await self._emit_notification(
|
||||
event_emitter,
|
||||
"warning",
|
||||
f"AI 文件名生成失败,将使用默认名称。错误: {str(e)}",
|
||||
"warning",
|
||||
)
|
||||
|
||||
return ""
|
||||
@@ -438,24 +486,56 @@ class Action:
|
||||
return match.group(1).strip()
|
||||
return ""
|
||||
|
||||
def extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||
"""从 body 或 metadata 中提取 chat_id"""
|
||||
if isinstance(body, dict):
|
||||
chat_id = body.get("chat_id") or body.get("id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> 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 = {}
|
||||
|
||||
for key in ("chat", "conversation"):
|
||||
nested = body.get(key)
|
||||
if isinstance(nested, dict):
|
||||
nested_id = nested.get("id") or nested.get("chat_id")
|
||||
if isinstance(nested_id, str) and nested_id.strip():
|
||||
return nested_id.strip()
|
||||
if isinstance(metadata, dict):
|
||||
chat_id = metadata.get("chat_id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
return ""
|
||||
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 fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
|
||||
"""通过 chat_id 从数据库获取对话标题"""
|
||||
|
||||
@@ -2,9 +2,15 @@
|
||||
|
||||
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
|
||||
|
||||
## 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.
|
||||
- **Concise extraction**: 3–5 key points and 2–4 tags automatically surfaced.
|
||||
@@ -12,7 +18,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.
|
||||
- **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 |
|
||||
| ------------------- | ------------------------------------------------------------ | ------- |
|
||||
@@ -23,34 +36,21 @@ Generate polished learning flashcards from any text—title, summary, key points
|
||||
| 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 |
|
||||
|
||||
## How to Use
|
||||
## ⭐ Support
|
||||
|
||||
1. Install and enable “Flash Card”.
|
||||
2. Send the text to the chat (multi-turn supported; governed by MESSAGE_COUNT).
|
||||
3. Watch status updates; the card HTML is embedded into the latest message.
|
||||
4. To regenerate from scratch, toggle CLEAR_PREVIOUS_HTML or resend text.
|
||||
If this plugin has been useful, a star on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) is a big motivation for me. Thank you for the support.
|
||||
|
||||
## Output Format
|
||||
## Troubleshooting ❓
|
||||
|
||||
- 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.
|
||||
- **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)
|
||||
|
||||
## Tips
|
||||
## Preview 📸
|
||||
|
||||
- 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
|
||||
See the full history on GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
@@ -2,17 +2,28 @@
|
||||
|
||||
快速将文本提炼为精美的学习记忆卡片,自动抽取标题、摘要、关键要点、标签和分类,适合复习与分享。
|
||||
|
||||

|
||||
**作者:** [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 个标签。
|
||||
- **多语言支持**:可设定目标语言(默认中文)。
|
||||
- **渐进合并**:多次调用会将新卡片合并到同一 HTML 容器中;如需重置可启用清空选项。
|
||||
- **状态提示**:实时推送“生成中/完成/错误”等状态与通知。
|
||||
* **输出优化**: 移除输出中的调试信息。
|
||||
|
||||
## 参数说明
|
||||
## 核心特性 🔑
|
||||
|
||||
* **一键生成**:输入任意文本,直接产出结构化卡片。
|
||||
* **要点聚合**:自动提取 3-5 个记忆要点与 2-4 个标签。
|
||||
* **多语言支持**:可设定目标语言(默认中文)。
|
||||
* **渐进合并**:多次调用会将新卡片合并到同一 HTML 容器中;如需重置可启用清空选项。
|
||||
* **状态提示**:实时推送“生成中/完成/错误”等状态与通知。
|
||||
|
||||
## 使用方法 🛠️
|
||||
|
||||
1. **安装**: 在插件市场安装并启用“闪记卡”。
|
||||
2. **配置**: 根据需要调整 Valves 设置(可选)。
|
||||
3. **触发**: 将待整理的文本发送到聊天框。
|
||||
4. **结果**: 等待状态提示,卡片将以 HTML 形式嵌入到最新消息中。
|
||||
|
||||
## 配置参数 (Valves) ⚙️
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
| ------------------- | ------------------------------------- | ------ |
|
||||
@@ -23,34 +34,21 @@
|
||||
| CLEAR_PREVIOUS_HTML | 是否清空旧的卡片 HTML(否则合并追加) | false |
|
||||
| MESSAGE_COUNT | 取最近 N 条消息生成卡片 | 1 |
|
||||
|
||||
## 使用步骤
|
||||
## ⭐ 支持
|
||||
|
||||
1. 在插件市场安装并启用“闪记卡”。
|
||||
2. 将待整理的文本发送到聊天框(可多轮对话,受 MESSAGE_COUNT 控制)。
|
||||
3. 等待状态提示,卡片将以 HTML 形式嵌入到最新消息中。
|
||||
4. 若需重新生成,开启 CLEAR_PREVIOUS_HTML 或直接重发文本。
|
||||
如果这个插件对你有帮助,欢迎到 [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 点个 Star,这将是我持续改进的动力,感谢支持。
|
||||
|
||||
## 输出格式
|
||||
## 故障排除 (Troubleshooting) ❓
|
||||
|
||||
- JSON 字段:`title`、`summary`、`key_points`(3-5 条)、`tags`(2-4 条)、`category`。
|
||||
- 前端呈现:单卡片带渐变主题、标签胶囊、要点列表,可连续追加多张卡片。
|
||||
* **插件不工作?**: 请检查是否在模型设置中启用了该过滤器/动作。
|
||||
* **调试日志**: 在 Valves 中启用 `SHOW_STATUS` 以查看进度更新。
|
||||
* **错误信息**: 如果看到错误,请复制完整的错误信息并报告。
|
||||
* **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||
|
||||
## 使用建议
|
||||
## 预览 📸
|
||||
|
||||
- 文本过短会提醒补充,可先汇总再生成卡片。
|
||||
- 长文本无需截断,直接生成;如需深度分析可先用其他工具精炼后再制作卡片。
|
||||
|
||||
## 作者
|
||||
|
||||
Fu-Jie
|
||||
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||

|
||||
|
||||
## 更新日志
|
||||
|
||||
### v0.2.4
|
||||
|
||||
- 移除输出中的调试信息
|
||||
完整历史请查看 GitHub 项目: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
title: Flash Card
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie
|
||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.2.4
|
||||
openwebui_id: 65a2ea8f-2a13-4587-9d76-55eea0035cc8
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
|
||||
@@ -89,6 +89,10 @@ class Action:
|
||||
default=True,
|
||||
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(
|
||||
default=False,
|
||||
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"),
|
||||
}
|
||||
|
||||
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(
|
||||
self,
|
||||
body: dict,
|
||||
@@ -331,6 +371,26 @@ Important Principles:
|
||||
{"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:
|
||||
"""Removes existing plugin-generated HTML code blocks from the content."""
|
||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
title: 闪记卡 (Flash Card)
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie
|
||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.2.4
|
||||
openwebui_id: 4a31eac3-a3c4-4c30-9ca5-dab36b5fac65
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
|
||||
@@ -86,6 +86,10 @@ class Action:
|
||||
SHOW_STATUS: bool = Field(
|
||||
default=True, description="是否在聊天界面显示状态更新。"
|
||||
)
|
||||
SHOW_DEBUG_LOG: bool = Field(
|
||||
default=False,
|
||||
description="是否在浏览器控制台打印调试日志。",
|
||||
)
|
||||
CLEAR_PREVIOUS_HTML: bool = Field(
|
||||
default=False,
|
||||
description="是否强制清除旧的插件结果(如果为 True,则不合并,直接覆盖)。",
|
||||
@@ -113,6 +117,42 @@ class Action:
|
||||
"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(
|
||||
self,
|
||||
body: dict,
|
||||
@@ -314,6 +354,26 @@ class Action:
|
||||
{"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:
|
||||
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
|
||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
# 📊 Smart Infographic (AntV)
|
||||
# Smart Infographic
|
||||
|
||||
**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.5.0 | **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.
|
||||
|
||||
## 🔥 What's New in v1.4.9
|
||||
## 🔥 What's New in v1.5.0
|
||||
|
||||
- 🎨 **70+ Official Templates**: Integrated comprehensive AntV infographic template library.
|
||||
- 🖼️ **Iconify & unDraw Support**: Richer visuals with official icons and illustrations.
|
||||
- 📏 **Visual Optimization**: Improved text wrapping, adaptive sizing, and layout refinement.
|
||||
- ✨ **PNG Upload**: Infographics now upload as PNG format for better Word export compatibility.
|
||||
- 🔧 **Canvas Conversion**: Uses browser canvas for high-quality SVG to PNG conversion (2x scale).
|
||||
|
||||
### Previous: v1.4.0
|
||||
|
||||
- ✨ **Default Mode Change**: Default output mode is now `image` (static image) for better compatibility.
|
||||
- 📱 **Responsive Sizing**: Images now auto-adapt to the chat container width.
|
||||
- 🌐 **Smart Language Detection**: Automatically detects the accurate UI language from your browser.
|
||||
- 🗣️ **Context-Aware Generation**: Generated infographics now strictly follow the language of your input content (e.g., input Japanese -> output Japanese infographic).
|
||||
- 🐛 **Bug Fixes**: Fixed issues with language synchronization between the UI and generated content.
|
||||
|
||||
## ✨ Key Features
|
||||
|
||||
@@ -46,6 +39,10 @@ You can adjust the following parameters in the plugin settings to optimize the g
|
||||
| **Message Count (MESSAGE_COUNT)** | `1` | Number of recent messages to use for analysis. Increase this for more context. |
|
||||
| **Output Mode (OUTPUT_MODE)** | `image` | `image` for static image embedding (default, better compatibility), `html` for interactive chart. |
|
||||
|
||||
## ⭐ Support
|
||||
|
||||
If this plugin has been useful, a star on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) is a big motivation for me. Thank you for the support.
|
||||
|
||||
## 🛠️ Supported Template Types
|
||||
|
||||
| Category | Template Name | Use Case |
|
||||
@@ -56,6 +53,17 @@ 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 |
|
||||
| **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)
|
||||
|
||||
## Changelog
|
||||
|
||||
See the full history on GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
## 📝 Syntax Example (For Advanced Users)
|
||||
|
||||
You can also input this syntax directly for AI to render:
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
# 📊 智能信息图 (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.5.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||
|
||||
基于 AntV Infographic 引擎的 Open WebUI 插件,能够将长文本内容一键转换为专业、美观的信息图表。
|
||||
|
||||
## 🔥 v1.4.9 更新日志
|
||||
## 🔥 最新更新 v1.5.0
|
||||
|
||||
- 🎨 **70+ 官方模板**:全面集成 AntV 官方信息图模板库。
|
||||
- 🖼️ **图标与插图支持**:支持 Iconify 图标库与 unDraw 插图库,视觉效果更丰富。
|
||||
- 📏 **视觉优化**:改进文本换行逻辑,优化自适应尺寸,提升卡片布局精细度。
|
||||
- ✨ **PNG 上传**:信息图现在以 PNG 格式上传,与 Word 导出完美兼容。
|
||||
- 🔧 **Canvas 转换**:使用浏览器 Canvas 高质量转换 SVG 为 PNG(2倍缩放)。
|
||||
|
||||
### 此前: v1.4.0
|
||||
|
||||
- ✨ **默认模式变更**:默认输出模式调整为 `image`(静态图片)。
|
||||
- 📱 **响应式尺寸**:图片模式下自动适应聊天容器宽度。
|
||||
- 🌐 **智能语言检测**:自动从浏览器准确识别当前界面语言设置。
|
||||
- 🗣️ **上下文感知生成**:生成的信息图内容现在严格跟随用户输入内容的语言(例如:输入日语 -> 生成日语信息图)。
|
||||
- 🐛 **问题修复**:修复了界面语言与生成内容语言不同步的问题。
|
||||
|
||||
## ✨ 核心特性
|
||||
|
||||
@@ -46,6 +39,10 @@
|
||||
| **上下文消息数 (MESSAGE_COUNT)** | `1` | 用于分析的最近消息条数。增加此值可让 AI 参考更多对话背景。 |
|
||||
| **输出模式 (OUTPUT_MODE)** | `image` | `image` 为静态图片嵌入(默认,兼容性好),`html` 为交互式图表。 |
|
||||
|
||||
## ⭐ 支持
|
||||
|
||||
如果这个插件对你有帮助,欢迎到 [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 点个 Star,这将是我持续改进的动力,感谢支持。
|
||||
|
||||
## 🛠️ 支持的模板类型
|
||||
|
||||
| 分类 | 模板名称 | 适用场景 |
|
||||
@@ -56,6 +53,17 @@
|
||||
| **层级与结构** | `hierarchy-tree-tech-style-capsule-item`, `hierarchy-structure` | 组织架构、层级关系 |
|
||||
| **图表与数据** | `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)
|
||||
|
||||
## 更新日志
|
||||
|
||||
完整历史请查看 GitHub 项目: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
## 📝 语法示例 (高级用户)
|
||||
|
||||
你也可以直接输入以下语法让 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 |
@@ -1,9 +1,10 @@
|
||||
"""
|
||||
title: 📊 Smart Infographic (AntV)
|
||||
title: Smart Infographic
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
|
||||
version: 1.4.9
|
||||
version: 1.5.0
|
||||
openwebui_id: ad6f0c7f-c571-4dea-821d-8e71697274cf
|
||||
description: AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.
|
||||
"""
|
||||
@@ -31,6 +32,10 @@ logger = logging.getLogger(__name__)
|
||||
SYSTEM_PROMPT_INFOGRAPHIC_ASSISTANT = """
|
||||
You are a professional infographic design expert who can analyze user-provided text content and convert it into AntV Infographic syntax format.
|
||||
|
||||
## Important Language Rule
|
||||
- **GENERATE CONTENT IN INPUT LANGUAGE**: You must generate the text content of the infographic in the **exact same language** as the user's input content (the text you are analyzing).
|
||||
- **Format Consistency**: Even if this system prompt is in English, if the user input is in Chinese, the infographic content must be in Chinese. If input is Japanese, output Japanese.
|
||||
|
||||
## Infographic Syntax Specification
|
||||
|
||||
Infographic syntax is a Mermaid-like declarative syntax for describing infographic templates, data, and themes.
|
||||
@@ -263,6 +268,8 @@ data
|
||||
4. **Indentation**: Use 2 spaces.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
USER_PROMPT_GENERATE_INFOGRAPHIC = """
|
||||
Please analyze the following text content and convert its core information into AntV Infographic syntax format.
|
||||
|
||||
@@ -947,49 +954,90 @@ class Action:
|
||||
default="image",
|
||||
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):
|
||||
self.valves = self.Valves()
|
||||
|
||||
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||
"""Extract chat_id from body or metadata"""
|
||||
async def _get_user_context(
|
||||
self,
|
||||
__user__: Optional[Dict[str, Any]],
|
||||
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
|
||||
) -> 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 = {}
|
||||
|
||||
user_id = user_data.get("id", "unknown_user")
|
||||
user_name = user_data.get("name", "User")
|
||||
user_language = user_data.get("language", "en-US")
|
||||
|
||||
if __event_call__:
|
||||
try:
|
||||
js_code = """
|
||||
return (
|
||||
localStorage.getItem('locale') ||
|
||||
localStorage.getItem('language') ||
|
||||
navigator.language ||
|
||||
'en-US'
|
||||
);
|
||||
"""
|
||||
frontend_lang = await __event_call__(
|
||||
{"type": "execute", "data": {"code": js_code}}
|
||||
)
|
||||
if frontend_lang and isinstance(frontend_lang, str):
|
||||
user_language = frontend_lang
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to retrieve frontend language: {e}")
|
||||
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"user_name": user_name,
|
||||
"user_language": user_language,
|
||||
}
|
||||
|
||||
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")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
chat_id = body.get("chat_id", "")
|
||||
message_id = body.get("id", "") # message_id is usually 'id' in body
|
||||
|
||||
body_metadata = body.get("metadata", {})
|
||||
if isinstance(body_metadata, dict):
|
||||
chat_id = body_metadata.get("chat_id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
# 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", "")
|
||||
|
||||
if isinstance(metadata, dict):
|
||||
chat_id = metadata.get("chat_id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
# 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 ""
|
||||
|
||||
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||
"""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 ""
|
||||
return {
|
||||
"chat_id": str(chat_id).strip(),
|
||||
"message_id": str(message_id).strip(),
|
||||
}
|
||||
|
||||
def _extract_infographic_syntax(self, llm_output: str) -> str:
|
||||
"""Extract infographic syntax from LLM output"""
|
||||
@@ -1018,6 +1066,24 @@ class Action:
|
||||
{"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:
|
||||
"""Remove existing plugin-generated HTML code blocks from content"""
|
||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||
@@ -1433,18 +1499,10 @@ class Action:
|
||||
logger.info("Action: Infographic started (v1.4.0)")
|
||||
|
||||
# Get user information
|
||||
if isinstance(__user__, (list, tuple)):
|
||||
user_language = __user__[0].get("language", "en") if __user__ else "en"
|
||||
user_name = __user__[0].get("name", "User") if __user__[0] else "User"
|
||||
user_id = (
|
||||
__user__[0]["id"]
|
||||
if __user__ and "id" in __user__[0]
|
||||
else "unknown_user"
|
||||
)
|
||||
elif isinstance(__user__, dict):
|
||||
user_language = __user__.get("language", "en")
|
||||
user_name = __user__.get("name", "User")
|
||||
user_id = __user__.get("id", "unknown_user")
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__)
|
||||
user_name = user_ctx["user_name"]
|
||||
user_id = user_ctx["user_id"]
|
||||
user_language = user_ctx["user_language"]
|
||||
|
||||
# Get current time
|
||||
now = datetime.now()
|
||||
@@ -1628,8 +1686,9 @@ class Action:
|
||||
# Check output mode
|
||||
if self.valves.OUTPUT_MODE == "image":
|
||||
# Image mode: use JavaScript to render and embed as Markdown image
|
||||
chat_id = self._extract_chat_id(body, body.get("metadata"))
|
||||
message_id = self._extract_message_id(body, body.get("metadata"))
|
||||
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||
chat_id = chat_ctx["chat_id"]
|
||||
message_id = chat_ctx["message_id"]
|
||||
|
||||
await self._emit_status(
|
||||
__event_emitter__,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"""
|
||||
title: 📊 智能信息图 (AntV Infographic)
|
||||
title: 智能信息图
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
|
||||
version: 1.4.9
|
||||
version: 1.5.0
|
||||
openwebui_id: e04a48ff-23ee-4a41-8ea7-66c19524e7c8
|
||||
description: 基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。
|
||||
"""
|
||||
@@ -31,6 +32,10 @@ logger = logging.getLogger(__name__)
|
||||
SYSTEM_PROMPT_INFOGRAPHIC_ASSISTANT = """
|
||||
You are a professional infographic design expert who can analyze user-provided text content and convert it into AntV Infographic syntax format.
|
||||
|
||||
## Important Language Rule (语言规则)
|
||||
- **Priority Input Language (优先使用输入语言)**: You must generate the text content of the infographic in the **exact same language** as the user's input content.
|
||||
- **Example**: If the user provides a summary in Chinese, the labels and descriptions in the infographic must be in Chinese.
|
||||
|
||||
## Infographic Syntax Specification
|
||||
|
||||
Infographic syntax is a Mermaid-like declarative syntax for describing infographic templates, data, and themes.
|
||||
@@ -244,6 +249,8 @@ data
|
||||
3. **Language**: Use the user's requested language for content.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
USER_PROMPT_GENERATE_INFOGRAPHIC = """
|
||||
请分析以下文本内容,将其核心信息转换为 AntV Infographic 语法格式。
|
||||
|
||||
@@ -954,6 +961,10 @@ class Action:
|
||||
default="image",
|
||||
description="输出模式:'html' 为交互式HTML,'image' 将嵌入为Markdown图片(默认)。",
|
||||
)
|
||||
SHOW_DEBUG_LOG: bool = Field(
|
||||
default=False,
|
||||
description="是否在浏览器控制台打印调试日志。",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
@@ -967,45 +978,82 @@ class Action:
|
||||
"Sunday": "星期日",
|
||||
}
|
||||
|
||||
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||
"""从 body 或 metadata 中提取 chat_id"""
|
||||
async def _get_user_context(
|
||||
self,
|
||||
__user__: Optional[Dict[str, Any]],
|
||||
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
|
||||
) -> 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 = {}
|
||||
|
||||
user_id = user_data.get("id", "unknown_user")
|
||||
user_name = user_data.get("name", "用户")
|
||||
user_language = user_data.get("language", "zh-CN")
|
||||
|
||||
if __event_call__:
|
||||
try:
|
||||
js_code = """
|
||||
return (
|
||||
localStorage.getItem('locale') ||
|
||||
localStorage.getItem('language') ||
|
||||
navigator.language ||
|
||||
'zh-CN'
|
||||
);
|
||||
"""
|
||||
frontend_lang = await __event_call__(
|
||||
{"type": "execute", "data": {"code": js_code}}
|
||||
)
|
||||
if frontend_lang and isinstance(frontend_lang, str):
|
||||
user_language = frontend_lang
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"user_name": user_name,
|
||||
"user_language": user_language,
|
||||
}
|
||||
|
||||
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")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
chat_id = body.get("chat_id", "")
|
||||
message_id = body.get("id", "") # message_id 在 body 中通常是 id
|
||||
|
||||
body_metadata = body.get("metadata", {})
|
||||
if isinstance(body_metadata, dict):
|
||||
chat_id = body_metadata.get("chat_id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
# 再次检查 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", "")
|
||||
|
||||
if isinstance(metadata, dict):
|
||||
chat_id = metadata.get("chat_id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
# 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 ""
|
||||
|
||||
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||
"""从 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 ""
|
||||
return {
|
||||
"chat_id": str(chat_id).strip(),
|
||||
"message_id": str(message_id).strip(),
|
||||
}
|
||||
|
||||
def _extract_infographic_syntax(self, llm_output: str) -> str:
|
||||
"""提取LLM输出中的infographic语法"""
|
||||
@@ -1058,6 +1106,24 @@ class Action:
|
||||
{"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:
|
||||
"""移除内容中已有的插件生成 HTML 代码块"""
|
||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||
@@ -1473,20 +1539,10 @@ class Action:
|
||||
logger.info("Action: 信息图启动 (v1.4.0)")
|
||||
|
||||
# 获取用户信息
|
||||
if isinstance(__user__, (list, tuple)):
|
||||
user_language = (
|
||||
__user__[0].get("language", "zh-CN") if __user__ else "zh-CN"
|
||||
)
|
||||
user_name = __user__[0].get("name", "用户") if __user__[0] else "用户"
|
||||
user_id = (
|
||||
__user__[0]["id"]
|
||||
if __user__ and "id" in __user__[0]
|
||||
else "unknown_user"
|
||||
)
|
||||
elif isinstance(__user__, dict):
|
||||
user_language = __user__.get("language", "zh-CN")
|
||||
user_name = __user__.get("name", "用户")
|
||||
user_id = __user__.get("id", "unknown_user")
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__)
|
||||
user_name = user_ctx["user_name"]
|
||||
user_id = user_ctx["user_id"]
|
||||
user_language = user_ctx["user_language"]
|
||||
|
||||
# 获取当前时间
|
||||
now = datetime.now()
|
||||
@@ -1662,8 +1718,9 @@ class Action:
|
||||
# 检查输出模式
|
||||
if self.valves.OUTPUT_MODE == "image":
|
||||
# 图片模式:使用 JavaScript 渲染并嵌入为 Markdown 图片
|
||||
chat_id = self._extract_chat_id(body, body.get("metadata"))
|
||||
message_id = self._extract_message_id(body, body.get("metadata"))
|
||||
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||
chat_id = chat_ctx["chat_id"]
|
||||
message_id = chat_ctx["message_id"]
|
||||
|
||||
await self._emit_status(
|
||||
__event_emitter__,
|
||||
|
||||
@@ -1,379 +1,69 @@
|
||||
# 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.
|
||||
|
||||
---
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.9.2 | **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.2
|
||||
|
||||
**New Feature: Image Output Mode**
|
||||
**Language Rule Alignment**
|
||||
|
||||
- **Static Image Support**: Added `OUTPUT_MODE` configuration parameter.
|
||||
- `html` (default): Interactive HTML mind map.
|
||||
- `image`: Static SVG image embedded directly in Markdown (**No HTML code output**, cleaner 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.
|
||||
- **Input Language First**: Mind map output now strictly matches the input text language.
|
||||
- **Consistent Behavior**: Matches the infographic language rule for predictable multilingual output.
|
||||
|
||||
| Feature | HTML Mode (Default) | Image Mode |
|
||||
| :--- | :--- | :--- |
|
||||
| **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 |
|
||||
## Key Features 🔑
|
||||
|
||||
---
|
||||
- ✅ **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
|
||||
- ✅ **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, ~1-2MB file size)
|
||||
- ✅ **Complete Control Panel**: Zoom controls (+/-/reset), expand level selection (All/2/3 levels), and fullscreen mode
|
||||
- ✅ **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)
|
||||
1. **Install**: Upload the `smart_mind_map.py` file in OpenWebUI Admin Settings -> Plugins -> Actions.
|
||||
2. **Configure**: Ensure you have an LLM model configured (e.g., `gemini-2.5-flash`).
|
||||
3. **Trigger**: Enable the "Smart Mind Map" action in chat settings and send text (at least 100 characters).
|
||||
4. **Result**: The mind map will be rendered directly in the chat interface.
|
||||
|
||||
---
|
||||
|
||||
## 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):
|
||||
## Configuration (Valves) ⚙️
|
||||
|
||||
| Parameter | Default | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `show_status` | `true` | Whether to display operation status updates in the chat interface (e.g., "Analyzing..."). |
|
||||
| `LLM_MODEL_ID` | `gemini-2.5-flash` | LLM model ID for text analysis. Recommended to use fast and economical models. |
|
||||
| `MIN_TEXT_LENGTH` | `100` | Minimum text length (in characters) required for mind map analysis. Text that's too short cannot generate valid mind maps. |
|
||||
| `CLEAR_PREVIOUS_HTML` | `false` | Whether to clear previous plugin-generated HTML content when generating a new mind map. |
|
||||
| `MESSAGE_COUNT` | `1` | Number of recent messages to use for mind map generation (1-5). |
|
||||
| `OUTPUT_MODE` | `html` | Output mode: `html` for interactive HTML (default), or `image` to embed as static Markdown image. |
|
||||
| `show_status` | `true` | Whether to display operation status updates. |
|
||||
| `LLM_MODEL_ID` | `gemini-2.5-flash` | LLM model ID for text analysis. |
|
||||
| `MIN_TEXT_LENGTH` | `100` | Minimum text length required for analysis. |
|
||||
| `CLEAR_PREVIOUS_HTML` | `false` | Whether to clear previous plugin-generated HTML content. |
|
||||
| `MESSAGE_COUNT` | `1` | Number of recent messages to use for generation (1-5). |
|
||||
| `OUTPUT_MODE` | `html` | Output mode: `html` (interactive) or `image` (static). |
|
||||
|
||||
---
|
||||
## ⭐ Support
|
||||
|
||||
## Usage
|
||||
If this plugin has been useful, a star on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) is a big motivation for me. Thank you for the support.
|
||||
|
||||
### Basic Usage
|
||||
## Troubleshooting ❓
|
||||
|
||||
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
|
||||
- **Plugin not working?**: Check if the action is enabled in the chat settings.
|
||||
- **Text too short**: Ensure input text contains at least 100 characters.
|
||||
- **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)
|
||||
|
||||
---
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
### Frontend Rendering
|
||||
|
||||
- **Markmap.js**: Open-source mind mapping rendering engine
|
||||
- **D3.js**: Data visualization foundation library
|
||||
- **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.)
|
||||
|
||||
---
|
||||
- **Markmap.js**: Open-source mind mapping rendering engine.
|
||||
- **PNG Export**: 9x scale factor for print-quality output (~1-2MB file size).
|
||||
- **Theme Detection**: 4-level priority detection (Manual > Meta > Class > System).
|
||||
- **Security**: XSS protection and input validation.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Text Preparation**
|
||||
- Provide text content with clear structure and distinct hierarchies
|
||||
- Use paragraphs, lists, and other formatting to help LLM understand text structure
|
||||
- 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.**
|
||||
|
||||
---
|
||||
1. **Text Preparation**: Provide text with clear structure and distinct hierarchies.
|
||||
2. **Model Selection**: Use fast models like `gemini-2.5-flash` for daily use.
|
||||
3. **Export Quality**: Use PNG for presentations and SVG for further editing.
|
||||
|
||||
## 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/)
|
||||
See the full history on GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
@@ -1,379 +1,69 @@
|
||||
# 思维导图 - 思维导图生成插件
|
||||
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 0.9.1 | **许可证:** MIT
|
||||
|
||||
> **重要提示**:为了确保所有插件的可维护性和易用性,每个插件都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
|
||||
|
||||
思维导图是一个强大的 OpenWebUI 动作插件,能够智能分析长篇文本内容,自动生成交互式思维导图,帮助用户结构化和可视化知识。
|
||||
|
||||
---
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.9.2 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
|
||||
|
||||
## 🔥 v0.9.1 更新亮点
|
||||
## v0.9.2 更新亮点
|
||||
|
||||
**新功能:图片输出模式**
|
||||
**语言规则对齐**
|
||||
|
||||
- **静态图片支持**:新增 `OUTPUT_MODE` 配置参数。
|
||||
- `html`(默认):交互式 HTML 思维导图。
|
||||
- `image`:静态 SVG 图片直接嵌入 Markdown(**不输出 HTML 代码**,聊天记录更简洁)。
|
||||
- **高效存储**:图片模式将 SVG 上传至 `/api/v1/files`,避免聊天记录中出现超长 Base64 字符串。
|
||||
- **智能特性**:生成的图片支持自动响应式宽度和自动主题检测(亮色/暗色)。
|
||||
- **输入语言优先**:导图输出严格与输入文本语言一致。
|
||||
- **一致性提升**:与信息图语言规则保持一致,多语言输出更可预期。
|
||||
|
||||
| 特性 | HTML 模式 (默认) | 图片模式 |
|
||||
| :--- | :--- | :--- |
|
||||
| **输出格式** | 交互式 HTML 代码块 | 静态 Markdown 图片 |
|
||||
| **交互性** | 缩放、拖拽、展开/折叠 | 无 (静态图片) |
|
||||
| **聊天记录** | 包含 HTML 代码 | 简洁 (仅图片链接) |
|
||||
| **存储方式** | 浏览器实时渲染 | `/api/v1/files` 上传 |
|
||||
## 核心特性 🔑
|
||||
|
||||
---
|
||||
- ✅ **智能文本分析**:自动识别文本的核心主题、关键概念和层次结构。
|
||||
- ✅ **交互式可视化**:基于 Markmap.js 生成美观的交互式思维导图。
|
||||
- ✅ **高分辨率 PNG 导出**:导出高质量的 PNG 图片(9 倍分辨率)。
|
||||
- ✅ **完整控制面板**:缩放控制、展开层级选择、全屏模式。
|
||||
- ✅ **主题切换**:手动主题切换按钮与自动主题检测。
|
||||
- ✅ **图片输出模式**:生成静态 SVG 图片直接嵌入 Markdown,聊天记录更简洁。
|
||||
|
||||
## 核心特性
|
||||
## 使用方法 🛠️
|
||||
|
||||
- ✅ **智能文本分析**:自动识别文本的核心主题、关键概念和层次结构
|
||||
- ✅ **交互式可视化**:基于 Markmap.js 生成美观的交互式思维导图
|
||||
- ✅ **高分辨率 PNG 导出**:导出高质量的 PNG 图片(9 倍分辨率,约 1-2MB 文件大小)
|
||||
- ✅ **完整控制面板**:缩放控制(+/-/重置)、展开层级选择(全部/2级/3级)、全屏模式
|
||||
- ✅ **主题切换**:手动主题切换按钮(亮色/暗色)与自动主题检测
|
||||
- ✅ **深色模式支持**:完整的深色模式支持,自动检测与手动覆盖
|
||||
- ✅ **多语言支持**:根据用户语言自动调整输出
|
||||
- ✅ **实时渲染**:在聊天界面中直接渲染思维导图,无需跳转
|
||||
- ✅ **导出功能**:支持 PNG、SVG 代码和 Markdown 源码导出
|
||||
- ✅ **自定义配置**:可配置 LLM 模型、最小文本长度等参数
|
||||
- ✅ **图片输出模式**:生成静态 SVG 图片直接嵌入 Markdown(**不输出 HTML 代码**,聊天记录更简洁)
|
||||
1. **安装**: 在 OpenWebUI 管理员设置 -> 插件 -> 动作中上传 `smart_mind_map_cn.py`。
|
||||
2. **配置**: 确保配置了 LLM 模型(如 `gemini-2.5-flash`)。
|
||||
3. **触发**: 在聊天设置中启用“思维导图”动作,并发送文本(至少 100 字符)。
|
||||
4. **结果**: 思维导图将在聊天界面中直接渲染显示。
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
|
||||
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)中调整以下参数:
|
||||
## 配置参数 (Valves) ⚙️
|
||||
|
||||
| 参数 | 默认值 | 描述 |
|
||||
| :--- | :--- | :--- |
|
||||
| `show_status` | `true` | 是否在聊天界面显示操作状态更新(如"正在分析...")。 |
|
||||
| `LLM_MODEL_ID` | `gemini-2.5-flash` | 用于文本分析的 LLM 模型 ID。推荐使用快速且经济的模型。 |
|
||||
| `MIN_TEXT_LENGTH` | `100` | 进行思维导图分析所需的最小文本长度(字符数)。文本过短将无法生成有效的导图。 |
|
||||
| `CLEAR_PREVIOUS_HTML` | `false` | 在生成新的思维导图时,是否清除之前由插件生成的 HTML 内容。 |
|
||||
| `show_status` | `true` | 是否在聊天界面显示操作状态更新。 |
|
||||
| `LLM_MODEL_ID` | `gemini-2.5-flash` | 用于文本分析的 LLM 模型 ID。 |
|
||||
| `MIN_TEXT_LENGTH` | `100` | 进行思维导图分析所需的最小文本长度。 |
|
||||
| `CLEAR_PREVIOUS_HTML` | `false` | 在生成新的思维导图时,是否清除之前的 HTML 内容。 |
|
||||
| `MESSAGE_COUNT` | `1` | 用于生成思维导图的最近消息数量(1-5)。 |
|
||||
| `OUTPUT_MODE` | `html` | 输出模式:`html` 为交互式 HTML(默认),`image` 为嵌入静态 Markdown 图片。 |
|
||||
| `OUTPUT_MODE` | `html` | 输出模式:`html`(交互式)或 `image`(静态图片)。 |
|
||||
|
||||
---
|
||||
## ⭐ 支持
|
||||
|
||||
## 使用方法
|
||||
如果这个插件对你有帮助,欢迎到 [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 点个 Star,这将是我持续改进的动力,感谢支持。
|
||||
|
||||
### 基本使用
|
||||
## 故障排除 (Troubleshooting) ❓
|
||||
|
||||
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 级”之间切换,控制节点展开深度
|
||||
- **全屏模式**:进入全屏模式,获得更好的查看体验
|
||||
- **主题切换**:手动在亮色和暗色主题之间切换
|
||||
- **插件无法启动**:检查 OpenWebUI 日志,确认插件已正确上传并启用。
|
||||
- **文本内容过短**:确保输入的文本至少包含 100 个字符。
|
||||
- **渲染失败**:检查浏览器控制台,确认 Markmap.js 和 D3.js 库是否正确加载。
|
||||
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue:[Awesome OpenWebUI Issues](https://github.com/Fu-Jie/awesome-openwebui/issues)
|
||||
|
||||
---
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 前端渲染
|
||||
|
||||
- **Markmap.js**:开源的思维导图渲染引擎
|
||||
- **D3.js**:数据可视化基础库
|
||||
- **响应式设计**:适配不同屏幕尺寸
|
||||
- **字体层级**:优化的字体排版,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 等)
|
||||
|
||||
---
|
||||
- **Markmap.js**:开源的思维导图渲染引擎。
|
||||
- **PNG 导出技术**:9 倍缩放因子,输出打印级质量。
|
||||
- **主题检测机制**:4 级优先级检测(手动 > Meta > Class > 系统)。
|
||||
- **安全性增强**:XSS 防护与输入验证。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **文本准备**
|
||||
- 提供结构清晰、层次分明的文本内容
|
||||
- 使用段落、列表等格式帮助 LLM 理解文本结构
|
||||
- 避免过于冗长或无结构的文本
|
||||
|
||||
2. **模型选择**
|
||||
- 对于日常使用,推荐 `gemini-2.5-flash` 等快速模型
|
||||
- 对于复杂文本分析,可以使用更强大的模型(如 GPT-4)
|
||||
- 根据需求平衡速度和分析质量
|
||||
|
||||
3. **性能优化**
|
||||
- 合理设置 `MIN_TEXT_LENGTH`,避免处理过短的文本
|
||||
- 对于特别长的文本,考虑先进行摘要再生成思维导图
|
||||
- 在生产环境中关闭 `show_status` 以减少界面更新
|
||||
|
||||
4. **导出质量**
|
||||
- **PNG 导出**:最适合演示、文档和分享(9 倍分辨率适合打印)
|
||||
- **SVG 导出**:最适合在矢量图形工具中进一步编辑(无限缩放)
|
||||
- **Markdown 导出**:最适合版本控制、协作和重新生成
|
||||
|
||||
5. **主题一致性**
|
||||
- 启用同源访问以实现自动主题检测
|
||||
- 如果自动检测失败,使用手动主题切换
|
||||
- 在切换到所需主题后导出 PNG,以保持视觉一致性
|
||||
|
||||
---
|
||||
|
||||
## 依赖要求
|
||||
|
||||
本插件仅使用 OpenWebUI 的内置依赖,**无需安装额外的软件包。**
|
||||
|
||||
---
|
||||
1. **文本准备**:提供结构清晰、层次分明的文本内容。
|
||||
2. **模型选择**:日常使用推荐 `gemini-2.5-flash` 等快速模型。
|
||||
3. **导出质量**:PNG 适合演示分享,SVG 适合进一步矢量编辑。
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 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/)
|
||||
完整历史请查看 GitHub 项目: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"""
|
||||
title: Smart Mind Map
|
||||
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
|
||||
version: 0.9.1
|
||||
version: 0.9.2
|
||||
openwebui_id: 3094c59a-b4dd-4e0c-9449-15e2dd547dc4
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
|
||||
description: Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.
|
||||
@@ -32,7 +33,8 @@ SYSTEM_PROMPT_MINDMAP_ASSISTANT = """
|
||||
You are a professional mind map generation assistant, capable of efficiently analyzing long-form text provided by users and structuring its core themes, key concepts, branches, and sub-branches into standard Markdown list syntax for rendering by Markmap.js.
|
||||
|
||||
Please strictly follow these guidelines:
|
||||
- **Language**: All output must be in the language specified by the user.
|
||||
- **Language**: All output must be in the exact same language as the input text (the text you are analyzing).
|
||||
- **Format Consistency**: Even if this system prompt is in English, if the user input is in Chinese, the mind map content must be in Chinese. If input is Japanese, output Japanese.
|
||||
- **Format**: Your output must strictly be in Markdown list format, wrapped with ```markdown and ```.
|
||||
- Use `#` to define the central theme (root node).
|
||||
- Use `-` with two-space indentation to represent branches and sub-branches.
|
||||
@@ -49,6 +51,8 @@ Please strictly follow these guidelines:
|
||||
```
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
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.
|
||||
|
||||
@@ -791,6 +795,10 @@ class Action:
|
||||
default="html",
|
||||
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):
|
||||
self.valves = self.Valves()
|
||||
@@ -804,7 +812,11 @@ class Action:
|
||||
"Sunday": "Sunday",
|
||||
}
|
||||
|
||||
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
||||
async def _get_user_context(
|
||||
self,
|
||||
__user__: Optional[Dict[str, Any]],
|
||||
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
|
||||
) -> Dict[str, str]:
|
||||
"""Extract basic user context with safe fallbacks."""
|
||||
if isinstance(__user__, (list, tuple)):
|
||||
user_data = __user__[0] if __user__ else {}
|
||||
@@ -813,51 +825,69 @@ class Action:
|
||||
else:
|
||||
user_data = {}
|
||||
|
||||
user_id = user_data.get("id", "unknown_user")
|
||||
user_name = user_data.get("name", "User")
|
||||
user_language = user_data.get("language", "en-US")
|
||||
|
||||
if __event_call__:
|
||||
try:
|
||||
js_code = """
|
||||
return (
|
||||
localStorage.getItem('locale') ||
|
||||
localStorage.getItem('language') ||
|
||||
navigator.language ||
|
||||
'en-US'
|
||||
);
|
||||
"""
|
||||
frontend_lang = await __event_call__(
|
||||
{"type": "execute", "data": {"code": js_code}}
|
||||
)
|
||||
if frontend_lang and isinstance(frontend_lang, str):
|
||||
user_language = frontend_lang
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to retrieve frontend language: {e}")
|
||||
|
||||
return {
|
||||
"user_id": user_data.get("id", "unknown_user"),
|
||||
"user_name": user_data.get("name", "User"),
|
||||
"user_language": user_data.get("language", "en-US"),
|
||||
"user_id": user_id,
|
||||
"user_name": user_name,
|
||||
"user_language": user_language,
|
||||
}
|
||||
|
||||
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||
"""Extract chat_id from body or metadata"""
|
||||
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")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
chat_id = body.get("chat_id", "")
|
||||
message_id = body.get("id", "") # message_id is usually 'id' in body
|
||||
|
||||
body_metadata = body.get("metadata", {})
|
||||
if isinstance(body_metadata, dict):
|
||||
chat_id = body_metadata.get("chat_id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
# 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", "")
|
||||
|
||||
if isinstance(metadata, dict):
|
||||
chat_id = metadata.get("chat_id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
# 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 ""
|
||||
|
||||
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||
"""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 ""
|
||||
return {
|
||||
"chat_id": str(chat_id).strip(),
|
||||
"message_id": str(message_id).strip(),
|
||||
}
|
||||
|
||||
def _extract_markdown_syntax(self, llm_output: str) -> str:
|
||||
match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL)
|
||||
@@ -884,6 +914,42 @@ class Action:
|
||||
{"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:
|
||||
"""Removes existing plugin-generated HTML code blocks from the content."""
|
||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||
@@ -1330,8 +1396,8 @@ class Action:
|
||||
__metadata__: Optional[dict] = None,
|
||||
__request__: Optional[Request] = None,
|
||||
) -> Optional[dict]:
|
||||
logger.info("Action: Smart Mind Map (v0.9.1) started")
|
||||
user_ctx = self._get_user_context(__user__)
|
||||
logger.info("Action: Smart Mind Map (v0.9.2) started")
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__)
|
||||
user_language = user_ctx["user_language"]
|
||||
user_name = user_ctx["user_name"]
|
||||
user_id = user_ctx["user_id"]
|
||||
@@ -1515,8 +1581,9 @@ class Action:
|
||||
# Check output mode
|
||||
if self.valves.OUTPUT_MODE == "image":
|
||||
# Image mode: use JavaScript to render and embed as Markdown image
|
||||
chat_id = self._extract_chat_id(body, __metadata__)
|
||||
message_id = self._extract_message_id(body, __metadata__)
|
||||
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||
chat_id = chat_ctx["chat_id"]
|
||||
message_id = chat_ctx["message_id"]
|
||||
|
||||
await self._emit_status(
|
||||
__event_emitter__,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""
|
||||
title: 思维导图
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie
|
||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
version: 0.9.1
|
||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.9.2
|
||||
openwebui_id: 8d4b097b-219b-4dd2-b509-05fbe6388335
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
|
||||
description: 智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。
|
||||
@@ -32,7 +32,8 @@ SYSTEM_PROMPT_MINDMAP_ASSISTANT = """
|
||||
你是一个专业的思维导图生成助手,能够高效地分析用户提供的长篇文本,并将其核心主题、关键概念、分支和子分支结构化为标准的Markdown列表语法,以便Markmap.js进行渲染。
|
||||
|
||||
请严格遵循以下指导原则:
|
||||
- **语言**: 所有输出必须使用用户指定的语言。
|
||||
- **语言**: 所有输出必须与输入文本(正在分析的文本)保持完全一致的语言。
|
||||
- **格式一致性**: 即使系统提示词是中文,只要用户输入是英文,导图内容必须是英文;若输入为日文,则输出日文。
|
||||
- **格式**: 你的输出必须严格为Markdown列表格式,并用```markdown 和 ``` 包裹。
|
||||
- 使用 `#` 定义中心主题(根节点)。
|
||||
- 使用 `-` 和两个空格的缩进表示分支和子分支。
|
||||
@@ -49,6 +50,8 @@ SYSTEM_PROMPT_MINDMAP_ASSISTANT = """
|
||||
```
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
USER_PROMPT_GENERATE_MINDMAP = """
|
||||
请分析以下长篇文本,并将其核心主题、关键概念、分支和子分支结构化为标准的Markdown列表语法,以供Markmap.js渲染。
|
||||
|
||||
@@ -790,6 +793,10 @@ class Action:
|
||||
default="html",
|
||||
description="输出模式: 'html' 为交互式HTML(默认),'image' 为嵌入Markdown图片。",
|
||||
)
|
||||
SHOW_DEBUG_LOG: bool = Field(
|
||||
default=False,
|
||||
description="是否在浏览器控制台打印调试日志。",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
@@ -803,7 +810,11 @@ class Action:
|
||||
"Sunday": "星期日",
|
||||
}
|
||||
|
||||
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
||||
async def _get_user_context(
|
||||
self,
|
||||
__user__: Optional[Dict[str, Any]],
|
||||
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
|
||||
) -> Dict[str, str]:
|
||||
"""Extract basic user context with safe fallbacks."""
|
||||
if isinstance(__user__, (list, tuple)):
|
||||
user_data = __user__[0] if __user__ else {}
|
||||
@@ -812,51 +823,69 @@ class Action:
|
||||
else:
|
||||
user_data = {}
|
||||
|
||||
user_id = user_data.get("id", "unknown_user")
|
||||
user_name = user_data.get("name", "User")
|
||||
user_language = user_data.get("language", "en-US")
|
||||
|
||||
if __event_call__:
|
||||
try:
|
||||
js_code = """
|
||||
return (
|
||||
localStorage.getItem('locale') ||
|
||||
localStorage.getItem('language') ||
|
||||
navigator.language ||
|
||||
'en-US'
|
||||
);
|
||||
"""
|
||||
frontend_lang = await __event_call__(
|
||||
{"type": "execute", "data": {"code": js_code}}
|
||||
)
|
||||
if frontend_lang and isinstance(frontend_lang, str):
|
||||
user_language = frontend_lang
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to retrieve frontend language: {e}")
|
||||
|
||||
return {
|
||||
"user_id": user_data.get("id", "unknown_user"),
|
||||
"user_name": user_data.get("name", "用户"),
|
||||
"user_language": user_data.get("language", "zh-CN"),
|
||||
"user_id": user_id,
|
||||
"user_name": user_name,
|
||||
"user_language": user_language,
|
||||
}
|
||||
|
||||
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||
"""从 body 或 metadata 中提取 chat_id"""
|
||||
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")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
chat_id = body.get("chat_id", "")
|
||||
message_id = body.get("id", "") # message_id 在 body 中通常是 id
|
||||
|
||||
body_metadata = body.get("metadata", {})
|
||||
if isinstance(body_metadata, dict):
|
||||
chat_id = body_metadata.get("chat_id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
# 再次检查 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", "")
|
||||
|
||||
if isinstance(metadata, dict):
|
||||
chat_id = metadata.get("chat_id")
|
||||
if isinstance(chat_id, str) and chat_id.strip():
|
||||
return chat_id.strip()
|
||||
# 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 ""
|
||||
|
||||
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||
"""从 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 ""
|
||||
return {
|
||||
"chat_id": str(chat_id).strip(),
|
||||
"message_id": str(message_id).strip(),
|
||||
}
|
||||
|
||||
def _extract_markdown_syntax(self, llm_output: str) -> str:
|
||||
match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL)
|
||||
@@ -881,6 +910,24 @@ class Action:
|
||||
{"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:
|
||||
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
|
||||
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
|
||||
@@ -1328,8 +1375,8 @@ class Action:
|
||||
__metadata__: Optional[dict] = None,
|
||||
__request__: Optional[Request] = None,
|
||||
) -> Optional[dict]:
|
||||
logger.info("Action: 思维导图 (v0.9.1) started")
|
||||
user_ctx = self._get_user_context(__user__)
|
||||
logger.info("Action: 思维导图 (v0.9.2) started")
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__)
|
||||
user_language = user_ctx["user_language"]
|
||||
user_name = user_ctx["user_name"]
|
||||
user_id = user_ctx["user_id"]
|
||||
@@ -1508,8 +1555,9 @@ class Action:
|
||||
# 检查输出模式
|
||||
if self.valves.OUTPUT_MODE == "image":
|
||||
# 图片模式: 使用 JavaScript 渲染并嵌入为 Markdown 图片
|
||||
chat_id = self._extract_chat_id(body, __metadata__)
|
||||
message_id = self._extract_message_id(body, __metadata__)
|
||||
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||
chat_id = chat_ctx["chat_id"]
|
||||
message_id = chat_ctx["message_id"]
|
||||
|
||||
await self._emit_status(
|
||||
__event_emitter__,
|
||||
|
||||
359
plugins/debug/common_tools/update_readmes_to_market.py
Normal file
359
plugins/debug/common_tools/update_readmes_to_market.py
Normal file
@@ -0,0 +1,359 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
======================================================================
|
||||
Staged README Synchronizer to OpenWebUI Community
|
||||
暂存 README 文件同步到 OpenWebUI 社区工具
|
||||
======================================================================
|
||||
|
||||
PURPOSE / 用途:
|
||||
--------------
|
||||
This script synchronizes staged README.md/README_CN.md files to their
|
||||
corresponding OpenWebUI Community posts automatically. It's designed for
|
||||
batch updating documentation content without modifying plugin versions
|
||||
or media attachments.
|
||||
|
||||
本脚本自动将暂存的 README.md/README_CN.md 文件同步到对应的 OpenWebUI
|
||||
社区帖子。专为批量更新文档内容设计,不修改插件版本或媒体附件。
|
||||
|
||||
USAGE / 使用方法:
|
||||
----------------
|
||||
1. Set up environment:
|
||||
配置环境:
|
||||
|
||||
Create a .env file in the repository root with:
|
||||
在仓库根目录创建 .env 文件,包含:
|
||||
|
||||
OPENWEBUI_API_KEY=your_api_key_here
|
||||
|
||||
2. Stage README files to sync:
|
||||
暂存需要同步的 README 文件:
|
||||
|
||||
git add plugins/actions/my_plugin/README.md
|
||||
git add plugins/actions/my_plugin/README_CN.md
|
||||
|
||||
3. Run the script:
|
||||
运行脚本:
|
||||
|
||||
python plugins/debug/common_tools/update_readmes_to_market.py
|
||||
|
||||
WORKFLOW / 工作流程:
|
||||
-------------------
|
||||
1. Load OPENWEBUI_API_KEY from .env file
|
||||
从 .env 文件加载 OPENWEBUI_API_KEY
|
||||
|
||||
2. Get list of staged README.md/README_CN.md files via git
|
||||
通过 git 获取暂存的 README.md/README_CN.md 文件列表
|
||||
|
||||
3. For each staged README:
|
||||
对于每个暂存的 README:
|
||||
|
||||
a. Locate the corresponding plugin .py file
|
||||
定位对应的插件 .py 文件
|
||||
|
||||
b. Extract openwebui_id/post_id from plugin frontmatter
|
||||
从插件前置信息中提取 openwebui_id/post_id
|
||||
|
||||
c. Fetch existing post data from OpenWebUI Community API
|
||||
从 OpenWebUI 社区 API 获取现有帖子数据
|
||||
|
||||
d. Update post content with new README content
|
||||
用新的 README 内容更新帖子内容
|
||||
|
||||
e. Push changes via API (preserves version & media)
|
||||
通过 API 推送更改(保留版本和媒体)
|
||||
|
||||
REQUIREMENTS / 依赖要求:
|
||||
-----------------------
|
||||
- python-dotenv: For loading .env configuration
|
||||
用于加载 .env 配置文件
|
||||
- Git repository: Must be run from a git-tracked workspace
|
||||
必须在 git 跟踪的工作区中运行
|
||||
|
||||
KEY FEATURES / 关键特性:
|
||||
-----------------------
|
||||
✅ Only updates content field (不仅更新内容字段)
|
||||
✅ Skips files without openwebui_id (跳过没有 openwebui_id 的文件)
|
||||
✅ Automatically matches CN/EN plugin files (自动匹配中英文插件文件)
|
||||
✅ Supports staged plugin source code updates (支持暂存插件源码更新)
|
||||
✅ Safe: Won't modify version or media fields (安全:不会修改版本或媒体字段)
|
||||
|
||||
NOTES / 注意事项:
|
||||
---------------
|
||||
- This is a DEBUG/DEVELOPMENT tool, not for production workflows
|
||||
这是一个调试/开发工具,不用于生产工作流
|
||||
|
||||
- Always verify changes in OpenWebUI Community after sync
|
||||
同步后务必在 OpenWebUI 社区中验证更改
|
||||
|
||||
- Requires valid API key with update permissions
|
||||
需要具有更新权限的有效 API 密钥
|
||||
|
||||
AUTHOR / 作者:
|
||||
-------------
|
||||
Fu-Jie
|
||||
GitHub: https://github.com/Fu-Jie/awesome-openwebui
|
||||
|
||||
======================================================================
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, List
|
||||
|
||||
|
||||
def _load_dotenv(repo_root: Path) -> None:
|
||||
try:
|
||||
from dotenv import load_dotenv # type: ignore
|
||||
except Exception as exc: # pragma: no cover
|
||||
print("Missing dependency: python-dotenv. Please install it and retry.")
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
env_path = repo_root / ".env"
|
||||
load_dotenv(env_path)
|
||||
|
||||
|
||||
def _get_repo_root() -> Path:
|
||||
return Path(__file__).resolve().parents[3]
|
||||
|
||||
|
||||
def _get_staged_readmes(repo_root: Path) -> List[Path]:
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
[
|
||||
"git",
|
||||
"-C",
|
||||
str(repo_root),
|
||||
"diff",
|
||||
"--cached",
|
||||
"--name-only",
|
||||
"--",
|
||||
"*.md",
|
||||
],
|
||||
text=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print(f"Failed to read staged files: {exc}")
|
||||
return []
|
||||
|
||||
paths = []
|
||||
for line in output.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.endswith("README.md") or line.endswith("README_CN.md"):
|
||||
paths.append(repo_root / line)
|
||||
return paths
|
||||
|
||||
|
||||
def _get_staged_plugin_files(repo_root: Path) -> List[Path]:
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
[
|
||||
"git",
|
||||
"-C",
|
||||
str(repo_root),
|
||||
"diff",
|
||||
"--cached",
|
||||
"--name-only",
|
||||
"--",
|
||||
"*.py",
|
||||
],
|
||||
text=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print(f"Failed to read staged files: {exc}")
|
||||
return []
|
||||
|
||||
paths = []
|
||||
for line in output.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
if "/plugins/" not in line:
|
||||
continue
|
||||
if line.endswith("__init__.py") or os.path.basename(line).startswith("test_"):
|
||||
continue
|
||||
paths.append(repo_root / line)
|
||||
return paths
|
||||
|
||||
|
||||
def _parse_frontmatter(content: str) -> Dict[str, str]:
|
||||
match = re.search(r'^\s*"""\n(.*?)\n"""', content, re.DOTALL)
|
||||
if not match:
|
||||
match = re.search(r'"""\n(.*?)\n"""', content, re.DOTALL)
|
||||
if not match:
|
||||
return {}
|
||||
|
||||
frontmatter = match.group(1)
|
||||
meta: Dict[str, str] = {}
|
||||
for line in frontmatter.split("\n"):
|
||||
if ":" in line:
|
||||
key, value = line.split(":", 1)
|
||||
meta[key.strip()] = value.strip()
|
||||
return meta
|
||||
|
||||
|
||||
def _find_plugin_file(readme_path: Path) -> Optional[Path]:
|
||||
plugin_dir = readme_path.parent
|
||||
is_cn = readme_path.name.lower().endswith("readme_cn.md")
|
||||
|
||||
py_files = [
|
||||
p
|
||||
for p in plugin_dir.glob("*.py")
|
||||
if p.name != "__init__.py" and not p.name.startswith("test_")
|
||||
]
|
||||
if not py_files:
|
||||
return None
|
||||
|
||||
cn_files = [p for p in py_files if p.stem.endswith("_cn")]
|
||||
en_files = [p for p in py_files if not p.stem.endswith("_cn")]
|
||||
|
||||
candidates = cn_files + en_files if is_cn else en_files + cn_files
|
||||
|
||||
# Prefer files that contain openwebui_id/post_id in frontmatter
|
||||
for candidate in candidates:
|
||||
post_id = _get_post_id(candidate)
|
||||
if post_id:
|
||||
return candidate
|
||||
|
||||
return candidates[0] if candidates else None
|
||||
|
||||
|
||||
def _get_post_id(plugin_file: Path) -> Optional[str]:
|
||||
try:
|
||||
content = plugin_file.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
meta = _parse_frontmatter(content)
|
||||
return meta.get("openwebui_id") or meta.get("post_id")
|
||||
|
||||
|
||||
def _get_plugin_metadata(plugin_file: Path) -> Dict[str, str]:
|
||||
try:
|
||||
content = plugin_file.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
return {}
|
||||
return _parse_frontmatter(content)
|
||||
|
||||
|
||||
def _find_readme_for_plugin(plugin_file: Path) -> Optional[str]:
|
||||
plugin_dir = plugin_file.parent
|
||||
is_cn = plugin_file.stem.endswith("_cn")
|
||||
readme_candidates = ["README_CN.md", "README.md"] if is_cn else ["README.md", "README_CN.md"]
|
||||
for name in readme_candidates:
|
||||
readme_path = plugin_dir / name
|
||||
if readme_path.exists():
|
||||
return readme_path.read_text(encoding="utf-8")
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
repo_root = _get_repo_root()
|
||||
_load_dotenv(repo_root)
|
||||
|
||||
api_key = os.environ.get("OPENWEBUI_API_KEY")
|
||||
if not api_key:
|
||||
print("OPENWEBUI_API_KEY is not set in environment.")
|
||||
return 1
|
||||
|
||||
client_module_path = repo_root / "scripts" / "openwebui_community_client.py"
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"openwebui_community_client", client_module_path
|
||||
)
|
||||
if not spec or not spec.loader:
|
||||
print("Failed to load openwebui_community_client module.")
|
||||
return 1
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
client = module.get_client(api_key)
|
||||
|
||||
staged_plugins = _get_staged_plugin_files(repo_root)
|
||||
staged_readmes = _get_staged_readmes(repo_root)
|
||||
if not staged_plugins and not staged_readmes:
|
||||
print("No staged README or plugin files found.")
|
||||
return 0
|
||||
|
||||
updated_post_ids: set[str] = set()
|
||||
|
||||
for plugin_file in staged_plugins:
|
||||
if not plugin_file.exists():
|
||||
print(f"Skipped (missing): {plugin_file}")
|
||||
continue
|
||||
|
||||
post_id = _get_post_id(plugin_file)
|
||||
if not post_id:
|
||||
print(f"Skipped (no openwebui_id): {plugin_file}")
|
||||
continue
|
||||
|
||||
try:
|
||||
post_data = client.get_post(post_id)
|
||||
if not post_data:
|
||||
print(f"Skipped (post not found): {plugin_file}")
|
||||
continue
|
||||
|
||||
source_code = plugin_file.read_text(encoding="utf-8")
|
||||
metadata = _get_plugin_metadata(plugin_file)
|
||||
readme_content = _find_readme_for_plugin(plugin_file)
|
||||
|
||||
ok = client.update_plugin(
|
||||
post_id=post_id,
|
||||
source_code=source_code,
|
||||
readme_content=readme_content or metadata.get("description", ""),
|
||||
metadata=metadata,
|
||||
media_urls=None,
|
||||
)
|
||||
if ok:
|
||||
updated_post_ids.add(post_id)
|
||||
print(f"Updated plugin -> {plugin_file} (post_id: {post_id})")
|
||||
except Exception as exc:
|
||||
print(f"Failed: {plugin_file} ({exc})")
|
||||
|
||||
for readme_path in staged_readmes:
|
||||
if not readme_path.exists():
|
||||
print(f"Skipped (missing): {readme_path}")
|
||||
continue
|
||||
|
||||
plugin_file = _find_plugin_file(readme_path)
|
||||
if not plugin_file:
|
||||
print(f"Skipped (no plugin file): {readme_path}")
|
||||
continue
|
||||
|
||||
post_id = _get_post_id(plugin_file)
|
||||
if not post_id:
|
||||
print(f"Skipped (no openwebui_id): {readme_path}")
|
||||
continue
|
||||
|
||||
try:
|
||||
if post_id in updated_post_ids:
|
||||
print(f"Skipped (already updated via plugin): {readme_path}")
|
||||
continue
|
||||
|
||||
post_data = client.get_post(post_id)
|
||||
if not post_data:
|
||||
print(f"Skipped (post not found): {readme_path}")
|
||||
continue
|
||||
|
||||
readme_content = readme_path.read_text(encoding="utf-8")
|
||||
|
||||
# Update README content only, keep other fields unchanged.
|
||||
post_data["content"] = readme_content
|
||||
|
||||
ok = client.update_post(post_id, post_data)
|
||||
if ok:
|
||||
print(f"Updated README -> {readme_path} (post_id: {post_id})")
|
||||
except Exception as exc:
|
||||
print(f"Failed: {readme_path} ({exc})")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
98
plugins/debug/copilot_sdk_research/GUIDE_ERRATA.md
Normal file
98
plugins/debug/copilot_sdk_research/GUIDE_ERRATA.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 开发指南勘误与更新
|
||||
|
||||
## 权限控制章节修正(第 2.2 节)
|
||||
|
||||
### ⚠️ 关键勘误
|
||||
|
||||
在实际测试中发现,Shell 权限请求使用的是 **`fullCommandText`** 字段,而非文档中提到的 `command` 字段。
|
||||
|
||||
### 需要修改的代码行
|
||||
|
||||
**第 89 行(错误):**
|
||||
|
||||
```python
|
||||
command = request.get("command", "")
|
||||
```
|
||||
|
||||
**应改为(正确):**
|
||||
|
||||
```python
|
||||
command = request.get("fullCommandText", "") or request.get("command", "")
|
||||
```
|
||||
|
||||
### 完整的正确实现
|
||||
|
||||
```python
|
||||
async def on_user_permission_request(request, context):
|
||||
"""
|
||||
统一权限审批网关
|
||||
"""
|
||||
kind = request.get("kind") # shell, write, mcp, read, url
|
||||
# ✅ 正确:使用 fullCommandText(shell)或 command(其他)
|
||||
command = request.get("fullCommandText", "") or request.get("command", "")
|
||||
|
||||
# 1. 超级模式:全部允许
|
||||
if self.valves.PERMISSIONS_ALLOW_ALL:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 2. 默认安全:始终允许 "读" 和 "Web浏览"
|
||||
if kind in ["read", "url"]:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 3. 细粒度控制
|
||||
if kind == "shell":
|
||||
if self.valves.PERMISSIONS_ALLOW_SHELL:
|
||||
return {"kind": "approved"}
|
||||
|
||||
pattern = self.valves.PERMISSIONS_SHELL_ALLOW_PATTERN
|
||||
if pattern and command:
|
||||
try:
|
||||
if re.match(pattern, command):
|
||||
return {"kind": "approved"}
|
||||
except re.error:
|
||||
print(f"[Config Error] Invalid Regex: {pattern}")
|
||||
|
||||
if kind == "write" and self.valves.PERMISSIONS_ALLOW_WRITE:
|
||||
return {"kind": "approved"}
|
||||
|
||||
if kind == "mcp" and self.valves.PERMISSIONS_ALLOW_MCP:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 4. 默认拒绝
|
||||
print(f"[Permission Denied] Blocked: {kind} {command}")
|
||||
return {
|
||||
"kind": "denied-by-rules",
|
||||
"rules": [{"kind": "check-openwebui-valves"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Shell 权限请求的完整结构
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "shell",
|
||||
"toolCallId": "call_xxx",
|
||||
"fullCommandText": "ls -la", // ← 关键字段
|
||||
"intention": "List all files and directories",
|
||||
"commands": [
|
||||
{
|
||||
"identifier": "ls -la",
|
||||
"readOnly": false
|
||||
}
|
||||
],
|
||||
"possiblePaths": [],
|
||||
"possibleUrls": [],
|
||||
"hasWriteFileRedirection": false,
|
||||
"canOfferSessionApproval": false
|
||||
}
|
||||
```
|
||||
|
||||
## 测试验证
|
||||
|
||||
已通过完整测试套件验证(8/8 通过),详见 [PERMISSION_TEST_REPORT.md](./PERMISSION_TEST_REPORT.md)。
|
||||
|
||||
---
|
||||
|
||||
**更新日期**: 2026-01-30
|
||||
**验证状态**: ✅ 已测试
|
||||
**影响范围**: 2.2 权限与确认章节
|
||||
173
plugins/debug/copilot_sdk_research/PERMISSION_TEST_REPORT.md
Normal file
173
plugins/debug/copilot_sdk_research/PERMISSION_TEST_REPORT.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# GitHub Copilot SDK 权限控制测试报告
|
||||
|
||||
## 测试日期
|
||||
|
||||
2026-01-30
|
||||
|
||||
## 测试环境
|
||||
|
||||
- **Model**: gpt-4.1
|
||||
- **Python**: 3.12
|
||||
- **Copilot SDK**: Latest
|
||||
|
||||
## 关键发现
|
||||
|
||||
### 1. Shell 权限请求结构
|
||||
|
||||
Shell 类型的权限请求使用 **`fullCommandText`** 字段,而非 `command` 字段。
|
||||
|
||||
**完整请求示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "shell",
|
||||
"toolCallId": "call_JKLi7tz3uSDQWE3LgzCpvSVy",
|
||||
"fullCommandText": "ls -la",
|
||||
"intention": "List all files and directories with details in the current directory",
|
||||
"commands": [
|
||||
{
|
||||
"identifier": "ls -la",
|
||||
"readOnly": false
|
||||
}
|
||||
],
|
||||
"possiblePaths": [],
|
||||
"possibleUrls": [],
|
||||
"hasWriteFileRedirection": false,
|
||||
"canOfferSessionApproval": false
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 正则匹配模式验证
|
||||
|
||||
正则白名单模式已验证有效,必须使用 `fullCommandText` 字段:
|
||||
|
||||
```python
|
||||
command = request.get("fullCommandText", "") or request.get("command", "")
|
||||
pattern = self.valves.PERMISSIONS_SHELL_ALLOW_PATTERN
|
||||
if pattern and command:
|
||||
if re.match(pattern, command):
|
||||
return {"kind": "approved"}
|
||||
```
|
||||
|
||||
## 测试结果
|
||||
|
||||
### 完整测试套件(8/8 通过 ✅)
|
||||
|
||||
| # | 测试用例 | 配置 | 提示词 | 预期 | 结果 | 状态 |
|
||||
|---|---------|------|--------|------|------|------|
|
||||
| 1 | Default Deny Shell | 默认 | 请执行: ls -la | ❌ Denied | ❌ Denied | ✅ |
|
||||
| 2 | Allow All | allow_all=True | 请执行: ls -la | ✅ Approved | ✅ Approved | ✅ |
|
||||
| 3 | Allow Shell | allow_shell=True | 请执行: pwd | ✅ Approved | ✅ Approved | ✅ |
|
||||
| 4 | Regex Match: ^ls | pattern='^ls' | 请执行: ls -la | ✅ Approved | ✅ Approved | ✅ |
|
||||
| 5 | Regex No Match | pattern='^ls' | 请执行: pwd | ❌ Denied | ❌ Denied | ✅ |
|
||||
| 6 | Regex Complex | pattern='^(ls\|pwd\|echo)' | 请执行: pwd | ✅ Approved | ✅ Approved | ✅ |
|
||||
| 7 | Regex No Match: git | pattern='^(ls\|pwd\|echo)' | 请执行: git status | ❌ Denied | ❌ Denied | ✅ |
|
||||
| 8 | Read Permission | 默认 | Read: README.md | ✅ Approved | ✅ Approved | ✅ |
|
||||
|
||||
**总体通过率: 100%** 🎉
|
||||
|
||||
## 推荐配置示例
|
||||
|
||||
### 1. 安全模式(推荐生产环境)
|
||||
|
||||
```python
|
||||
PERMISSIONS_ALLOW_ALL: bool = False
|
||||
PERMISSIONS_ALLOW_SHELL: bool = False
|
||||
PERMISSIONS_SHELL_ALLOW_PATTERN: str = "^(ls|pwd|echo|cat).*"
|
||||
PERMISSIONS_ALLOW_WRITE: bool = False
|
||||
PERMISSIONS_ALLOW_MCP: bool = True
|
||||
```
|
||||
|
||||
### 2. 开发模式
|
||||
|
||||
```python
|
||||
PERMISSIONS_ALLOW_ALL: bool = False
|
||||
PERMISSIONS_ALLOW_SHELL: bool = False
|
||||
PERMISSIONS_SHELL_ALLOW_PATTERN: str = "^(ls|pwd|echo|cat|grep|git status|npm test).*"
|
||||
PERMISSIONS_ALLOW_WRITE: bool = False
|
||||
PERMISSIONS_ALLOW_MCP: bool = True
|
||||
```
|
||||
|
||||
### 3. 完全信任模式(仅限受控环境)
|
||||
|
||||
```python
|
||||
PERMISSIONS_ALLOW_ALL: bool = True
|
||||
```
|
||||
|
||||
## 实现建议
|
||||
|
||||
### 正确的权限处理代码
|
||||
|
||||
```python
|
||||
import re
|
||||
from typing import Any, Dict
|
||||
|
||||
async def on_user_permission_request(request: Dict[str, Any], context: Dict[str, str]):
|
||||
"""
|
||||
统一权限审批网关
|
||||
"""
|
||||
kind = request.get("kind")
|
||||
# 关键:使用 fullCommandText 而非 command
|
||||
command = request.get("fullCommandText", "") or request.get("command", "")
|
||||
|
||||
# 1. 超级模式
|
||||
if self.valves.PERMISSIONS_ALLOW_ALL:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 2. 默认安全(read、url)
|
||||
if kind in ["read", "url"]:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 3. Shell 细粒度控制
|
||||
if kind == "shell":
|
||||
if self.valves.PERMISSIONS_ALLOW_SHELL:
|
||||
return {"kind": "approved"}
|
||||
|
||||
pattern = self.valves.PERMISSIONS_SHELL_ALLOW_PATTERN
|
||||
if pattern and command:
|
||||
try:
|
||||
if re.match(pattern, command):
|
||||
return {"kind": "approved"}
|
||||
except re.error as e:
|
||||
logger.error(f"Invalid regex: {pattern} - {e}")
|
||||
|
||||
# 4. Write 权限
|
||||
if kind == "write" and self.valves.PERMISSIONS_ALLOW_WRITE:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 5. MCP 权限
|
||||
if kind == "mcp" and self.valves.PERMISSIONS_ALLOW_MCP:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 6. 默认拒绝
|
||||
logger.warning(f"Permission Denied: {kind} {command}")
|
||||
return {
|
||||
"kind": "denied-by-rules",
|
||||
"rules": [{"kind": "security-policy"}]
|
||||
}
|
||||
```
|
||||
|
||||
## 常见正则模式示例
|
||||
|
||||
| 用途 | 正则表达式 | 说明 |
|
||||
|------|-----------|------|
|
||||
| 只读命令 | `^(ls|pwd|cat|echo|grep).*` | 允许常见只读命令 |
|
||||
| Git 只读 | `^git (status\|log\|diff\|show).*` | 允许 Git 只读操作 |
|
||||
| npm/yarn 测试 | `^(npm\|yarn) (test\|run).*` | 允许测试脚本 |
|
||||
| 完全 shell | `.*` | ⚠️ 危险:允许所有命令 |
|
||||
|
||||
## 测试脚本位置
|
||||
|
||||
- 基础测试: [test_shell_permission_pattern.py](./test_shell_permission_pattern.py)
|
||||
- 完整测试套件: [test_permission_comprehensive.py](./test_permission_comprehensive.py)
|
||||
|
||||
## 结论
|
||||
|
||||
✅ **GitHub Copilot SDK 的权限控制机制完全有效**
|
||||
✅ **正则白名单模式已验证可用**
|
||||
⚠️ **必须使用 `fullCommandText` 字段获取命令内容**
|
||||
|
||||
---
|
||||
|
||||
**测试执行者**: GitHub Copilot
|
||||
**审核状态**: ✅ 已验证
|
||||
@@ -0,0 +1,238 @@
|
||||
# OpenWebUI GitHub Copilot Pipe Enhancement Guide
|
||||
|
||||
基于 Copilot SDK 源码级研究的深度技术总结,旨在指导 OpenWebUI Pipe 的功能增强开发。
|
||||
|
||||
## 1. 认证机制 (Authentication)
|
||||
|
||||
官方支持通过环境变量传递 Token。在 Pipe 中,只要确保 `GH_TOKEN` 或 `GITHUB_TOKEN` 存在于环境变量中,Copilot CLI 即可自动识别,无需在 `CopilotClient` 构造函数中重复注入。
|
||||
|
||||
### 核心实现
|
||||
|
||||
Pipe 应确保将 Token(来自 Valve 或 Env)正确设置到当前进程的环境变量中。
|
||||
|
||||
```python
|
||||
import os
|
||||
from copilot import CopilotClient
|
||||
|
||||
# 1. 设置环境变量 (如果从 Valve 获取)
|
||||
if self.valves.GH_TOKEN:
|
||||
os.environ["GH_TOKEN"] = self.valves.GH_TOKEN
|
||||
|
||||
# 2. 初始化客户端
|
||||
# CopilotClient 启动的 CLI 子进程会自动继承当前环境中的 GH_TOKEN
|
||||
client = CopilotClient({
|
||||
# "cli_path": ...,
|
||||
# 注意:无需在此处重复传入 github_token,CLI 会自动读取环境变量
|
||||
})
|
||||
|
||||
# 3. 启动前检查 (建议)
|
||||
# status = await client.get_auth_status()
|
||||
# if not status.isAuthenticated: ...
|
||||
```
|
||||
|
||||
## 2. 权限与确认 (Permissions & Tools) - 核心控制点
|
||||
|
||||
这是用户最关心的部分:如何知道有哪些工具,以及如何控制它们的执行。
|
||||
|
||||
### 2.1 内置工具 (Built-in Tools)
|
||||
|
||||
Copilot CLI 内部管理了一组标准工具,**Python SDK 目前没有直接的 API (`client.list_tools()`) 来列出这些工具**。
|
||||
|
||||
但是,根据 SDK 的 `PermissionRequest` 类型定义 (`copilot/types.py`),我们可以反推其能力类别:
|
||||
|
||||
* **`shell`**: 执行终端命令 (对应 `run_terminal_command` 等)
|
||||
* **`filesystem`** (对应 `read/write`): 文件读写 (对应 `read_file`, `edit_file`, `delete_file` 等)
|
||||
* **`url`**: 网络访问 (对应 `fetch_url` 等)
|
||||
* **`mcp`**: 连接的 MCP 服务器工具
|
||||
|
||||
> **提示**: `available_tools` 参数可以用来“隐藏”工具,让 Agent 根本不知道它有一把锤子。而 `on_permission_request` 是用来拦截 Agent 挥舞锤子的动作。通常我们建议**能力全开 (不设置 available_tools 限制)**,而在**权限层 (on_permission_request) 做拦截**。
|
||||
|
||||
### 2.2 实现“全部允许”与“按需允许”
|
||||
|
||||
建议在 Valves 中增加权限控制字段,并在 `on_permission_request` 中实现逻辑。
|
||||
|
||||
```python
|
||||
import re
|
||||
|
||||
class Valves(BaseModel):
|
||||
# ... 其他 Valve ...
|
||||
# 权限控制开关
|
||||
PERMISSIONS_ALLOW_ALL: bool = Field(default=False, description="DANGER: Auto-approve ALL actions (shell, write, etc).")
|
||||
PERMISSIONS_ALLOW_SHELL: bool = Field(default=False, description="Auto-approve shell commands.")
|
||||
PERMISSIONS_SHELL_ALLOW_PATTERN: str = Field(default="", description="Regex for approved shell commands (e.g., '^ls|^grep').")
|
||||
PERMISSIONS_ALLOW_WRITE: bool = Field(default=False, description="Auto-approve file write/edit/delete.")
|
||||
PERMISSIONS_ALLOW_MCP: bool = Field(default=True, description="Auto-approve MCP tool execution.")
|
||||
|
||||
# 权限处理 Hook 实现
|
||||
async def on_user_permission_request(request, context):
|
||||
"""
|
||||
统一权限审批网关
|
||||
request keys: kind, toolCallId, ... (shell requests have 'command')
|
||||
"""
|
||||
kind = request.get("kind") # shell, write, mcp, read, url
|
||||
|
||||
# 1. 超级模式:全部允许
|
||||
if self.valves.PERMISSIONS_ALLOW_ALL:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 2. 默认安全:始终允许 "读" 和 "Web浏览" (根据需求调整)
|
||||
if kind in ["read", "url"]:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 3. 细粒度控制
|
||||
if kind == "shell":
|
||||
# 3.1 完全允许 Shell
|
||||
if self.valves.PERMISSIONS_ALLOW_SHELL:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 3.2 基于正则允许特定命令
|
||||
command = request.get("command", "")
|
||||
pattern = self.valves.PERMISSIONS_SHELL_ALLOW_PATTERN
|
||||
if pattern and command:
|
||||
try:
|
||||
if re.match(pattern, command):
|
||||
return {"kind": "approved"}
|
||||
except re.error:
|
||||
print(f"[Config Error] Invalid Regex: {pattern}")
|
||||
|
||||
if kind == "write" and self.valves.PERMISSIONS_ALLOW_WRITE:
|
||||
return {"kind": "approved"}
|
||||
|
||||
if kind == "mcp" and self.valves.PERMISSIONS_ALLOW_MCP:
|
||||
return {"kind": "approved"}
|
||||
|
||||
# 4. 默认拒绝
|
||||
print(f"[Permission Denied] Blocked request for: {kind} {request.get('command', '')}")
|
||||
return {
|
||||
"kind": "denied-by-rules",
|
||||
"rules": [{"kind": "check-openwebui-valves"}]
|
||||
}
|
||||
|
||||
# 注册 Hook
|
||||
session = await client.create_session({
|
||||
# ...
|
||||
"on_permission_request": on_user_permission_request
|
||||
})
|
||||
```
|
||||
|
||||
## 3. Agent 与 MCP 集成 (Agents & MCP)
|
||||
|
||||
SDK 中的 Agent 和 MCP 并非独立文件,而是会话配置 (`SessionConfig`) 的一部分。Pipe 可以通过 Valves 动态构建这些配置。
|
||||
|
||||
### 关键映射关系
|
||||
|
||||
| SDK 概念 | OpenWebUI 对应 | 实现位置 | 关键参数 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Custom Agent** | 自定义模型 / Persona | `create_session(custom_agents=[...])` | `name`, `prompt`, `tools` (仅名称) |
|
||||
| **Agent Tools** | Valve 开关 / 预置工具 | `create_session(tools=[func1, func2])` | 必须先在 `tools` 注册函数,Agent 才能引用 |
|
||||
| **MCP Server** | Valve 配置 (JSON) | `create_session(mcp_servers={...})` | `command`, `args`, `env` (本地) |
|
||||
|
||||
### 代码范式:动态构建 Agent
|
||||
|
||||
```python
|
||||
async def create_agent_session(client, user_prompt, model_name):
|
||||
# 1. 定义工具 (必须是函数引用)
|
||||
# 假设已从 OpenWebUI Tools 转换或内置
|
||||
available_tools = [tool_web_search, tool_run_script]
|
||||
|
||||
# 2. 构建 Agent Manifest (针对当前请求的虚拟 Agent)
|
||||
agent_manifest = {
|
||||
"name": "openwebui_agent",
|
||||
"description": "Dynamic agent from OpenWebUI",
|
||||
"prompt": "You are a helpful assistant...", # 这里注入 System Prompt
|
||||
"tools": ["web_search", "run_script"], # 引用上方工具的 name
|
||||
"mcp_servers": {
|
||||
# 可以在这里为特定 Agent 绑定 MCP
|
||||
}
|
||||
}
|
||||
|
||||
# 3. 创建会话
|
||||
session = await client.create_session({
|
||||
"model": "gpt-4", # 底层模型
|
||||
"custom_agents": [agent_manifest],
|
||||
"tools": available_tools, # 注册实际代码
|
||||
"available_tools": ["web_search"], # 白名单控制当前可用工具
|
||||
# ... 权限配置
|
||||
})
|
||||
```
|
||||
|
||||
## 4. MCP 服务器配置 (Native MCP Support)
|
||||
|
||||
Pipe 可以直接支持标准 MCP 协议(Stdio)。不需要额外的 MCP 客户端代理,SDK 原生支持。
|
||||
|
||||
### Valve 配置结构建议
|
||||
|
||||
建议在 Pipe 的 Valves 中增加一个 `MCP_CONFIG` 字段(JSON 字符串),解析后直接传给 SDK。
|
||||
|
||||
```python
|
||||
# Valve 输入示例 (JSON)
|
||||
# {
|
||||
# "brave_search": {
|
||||
# "type": "local",
|
||||
# "command": "npx",
|
||||
# "args": ["-y", "@modelcontextprotocol/server-brave-search"],
|
||||
# "env": {"BRAVE_API_KEY": "..."}
|
||||
# }
|
||||
# }
|
||||
|
||||
# 代码实现
|
||||
mcp_config = json.loads(self.valves.MCP_CONFIG)
|
||||
session = await client.create_session({
|
||||
# ...
|
||||
"mcp_servers": mcp_config,
|
||||
# 注意:必须配合权限自动审批,否则 MCP 工具无法调用
|
||||
"on_permission_request": auto_approve_policy
|
||||
})
|
||||
```
|
||||
|
||||
## 5. 会话管理:持久化 vs 重放 (Persistence)
|
||||
|
||||
OpenWebUI 是无状态的,但 Copilot SDK 是有状态的(保留上下文窗口优化)。
|
||||
|
||||
### 最佳实践:以 `chat_id` 为锚点
|
||||
|
||||
利用 OpenWebUI 提供的 `chat_id` 来决定是 `resume` 还是 `start`。
|
||||
|
||||
1. **Map**: 维护 `Dict[chat_id, session_id]` (内存或数据库)。
|
||||
2. **Flow**:
|
||||
* 请求进来 -> 检查 `chat_id` 是否有对应的 `session_id`。
|
||||
* **有**: 尝试 `client.resume_session(session_id)`。
|
||||
* *注意*:Resume 时必须重新传入 `tools`, `hooks`, `on_permission_request`,因为这些 Python 对象不会被序列化保存。
|
||||
* **无/失败**: 调用 `client.create_session()`,并将新 `session_id` 存入 Map。
|
||||
3. **Fallback**: 如果 Resume 失败(例如后端重启 SDK 进程丢失),回退到 Create 新会话,并可选地将 OpenWebUI 传来的 `messages` 历史以 System Message 或历史插入的方式“重放”进去(虽然 SDK 不直接支持 insert history,但可以通过连续的 `send` 模拟,但这很慢)。
|
||||
* *简易方案*:Resume 失败就作为新对话开始,只带入 System Prompt。
|
||||
|
||||
## 6. 高级 Hook:提示词增强
|
||||
|
||||
利用 `on_user_prompt_submitted` 钩子,可以在不修改用户可见内容的情况下,向 Copilot 注入隐式上下文(例如当前文件内容、Pipe 的元指令)。
|
||||
|
||||
```python
|
||||
async def inject_context_hook(input_data, ctx):
|
||||
user_prompt = input_data["prompt"]
|
||||
|
||||
# 比如:检测到用户在问代码,自动附加上下文
|
||||
additional_context = "Current Language: Python. Framework: OpenWebUI."
|
||||
|
||||
return {
|
||||
"modifiedPrompt": user_prompt, # 可以在这里改写提示词
|
||||
"additionalContext": additional_context # 注入隐藏上下文
|
||||
}
|
||||
|
||||
session = await client.create_session({
|
||||
# ...
|
||||
"hooks": {
|
||||
"on_user_prompt_submitted": inject_context_hook
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**总结开发清单:**
|
||||
|
||||
1. [ ] **Env Auth**: 读取环境变量 -> `CopilotClient`。
|
||||
2. [ ] **Permission Valve**: 实现 `PERMISSIONS_ALLOW_ALL/SHELL` 等 Valves。
|
||||
3. [ ] **Auto-Approve Hook**: 实现 `on_permission_request` 逻辑。
|
||||
4. [ ] **MCP Valve**: 添加 JSON Valve -> `session.mcp_servers`。
|
||||
5. [ ] **Session Map**: 实现 `chat_id` <-> `session_id` 的简单的内存映射。
|
||||
6. [ ] **Resume Logic**: 优先 `resume_session`,并记得并在 resume 时重传 Hook 和 Tools。
|
||||
620
plugins/debug/copilot_sdk_research/copilot_sdk_guide.py
Normal file
620
plugins/debug/copilot_sdk_research/copilot_sdk_guide.py
Normal file
@@ -0,0 +1,620 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import asyncio
|
||||
import datetime as dt
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
from typing import Iterable, List, Optional
|
||||
|
||||
from copilot import CopilotClient
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
logger = logging.getLogger("copilot_sdk_guide")
|
||||
|
||||
DEFAULT_CONTEXT_URLS = [
|
||||
"https://raw.githubusercontent.com/github/copilot-sdk/main/README.md",
|
||||
"https://raw.githubusercontent.com/github/copilot-sdk/main/python/README.md",
|
||||
"https://raw.githubusercontent.com/github/copilot-sdk/main/docs/getting-started.md",
|
||||
"https://raw.githubusercontent.com/github/copilot-cli/main/README.md",
|
||||
"https://raw.githubusercontent.com/github/copilot-cli/main/changelog.md",
|
||||
"https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli",
|
||||
"https://docs.github.com/en/copilot/concepts/agents/about-agent-skills",
|
||||
"https://raw.githubusercontent.com/github/awesome-copilot/main/README.md",
|
||||
"https://raw.githubusercontent.com/github/awesome-copilot/main/skills/copilot-sdk/SKILL.md",
|
||||
"https://raw.githubusercontent.com/github/awesome-copilot/main/instructions/agent-skills.instructions.md",
|
||||
]
|
||||
|
||||
AWESOME_COPILOT_REPO = "github/awesome-copilot"
|
||||
AWESOME_COPILOT_BRANCH = "main"
|
||||
AWESOME_COPILOT_DOC_DIRS = ["docs/", "instructions/"]
|
||||
|
||||
TOPICS = [
|
||||
"MCP Server Integration: JSON-RPC config and SDK hooks",
|
||||
"Agent Manifests: Defining capabilities and permissions programmatically",
|
||||
"Headless Auth: Device Code Flow and credential persistence",
|
||||
"Session Replay vs Resume: Handling stateless frontend history",
|
||||
"Advanced Session Hooks: Intercepting and modifying user prompts",
|
||||
"Workspace Virtualization: Handling CWD for remote/virtual files",
|
||||
"Error Recovery: Handling session disconnects and re-auth",
|
||||
"Confirmation Events: programmatic handling of 'confirmation_required'",
|
||||
"Skills: Conflict resolution and precedence defaults",
|
||||
"Debugging: Tracing JSON-RPC traffic in the SDK",
|
||||
"Billing & Policies: How seat management affects SDK features",
|
||||
]
|
||||
|
||||
QUESTION_TEMPLATES = [
|
||||
"Give a concise overview of {topic}.",
|
||||
"Provide best practices and common pitfalls for {topic}.",
|
||||
"Show a minimal example snippet for {topic}.",
|
||||
"List recommended configuration defaults for {topic}.",
|
||||
"How does {topic} relate to building a custom Agent?",
|
||||
]
|
||||
|
||||
CLI_FOCUS_QUESTIONS = [
|
||||
"How to configure MCP servers in ~/.copilot/config.json for SDK usage?",
|
||||
"What CLI environment variables force 'Agent' mode vs 'Generic' mode?",
|
||||
"Explain the 'confirmation' flow in CLI and how it maps to SDK events.",
|
||||
"Does the CLI support 'dry-run' permission checks for tools?",
|
||||
"What are the undocumented requirements for 'workspace' context updates?",
|
||||
"How does the CLI handle 'device code' re-authentication automatically?",
|
||||
]
|
||||
|
||||
|
||||
def build_questions(max_questions: int) -> List[str]:
|
||||
questions: List[str] = []
|
||||
|
||||
for topic in TOPICS:
|
||||
for template in QUESTION_TEMPLATES:
|
||||
questions.append(template.format(topic=topic))
|
||||
|
||||
questions.extend(CLI_FOCUS_QUESTIONS)
|
||||
|
||||
# De-duplicate while preserving order
|
||||
seen = set()
|
||||
uniq: List[str] = []
|
||||
for q in questions:
|
||||
if q in seen:
|
||||
continue
|
||||
seen.add(q)
|
||||
uniq.append(q)
|
||||
|
||||
return uniq[:max_questions]
|
||||
|
||||
|
||||
def build_deep_dive_prompts() -> List[str]:
|
||||
return [
|
||||
"Provide a python code example for configuring `CopilotClient` to connect to a local MCP server (e.g. Brave Search) via `CopilotClient` config.",
|
||||
"Explain how to programmatically handle `tool.confirmation_required` events in a non-interactive stream using `session.on()`.",
|
||||
"Show how to implement a 'Device Flow' login helper using SDK primitives (if available) or raw HTTP showing how to persist credentials.",
|
||||
"Compare the pros and cons of 'Session Replay' (fast-forwarding history) vs 'Session Resume' (stateful ID) for a stateless web backend like OpenWebUI.",
|
||||
"Detail the exact protocol for 'Virtual Workspace': how to implement a file system provider that feeds content to Copilot without physical files.",
|
||||
"Create an 'Agent Manifest' example: how to define an Agent capable of specific high-privileged tools via SDK.",
|
||||
"List all 'hidden' `SessionConfig` parameters relevant to Agent behavior and personality.",
|
||||
]
|
||||
|
||||
|
||||
def load_questions(path: str) -> List[str]:
|
||||
if path.lower().endswith(".json"):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
if isinstance(data, list):
|
||||
return [str(x).strip() for x in data if str(x).strip()]
|
||||
raise ValueError("JSON must be an array of strings")
|
||||
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
lines = [line.strip() for line in f.readlines()]
|
||||
return [line for line in lines if line]
|
||||
|
||||
|
||||
def fetch_url(url: str, headers: Optional[dict] = None) -> str:
|
||||
import urllib.request
|
||||
import time
|
||||
|
||||
retries = 3
|
||||
if headers is None:
|
||||
headers = {}
|
||||
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
|
||||
for i in range(retries):
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=20) as response:
|
||||
return response.read().decode("utf-8", errors="replace")
|
||||
except Exception as exc:
|
||||
if i == retries - 1:
|
||||
logger.warning(
|
||||
"Failed to fetch %s after %d attempts: %s", url, retries, exc
|
||||
)
|
||||
return ""
|
||||
time.sleep(1 * (i + 1))
|
||||
return ""
|
||||
|
||||
|
||||
def list_repo_markdown_urls(
|
||||
repo: str,
|
||||
branch: str,
|
||||
dir_prefixes: List[str],
|
||||
) -> List[str]:
|
||||
api_url = f"https://api.github.com/repos/{repo}/git/trees/{branch}?recursive=1"
|
||||
headers = {}
|
||||
if os.environ.get("GITHUB_TOKEN"):
|
||||
headers["Authorization"] = f"token {os.environ.get('GITHUB_TOKEN')}"
|
||||
|
||||
try:
|
||||
content = fetch_url(api_url, headers=headers)
|
||||
if not content:
|
||||
return []
|
||||
data = json.loads(content)
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to list repo tree: %s", exc)
|
||||
return []
|
||||
|
||||
tree = data.get("tree", []) if isinstance(data, dict) else []
|
||||
urls: List[str] = []
|
||||
for item in tree:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
path = item.get("path", "")
|
||||
if not path or not path.endswith(".md"):
|
||||
continue
|
||||
if any(path.startswith(prefix) for prefix in dir_prefixes):
|
||||
raw = f"https://raw.githubusercontent.com/{repo}/{branch}/{path}"
|
||||
urls.append(raw)
|
||||
return urls
|
||||
|
||||
|
||||
def read_local_sdk_source(max_chars: int = 300000) -> str:
|
||||
"""
|
||||
Locates the installed 'copilot' package and reads its source code.
|
||||
This ensures analysis is based on the actual installed version, not just docs.
|
||||
"""
|
||||
try:
|
||||
import copilot
|
||||
except ImportError:
|
||||
logger.error("Could not import 'copilot' SDK. Is it installed?")
|
||||
return ""
|
||||
|
||||
package_dir = os.path.dirname(copilot.__file__)
|
||||
logger.info(f"Reading SDK source from: {package_dir}")
|
||||
|
||||
source_chunks = []
|
||||
total_chars = 0
|
||||
|
||||
# Prioritize key files that define core logic
|
||||
priority_files = ["client.py", "session.py", "types.py", "events.py", "__init__.py"]
|
||||
|
||||
# First pass: Recursively find all .py files
|
||||
all_py_files = []
|
||||
for root, dirs, files in os.walk(package_dir):
|
||||
if "__pycache__" in root:
|
||||
continue
|
||||
for file in files:
|
||||
if file.endswith(".py"):
|
||||
all_py_files.append(os.path.join(root, file))
|
||||
|
||||
# Sort files: priority files first, then alphabetical
|
||||
def sort_key(path):
|
||||
fname = os.path.basename(path)
|
||||
if fname in priority_files:
|
||||
return (0, priority_files.index(fname))
|
||||
return (1, path)
|
||||
|
||||
all_py_files.sort(key=sort_key)
|
||||
|
||||
for path in all_py_files:
|
||||
rel_path = os.path.relpath(path, os.path.dirname(package_dir))
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Add file delimiter for the model
|
||||
header = f"\n\n# ==================================================\n# SOURCE CODE FILE: {rel_path}\n# ==================================================\n"
|
||||
chunk = header + content
|
||||
|
||||
if total_chars + len(chunk) > max_chars:
|
||||
remaining = max_chars - total_chars
|
||||
if remaining > len(header) + 100:
|
||||
source_chunks.append(
|
||||
chunk[:remaining] + "\n# [TRUNCATED DUE TO LENGTH LIMIT]"
|
||||
)
|
||||
logger.warning(f"Context limit reached. Stopping at {rel_path}")
|
||||
break
|
||||
|
||||
source_chunks.append(chunk)
|
||||
total_chars += len(chunk)
|
||||
logger.info(f"Loaded source file: {rel_path} ({len(content)} chars)")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read source file {path}: {e}")
|
||||
|
||||
return "".join(source_chunks)
|
||||
|
||||
|
||||
def build_context(urls: Iterable[str], max_chars: int) -> str:
|
||||
chunks: List[str] = []
|
||||
remaining = max_chars
|
||||
|
||||
for url in urls:
|
||||
if remaining <= 0:
|
||||
break
|
||||
try:
|
||||
content = fetch_url(url)
|
||||
header = f"[Source: {url}]\n"
|
||||
if len(header) >= remaining:
|
||||
break
|
||||
remaining -= len(header)
|
||||
|
||||
if len(content) > remaining:
|
||||
content = content[:remaining] + "\n[TRUNCATED]\n"
|
||||
remaining = 0
|
||||
else:
|
||||
remaining -= len(content)
|
||||
|
||||
chunks.append(header + content)
|
||||
logger.info("Fetched context: %s", url)
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to fetch %s: %s", url, exc)
|
||||
|
||||
return "\n\n".join(chunks)
|
||||
|
||||
|
||||
def write_jsonl(path: str, item: dict) -> None:
|
||||
with open(path, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(item, ensure_ascii=False) + "\n")
|
||||
|
||||
|
||||
def write_markdown_header(path: str, title: str, meta: dict) -> None:
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(f"# {title}\n\n")
|
||||
for k, v in meta.items():
|
||||
f.write(f"- **{k}**: {v}\n")
|
||||
f.write("\n---\n\n")
|
||||
|
||||
|
||||
def append_markdown_qa(path: str, question: str, answer: str) -> None:
|
||||
with open(path, "a", encoding="utf-8") as f:
|
||||
f.write(f"## Q: {question}\n\n")
|
||||
f.write(f"{answer}\n\n")
|
||||
|
||||
|
||||
def clamp_questions(questions: List[str], max_questions: int) -> List[str]:
|
||||
return questions[: max(1, min(max_questions, 400))]
|
||||
|
||||
|
||||
def print_progress_bar(
|
||||
iteration,
|
||||
total,
|
||||
prefix="",
|
||||
suffix="",
|
||||
decimals=1,
|
||||
length=50,
|
||||
fill="█",
|
||||
printEnd="\r",
|
||||
):
|
||||
"""
|
||||
Call in a loop to create terminal progress bar
|
||||
"""
|
||||
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
|
||||
filledLength = int(length * iteration // total)
|
||||
bar = fill * filledLength + "-" * (length - filledLength)
|
||||
# Clear line extension to handle shrinking suffixes
|
||||
print(f"\r{prefix} |{bar}| {percent}% {suffix}\033[K", end=printEnd)
|
||||
# Print New Line on Complete
|
||||
if iteration == total:
|
||||
print()
|
||||
|
||||
|
||||
async def run_session(
|
||||
model: str,
|
||||
questions: List[str],
|
||||
output_dir: str,
|
||||
context: str,
|
||||
session_id: Optional[str],
|
||||
delay: float,
|
||||
output_lang: str,
|
||||
enable_infinite_sessions: bool,
|
||||
timeout: int,
|
||||
) -> None:
|
||||
client = CopilotClient()
|
||||
await client.start()
|
||||
|
||||
session_config = {"model": model}
|
||||
if session_id:
|
||||
session_config["session_id"] = session_id
|
||||
if enable_infinite_sessions:
|
||||
session_config["infinite_sessions"] = {
|
||||
"enabled": True,
|
||||
"background_compaction_threshold": 0.8,
|
||||
"buffer_exhaustion_threshold": 0.95,
|
||||
}
|
||||
|
||||
session = await client.create_session(session_config)
|
||||
|
||||
timestamp = dt.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
jsonl_path = os.path.join(output_dir, f"copilot_sdk_guide_{timestamp}.jsonl")
|
||||
md_path = os.path.join(output_dir, f"copilot_sdk_guide_{timestamp}.md")
|
||||
|
||||
write_markdown_header(
|
||||
md_path,
|
||||
"GitHub Copilot SDK & CLI 研究报告",
|
||||
{
|
||||
"model": model,
|
||||
"questions": len(questions),
|
||||
"timestamp": timestamp,
|
||||
"language": output_lang,
|
||||
},
|
||||
)
|
||||
|
||||
lang_instruction = "Chinese" if "zh" in output_lang.lower() else "English"
|
||||
|
||||
system_prompt = textwrap.dedent(
|
||||
f"""
|
||||
You are an expert assistant. Focus on GitHub Copilot SDK and GitHub Copilot CLI.
|
||||
|
||||
CRITICAL INSTRUCTION: SOURCE CODE FIRST.
|
||||
You have been provided with the ACTUAL PYTHON SOURCE CODE of the `copilot` SDK in the context.
|
||||
When answering questions:
|
||||
1. FIRST, analyze the provided source code (look for class definitions, type hints, methods).
|
||||
2. THEN, refer to documentation if source code is ambiguous.
|
||||
3. Do NOT hallucinate methods that do not exist in the source code.
|
||||
4. If a feature (like MCP) is not explicitly in the code, explain how to implement it using the available primitives (low-level hooks/events).
|
||||
|
||||
Provide accurate, concise answers in {lang_instruction}. When relevant, include command names,
|
||||
configuration keys, and pitfalls. Use bullet points where useful.
|
||||
|
||||
Output requirements:
|
||||
- Write in {lang_instruction}.
|
||||
- Provide practical code snippets (Python/TypeScript/CLI) when helpful.
|
||||
- Include a short "建议/落地" section for integration into a pipe.
|
||||
- If citing facts from provided context, briefly mention the source URL.
|
||||
"""
|
||||
).strip()
|
||||
|
||||
if context:
|
||||
system_prompt += "\n\nAdditional context:\n" + context
|
||||
|
||||
await session.send_and_wait({"prompt": system_prompt}, timeout=timeout)
|
||||
|
||||
total_q = len(questions)
|
||||
print_progress_bar(0, total_q, prefix="Progress:", suffix="Starting...", length=30)
|
||||
|
||||
for idx, question in enumerate(questions, start=1):
|
||||
# Update progress bar (Asking...)
|
||||
q_short = (question[:40] + "...") if len(question) > 40 else question.ljust(43)
|
||||
print_progress_bar(
|
||||
idx - 1, total_q, prefix="Progress:", suffix=f"Asking: {q_short}", length=30
|
||||
)
|
||||
|
||||
# Log to file/debug only
|
||||
logger.debug("[%s/%s] Asking: %s", idx, total_q, question)
|
||||
|
||||
answer = ""
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = await session.send_and_wait(
|
||||
{"prompt": question}, timeout=timeout
|
||||
)
|
||||
answer = response.data.content if response and response.data else ""
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error asking question (Attempt {attempt+1}/{max_retries}): {e}"
|
||||
)
|
||||
if attempt < max_retries - 1:
|
||||
await asyncio.sleep(2)
|
||||
else:
|
||||
answer = f"Error retrieving answer: {e}"
|
||||
|
||||
write_jsonl(
|
||||
jsonl_path,
|
||||
{
|
||||
"index": idx,
|
||||
"question": question,
|
||||
"answer": answer,
|
||||
"model": model,
|
||||
},
|
||||
)
|
||||
append_markdown_qa(md_path, question, answer)
|
||||
|
||||
# Update progress bar (Done...)
|
||||
print_progress_bar(
|
||||
idx, total_q, prefix="Progress:", suffix=f"Done: {q_short}", length=30
|
||||
)
|
||||
|
||||
if delay > 0:
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
await session.destroy()
|
||||
await client.stop()
|
||||
|
||||
logger.info("Saved output to %s and %s", jsonl_path, md_path)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Ask up to 100 Copilot SDK questions via GitHub Copilot SDK",
|
||||
)
|
||||
parser.add_argument("--model", default="gpt-5.2-codex", help="Model to use")
|
||||
parser.add_argument(
|
||||
"--max-questions",
|
||||
type=int,
|
||||
default=100,
|
||||
help="Max number of questions (1-400)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--questions-file",
|
||||
default="",
|
||||
help="Path to .txt or .json list of questions",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--context-url",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Additional context URL (repeatable)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-default-context",
|
||||
action="store_true",
|
||||
help="Disable default Copilot SDK context URLs",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-awesome-copilot-docs",
|
||||
action="store_true",
|
||||
help="Include all markdown files from awesome-copilot/docs",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-awesome-copilot-instructions",
|
||||
action="store_true",
|
||||
help="Include all markdown files from awesome-copilot/instructions",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-sdk-source",
|
||||
action="store_true",
|
||||
help="Do NOT read local SDK source code (default: reads source)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--session-id",
|
||||
default="",
|
||||
help="Optional custom session ID",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
default="",
|
||||
help="Directory to save outputs",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--delay",
|
||||
type=float,
|
||||
default=0.5,
|
||||
help="Delay between questions (seconds)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-context-chars",
|
||||
type=int,
|
||||
default=400000,
|
||||
help="Max characters of aggregated context (default: 400000)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--disable-infinite-sessions",
|
||||
action="store_true",
|
||||
help="Disable infinite sessions (default: enabled)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-lang",
|
||||
default="zh-CN",
|
||||
help="Output language (default: zh-CN)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--deep-dive",
|
||||
action="store_true",
|
||||
help="Append deep-dive prompts for more detailed research",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timeout",
|
||||
type=int,
|
||||
default=3600,
|
||||
help="Session request timeout in seconds (default: 3600)",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
|
||||
if args.questions_file:
|
||||
questions = load_questions(args.questions_file)
|
||||
else:
|
||||
# Generate enough questions to cover everything
|
||||
questions = build_questions(9999)
|
||||
|
||||
if args.deep_dive:
|
||||
# Prepend deep dive questions to ensure they are prioritized
|
||||
questions = build_deep_dive_prompts() + questions
|
||||
|
||||
questions = clamp_questions(questions, args.max_questions)
|
||||
if not questions:
|
||||
logger.error("No questions to ask")
|
||||
sys.exit(1)
|
||||
|
||||
context_urls = [] if args.no_default_context else list(DEFAULT_CONTEXT_URLS)
|
||||
|
||||
if args.include_awesome_copilot_docs:
|
||||
context_urls.extend(
|
||||
list_repo_markdown_urls(
|
||||
AWESOME_COPILOT_REPO,
|
||||
AWESOME_COPILOT_BRANCH,
|
||||
["docs/"],
|
||||
)
|
||||
)
|
||||
|
||||
if args.include_awesome_copilot_instructions:
|
||||
context_urls.extend(
|
||||
list_repo_markdown_urls(
|
||||
AWESOME_COPILOT_REPO,
|
||||
AWESOME_COPILOT_BRANCH,
|
||||
["instructions/"],
|
||||
)
|
||||
)
|
||||
|
||||
context_urls.extend(args.context_url or [])
|
||||
|
||||
# 1. Read local source code first (Priority: High)
|
||||
# We allocate up to max_context_chars to source code initially.
|
||||
# The actual usage will likely be less for a typical SDK.
|
||||
source_context = ""
|
||||
source_chars_count = 0
|
||||
if not args.no_sdk_source:
|
||||
source_context = read_local_sdk_source(args.max_context_chars)
|
||||
source_chars_count = len(source_context)
|
||||
logger.info(f"Source context usage: {source_chars_count} chars")
|
||||
|
||||
# 2. Calculate remaining budget for Web Docs (Priority: Secondary)
|
||||
# We ensure we don't exceed the global limit.
|
||||
remaining_chars = max(10000, args.max_context_chars - source_chars_count)
|
||||
logger.info(f"Remaining budget for web docs: {remaining_chars} chars")
|
||||
|
||||
# 3. Fetch remote docs
|
||||
web_context = build_context(context_urls, remaining_chars)
|
||||
|
||||
combined_context = ""
|
||||
# Assemble context in order of authority (Source > Docs)
|
||||
if source_context:
|
||||
combined_context += (
|
||||
"# PRIMARY SOURCE: LOCAL SDK CODE (AUTHORITATIVE)\n"
|
||||
+ source_context
|
||||
+ "\n\n"
|
||||
)
|
||||
if web_context:
|
||||
combined_context += (
|
||||
"# SECONDARY SOURCE: WEB DOCUMENTATION & AWESOME-COPILOT\n" + web_context
|
||||
)
|
||||
|
||||
output_dir = args.output_dir or os.path.join(
|
||||
os.getcwd(), "plugins", "debug", "copilot_sdk_research", "outputs"
|
||||
)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
asyncio.run(
|
||||
run_session(
|
||||
model=args.model,
|
||||
questions=questions,
|
||||
output_dir=output_dir,
|
||||
context=combined_context,
|
||||
session_id=args.session_id or None,
|
||||
delay=args.delay,
|
||||
output_lang=args.output_lang,
|
||||
enable_infinite_sessions=not args.disable_infinite_sessions,
|
||||
timeout=args.timeout,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2
plugins/debug/copilot_sdk_research/dummy_test_file.txt
Normal file
2
plugins/debug/copilot_sdk_research/dummy_test_file.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
This is a dummy test file for verifying permission controls.
|
||||
You can delete this file safely.
|
||||
26
plugins/debug/copilot_sdk_research/inspect_permissions.py
Normal file
26
plugins/debug/copilot_sdk_research/inspect_permissions.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import inspect
|
||||
import json
|
||||
|
||||
try:
|
||||
import copilot
|
||||
from copilot import types
|
||||
|
||||
print(f"Copilot location: {copilot.__file__}")
|
||||
|
||||
print("\n=== Inspecting PermissionRequest types ===")
|
||||
# Look for definition of PermissionRequest or similar
|
||||
|
||||
# In the absence of direct access to the CLI output structure documentation,
|
||||
# we can check if there are type hints or typed dicts in copilot.types
|
||||
|
||||
for name, obj in inspect.getmembers(types):
|
||||
if "Permission" in name or "Request" in name:
|
||||
print(f"\nType: {name}")
|
||||
try:
|
||||
if hasattr(obj, "__annotations__"):
|
||||
print(obj.__annotations__)
|
||||
except:
|
||||
pass
|
||||
|
||||
except ImportError:
|
||||
print("copilot module not installed")
|
||||
@@ -0,0 +1,68 @@
|
||||
{"index": 1, "question": "Provide a python code example for configuring `CopilotClient` to connect to a local MCP server (e.g. Brave Search) via `CopilotClient` config.", "answer": "SDK 源码显示 MCP 服务器通过 `create_session`/`resume_session` 的 `mcp_servers` 配置传入并映射为 `mcpServers`,不是 CopilotClient 构造参数(源:本地源码 copilot/types.py / copilot/client.py)。下面是本地 stdio MCP(以 Brave Search 为例,命令/密钥请替换)的 Python 示例。 \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"mcp_servers\": {\n \"brave_search\": {\n \"type\": \"local\", # 或 \"stdio\"\n \"command\": \"/path/to/brave-search-mcp\", # 替换为你的 MCP server 启动命令\n \"args\": [],\n \"env\": {\"BRAVE_API_KEY\": \"YOUR_KEY\"},\n \"tools\": [\"*\"],\n \"timeout\": 30000, # 毫秒\n }\n },\n })\n\n await session.send_and_wait({\"prompt\": \"用 Brave Search 查询 OpenAI 最新公告\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n要点/坑:\n- `tools`: `[\"*\"]` 表示全量,`[]` 表示不启用\n- `timeout`: 毫秒\n- `command/args/env`: 按你的 MCP server 实际启动方式替换\n\n建议/落地:\n- pipe 内复用同一 session,结束时统一 `client.stop()`", "model": "gpt-5.2-codex"}
|
||||
{"index": 2, "question": "Explain how to programmatically handle `tool.confirmation_required` events in a non-interactive stream using `session.on()`.", "answer": "SDK 源码未定义 `tool.confirmation_required` 事件,权限确认由 JSON‑RPC `permission.request` 触发并交给 `on_permission_request` 回调处理,`session.on()` 只能订阅 `tool.user_requested`/`tool.execution_start` 等事件做记录关联(来源:本地源码 copilot/client.py、copilot/session.py、copilot/types.py)。 \n在非交互流里必须提供 `on_permission_request` 自动审批/拒绝,否则默认拒绝且回调异常也会被拒绝。\n\n要点/坑 \n- 配置键: `on_permission_request`(SessionConfig) \n- 权限类型: `shell` / `write` / `mcp` / `read` / `url` \n- 回调异常或缺失 -> `denied-no-approval-rule-and-could-not-request-from-user`\n\n```python\nimport asyncio\nfrom copilot import CopilotClient\nfrom copilot.generated.session_events import SessionEventType\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n def on_event(ev):\n if ev.type == SessionEventType.TOOL_USER_REQUESTED:\n print(f\"tool requested: {ev.data.tool_name} {ev.data.tool_call_id}\")\n elif ev.type == SessionEventType.TOOL_EXECUTION_START:\n print(f\"tool start: {ev.data.tool_name}\")\n\n async def on_permission_request(req, ctx):\n kind = req.get(\"kind\")\n if kind in (\"read\", \"mcp\"):\n return {\"kind\": \"approved\"}\n return {\"kind\": \"denied-by-rules\", \"rules\": [{\"kind\": \"deny-non-read\"}]}\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"on_permission_request\": on_permission_request,\n })\n session.on(on_event)\n\n await session.send_and_wait({\"prompt\": \"请调用需要权限的工具\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n建议/落地 \n- pipe 启动时注册审批策略 + `session.on` 记录 `tool_call_id` 做审计", "model": "gpt-5.2-codex"}
|
||||
{"index": 3, "question": "Show how to implement a 'Device Flow' login helper using SDK primitives (if available) or raw HTTP showing how to persist credentials.", "answer": "SDK 本身没有 device‑flow 登录 API;可用鉴权原语只有 CopilotClient 的 `github_token`/`use_logged_in_user` 与 `get_auth_status`(源:本地源码 copilot/client.py、copilot/types.py)。 \n下面用 GitHub OAuth Device Flow 原始 HTTP 获取 token、持久化本地并注入 `github_token`(端点说明:https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow)。\n\n```python\nimport asyncio\nimport json\nimport os\nimport time\nimport urllib.parse\nimport urllib.request\nfrom pathlib import Path\n\nfrom copilot import CopilotClient\n\nCLIENT_ID = \"YOUR_OAUTH_APP_CLIENT_ID\"\nSCOPE = \"read:user\" # 按你的 OAuth App 需求调整\nTOKEN_PATH = Path.home() / \".config\" / \"myapp\" / \"copilot_token.json\"\n\ndef http_post(url, data):\n body = urllib.parse.urlencode(data).encode()\n req = urllib.request.Request(url, data=body, headers={\"Accept\": \"application/json\"})\n with urllib.request.urlopen(req) as resp:\n return json.loads(resp.read().decode())\n\ndef load_token():\n if TOKEN_PATH.exists():\n return json.loads(TOKEN_PATH.read_text()).get(\"access_token\")\n return None\n\ndef save_token(token):\n TOKEN_PATH.parent.mkdir(parents=True, exist_ok=True)\n TOKEN_PATH.write_text(json.dumps(token))\n os.chmod(TOKEN_PATH, 0o600)\n\ndef device_flow():\n code = http_post(\n \"https://github.com/login/device/code\",\n {\"client_id\": CLIENT_ID, \"scope\": SCOPE},\n )\n print(f\"Open {code['verification_uri']} and enter {code['user_code']}\")\n interval = int(code.get(\"interval\", 5))\n\n while True:\n token = http_post(\n \"https://github.com/login/oauth/access_token\",\n {\n \"client_id\": CLIENT_ID,\n \"device_code\": code[\"device_code\"],\n \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",\n },\n )\n if \"access_token\" in token:\n return token\n if token.get(\"error\") == \"slow_down\":\n interval += 5\n elif token.get(\"error\") not in (\"authorization_pending\",):\n raise RuntimeError(token)\n time.sleep(interval)\n\nasync def main():\n access_token = load_token()\n if not access_token:\n token = device_flow()\n save_token(token)\n access_token = token[\"access_token\"]\n\n client = CopilotClient({\"github_token\": access_token})\n await client.start()\n # ... create_session / send ...\n await client.stop()\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n```\n\n要点/坑 \n- `cli_url` 与 `github_token` 互斥 \n- SDK 不保存凭据 需自行持久化 \n- 轮询需处理 `authorization_pending` / `slow_down`\n\n建议/落地 \n- pipe 启动时读取 token 文件 不存在则走 device flow \n- 用 `github_token` 注入 保持非交互运行", "model": "gpt-5.2-codex"}
|
||||
{"index": 4, "question": "Compare the pros and cons of 'Session Replay' (fast-forwarding history) vs 'Session Resume' (stateful ID) for a stateless web backend like OpenWebUI.", "answer": "对无状态后端(OpenWebUI)而言,SDK 只支持通过 `resume_session(session_id)` 的状态续接;所谓“Session Replay”需要你自行重放历史 prompt(来源:本地 SDK 源码 copilot/client.py、copilot/session.py)。 \n对比要点(短语): \n- Replay:无状态/易扩展;缺点是耗时耗 tokens、易截断/丢工具状态、可能重复副作用 \n- Resume:省 tokens、保留工具/compaction/`workspace_path` 状态;缺点是需持久化 session_id、依赖会话存活/跨实例一致性。 \n建议/落地:优先持久化 chat_id→session_id 并 resume_session,失效时回退为重放最近 N 轮并重建工具上下文。", "model": "gpt-5.2-codex"}
|
||||
{"index": 5, "question": "Detail the exact protocol for 'Virtual Workspace': how to implement a file system provider that feeds content to Copilot without physical files.", "answer": "SDK 源码里没有“Virtual Workspace”协议或文件系统 provider 接口;会话只识别真实路径(`SessionConfig.working_directory`、`MessageOptions.attachments`)。要在无物理文件情况下供给内容,只能用自定义工具或 MCP 服务器把“读写文件”变成 tool 调用(来源:本地 SDK 源码 copilot/types.py / copilot/client.py)。\n\n可用原语(源码)\n- `SessionConfig.tools` / `available_tools`:自定义工具与白名单\n- `MessageOptions.attachments`:仅支持真实路径文件/目录\n\n方案 A:自定义工具实现 VFS(推荐)\n```python\nimport asyncio\nfrom pydantic import BaseModel, Field\nfrom copilot import CopilotClient, define_tool\n\nVFS = {\"README.md\": \"# Virtual README\\n内容来自内存\\n\"}\n\nclass VfsReadParams(BaseModel):\n path: str = Field(description=\"virtual path\")\n\n@define_tool(name=\"vfs_read\", description=\"Read a virtual file\")\ndef vfs_read(params: VfsReadParams) -> str:\n return VFS.get(params.path, \"\")\n\n@define_tool(name=\"vfs_list\", description=\"List virtual files\")\ndef vfs_list() -> list[str]:\n return sorted(VFS.keys())\n\nasync def main():\n client = CopilotClient()\n await client.start()\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"tools\": [vfs_read, vfs_list],\n \"available_tools\": [\"vfs_read\", \"vfs_list\"],\n \"system_message\": {\"content\": \"Use vfs_* tools for all file access.\"},\n })\n await session.send_and_wait({\"prompt\": \"打开 README.md\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n方案 B:MCP 服务器(本地 stdio,VFS 由你实现)\n```python\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"mcp_servers\": {\n \"vfs\": {\n \"type\": \"local\",\n \"command\": \"python\",\n \"args\": [\"-m\", \"my_vfs_mcp_server\"],\n \"tools\": [\"*\"],\n }\n },\n})\n```\n\n注意/坑 \n- `available_tools` 只保留你的 VFS 工具以强制使用 \n- 内置文件工具只读真实文件;要兼容只能落地临时文件 \n- 大文件需分块返回,避免上下文爆炸 \n\n建议/落地 \n- pipe 内维护 VFS 存储(DB/Redis/内存) \n- 启动 session 时注入 `system_message` + `available_tools` 强制 VFS 入口", "model": "gpt-5.2-codex"}
|
||||
{"index": 6, "question": "Create an 'Agent Manifest' example: how to define an Agent capable of specific high-privileged tools via SDK.", "answer": "SDK 没有独立“Agent Manifest”文件格式,等价于 `create_session` 的 `custom_agents`(CustomAgentConfig),并用 `tools`/`available_tools` 约束高权限工具,权限由 `on_permission_request` 处理(来源:本地 SDK 源码 copilot/types.py、copilot/client.py)。 \n示例如下(manifest 为 Python dict,可序列化为 JSON 配置)。\n\n```python\nimport asyncio\nfrom pydantic import BaseModel, Field\nfrom copilot import CopilotClient, define_tool\n\nclass AdminShellParams(BaseModel):\n cmd: str = Field(description=\"approved shell command\")\n\n@define_tool(name=\"admin_shell\", description=\"Run an approved shell command\")\ndef admin_shell(params: AdminShellParams) -> str:\n # TODO: 执行受控命令(白名单/沙箱)\n return f\"approved: {params.cmd}\"\n\nclass AdminWriteParams(BaseModel):\n path: str = Field(description=\"target path\")\n content: str = Field(description=\"content\")\n\n@define_tool(name=\"admin_write\", description=\"Write to privileged store\")\ndef admin_write(params: AdminWriteParams) -> str:\n # TODO: 受控写入\n return \"ok\"\n\nasync def on_permission_request(req, _ctx):\n if req.get(\"kind\") in (\"shell\", \"write\"):\n return {\"kind\": \"approved\"}\n return {\"kind\": \"denied-by-rules\", \"rules\": [{\"kind\": \"deny\"}]}\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n agent_manifest = {\n \"name\": \"ops-admin\",\n \"display_name\": \"Ops Admin\",\n \"description\": \"Privileged ops agent\",\n \"prompt\": \"Use only admin_* tools for privileged ops.\",\n \"tools\": [\"admin_shell\", \"admin_write\"],\n \"infer\": True,\n }\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"tools\": [admin_shell, admin_write],\n \"custom_agents\": [agent_manifest],\n \"available_tools\": [\"admin_shell\", \"admin_write\"],\n \"on_permission_request\": on_permission_request,\n })\n\n await session.send_and_wait({\"prompt\": \"使用 ops-admin 执行受控操作\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n要点/坑 \n- session.tools 未注册 -> agent.tools 不可用 \n- custom_agents.tools 仅写名称 \n- 非交互缺少 on_permission_request -> 默认拒绝 \n- SDK 无显式 agent 选择参数 -> 需在 prompt 指定 \n\n建议/落地 \n- pipe 启动时加载 manifest 注入 custom_agents + available_tools + on_permission_request", "model": "gpt-5.2-codex"}
|
||||
{"index": 7, "question": "List all 'hidden' `SessionConfig` parameters relevant to Agent behavior and personality.", "answer": "基于本地 SDK 源码 `copilot/types.py` 的 SessionConfig,影响 Agent 行为/人格的相关参数如下(来源:本地 SDK 源码)。 \n- `system_message`: `append`/`replace`;`replace` 会移除 SDK guardrails(风险) \n- `custom_agents`: `prompt`/`description`/`display_name`/`tools`/`mcp_servers`/`infer`;`tools` 仅名称且必须在 `tools` 注册 \n- `tools` / `available_tools` / `excluded_tools`: 能力与行为边界;`available_tools` 优先于 `excluded_tools` \n- `hooks`: `on_user_prompt_submitted`/`on_pre_tool_use`/`on_post_tool_use`/`on_session_start`/`on_session_end`/`on_error_occurred`,可改写 prompt/工具参数/结果 \n- `on_permission_request`: 非交互审批策略;缺失或异常 => 拒绝 \n- `on_user_input_request`: 启用/处理 `ask_user` \n- `mcp_servers`: 注入外部工具集 \n- `skill_directories` / `disabled_skills`: 技能启用/禁用 \n- `model` / `provider`: 模型与 BYOK 影响输出风格与能力 \n- `working_directory`: 上下文范围 \n- `infinite_sessions`: compaction 阈值影响记忆保留 \n- `config_dir`: CLI 配置/状态目录,行为取决于 CLI 侧配置 \n\n建议/落地:在 pipe 启动时固定 `system_message` + `available_tools/excluded_tools` + `hooks`,并持久化 session_id 以便 `resume_session`。", "model": "gpt-5.2-codex"}
|
||||
{"index": 8, "question": "Give a concise overview of MCP Server Integration: JSON-RPC config and SDK hooks.", "answer": "SDK 中 MCP 通过 create_session/resume_session 的 mcp_servers 传入(映射到 JSON‑RPC 的 mcpServers),支持 local/stdio 与 http/sse,两类配置字段分别为 type/command/args/env/cwd/timeout/tools 或 type/url/headers/timeout/tools(来源:本地 SDK 源码)。 \nSDK hooks 使用 hooks(on_pre_tool_use/on_post_tool_use/on_user_prompt_submitted/on_session_start/on_session_end/on_error_occurred) 进行拦截与附加上下文,权限需 on_permission_request 对 kind='mcp' 明确批准,且 session.on 可审计 tool.execution_* 事件中的 mcp_server_name/mcp_tool_name。 \n示例见下,建议/落地:pipe 中启用 on_permission_request、限制 tools 白名单并记录 MCP 事件。\n\n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def on_permission_request(req, _):\n return {\"kind\": \"approved\"} if req.get(\"kind\") == \"mcp\" else {\n \"kind\": \"denied-by-rules\",\n \"rules\": [{\"kind\": \"deny\"}],\n }\n\nasync def on_pre_tool_use(input, _):\n if input.get(\"toolName\", \"\").startswith(\"mcp.\"):\n return {\"additionalContext\": \"MCP tool call observed\"}\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"mcp_servers\": {\n \"brave\": {\n \"type\": \"local\",\n \"command\": \"/path/to/brave-mcp\",\n \"args\": [],\n \"env\": {\"BRAVE_API_KEY\": \"YOUR_KEY\"},\n \"tools\": [\"*\"],\n \"timeout\": 30000,\n }\n },\n \"hooks\": {\"on_pre_tool_use\": on_pre_tool_use},\n \"on_permission_request\": on_permission_request,\n })\n\n await session.send_and_wait({\"prompt\": \"用 Brave Search 查询 Copilot SDK 新闻\"})\n await client.stop()\n\nasyncio.run(main())\n```", "model": "gpt-5.2-codex"}
|
||||
{"index": 9, "question": "Provide best practices and common pitfalls for MCP Server Integration: JSON-RPC config and SDK hooks.", "answer": "基于本地 SDK 源码,MCP 配置通过 `create_session`/`resume_session` 的 `mcp_servers` 进入 JSON‑RPC `mcpServers`,并通过 `on_permission_request` 与 `hooks` 控制工具审批与行为(来源:本地 SDK 源码) \n- 最佳实践:local/stdio 用 `type/command/args/env/cwd/timeout/tools`;remote http/sse 用 `type/url/headers/timeout/tools`;`tools` 尽量白名单、必要时 `[\"*\"]`;`timeout` 毫秒;`hooks.on_pre_tool_use` 做改参/拒绝,`session.on` 记录 `tool.execution_*` 与 `mcp_server_name`/`mcp_tool_name` \n- 常见坑:把 MCP 配置放在 CopilotClient;缺少 `on_permission_request`(kind=\"mcp\")导致默认拒绝;`tools: []` 等于禁用全部;设置 `available_tools` 却未包含 MCP 工具名导致被过滤 \n\n建议/落地:pipe 中固定审批策略 + 统一 MCP 配置模板 + 审计日志", "model": "gpt-5.2-codex"}
|
||||
{"index": 10, "question": "Show a minimal example snippet for MCP Server Integration: JSON-RPC config and SDK hooks.", "answer": "基于本地 SDK 源码,MCP 通过 `mcp_servers` 传入并用 `hooks` 处理工具调用(来源:本地 SDK 源码)。最小示例: \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"mcp_servers\": {\n \"brave\": {\n \"type\": \"local\",\n \"command\": \"/path/to/brave-mcp\",\n \"args\": [],\n \"env\": {\"BRAVE_API_KEY\": \"YOUR_KEY\"},\n \"tools\": [\"*\"],\n }\n },\n \"hooks\": {\n \"on_pre_tool_use\": lambda input, _: {\"additionalContext\": \"MCP tool call\"},\n },\n \"on_permission_request\": lambda req, _: {\"kind\": \"approved\"},\n })\n\n await session.send_and_wait({\"prompt\": \"用 Brave Search 搜索 Copilot SDK\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n建议/落地:固定 `on_permission_request`(kind=\"mcp\")并限制 `tools` 白名单。", "model": "gpt-5.2-codex"}
|
||||
{"index": 11, "question": "List recommended configuration defaults for MCP Server Integration: JSON-RPC config and SDK hooks.", "answer": "推荐默认值(MCP JSON‑RPC 配置,来源:本地 SDK 源码):local/stdio 服务器用 `type=\"local\"/\"stdio\"`(远程用 `http`/`sse`)、`tools` 白名单(无则 `\"*\"`)、`timeout=30000` 毫秒、`env` 仅放密钥、`cwd` 指向受控目录。 \nHooks 默认:`on_pre_tool_use` 追加上下文并可设 `permissionDecision`(ask/allow)、必要时改参,`on_post_tool_use` 记录结果,`on_error_occurred` 设置 retry/skip。 \n建议/落地:非交互场景配置 `on_permission_request` 仅放行 `kind=\"mcp\"`,否则拒绝;工具名不确定时先不设 `available_tools`,避免误过滤。", "model": "gpt-5.2-codex"}
|
||||
{"index": 12, "question": "How does MCP Server Integration: JSON-RPC config and SDK hooks relate to building a custom Agent?", "answer": "SDK 源码中,自定义 Agent 通过 `SessionConfig.custom_agents` 定义,`CustomAgentConfig` 支持 `mcp_servers`,因此“Agent 绑定 MCP”就是在 agent config 里声明 `mcp_servers`,底层在 `create_session` 时转成 JSON‑RPC 的 `mcpServers`(来源:本地 SDK 源码 copilot/types.py、copilot/client.py)。 \nHooks 是会话级拦截器(`on_pre_tool_use`/`on_post_tool_use` 等),对 MCP 工具同样生效;权限确认需 `on_permission_request` 审批 `kind=\"mcp\"`。 \n建议/落地:pipe 中为目标 Agent 绑定 MCP + 审批钩子,示例: \n```python\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"custom_agents\": [{\n \"name\": \"search\",\n \"prompt\": \"Use MCP for search.\",\n \"mcp_servers\": {\n \"brave\": {\n \"type\": \"local\",\n \"command\": \"/path/to/brave-mcp\",\n \"args\": [],\n \"env\": {\"BRAVE_API_KEY\": \"YOUR_KEY\"},\n \"tools\": [\"*\"],\n }\n },\n }],\n \"hooks\": {\"on_pre_tool_use\": lambda i, _: {\"additionalContext\": \"mcp\"}},\n \"on_permission_request\": lambda r, _: {\"kind\": \"approved\"},\n})\n```", "model": "gpt-5.2-codex"}
|
||||
{"index": 13, "question": "Give a concise overview of Agent Manifests: Defining capabilities and permissions programmatically.", "answer": "SDK 没有独立的 Agent Manifest 文件,等价做法是用 `SessionConfig.custom_agents` 描述 agent,并用 `tools`/`available_tools`/`excluded_tools` 限定能力。 \n权限通过 `on_permission_request` 与 `hooks`(如 `on_pre_tool_use`)在会话级控制,且 `custom_agents.tools` 只能写工具名并需在 `SessionConfig.tools` 注册。 \n建议/落地:pipe 启动时加载 manifest→注入 `custom_agents` + `available_tools` + `on_permission_request` 固化权限边界。", "model": "gpt-5.2-codex"}
|
||||
{"index": 14, "question": "Provide best practices and common pitfalls for Agent Manifests: Defining capabilities and permissions programmatically.", "answer": "SDK 没有独立 Manifest 文件,等价于 `SessionConfig.custom_agents` + `tools/available_tools/excluded_tools` + `on_permission_request` + `hooks` 来定义能力与权限(来源:本地 SDK 源码)。 \n最佳实践:`custom_agents.tools` 只写已在 `SessionConfig.tools` 注册的名称,配合 `available_tools` 白名单与 `on_permission_request` 精准放行,必要时用 `hooks.on_pre_tool_use` 做参数校验/审计,`system_message` 选 append 以保留 guardrails。 \n常见坑:工具未注册或名称不一致、缺少 `on_permission_request` 导致默认拒绝、误用 `system_message` replace 去掉安全限制、期待 agent 自动被选中或跨实例不持久 `session_id`;建议/落地:pipe 启动时加载 manifest→注入 `custom_agents/available_tools/on_permission_request` 并持久化 `session_id`。", "model": "gpt-5.2-codex"}
|
||||
{"index": 15, "question": "Show a minimal example snippet for Agent Manifests: Defining capabilities and permissions programmatically.", "answer": "最小示例(本地 SDK 源码:`custom_agents` + `tools` + `available_tools` + `on_permission_request`): \n```python\nimport asyncio\nfrom pydantic import BaseModel, Field\nfrom copilot import CopilotClient, define_tool\n\nclass PingParams(BaseModel):\n text: str = Field(description=\"text to echo\")\n\n@define_tool(name=\"priv_ping\", description=\"Privileged echo\")\ndef priv_ping(params: PingParams) -> str:\n return f\"ok: {params.text}\"\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n agent_manifest = {\n \"name\": \"priv-agent\",\n \"prompt\": \"Only use priv_ping for actions.\",\n \"tools\": [\"priv_ping\"],\n \"infer\": True,\n }\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"tools\": [priv_ping],\n \"custom_agents\": [agent_manifest],\n \"available_tools\": [\"priv_ping\"],\n \"on_permission_request\": lambda req, _: {\"kind\": \"approved\"},\n })\n\n await session.send_and_wait({\"prompt\": \"调用 priv_ping\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n建议/落地:保持工具名一致并白名单化,非交互务必提供 `on_permission_request`。", "model": "gpt-5.2-codex"}
|
||||
{"index": 16, "question": "List recommended configuration defaults for Agent Manifests: Defining capabilities and permissions programmatically.", "answer": "基于本地 SDK 源码(copilot/types.py / copilot/client.py),推荐默认值如下: \n- custom_agents:name 必填、prompt 必填、tools 仅名称、infer=True,display_name/description 可选 \n- available_tools=白名单、excluded_tools=空、system_message=append \n- on_permission_request:除明确允许的 kind 其余拒绝;hooks.on_pre_tool_use 做审计/参数校验(其余 hooks 按需) \n建议/落地:pipe 启动时加载 manifest→注册 tools + available_tools + on_permission_request,并持久化 session_id。", "model": "gpt-5.2-codex"}
|
||||
{"index": 17, "question": "How does Agent Manifests: Defining capabilities and permissions programmatically relate to building a custom Agent?", "answer": "“Agent Manifest”在 SDK 里就是 `SessionConfig.custom_agents` 的配置对象,用它定义自定义 Agent 的 `name/prompt/tools/mcp_servers/infer`,并在同一 session 里用 `tools` 注册实际工具、用 `available_tools/excluded_tools` 限定能力(来源:本地 SDK 源码)。 \n权限不在 manifest 内,而是由会话级 `on_permission_request` 和 hooks(如 `on_pre_tool_use`)统一控制,且对所有 Agent 生效。 \n建议/落地:pipe 启动时把 manifest 转成 `custom_agents` 并同步注册工具 + 白名单,再配置 `on_permission_request` 作为权限策略。", "model": "gpt-5.2-codex"}
|
||||
{"index": 18, "question": "Give a concise overview of Headless Auth: Device Code Flow and credential persistence.", "answer": "SDK 仅提供 `CopilotClient` 的 `github_token/use_logged_in_user` 与 `get_auth_status()`,没有内置 device‑flow API(源码:copilot/client.py、copilot/types.py)。 \nHeadless Device Flow 需走 GitHub OAuth Device Flow HTTP 端点获取 token(https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow),将 token 持久化(权限 600)并通过 `CopilotClient({\"github_token\": token})` 注入,且与 `cli_url` 互斥。 \n建议/落地:启动时读 token 文件,不存在则执行 device flow 并保存,再启动客户端。\n\n```python\ntoken = load_token() or device_flow() # 调用 device code + access_token 端点\nsave_token(token) # chmod 600\nclient = CopilotClient({\"github_token\": token[\"access_token\"]})\nawait client.start()\n```", "model": "gpt-5.2-codex"}
|
||||
{"index": 19, "question": "Provide best practices and common pitfalls for Headless Auth: Device Code Flow and credential persistence.", "answer": "SDK 不内置 device flow,只能用 GitHub OAuth Device Flow HTTP 换取 token 并通过 CopilotClient({github_token}) 注入,且与 cli_url 互斥(源码:copilot/client.py、copilot/types.py)。 \n最佳实践/坑:- token 文件 chmod 600 或 OS keychain;- 记录到期与刷新策略,启动时用 get_auth_status 验证;- 轮询需处理 authorization_pending/slow_down;- 未持久化、use_logged_in_user 与 github_token 同时使用或与 cli_url 混用会失败(端点说明:https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow)。 \n建议/落地:pipe 启动时读 token 文件,不存在则走 device flow 并保存,失败时回退重新授权。", "model": "gpt-5.2-codex"}
|
||||
{"index": 20, "question": "Show a minimal example snippet for Headless Auth: Device Code Flow and credential persistence.", "answer": "最小示例:用 GitHub Device Flow(端点说明见 https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow)拿 token,持久化后注入 CopilotClient({github_token}). \n```python\nimport asyncio\nimport json\nimport os\nimport time\nimport urllib.parse\nimport urllib.request\nfrom pathlib import Path\nfrom copilot import CopilotClient\n\nCLIENT_ID = \"YOUR_OAUTH_APP_CLIENT_ID\"\nTOKEN_PATH = Path.home() / \".config\" / \"myapp\" / \"copilot_token.json\"\n\ndef post(url, data):\n body = urllib.parse.urlencode(data).encode()\n req = urllib.request.Request(url, data=body, headers={\"Accept\": \"application/json\"})\n with urllib.request.urlopen(req) as resp:\n return json.loads(resp.read().decode())\n\ndef device_flow():\n code = post(\"https://github.com/login/device/code\", {\"client_id\": CLIENT_ID, \"scope\": \"read:user\"})\n print(f\"Open {code['verification_uri']} and enter {code['user_code']}\")\n interval = int(code.get(\"interval\", 5))\n while True:\n tok = post(\n \"https://github.com/login/oauth/access_token\",\n {\n \"client_id\": CLIENT_ID,\n \"device_code\": code[\"device_code\"],\n \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",\n },\n )\n if \"access_token\" in tok:\n return tok\n if tok.get(\"error\") == \"slow_down\":\n interval += 5\n elif tok.get(\"error\") != \"authorization_pending\":\n raise RuntimeError(tok)\n time.sleep(interval)\n\ndef load_token():\n return json.loads(TOKEN_PATH.read_text()).get(\"access_token\") if TOKEN_PATH.exists() else None\n\ndef save_token(tok):\n TOKEN_PATH.parent.mkdir(parents=True, exist_ok=True)\n TOKEN_PATH.write_text(json.dumps(tok))\n os.chmod(TOKEN_PATH, 0o600)\n\nasync def main():\n access_token = load_token()\n if not access_token:\n tok = device_flow()\n save_token(tok)\n access_token = tok[\"access_token\"]\n\n client = CopilotClient({\"github_token\": access_token})\n await client.start()\n # ... create_session / send ...\n await client.stop()\n\nasyncio.run(main())\n```\n\n建议/落地 \n- 启动先读 token 文件;缺失再 device flow \n- token 文件权限 600 \n- 失效则重新授权", "model": "gpt-5.2-codex"}
|
||||
{"index": 21, "question": "List recommended configuration defaults for Headless Auth: Device Code Flow and credential persistence.", "answer": "基于本地 SDK 源码(`github_token` 与 `cli_url` 互斥)与 GitHub Device Flow 文档(https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow),推荐默认值: \n- Token 持久化:`~/.config/<app>/copilot_token.json` 或系统密钥链,文件权限 600 \n- Client 选项:仅 `github_token`,`use_logged_in_user` 留空/False,禁用 `cli_url` \n- 轮询:使用 device code 返回的 `interval`(缺省 5s),处理 `authorization_pending`/`slow_down` \n- 启动校验:`get_auth_status()` 失败即重新授权 \n\n建议/落地:启动先读 token 文件 → 无则 device flow → 保存 → `client.start()`", "model": "gpt-5.2-codex"}
|
||||
{"index": 22, "question": "How does Headless Auth: Device Code Flow and credential persistence relate to building a custom Agent?", "answer": "Headless Auth 只负责拿到并持久化 `github_token`,让 `CopilotClient` 能在无交互环境启动会话并加载 `custom_agents`;因此它是自定义 Agent 能稳定运行的前置条件,而不是 Agent 定义本身(来源:本地 SDK 源码)。 \n建议/落地:在无状态后端启动时读取 token 文件(不存在就走 device flow)→ `CopilotClient({\"github_token\": token})` → `create_session(custom_agents=...)`。", "model": "gpt-5.2-codex"}
|
||||
{"index": 23, "question": "Give a concise overview of Session Replay vs Resume: Handling stateless frontend history.", "answer": "基于本地 SDK 源码(copilot/client.py、copilot/session.py),Resume 是 `resume_session(session_id)` 保留会话状态,Replay 只能由应用重放历史 prompt。优缺点:Resume 省 tokens/保工具与 compaction 状态但依赖持久化 session_id;Replay 无状态易扩展但成本高且易丢副作用控制。建议/落地:无状态前端请保存 session_id 并优先 Resume,失效时回放最近 N 轮。", "model": "gpt-5.2-codex"}
|
||||
{"index": 24, "question": "Provide best practices and common pitfalls for Session Replay vs Resume: Handling stateless frontend history.", "answer": "最佳实践:在无状态前端每次请求携带 session_id 并优先 `resume_session`,同时恢复 tools/hooks/on_permission_request/system_message 以保持行为一致(来源:本地 SDK 源码)。 \n常见坑:Replay 会重复执行工具且耗 tokens;Resume 未重新注册工具或权限处理器会出现 tool not supported/默认拒绝;会话失效未回退。 \n建议/落地:持久化 chat_id→session_id,resume 失败时仅回放最近 N 轮并禁用/幂等化有副作用的工具。", "model": "gpt-5.2-codex"}
|
||||
{"index": 25, "question": "Show a minimal example snippet for Session Replay vs Resume: Handling stateless frontend history.", "answer": "来源:本地 SDK 源码(copilot/client.py, copilot/session.py),最小示例如下(优先 Resume,失败则 Replay)。 \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def handle_request(session_id: str | None, history: list[str], latest: str):\n client = CopilotClient()\n await client.start()\n\n try:\n # Resume: 复用会话状态\n if not session_id:\n raise RuntimeError(\"no session_id\")\n session = await client.resume_session(session_id)\n except Exception:\n # Replay: 逐条重放历史\n session = await client.create_session({\"model\": \"gpt-5\"})\n session_id = session.session_id\n for prompt in history:\n await session.send_and_wait({\"prompt\": prompt})\n\n response = await session.send_and_wait({\"prompt\": latest})\n await client.stop()\n return response.data.content, session_id\n\n# asyncio.run(handle_request(session_id, history, latest_prompt))\n```\n建议/落地:持久化 session_id,resume 失败时仅回放最近 N 轮。", "model": "gpt-5.2-codex"}
|
||||
{"index": 26, "question": "List recommended configuration defaults for Session Replay vs Resume: Handling stateless frontend history.", "answer": "基于本地 SDK 源码,推荐默认配置如下(Resume / Replay): \n- Resume:持久化 session_id;`resume_session({...})` 传回 tools、hooks、on_permission_request、working_directory、streaming、provider、mcp_servers、custom_agents、skill_directories、disabled_skills;`disable_resume` 保持默认 False \n- Replay:`create_session({...})` 传回 model、system_message、tools、available_tools/excluded_tools、hooks、on_permission_request、working_directory、provider、mcp_servers、custom_agents、infinite_sessions(默认启用);仅回放最近 N 轮;默认拒绝 write/shell 或用只读白名单", "model": "gpt-5.2-codex"}
|
||||
{"index": 27, "question": "How does Session Replay vs Resume: Handling stateless frontend history relate to building a custom Agent?", "answer": "Resume 会保留 session_id 关联的 custom_agents/工具状态,Replay 需要每次 create_session 时重新注入 custom_agents + tools,否则 persona/能力会丢失(来源:本地 SDK 源码)。对于无状态前端,Resume 更可靠且省 token,但要持久化 session_id 并在 resume 时重新注册 tools/hooks;Replay 则需回放历史并保证相同 custom_agents 配置。建议/落地:pipe 优先 resume_session,失败再 replay 最近 N 轮并重建 custom_agents/tools/权限策略。", "model": "gpt-5.2-codex"}
|
||||
{"index": 28, "question": "Give a concise overview of Advanced Session Hooks: Intercepting and modifying user prompts.", "answer": "拦截/修改用户提示要用 `SessionHooks.on_user_prompt_submitted`(不是 `session.on` 事件订阅),`input.prompt` 可改为 `output.modifiedPrompt` 并附加 `additionalContext`/`suppressOutput`,且需在 `create_session`/`resume_session` 传入 `hooks` 才会触发 `hooks.invoke`(来源:本地 SDK 源码 copilot/types.py、copilot/session.py、copilot/client.py)。 \n```python\nfrom copilot import CopilotClient\n\nasync def on_user_prompt_submitted(input, _ctx):\n return {\"modifiedPrompt\": input[\"prompt\"].strip(), \"additionalContext\": \"normalize whitespace\"}\n\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"hooks\": {\"on_user_prompt_submitted\": on_user_prompt_submitted},\n})\n```\n建议/落地:在 pipe 入口统一做 prompt 清洗/拼接上下文,并记录修改前后用于审计。", "model": "gpt-5.2-codex"}
|
||||
{"index": 29, "question": "Provide best practices and common pitfalls for Advanced Session Hooks: Intercepting and modifying user prompts.", "answer": "最佳实践:在 create_session/resume_session 传入 hooks.on_user_prompt_submitted,返回 {modifiedPrompt, additionalContext, suppressOutput},修改保持幂等、最小化、可审计(来源:本地 SDK 源码 copilot/types.py、copilot/client.py、copilot/session.py)。 \n常见坑:忘记传 hooks 或 resume 时未重新传、handler 抛异常会被吞并返回 None、返回非 dict 或过度改写提示导致无效/偏航。 \n建议/落地:在 pipe 入口统一注册该 hook 并记录修改前后,保持 system_message 为 append。", "model": "gpt-5.2-codex"}
|
||||
{"index": 30, "question": "Show a minimal example snippet for Advanced Session Hooks: Intercepting and modifying user prompts.", "answer": "最小示例(来源:本地 SDK 源码): \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def on_user_prompt_submitted(hook_input, _ctx):\n return {\n \"modifiedPrompt\": hook_input[\"prompt\"].strip(),\n \"additionalContext\": \"normalized\",\n }\n\nasync def main():\n client = CopilotClient()\n await client.start()\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"hooks\": {\"on_user_prompt_submitted\": on_user_prompt_submitted},\n })\n await session.send_and_wait({\"prompt\": \" hello \"})\n await client.stop()\n\nasyncio.run(main())\n```\n建议/落地:在 pipe 入口统一注册该 hook,并记录修改前后以便审计。", "model": "gpt-5.2-codex"}
|
||||
{"index": 31, "question": "List recommended configuration defaults for Advanced Session Hooks: Intercepting and modifying user prompts.", "answer": "- 推荐默认值(来源:本地 SDK 源码 copilot/types.py):`hooks={\"on_user_prompt_submitted\": handler}`,handler 默认返回 `{\"modifiedPrompt\": input[\"prompt\"], \"additionalContext\": \"\", \"suppressOutput\": False}` 或 `None`(不改写)。 \n- `create_session`/`resume_session` 都要传同一 hooks;`system_message` 保持 append 以保留 guardrails。 \n- 建议/落地:pipe 入口统一注册该 hook 并记录原/改提示用于审计。", "model": "gpt-5.2-codex"}
|
||||
{"index": 32, "question": "How does Advanced Session Hooks: Intercepting and modifying user prompts relate to building a custom Agent?", "answer": "Hooks 是会话级拦截器,对所有自定义 Agent 都生效;因此它是定义 Agent 行为边界和输入规范化的“外层控制”,而不是 Agent Manifest 本身(来源:本地 SDK 源码 copilot/types.py、copilot/session.py)。 \n在自定义 Agent 场景下,`on_user_prompt_submitted` 可统一清洗/增强用户指令,再由 custom_agents 的 prompt 决定具体风格。 \n建议/落地:pipe 启动时同时注入 custom_agents + hooks,确保 resume 时同样注册 hooks。", "model": "gpt-5.2-codex"}
|
||||
{"index": 33, "question": "Give a concise overview of Workspace Virtualization: Handling CWD for remote/virtual files.", "answer": "SDK 只有 `SessionConfig.working_directory` 来约束 CWD,`attachments` 也要求真实路径,没有“虚拟工作区/FS provider”协议(来源:本地 SDK 源码 copilot/types.py、copilot/client.py)。要处理远程/虚拟文件,需用自定义工具或 MCP 服务器实现 VFS 读写,或把内容落地到 working_directory 下的临时文件再交给模型。建议/落地:pipe 固定 working_directory 映射到会话工作区,统一走 vfs_* 工具/MCP,且在 on_permission_request 限制 write/shell。", "model": "gpt-5.2-codex"}
|
||||
{"index": 34, "question": "Provide best practices and common pitfalls for Workspace Virtualization: Handling CWD for remote/virtual files.", "answer": "SDK 没有虚拟工作区/FS provider,仅支持 `SessionConfig.working_directory` 与真实路径 `attachments`(来源:本地 SDK 源码),因此远程/虚拟文件需落地或用自定义工具/MCP 提供内容。 \n最佳实践:固定 working_directory 到可控目录/会话 workspace,VFS 工具分块返回,配合 `available_tools`/`on_permission_request` 限权;常见坑:直接传虚拟路径/URL、working_directory 不存在、resume 未重注册工具/权限、一次性返回过大内容导致上下文截断。 \n建议/落地:pipe 入口做 remote→临时文件或 vfs_* 映射,并持久化路径索引。", "model": "gpt-5.2-codex"}
|
||||
{"index": 35, "question": "Show a minimal example snippet for Workspace Virtualization: Handling CWD for remote/virtual files.", "answer": "SDK 仅提供 `SessionConfig.working_directory` 与真实路径附件,没有虚拟 FS/provider(来源:本地 SDK 源码 copilot/types.py)。最小做法是把远程内容落地到 CWD 下再作为附件传入: \n```python\nimport asyncio\nimport pathlib\nfrom copilot import CopilotClient\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n workdir = \"/tmp/owui\"\n pathlib.Path(workdir).mkdir(parents=True, exist_ok=True)\n remote_content = \"...\"\n path = f\"{workdir}/remote.txt\"\n pathlib.Path(path).write_text(remote_content)\n\n session = await client.create_session({\"model\": \"gpt-5\", \"working_directory\": workdir})\n await session.send_and_wait({\n \"prompt\": \"Summarize remote.txt\",\n \"attachments\": [{\"type\": \"file\", \"path\": path}],\n })\n await client.stop()\n\nasyncio.run(main())\n```\n建议/落地:pipe 入口固定 working_directory,落地临时文件后立即处理,并用 `on_permission_request` 限制 write/shell。", "model": "gpt-5.2-codex"}
|
||||
{"index": 36, "question": "List recommended configuration defaults for Workspace Virtualization: Handling CWD for remote/virtual files.", "answer": "推荐默认值(来源:本地 SDK 源码 copilot/types.py): \n- `working_directory`: 指向可控的会话目录(如 session.workspace_path 或 /tmp/<app>/<session_id>) \n- `available_tools`: 只允许 vfs_* 或只读工具,避免任意 shell/write \n- `on_permission_request`: 默认拒绝 write/shell,仅放行 read/mcp \n- `attachments`: 仅真实路径文件;远程内容先落地 \n\n建议/落地:pipe 入口统一映射 remote→临时文件或 VFS 工具,并持久化映射表。", "model": "gpt-5.2-codex"}
|
||||
{"index": 37, "question": "How does Workspace Virtualization: Handling CWD for remote/virtual files relate to building a custom Agent?", "answer": "SDK 没有虚拟文件系统协议,custom agent 只能依赖 `working_directory` 和真实路径附件,因此处理远程/虚拟文件需通过自定义工具/MCP 并在 agent prompt/available_tools 中强制使用(来源:本地 SDK 源码 copilot/types.py、copilot/client.py)。 \n这使“工作区虚拟化”成为 agent 能力与权限设计的一部分,需要 `available_tools`/`on_permission_request`/hooks 来限定访问范围。 \n建议/落地:pipe 中固定 working_directory 到会话目录,注入 vfs_* 工具白名单并在 prompt 明确使用 VFS。", "model": "gpt-5.2-codex"}
|
||||
{"index": 38, "question": "Give a concise overview of Error Recovery: Handling session disconnects and re-auth.", "answer": "本地 SDK 源码中没有显式“重连”API,断线通常表现为请求异常或 `SessionEventType.SESSION_ERROR`,需要 `stop()/force_stop()` 后重新 `start()` 并 `resume_session`(失败则 replay)(来源:本地 SDK 源码)。 \n认证可用 `get_auth_status()` 检测,未认证时重新创建 `CopilotClient({\"github_token\": token})`;若 `cli_url` 连接外部服务器则认证由外部处理(来源:本地 SDK 源码)。\n\n```python\nimport asyncio\nfrom copilot import CopilotClient\nfrom copilot.generated.session_events import SessionEventType\n\nasync def safe_send(session_id, prompt, token):\n client = CopilotClient({\"github_token\": token})\n await client.start()\n try:\n session = await (client.resume_session(session_id) if session_id else client.create_session({\"model\": \"gpt-5\"}))\n session.on(lambda ev: ev.type == SessionEventType.SESSION_ERROR and print(ev.data.message))\n resp = await session.send_and_wait({\"prompt\": prompt})\n return resp, session.session_id\n except Exception:\n await client.stop()\n client = CopilotClient({\"github_token\": token})\n await client.start()\n session = await client.create_session({\"model\": \"gpt-5\"})\n resp = await session.send_and_wait({\"prompt\": prompt})\n return resp, session.session_id\n finally:\n await client.stop()\n```\n\n建议/落地:持久化 session_id 与 token,优先 resume,失败再回放最近 N 轮。", "model": "gpt-5.2-codex"}
|
||||
{"index": 39, "question": "Provide best practices and common pitfalls for Error Recovery: Handling session disconnects and re-auth.", "answer": "最佳实践(基于本地 SDK 源码):监听 `SESSION_ERROR`/捕获请求异常后 `stop()/force_stop()`→重新 `start()`→`resume_session`(失败再 replay),启动时用 `get_auth_status()` 校验并在失效时重建 `CopilotClient({\"github_token\": ...})`,同时在 resume 时重新注册 tools/hooks/权限处理器。 \n常见坑:把 `cli_url` 与 `github_token` 混用、忘记重注册工具导致 `tool not supported`、缺少 `on_permission_request` 导致默认拒绝、以及不回退到 replay 造成会话中断。 \n建议/落地:无状态后端持久化 `session_id+token`、实现幂等的重试/回放策略并记录错误上下文用于告警。", "model": "gpt-5.2-codex"}
|
||||
{"index": 40, "question": "Show a minimal example snippet for Error Recovery: Handling session disconnects and re-auth.", "answer": "最小示例(本地 SDK 源码:copilot/client.py、copilot/session.py): \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def send_with_recovery(session_id, prompt, token):\n client = CopilotClient({\"github_token\": token})\n await client.start()\n try:\n session = await (client.resume_session(session_id) if session_id else client.create_session({\"model\": \"gpt-5\"}))\n resp = await session.send_and_wait({\"prompt\": prompt})\n return resp.data.content, session.session_id\n except Exception:\n await client.stop()\n client = CopilotClient({\"github_token\": token})\n await client.start()\n session = await client.create_session({\"model\": \"gpt-5\"})\n resp = await session.send_and_wait({\"prompt\": prompt})\n return resp.data.content, session.session_id\n finally:\n await client.stop()\n```\n建议/落地:持久化 token+session_id,resume 失败再重建并回放必要历史。", "model": "gpt-5.2-codex"}
|
||||
{"index": 41, "question": "List recommended configuration defaults for Error Recovery: Handling session disconnects and re-auth.", "answer": "推荐默认值(字段来源:本地 SDK 源码):CopilotClientOptions—auto_start=True、log_level='info'、use_stdio=True(非 cli_url)、github_token=持久化 token;SessionConfig/ResumeSessionConfig—hooks.on_error_occurred=handler、on_permission_request=非交互审批、working_directory 固定、mcp_servers/custom_agents/streaming 与首次一致、可固定 config_dir 并持久化 session_id。 \n常见坑:cli_url 与 github_token 互斥、resume 时未重传 tools/hooks 导致 tool not supported 或默认拒绝。 \n建议/落地:pipe 保存 token+session_id,断线后先 resume_session,失败再回放最近 N 轮。", "model": "gpt-5.2-codex"}
|
||||
{"index": 42, "question": "How does Error Recovery: Handling session disconnects and re-auth relate to building a custom Agent?", "answer": "错误恢复与自定义 Agent 强相关:Agent 定义与工具/权限处理器都是 session 级配置,断线或重认证后必须重建客户端并重新注入 `custom_agents`、`tools`、`hooks`、`on_permission_request`,否则能力/人格会丢失(来源:本地 SDK 源码)。 \n建议/落地:持久化 session_id+token,重连优先 `resume_session` 并重新注册上述配置,失败再回放最近 N 轮。", "model": "gpt-5.2-codex"}
|
||||
{"index": 43, "question": "Give a concise overview of Confirmation Events: programmatic handling of 'confirmation_required'.", "answer": "SDK 源码里没有独立的 `confirmation_required` 事件;权限确认是 JSON‑RPC `permission.request`,通过 `SessionConfig.on_permission_request` 回调处理(缺失或异常会默认拒绝)。 \n`session.on()` 只能用于审计/关联工具事件(如 `tool.user_requested`、`tool.execution_start`),不能替代确认逻辑。", "model": "gpt-5.2-codex"}
|
||||
{"index": 44, "question": "Provide best practices and common pitfalls for Confirmation Events: programmatic handling of 'confirmation_required'.", "answer": "SDK 源码未定义 `confirmation_required` 事件,确认流程必须用 `SessionConfig.on_permission_request` 处理,缺失或异常会默认拒绝(来源:本地 SDK 源码)。 \n最佳实践/坑: \n- ✅ 对 `kind` 建立白名单策略(如仅允许 `read/mcp`),记录决策并用 `session.on` 仅做审计关联 `tool.user_requested` \n- ✅ `resume_session` 时重新注册 `on_permission_request`/hooks \n- ❌ 误以为 `session.on()` 可完成确认、返回非规范 dict 或抛异常导致全部拒绝", "model": "gpt-5.2-codex"}
|
||||
{"index": 45, "question": "Show a minimal example snippet for Confirmation Events: programmatic handling of 'confirmation_required'.", "answer": "SDK 里没有 `confirmation_required` 事件,需用 `SessionConfig.on_permission_request` 处理权限确认。 \n最小示例: \n```python\nfrom copilot import CopilotClient\n\nclient = CopilotClient()\nawait client.start()\n\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"on_permission_request\": lambda req, _: {\"kind\": \"approved\"},\n})\n```\n建议/落地:在 pipe 中统一注册 on_permission_request,并在 resume_session 时重复注册。", "model": "gpt-5.2-codex"}
|
||||
{"index": 46, "question": "List recommended configuration defaults for Confirmation Events: programmatic handling of 'confirmation_required'.", "answer": "推荐默认值(SDK 无 confirmation_required 事件,改用 on_permission_request,来源:本地 SDK 源码):- 对 kind 建白名单(常见只放行 read/mcp)- 未命中默认返回 denied-by-rules 并给 rules - resume_session 时重注册 on_permission_request/hooks。 \n建议/落地:pipe 启动即注入 on_permission_request 并记录审批日志。", "model": "gpt-5.2-codex"}
|
||||
{"index": 47, "question": "How does Confirmation Events: programmatic handling of 'confirmation_required' relate to building a custom Agent?", "answer": "SDK 没有 `confirmation_required` 事件,权限确认由 `create_session`/`resume_session` 的 `on_permission_request` 处理,且对同一 session 的所有 custom agent 生效(来源:本地 SDK 源码)。 \n建议/落地:在 pipe 启动时注入 `on_permission_request` 白名单策略并在 resume 时重注册,以约束高权限工具。", "model": "gpt-5.2-codex"}
|
||||
{"index": 48, "question": "Give a concise overview of Skills: Conflict resolution and precedence defaults.", "answer": "在本地 SDK 源码中,Skills 只通过 SessionConfig 的 `skill_directories` 与 `disabled_skills` 暴露,未定义冲突解析/优先级规则,因此行为取决于 CLI 运行时实现(来源:本地 SDK 源码 copilot/types.py)。 \n为可控性,建议确保技能名唯一、仅配置单一目录或由你自行按顺序拼接目录,并用 `disabled_skills` 明确屏蔽同名/不应启用的技能。 \n建议/落地:在无状态后端每次 `create_session`/`resume_session` 时显式传入同一份 `skill_directories` 与 `disabled_skills`,避免因默认加载顺序差异导致行为漂移。", "model": "gpt-5.2-codex"}
|
||||
{"index": 49, "question": "Provide best practices and common pitfalls for Skills: Conflict resolution and precedence defaults.", "answer": "SDK 源码仅暴露 SessionConfig/ResumeSessionConfig 的 `skill_directories` 和 `disabled_skills`,未定义冲突解析或优先级,行为由 CLI 侧决定(来源:本地 SDK 源码 copilot/types.py)。 \n最佳实践:技能名全局唯一、只配置单一目录或显式排序,并用 `disabled_skills` 禁用冲突项;常见坑是依赖默认加载顺序或 resume 未重传这两个字段导致行为漂移。 \n建议/落地:在 pipe 启动与 resume 时统一注入同一份 `skill_directories`/`disabled_skills` 并记录变更。", "model": "gpt-5.2-codex"}
|
||||
{"index": 50, "question": "Show a minimal example snippet for Skills: Conflict resolution and precedence defaults.", "answer": "SDK 里技能只通过 `skill_directories` 与 `disabled_skills` 配置,冲突/优先级未在源码定义,需自行避免同名。 \n最小示例: \n```python\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"skill_directories\": [\"/opt/skills\"],\n \"disabled_skills\": [\"legacy_skill\"],\n})\n``` \n建议:技能名唯一并在 `resume_session` 时重传这两项。", "model": "gpt-5.2-codex"}
|
||||
{"index": 51, "question": "List recommended configuration defaults for Skills: Conflict resolution and precedence defaults.", "answer": "推荐默认值(基于 SDK 仅暴露 `skill_directories`/`disabled_skills`): \n- `skill_directories`: 明确设置为单一目录(或你自己控制顺序的列表),不要依赖 CLI 默认搜索顺序 \n- `disabled_skills`: 默认空列表;如存在同名/旧技能则显式禁用 \n- Resume/Replay:始终复用同一份配置以避免“隐式优先级”漂移", "model": "gpt-5.2-codex"}
|
||||
{"index": 52, "question": "How does Skills: Conflict resolution and precedence defaults relate to building a custom Agent?", "answer": "Skills 是会话级配置(`skill_directories`/`disabled_skills`),会影响所有自定义 Agent 的可用能力与提示行为;SDK 不定义冲突优先级,因此需要你在构建自定义 Agent 时确保技能命名唯一并显式禁用冲突项(来源:本地 SDK 源码)。 \n建议/落地:为 custom agent 固定同一份 skills 配置,并在 resume 时重复传入,避免行为漂移。", "model": "gpt-5.2-codex"}
|
||||
{"index": 53, "question": "Give a concise overview of Debugging: Tracing JSON-RPC traffic in the SDK.", "answer": "本地 SDK 源码里 JsonRpcClient 没有内置 JSON‑RPC trace,只能通过 `CopilotClient({\"log_level\":\"debug\"})` 打开 CLI 侧日志(非原始包)(来源:本地 SDK 源码)。 \n要抓实际 JSON‑RPC 流量,可 monkey‑patch `JsonRpcClient._send_message`/`_read_message` 或包装 stdio 进行 tee,最小示例如下。 \n建议/落地:在 pipe 中开启 patch 并对日志做脱敏与采样。 \n```python\nfrom copilot.jsonrpc import JsonRpcClient\n\norig_send = JsonRpcClient._send_message\nasync def _send(self, message):\n print(\">>\", message)\n return await orig_send(self, message)\nJsonRpcClient._send_message = _send\n\norig_read = JsonRpcClient._read_message\ndef _read(self):\n msg = orig_read(self)\n if msg:\n print(\"<<\", msg)\n return msg\nJsonRpcClient._read_message = _read\n```", "model": "gpt-5.2-codex"}
|
||||
{"index": 54, "question": "Provide best practices and common pitfalls for Debugging: Tracing JSON-RPC traffic in the SDK.", "answer": "源码里 JsonRpcClient 没有内置 trace,只能用 `CopilotClient({\"log_level\":\"debug\"})` 查看 CLI 侧日志或 monkey‑patch `JsonRpcClient._send_message/_read_message` 做 tee(来源:本地 SDK 源码)。 \n最佳实践:仅在调试启用、对 github_token/API key/内容脱敏、采样/限流、只读不改消息并注意线程安全;常见坑:记录完整 prompt/工具参数导致泄露,或同步打印阻塞 IO 造成超时/断连。", "model": "gpt-5.2-codex"}
|
||||
{"index": 55, "question": "Show a minimal example snippet for Debugging: Tracing JSON-RPC traffic in the SDK.", "answer": "最小示例(monkey‑patch `JsonRpcClient` 打印收发): \n```python\nfrom copilot.jsonrpc import JsonRpcClient\n\norig_send = JsonRpcClient._send_message\nasync def _send(self, msg):\n print(\">>\", msg)\n return await orig_send(self, msg)\nJsonRpcClient._send_message = _send\n\norig_read = JsonRpcClient._read_message\ndef _read(self):\n msg = orig_read(self)\n if msg:\n print(\"<<\", msg)\n return msg\nJsonRpcClient._read_message = _read\n```\n建议:仅在调试启用并脱敏日志。", "model": "gpt-5.2-codex"}
|
||||
{"index": 56, "question": "List recommended configuration defaults for Debugging: Tracing JSON-RPC traffic in the SDK.", "answer": "推荐默认值(基于 SDK 源码):- 生产环境关闭 trace;- 调试时 `CopilotClient({\"log_level\":\"debug\"})`;- 如需 JSON‑RPC 报文,采用临时 monkey‑patch 并启用脱敏/采样;- 不修改消息内容,避免线程阻塞。 \n建议/落地:pipe 中通过环境变量开关调试与采样率。", "model": "gpt-5.2-codex"}
|
||||
{"index": 57, "question": "How does Debugging: Tracing JSON-RPC traffic in the SDK relate to building a custom Agent?", "answer": "JSON‑RPC trace 能看到 custom_agents 在 `session.create/session.resume` 的实际 payload,以及后续 `tool.call`/`permission.request`/`hooks.invoke` 是否按预期触发,从而验证自定义 Agent 的能力与权限边界(来源:本地 SDK 源码)。 \n建议/落地:仅在开发环境开启并脱敏日志;用它核对 customAgents/tools/权限决策与 MCP 配置。", "model": "gpt-5.2-codex"}
|
||||
{"index": 58, "question": "Give a concise overview of Billing & Policies: How seat management affects SDK features.", "answer": "- 访问与政策:SDK 依赖 Copilot CLI,需有效 Copilot 订阅且组织/企业未禁用 CLI,否则无法使用(源:https://raw.githubusercontent.com/github/copilot-sdk/main/README.md,https://raw.githubusercontent.com/github/copilot-cli/main/README.md) \n- 计费与认证:每次 prompt 计入 Copilot premium requests 配额,PAT 需 Copilot Requests 权限(源:https://raw.githubusercontent.com/github/copilot-sdk/main/README.md,https://raw.githubusercontent.com/github/copilot-cli/main/README.md)\n\n建议/落地:启动先 `get_auth_status()`,失败时提示分配席位/启用 CLI 或更新 token", "model": "gpt-5.2-codex"}
|
||||
{"index": 59, "question": "Provide best practices and common pitfalls for Billing & Policies: How seat management affects SDK features.", "answer": "最佳实践:确认用户/组织席位允许 Copilot CLI/SDK 使用,启动前用 `get_auth_status()` 预检,使用 PAT 时确保具备 Copilot Requests 权限(源:https://raw.githubusercontent.com/github/copilot-sdk/main/README.md,https://raw.githubusercontent.com/github/copilot-cli/main/README.md)。 \n常见坑:未分配席位或组织/企业策略禁用 CLI 导致 SDK 失效、PAT 权限不足或 premium requests 用尽引发认证/配额错误(源:https://raw.githubusercontent.com/github/copilot-sdk/main/README.md,https://raw.githubusercontent.com/github/copilot-cli/main/README.md)。 \n建议/落地:在 pipe 启动时做席位与鉴权检测并给出明确提示/自助链接,记录每次请求计费与配额告警阈值以便限流。", "model": "gpt-5.2-codex"}
|
||||
{"index": 60, "question": "Show a minimal example snippet for Billing & Policies: How seat management affects SDK features.", "answer": "基于 SDK README,最小示例是在启动时用 `get_auth_status()` 检查是否已分配席位/可用认证(源:https://raw.githubusercontent.com/github/copilot-sdk/main/README.md)。 \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n auth = await client.get_auth_status()\n if not auth.isAuthenticated:\n raise RuntimeError(\"Copilot 未认证/未分配席位,请先登录或分配权限\")\n\n # ...正常创建 session...\n await client.stop()\n\nasyncio.run(main())\n```\n建议/落地:pipe 启动先做该检查,失败时提示分配席位或更新 PAT 权限。", "model": "gpt-5.2-codex"}
|
||||
{"index": 61, "question": "List recommended configuration defaults for Billing & Policies: How seat management affects SDK features.", "answer": "推荐默认值:CopilotClient 用 `use_logged_in_user=True`、`auto_start=True`、`log_level=\"info\"`;如用 PAT 则用 `github_token` 注入并确保具备 Copilot Requests 权限,且启动前 `get_auth_status()` 失败即按席位/策略限制处理(来源:https://raw.githubusercontent.com/github/copilot-sdk/main/README.md ,https://raw.githubusercontent.com/github/copilot-cli/main/README.md)。 \n建议/落地:pipe 启动先做 auth 预检并给出续订/分配席位提示,避免会话中途才触发权限或配额错误。", "model": "gpt-5.2-codex"}
|
||||
{"index": 62, "question": "How does Billing & Policies: How seat management affects SDK features relate to building a custom Agent?", "answer": "席位与组织策略决定 Copilot CLI/SDK 是否可用,因此自定义 Agent 只是同一会话上的配置,前提仍是有有效订阅与允许的策略,否则 Agent 无法运行(源:https://raw.githubusercontent.com/github/copilot-sdk/main/README.md ,https://raw.githubusercontent.com/github/copilot-cli/main/README.md)。 \n建议/落地:在创建自定义 Agent 前先 `get_auth_status()` 预检并对配额/政策限制做降级提示。", "model": "gpt-5.2-codex"}
|
||||
{"index": 63, "question": "How to configure MCP servers in ~/.copilot/config.json for SDK usage?", "answer": "我在 CLI 文档里未看到 `~/.copilot/config.json` 的 MCP 配置结构;CLI 仅提供 `/mcp add|edit|delete|show` 命令管理配置(来源:Copilot CLI 文档)。 \n因此建议用 CLI 写入配置,并在 SDK 中用 `config_dir` 指向该配置目录;或直接在 SDK 的 `create_session(mcp_servers=...)` 传入。 \n如果你确认存在 `~/.copilot/config.json` 的具体 schema,请提供示例或路径,我可按你的文件格式给出精确配置。", "model": "gpt-5.2-codex"}
|
||||
{"index": 64, "question": "What CLI environment variables force 'Agent' mode vs 'Generic' mode?", "answer": "CLI 文档未提到任何“通过环境变量强制 Agent 模式/Generic 模式”的开关(源:Copilot CLI 文档)。 \n已知与模式相关的是命令/参数,例如 `--agent` 选择自定义 agent,或交互内 `/agent` 切换;但不是环境变量。 \n如果你指的是内部实验开关,请提供具体变量名或版本线索,我可继续追索。", "model": "gpt-5.2-codex"}
|
||||
{"index": 65, "question": "Explain the 'confirmation' flow in CLI and how it maps to SDK events.", "answer": "- CLI:工具权限弹窗确认,可用 `/allow-all` 或 `/yolo` 自动批准。(源: https://raw.githubusercontent.com/github/copilot-cli/main/README.md) \n- SDK:对应 JSON‑RPC `permission.request`→`on_permission_request` 返回 `approved/denied-*`,`session.on()` 只用于审计 tool.execution_*,无独立 confirmation 事件。(源: 本地 SDK 源码 copilot/client.py、copilot/session.py、copilot/types.py)\n\n示例:\n```python\nfrom copilot import CopilotClient\nfrom copilot.generated.session_events import SessionEventType\n\nclient = CopilotClient()\nawait client.start()\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"on_permission_request\": lambda req, _: {\"kind\": \"approved\"},\n})\nsession.on(lambda ev: ev.type == SessionEventType.TOOL_EXECUTION_START and print(ev.data.tool_name))\n```\n\n建议/落地:非交互模式实现 `on_permission_request` 并记录 `tool.execution_*` 事件。", "model": "gpt-5.2-codex"}
|
||||
{"index": 66, "question": "Does the CLI support 'dry-run' permission checks for tools?", "answer": "CLI 文档未提到“dry-run”权限检查,只有正常确认与 `/allow-all`、`/yolo` 自动批准(源:https://raw.githubusercontent.com/github/copilot-cli/main/README.md)。SDK 侧仅有 `permission.request`→`on_permission_request` 与 `hooks.on_pre_tool_use` 的拦截/拒绝(本地 SDK 源码),不提供不执行的试运行;建议/落地:pipe 中用 `on_permission_request` 记录/拒绝高风险工具并在 hooks 做审计。", "model": "gpt-5.2-codex"}
|
||||
{"index": 67, "question": "What are the undocumented requirements for 'workspace' context updates?", "answer": "CLI 文档与本地 SDK 源码都未提到“workspace context updates”的未公开要求;SDK 只暴露 `working_directory` 与 `session.workspace_path`,以及 `attachments` 真实路径(来源:本地 SDK 源码)。 \n如果你指的是 CLI 的“工作区/会话文件”更新规则,请提供具体页面或日志片段,我可以进一步定位。", "model": "gpt-5.2-codex"}
|
||||
{"index": 68, "question": "How does the CLI handle 'device code' re-authentication automatically?", "answer": "CLI 文档未说明“自动 device code 重新认证”的具体机制;只提到未登录时使用 `/login`,PAT 可通过 `GH_TOKEN/GITHUB_TOKEN` 提供(源:Copilot CLI 文档)。因此无法确认是否有自动 re-auth 或缓存刷新策略。 \n若需无交互场景,请使用 SDK 的 `github_token` 注入并自行实现 device flow 轮询与持久化。", "model": "gpt-5.2-codex"}
|
||||
File diff suppressed because it is too large
Load Diff
186
plugins/debug/copilot_sdk_research/test_multi_rule_permission.py
Normal file
186
plugins/debug/copilot_sdk_research/test_multi_rule_permission.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Test multi-rule permission control pattern (VSCode-style)
|
||||
Tests ordered rule matching like VSCode's chat.tools.terminal.autoApprove
|
||||
SAFE VERSION: Uses harmless commands (echo/ls) only. No rm, no git.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from copilot import CopilotClient
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def build_multi_rule_handler(rules_json: str):
|
||||
"""
|
||||
Build permission handler with ordered rules (VSCode-style)
|
||||
"""
|
||||
try:
|
||||
rules = json.loads(rules_json) if rules_json else {}
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("Invalid rules JSON: %s", e)
|
||||
rules = {}
|
||||
|
||||
async def on_permission_request(request: Dict[str, Any], context: Dict[str, str]):
|
||||
kind = request.get("kind")
|
||||
command = request.get("fullCommandText", "") or request.get("command", "")
|
||||
|
||||
# Always approve read and url
|
||||
if kind in ("read", "url"):
|
||||
return {"kind": "approved"}
|
||||
|
||||
# For shell commands, apply ordered rules
|
||||
if kind == "shell" and command:
|
||||
for pattern, approved in rules.items():
|
||||
try:
|
||||
if re.match(pattern, command):
|
||||
if approved:
|
||||
logger.info(
|
||||
"✅ Approved (rule match): pattern=%r command=%r",
|
||||
pattern,
|
||||
command,
|
||||
)
|
||||
return {"kind": "approved"}
|
||||
else:
|
||||
logger.warning(
|
||||
"❌ Denied (rule match): pattern=%r command=%r",
|
||||
pattern,
|
||||
command,
|
||||
)
|
||||
return {
|
||||
"kind": "denied-by-rules",
|
||||
"rules": [
|
||||
{"kind": "multi-rule-deny", "pattern": pattern}
|
||||
],
|
||||
}
|
||||
except re.error as exc:
|
||||
logger.error("Invalid pattern %r: %s", pattern, exc)
|
||||
continue
|
||||
|
||||
# Default deny for shell without matching rule
|
||||
logger.warning("❌ Denied (no matching rule): command=%r", command)
|
||||
return {"kind": "denied-by-rules", "rules": [{"kind": "no-rule-match"}]}
|
||||
|
||||
return on_permission_request
|
||||
|
||||
|
||||
async def run_test(model: str, rules_json: str, prompt: str) -> Tuple[bool, str]:
|
||||
"""Run a single test and return (approved, response)"""
|
||||
try:
|
||||
client = CopilotClient()
|
||||
await client.start()
|
||||
|
||||
session = await client.create_session(
|
||||
{
|
||||
"model": model,
|
||||
"on_permission_request": build_multi_rule_handler(rules_json),
|
||||
}
|
||||
)
|
||||
|
||||
# Set a short timeout
|
||||
try:
|
||||
response = await asyncio.wait_for(
|
||||
session.send_and_wait({"prompt": prompt}), timeout=15.0
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("Test Timed Out")
|
||||
return (False, "Timeout")
|
||||
finally:
|
||||
await client.stop()
|
||||
|
||||
content = response.data.content
|
||||
# Heuristics to detect denial in response
|
||||
denied_keywords = [
|
||||
"不允许",
|
||||
"无法",
|
||||
"对不起",
|
||||
"Sorry",
|
||||
"can't",
|
||||
"cannot",
|
||||
"not have permission",
|
||||
"denied",
|
||||
]
|
||||
is_denied = any(kw in content for kw in denied_keywords)
|
||||
|
||||
return (not is_denied, content)
|
||||
except Exception as e:
|
||||
logger.error("Test failed: %s", e)
|
||||
return (False, str(e))
|
||||
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--model", default="gpt-4.1", help="Model ID")
|
||||
args = parser.parse_args()
|
||||
|
||||
# LOGIC TEST RULES
|
||||
# 1. Deny "echo secret" explicitly (Specific Deny)
|
||||
# 2. Allow "echo" anything else (General Allow)
|
||||
# 3. Allow "ls" (General Allow)
|
||||
# 4. Deny everything else (Default Deny)
|
||||
logic_test_rules = {
|
||||
"^echo\\s+secret": False, # Higher priority: Deny specific echo
|
||||
"^echo": True, # Lower priority: Allow general echo
|
||||
"^ls": True, # Allow ls
|
||||
".*": False, # Deny everything else (e.g. whoami)
|
||||
}
|
||||
|
||||
rules_json = json.dumps(logic_test_rules)
|
||||
|
||||
test_cases = [
|
||||
# 1. Matches Rule 2 (^echo) -> Should be Approved
|
||||
("Allowed: Normal Echo", "请执行: echo 'hello world'", True),
|
||||
# 2. Matches Rule 3 (^ls) -> Should be Approved
|
||||
("Allowed: LS", "请执行: ls -la", True),
|
||||
# 3. Matches Rule 1 (^echo\s+secret) -> Should be DENIED
|
||||
# This proves the ORDER matters. If it matched Rule 2 first, it would be allowed.
|
||||
("Denied: Restricted Echo", "请执行: echo secret data", False),
|
||||
# 4. Matches Rule 4 (.*) -> Should be DENIED
|
||||
("Denied: Unknown Command", "请执行: whoami", False),
|
||||
]
|
||||
|
||||
logger.info("=" * 80)
|
||||
logger.info("Safe Multi-Rule Logic Test (Proving Precedence)")
|
||||
logger.info("Rules: %s", json.dumps(logic_test_rules, indent=2))
|
||||
logger.info("=" * 80)
|
||||
|
||||
results = []
|
||||
for i, (name, prompt, expected) in enumerate(test_cases, 1):
|
||||
logger.info("\n[Test %d/%d] %s", i, len(test_cases), name)
|
||||
logger.info(" Prompt: %s", prompt)
|
||||
|
||||
approved, response = await run_test(args.model, rules_json, prompt)
|
||||
passed = approved == expected
|
||||
|
||||
status = "✅ PASS" if passed else "❌ FAIL"
|
||||
results.append((name, passed))
|
||||
|
||||
logger.info(
|
||||
" Expected: %s, Got: %s - %s",
|
||||
"Approved" if expected else "Denied",
|
||||
"Approved" if approved else "Denied",
|
||||
status,
|
||||
)
|
||||
logger.info(" Response: %s", response[:100].replace("\n", " "))
|
||||
|
||||
# Summary
|
||||
logger.info("\n" + "=" * 80)
|
||||
logger.info("Test Summary")
|
||||
logger.info("=" * 80)
|
||||
passed_count = sum(1 for _, passed in results if passed)
|
||||
for name, passed in results:
|
||||
logger.info("%s %s", "✅" if passed else "❌", name)
|
||||
logger.info("Total: %d/%d tests passed", passed_count, len(results))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user