Compare commits

..

1 Commits

Author SHA1 Message Date
fujie
f061d82409 feat(async-context-compression): release v1.4.0 with structure-aware grouping and session locking
- Introduced Atomic Message Grouping to prevent tool-calling corruption (Issue #56)
- Implemented Tail Boundary Alignment for deterministic context truncation
- Added per-chat asynchronous session locking to prevent duplicate background tasks
- Enhanced summarization traceability with message IDs and names
- Synchronized version and changelog across all documentation files
- Optimized release-prep skill to remove redundant H1 titles

Closes #56
2026-03-09 20:34:34 +08:00
89 changed files with 1779 additions and 11694 deletions

Binary file not shown.

View File

@@ -1,27 +0,0 @@
# Async Context Compression Progress Mapping
> Discovered: 2026-03-10
## Context
Applies to `plugins/filters/async-context-compression/async_context_compression.py` once the inlet has already replaced early history with a synthetic summary message.
## Finding
`compressed_message_count` cannot be recalculated from the visible message list length after compression. Once a summary marker is present, the visible list mixes:
- preserved head messages that are still before the saved boundary
- one synthetic summary message
- tail messages that map to original history starting at the saved boundary
## Solution / Pattern
Store the original-history boundary on the injected summary message metadata, then recover future progress using:
- `original_count = covered_until + len(messages_after_summary_marker)`
- `target_progress = max(covered_until, original_count - keep_last)`
When the summary-model window is too small, trim newest atomic groups from the summary input so the saved boundary still matches what the summary actually covers.
## Gotchas
- If you trim from the head of the summary input, the saved progress can overstate coverage and hide messages that were never summarized.
- Status previews for the next context must convert the saved original-history boundary back into the current visible view before rebuilding head/summary/tail.
- `inlet(body["messages"])` and `outlet(body["messages"])` can both represent the full conversation while using different serializations:
- inlet may receive expanded native tool-call chains (`assistant(tool_calls) -> tool -> assistant`)
- outlet may receive a compact top-level transcript where tool calls are folded into assistant `<details type="tool_calls">` blocks
- These two views do not share a safe `compressed_message_count` coordinate system. If outlet is in the compact assistant/details view, do not persist summary progress derived from its top-level message count.

View File

@@ -1,171 +0,0 @@
# Filter: async-context-compression 设计模式与工程实践
**日期**: 2026-03-12
**模块**: `plugins/filters/async-context-compression/async_context_compression.py`
**关键特性**: 上下文压缩、异步摘要生成、状态管理、LLM 工程优化
---
## 核心工程洞察
### 1. Request 对象的 Filter-to-LLM 传导链
**问题**Filter 的 `outlet` 阶段启动背景异步任务(`asyncio.create_task`)调用 `generate_chat_completion`(内部 API但无法直接访问原始 HTTP `request`。早期代码用最小化合成 Request`{"type": "http", "app": webui_app}`),暴露兼容性风险。
**解决方案**
- OpenWebUI 对 `outlet` 同样支持 `__request__` 参数注入(即 `inlet` + `outlet` 都支持)
- 透传 `__request__` 通过整个异步调用链:`outlet → _locked_summary_task → _check_and_generate_summary_async → _generate_summary_async → _call_summary_llm`
- 在最终调用处:`request = __request__ or Request(...)`(兜底降级)
**收获**LLM 调用路径应始终倾向于使用真实请求上下文,而非人工合成。即使后台任务中,`request.app` 的应用级状态仍持续有效。
---
### 2. 异步摘要生成中的上下文完整性
**关键场景分化**
| 情况 | `summary_index` 值 | 旧摘要位置 | 需要 `previous_summary` |
|------|--------|----------|---------|
| Inlet 已注入旧摘要 | Not None | `messages[0]`middle_messages 首项) | ❌ 否,已在 conversation_text 中 |
| Outlet 收原始消息(未注入) | None | DB 存档 | ✅ **是**,必须显式读取并透传 |
**问题根源**`outlet` 收到的消息来自原始数据库查询,未经过 `inlet` 的摘要注入。当 LLM 看不到历史摘要时,已压缩的知识(旧对话、已解决的问题、先前的发现)会被重新处理或遗忘。
**实现要点**
```python
# 仅当 summary_index is None 时异步加载旧摘要
if summary_index is None:
previous_summary = await asyncio.to_thread(
self._load_summary, chat_id, body
)
else:
previous_summary = None
```
---
### 3. 上下文压缩的 LLM Prompt 设计
**工程原则**
1. **Clear Input Boundaries**:用 XML 风格标签(`<previous_working_memory>`, `<new_conversation>`)明确分界,避免 LLM 混淆"指令示例"与"待处理数据"
2. **State-Aware Merging**:不是"保留所有旧事实",而是**更新状态**——`"bug X exists" → "bug X fixed"` 或彻底移除已解决项
3. **Goal Evolution**Current Goal 反映**最新**意图;旧目标迁移到 Working Memory 作为上下文
4. **Error Verbatim**Stack trace、异常类型、错误码必须逐字引用是调试的一等公民
5. **Format Strictness**:结构变为 **REQUIRED**(而非 Suggested允许零内容项省略但布局一致
**新 Prompt 结构**
```
[Rules] → [Output Constraints] → [Required Structure Header] → [Boundaries] → <previous_working_memory> → <new_conversation>
```
关键改进:
- 规则 3Ruthless Denoising → 新增规则 4Error Verbatim + 规则 5Causal Chain
- "Suggested" Structure → "Required" Structure with Optional Sections
- 新增 `## Causal Log` 专项,强制单行因果链格式:`[MSG_ID?] action → result`
- Token 预算策略明确按近期性和紧迫性优先裁剪RRF
---
### 4. 异步任务中的错误边界与恢复
**现象**:背景摘要生成任务(`asyncio.create_task`)的异常不会阻塞用户响应,但需要:
- 完整的日志链路(`_log` 调用 + `event_emitter` 通知)
- 数据库事务的原子性(摘要和压缩状态同时保存)
- 前端 UI 反馈status event: "generating..." → "complete" 或 "error"
**最佳实践**
-`asyncio.Lock` 按 chat_id 防止并发摘要任务
- 后台执行繁重操作tokenize、LLM call`asyncio.to_thread`
- 所有 I/ODB reads/writes需包裹异步线程池
- 异常捕获限制在 try-except日志不要吞掉堆栈信息
---
### 5. Filter 单例与状态设计陷阱
**约束**Filter 实例是全局单例,所有会话共享同一个 `self`
**禁忌**
```python
# ❌ 错误self.temp_buffer = ... (会被其他并发会话污染)
self.temp_state = body # 危险!
# ✅ 正确:无状态或使用锁/chat_id 隔离
self._chat_locks[chat_id] = asyncio.Lock() # 每个 chat 一个锁
```
**设计**
- ValvesPydantic BaseModel保存全局配置 ✅
- 使用 dict 按 `chat_id` 键维护临时状态lock、计数器
- 传参而非全局变量保存请求级数据 ✅
---
## 集成场景Filter + Pipe 的配合
**当 Pipe 模型调用 Filter 时**
1. `inlet` 注入摘要,削减上下文会话消息数
2. Pipe 模型(通常为 Copilot SDK 或自定义内核)处理精简消息
3. `outlet` 触发背景摘要,无阻塞用户响应
4. 下一轮对话时,`inlet` 再次注入最新摘要
**关键约束**
- `_should_skip_compression` 检测 `__model__.get("pipe")``copilot_sdk`,必要时跳过注入
- Pipe 模型若有自己的上下文管理(如 Copilot 的 native tool calling过度压缩会失去工具调用链
- 摘要模型选择(`summary_model` Valve应兼容当前 Pipe 环境的 API推荐用通用模型如 gemini-flash
---
## 内部 API 契约速记
### `generate_chat_completion(request, payload, user)`
- **request**: FastAPI Request可来自真实 HTTP 或 `__request__` 注入
- **payload**: `{"model": id, "messages": [...], "stream": false, "max_tokens": N, "temperature": T}`
- **user**: UserModel从 DB 查询或 `__user__` 转换(需 `Users.get_user_by_id()`
- **返回**: dict 或 JSONResponse若是后者需 `response.body.decode()` + JSON parse
### Filter 生命周期
```
New Message → inlet (User input) → [Plugins wait] → LLM → outlet (Response) → Summary Task (Background)
```
---
## 调试清单
- [ ] `__request__``outlet` 签名中声明且被 OpenWebUI 注入(非 None
- [ ] 异步调用链中每层都透传 `__request__`,最底层兜底合成
- [ ] `summary_index is None` 时从 DB 异步读取 `previous_summary`
- [ ] LLM Prompt 中 `<previous_working_memory>``<new_conversation>` 有明确边界
- [ ] 错误处理不吞堆栈:`logger.exception()``exc_info=True`
- [ ] `asyncio.Lock` 按 chat_id 避免并发工作冲突
- [ ] Copilot SDK / Pipe 模型需 `_should_skip_compression()` 检查
- [ ] Token budget 在 max_summary_tokens 下规划,优先保留近期事件
---
## 相关文件
- 核心实现:`plugins/filters/async-context-compression/async_context_compression.py`
- README`plugins/filters/async-context-compression/README.md` + `README_CN.md`
- OpenWebUI 内部:`open_webui/utils/chat.py``generate_chat_completion()`
---
**版本**: 1.0
**维护者**: Fu-Jie
**最后更新**: 2026-03-12

View File

@@ -1,45 +0,0 @@
# OpenWebUI Community API Patterns
## Post Data Structure Variations
When fetching posts from the OpenWebUI Community API (`https://api.openwebui.com/api/v1/posts/...`), the structure of the `data` field varies significantly depending on the `type` of the post.
### Observed Mappings
| Post Type | Data Key (under `data`) | Usual Content |
|-----------|-------------------------|---------------|
| `action` | `function` | Plugin code and metadata |
| `filter` | `function` | Filter logic and metadata |
| `pipe` | `function` | Pipe logic and metadata |
| `tool` | `tool` | Tool definition and logic |
| `prompt` | `prompt` | Prompt template strings |
| `model` | `model` | Model configuration |
### Implementation Workaround
To robustly extract metadata (like `version` or `description`) regardless of the post type, the following heuristic logic is recommended:
```python
def _get_plugin_obj(post: dict) -> dict:
data = post.get("data", {}) or {}
post_type = post.get("type")
# Priority 1: Use specific type key
if post_type in data:
return data[post_type]
# Priority 2: Fallback to common keys
for k in ["function", "tool", "pipe"]:
if k in data:
return data[k]
# Priority 3: First available key
if data:
return list(data.values())[0]
return {}
```
### Gotchas
- Some older posts or different categories might not have a `version` field in `manifest`, leading to empty strings or `N/A` in reports.
- `slug` should be used as the unique identifier rather than `title` when tracking stats across history.

View File

@@ -1,26 +0,0 @@
# OpenWebUI Tool Call Context Inflation
> Discovered: 2026-03-11
## Context
When analyzing why the `async_context_compression` plugin sees different array lengths of `messages` between the `inlet` (e.g. 27 items) and `outlet` (e.g. 8 items) phases, especially when native tool calling (Function Calling) is involved in OpenWebUI.
## Finding
There is a fundamental disparity in how OpenWebUI serializes conversational history at different stages of the request lifecycle:
1. **Outlet (UI Rendering View)**:
After the LLM completes generation and tools have been executed, OpenWebUI's `middleware.py` (and streaming builders) bundles intermediate tool calls and their raw results. It hides them inside an HTML `<details type="tool_calls">...</details>` block within a single `role: assistant` message's `content`.
Concurrently, the actual native API tool-calling data is saved in a hidden `output` dict field attached to that message. At this stage, the `messages` array looks short (e.g., 8 items) because tool interactions are visually folded.
2. **Inlet (LLM Native View)**:
When the user sends the *next* message, the request enters `main.py` -> `process_chat_payload` -> `middleware.py:process_messages_with_output()`.
Here, OpenWebUI scans historical `assistant` messages for that hidden `output` field. If found, it completely **inflates (unfolds)** the raw data back into an exact sequence of OpenAI-compliant `tool_call` and `tool_result` messages (using `utils/misc.py:convert_output_to_messages`).
The HTML `<details>` string is entirely discarded before being sent to the LLM.
**Conclusion on Token Consumption**:
In the next turn, tool context is **NOT** compressed at all. It is fully re-expanded to its original verbose state (e.g., back to 27 items) and consumes the maximum amount of tokens required by the raw JSON arguments and results.
## Gotchas
- Any logic operating in the `outlet` phase (like background tasks) that relies on the `messages` array index will be completely misaligned with the array seen in the `inlet` phase.
- Attempting to slice or trim history based on `outlet` array lengths will cause index out-of-bounds errors or destructive cropping of recent messages.
- The only safe way to bridge these two views is either to translate the folded view back into the expanded view using `convert_output_to_messages`, or to rely on unique `id` fields (if available) rather than array indices.

View File

@@ -1,29 +0,0 @@
# Agent Coordination Protocol (FOR AGENTS ONLY)
## 🛡️ The Golden Rule
**NEVER modify code without verifying the lock status in the Agent Hub.**
## 🔑 Identity Management
- `claude-code`: Official Claude CLI
- `copilot-agent`: GitHub Copilot
- `gemini-cursor`: Cursor IDE or Gemini extension
- `iflow-agent`: iFlow SDK agent
## 🛠️ The Synchronization Tool
Script: `scripts/agent_sync.py` (SQLite-backed)
### 🏎️ Workflow Lifecycle
1. **Initialize Session**:
- `python3 scripts/agent_sync.py status`
- `python3 scripts/agent_sync.py register <id> <name> "<objective>"`
2. **Resource Acquisition**:
- `python3 scripts/agent_sync.py lock <id> <file_path>`
- If blocked, identify the owner from `status` and do not attempt to bypass.
3. **Collaboration (Research Mode)**:
- If the project mode is `RESEARCH`, prioritize the `note` command.
- Summarize findings: `python3 scripts/agent_sync.py note <id> "<topic>" "<summary>"`
4. **Cleanup**:
- `python3 scripts/agent_sync.py unlock <id> <file_path>`
## 📜 Shared Memory
Read `.agent/learnings/` to avoid reinventing the wheel.

View File

@@ -1,8 +0,0 @@
# 🤖 Cursor/Gemini Multi-Agent Protocol
1. **STATUS CHECK**: Always run `python3 scripts/agent_sync.py status` first.
2. **REGISTRATION**: Run `python3 scripts/agent_sync.py register gemini-id "Gemini" "Current task"`.
3. **LOCKING**: Never edit without `python3 scripts/agent_sync.py lock gemini-id <path>`.
4. **STANDARDS**: Refer to `.agent/rules/plugin_standards.md` for coding guidelines.
Full details in `COOPERATION.md`.

View File

@@ -73,21 +73,13 @@ Create two versioned release notes files:
#### Required Sections #### Required Sections
Each file must include: Each file must include:
0. **Marketplace Badge**: A prominent button linking to the plugin on openwebui.com using shields.io (e.g., `[![](https://img.shields.io/badge/OpenWebUI%20Community-Get%20Plugin-blue?style=for-the-badge)](URL)`). 0. **Marketplace Link**: Direct link to the plugin on openwebui.com (e.g., `**[🚀 Get/Update on OpenWebUI Community](URL)**`)
1. **Overview Header**: Use `## Overview` as the first header. 1. **Overview**: One paragraph summarizing this release
2. **Summary Paragraph**: A paragraph summarizing the release. **NEVER** include the version number as a title. 2. **New Features** / **新功能**: Bulleted list of features
3. **README Link**: Direct link to the plugin's README file on GitHub. 3. **Bug Fixes** / **问题修复**: Bulleted list of fixes
4. **New Features** / **新功能**: Bulleted list of features 4. **Related Issues** / **相关 Issue**: Link to the GitHub Issue(s) resolved in this release (e.g., `**[#56](URL)**`). MANDATORY if the release resolves a reported issue.
5. **Bug Fixes** / **问题修复**: Bulleted list of fixes 5. **Related PRs** / **相关 PR**: Link to the Pull Request(s) associated with this release. (e.g., `**[#123](URL)**`). MANDATORY if the release is being prepared within an existing PR.
6. **Related Issues** / **相关 Issue**: Link to GitHub Issues. **ONLY** include if a specific issue is resolved. **NEVER use placeholders.** 6. **Migration Notes** / **迁移说明**: Breaking changes or Valve key renames (omit section if none)
7. **Related PRs** / **相关 PR**: Link to the Pull Request. **ONLY** include if the PR is already created and the ID is known. **NEVER use placeholders.**
8. **Migration Notes**: Breaking changes or Valve key renames (omit section if none)
---
## Language Standard
- **Release Notes Files**: Use **English ONLY** for the final `.md` files to maintain professional consistency on GitHub. Avoid bilingual content in the release description.
6. **Companion Plugins** / **配套插件** (optional): If a companion plugin was updated 6. **Companion Plugins** / **配套插件** (optional): If a companion plugin was updated
If a release notes file already exists for this version, update it rather than creating a new one. If a release notes file already exists for this version, update it rather than creating a new one.

View File

@@ -78,28 +78,5 @@ Plugin: {type}/{name} → v{new_version}
### Verification Status ### Verification Status
{filled-in 9-file checklist for each changed plugin} {filled-in 9-file checklist for each changed plugin}
## Post-Release: Batch Plugin Installation
After release is published, users can quickly install all plugins:
```bash
# Clone the repository
git clone https://github.com/Fu-Jie/openwebui-extensions.git
cd openwebui-extensions
# Setup API key and instance URL
echo "api_key=sk-your-api-key-here" > scripts/.env
echo "url=http://localhost:3000" >> scripts/.env
# If using remote instance, configure the baseURL:
# echo "url=http://192.168.1.10:3000" >> scripts/.env
# echo "url=https://openwebui.example.com" >> scripts/.env
# Install all plugins at once
python scripts/install_all_plugins.py
```
See: [Deployment Guide](./scripts/DEPLOYMENT_GUIDE.md)
--- ---
⚠️ **Waiting for user confirmation — no git operations will run until explicitly approved.** ⚠️ **Waiting for user confirmation — no git operations will run until explicitly approved.**

View File

@@ -38,12 +38,9 @@ jobs:
id: old_stats id: old_stats
run: | run: |
if [ -f docs/community-stats.json ]; then if [ -f docs/community-stats.json ]; then
cp docs/community-stats.json docs/community-stats.json.old
echo "total_posts=$(jq -r '.total_posts // 0' docs/community-stats.json)" >> $GITHUB_OUTPUT echo "total_posts=$(jq -r '.total_posts // 0' docs/community-stats.json)" >> $GITHUB_OUTPUT
echo "versions=$(jq -r '[.posts[] | {slug: .slug, version: .version}] | sort_by(.slug) | map("\(.slug):\(.version)") | join(",")' docs/community-stats.json)" >> $GITHUB_OUTPUT
else else
echo "total_posts=0" >> $GITHUB_OUTPUT echo "total_posts=0" >> $GITHUB_OUTPUT
echo "versions=" >> $GITHUB_OUTPUT
fi fi
- name: Generate stats report - name: Generate stats report
@@ -59,15 +56,12 @@ jobs:
id: new_stats id: new_stats
run: | run: |
echo "total_posts=$(jq -r '.total_posts // 0' docs/community-stats.json)" >> $GITHUB_OUTPUT echo "total_posts=$(jq -r '.total_posts // 0' docs/community-stats.json)" >> $GITHUB_OUTPUT
echo "versions=$(jq -r '[.posts[] | {slug: .slug, version: .version}] | sort_by(.slug) | map("\(.slug):\(.version)") | join(",")' docs/community-stats.json)" >> $GITHUB_OUTPUT
- name: Check for significant changes - name: Check for significant changes
id: check_changes id: check_changes
run: | run: |
OLD_POSTS="${{ steps.old_stats.outputs.total_posts }}" OLD_POSTS="${{ steps.old_stats.outputs.total_posts }}"
NEW_POSTS="${{ steps.new_stats.outputs.total_posts }}" NEW_POSTS="${{ steps.new_stats.outputs.total_posts }}"
OLD_VERSIONS="${{ steps.old_stats.outputs.versions }}"
NEW_VERSIONS="${{ steps.new_stats.outputs.versions }}"
SHOULD_COMMIT="false" SHOULD_COMMIT="false"
CHANGE_REASON="" CHANGE_REASON=""
@@ -75,20 +69,14 @@ jobs:
if [ "$NEW_POSTS" -gt "$OLD_POSTS" ]; then if [ "$NEW_POSTS" -gt "$OLD_POSTS" ]; then
SHOULD_COMMIT="true" SHOULD_COMMIT="true"
CHANGE_REASON="new plugin added ($OLD_POSTS -> $NEW_POSTS)" CHANGE_REASON="new plugin added ($OLD_POSTS -> $NEW_POSTS)"
elif [ "$NEW_POSTS" -lt "$OLD_POSTS" ]; then echo "📦 New plugin detected: $OLD_POSTS -> $NEW_POSTS"
SHOULD_COMMIT="true"
CHANGE_REASON="plugin removed ($OLD_POSTS -> $NEW_POSTS)"
elif [ "$OLD_VERSIONS" != "$NEW_VERSIONS" ]; then
SHOULD_COMMIT="true"
CHANGE_REASON="plugin versions updated"
echo "🔄 Version change detected"
fi fi
echo "should_commit=$SHOULD_COMMIT" >> $GITHUB_OUTPUT echo "should_commit=$SHOULD_COMMIT" >> $GITHUB_OUTPUT
echo "change_reason=$CHANGE_REASON" >> $GITHUB_OUTPUT echo "change_reason=$CHANGE_REASON" >> $GITHUB_OUTPUT
if [ "$SHOULD_COMMIT" = "false" ]; then if [ "$SHOULD_COMMIT" = "false" ]; then
echo " No significant changes (posts or versions), skipping commit" echo " No significant changes detected, skipping commit"
else else
echo "✅ Significant changes detected: $CHANGE_REASON" echo "✅ Significant changes detected: $CHANGE_REASON"
fi fi

View File

@@ -436,34 +436,54 @@ jobs:
CHANGED_PLUGIN_TITLE: ${{ needs.check-changes.outputs.changed_plugin_title }} CHANGED_PLUGIN_TITLE: ${{ needs.check-changes.outputs.changed_plugin_title }}
CHANGED_PLUGIN_VERSION: ${{ needs.check-changes.outputs.changed_plugin_version }} CHANGED_PLUGIN_VERSION: ${{ needs.check-changes.outputs.changed_plugin_version }}
DETECTED_CHANGES: ${{ needs.check-changes.outputs.release_notes }} DETECTED_CHANGES: ${{ needs.check-changes.outputs.release_notes }}
COMMITS: ${{ steps.commits.outputs.commits }}
DOC_FILES: ${{ needs.check-changes.outputs.changed_doc_files }} DOC_FILES: ${{ needs.check-changes.outputs.changed_doc_files }}
run: | run: |
> release_notes.md > release_notes.md
# 1. Primary content from v*.md files (highest priority) if [ -n "$CHANGED_PLUGIN_TITLE" ] && [ -n "$CHANGED_PLUGIN_VERSION" ]; then
echo "# $CHANGED_PLUGIN_TITLE v$CHANGED_PLUGIN_VERSION" >> release_notes.md
echo "" >> release_notes.md
elif [ -n "$TITLE" ]; then
echo "# $TITLE" >> release_notes.md
echo "" >> release_notes.md
fi
# 1. Release notes from v*.md files (highest priority, shown first)
if [ -n "$DOC_FILES" ]; then if [ -n "$DOC_FILES" ]; then
RELEASE_NOTE_FILES=$(echo "$DOC_FILES" | grep -E '^plugins/.*/v[^/]*\.md$' | grep -v '_CN\.md$' || true) RELEASE_NOTE_FILES=$(echo "$DOC_FILES" | grep -E '^plugins/.*/v[^/]*\.md$' | grep -v '_CN\.md$' || true)
if [ -n "$RELEASE_NOTE_FILES" ]; then if [ -n "$RELEASE_NOTE_FILES" ]; then
while IFS= read -r file; do while IFS= read -r file; do
[ -z "$file" ] && continue [ -z "$file" ] && continue
if [ -f "$file" ]; then if [ -f "$file" ]; then
# Extract content, removing any H1 title from the file to avoid duplication python3 -c "import pathlib, re; file_path = pathlib.Path(r'''$file'''); text = file_path.read_text(encoding='utf-8'); text = re.sub(r'^#\\s+.+?(?:\\r?\\n)+', '', text, count=1, flags=re.MULTILINE); print(text.lstrip().rstrip())" >> release_notes.md
python3 -c "import pathlib, re; file_path = pathlib.Path(r'''$file'''); text = file_path.read_text(encoding='utf-8'); text = re.sub(r'^#\s+.+?(?:\r?\n)+', '', text, count=1, flags=re.MULTILINE); print(text.lstrip().rstrip())" >> release_notes.md
echo "" >> release_notes.md echo "" >> release_notes.md
fi fi
done <<< "$RELEASE_NOTE_FILES" done <<< "$RELEASE_NOTE_FILES"
fi fi
fi fi
# 2. Automated plugin version change summary # 2. Plugin version changes detected by script
if [ -z "$CHANGED_PLUGIN_TITLE" ] && [ -z "$CHANGED_PLUGIN_VERSION" ] && [ -n "$TITLE" ]; then
echo "## $TITLE" >> release_notes.md
echo "" >> release_notes.md
fi
if [ -n "$DETECTED_CHANGES" ] && ! echo "$DETECTED_CHANGES" | grep -q "No changes detected"; then if [ -n "$DETECTED_CHANGES" ] && ! echo "$DETECTED_CHANGES" | grep -q "No changes detected"; then
echo "## Version Changes" >> release_notes.md echo "## What's Changed" >> release_notes.md
echo "" >> release_notes.md echo "" >> release_notes.md
echo "$DETECTED_CHANGES" >> release_notes.md echo "$DETECTED_CHANGES" >> release_notes.md
echo "" >> release_notes.md echo "" >> release_notes.md
fi fi
# 3. Manual additional notes from workflow dispatch # 3. Commits (Conventional Commits format with body)
if [ -n "$COMMITS" ]; then
echo "## Commits" >> release_notes.md
echo "" >> release_notes.md
echo "$COMMITS" >> release_notes.md
echo "" >> release_notes.md
fi
if [ -n "$NOTES" ]; then if [ -n "$NOTES" ]; then
echo "## Additional Notes" >> release_notes.md echo "## Additional Notes" >> release_notes.md
echo "" >> release_notes.md echo "" >> release_notes.md
@@ -473,15 +493,30 @@ jobs:
cat >> release_notes.md << 'EOF' cat >> release_notes.md << 'EOF'
## Download
📦 **Download the updated plugin files below**
### Installation
#### From OpenWebUI Community
1. Open OpenWebUI Admin Panel
2. Navigate to Functions/Tools
3. Search for the plugin name
4. Click Install
#### Manual Installation
1. Download the plugin file (`.py`) from the assets below
2. Open OpenWebUI Admin Panel → Functions
3. Click "Create Function" → Import
4. Paste the plugin code
--- ---
📚 [Documentation Portal](https://fu-jie.github.io/openwebui-extensions/) 📚 [Documentation](https://fu-jie.github.io/openwebui-extensions/)
🐛 [Report Issues](https://github.com/Fu-Jie/openwebui-extensions/issues) 🐛 [Report Issues](https://github.com/Fu-Jie/openwebui-extensions/issues)
EOF EOF
echo "=== Final Release Notes ==="
cat release_notes.md
echo "=== Release Notes ===" echo "=== Release Notes ==="
cat release_notes.md cat release_notes.md

1
.gitignore vendored
View File

@@ -142,4 +142,3 @@ logs/
# OpenWebUI specific # OpenWebUI specific
# Add any specific ignores for OpenWebUI plugins if needed # Add any specific ignores for OpenWebUI plugins if needed
.git-worktrees/ .git-worktrees/
plugins/filters/auth_model_info/

75
CHANGELOG.md Normal file
View File

@@ -0,0 +1,75 @@
# Changelog / 更新日志
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
本项目的所有重要更改都将记录在此文件中。
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
---
## [Unreleased] / 未发布
### Added / 新增
- 插件发布工作流 (Plugin release workflow)
### Changed / 变更
### Fixed / 修复
### Removed / 移除
---
## Plugin Versions / 插件版本
### Actions
| Plugin / 插件 | Version / 版本 |
|---------------|----------------|
| Smart Mind Map / 思维导图 | 0.8.0 |
| Flash Card / 闪记卡 | 0.2.1 |
| Export to Word / 导出为 Word | 0.1.0 |
| Export to Excel / 导出为 Excel | 0.3.3 |
| Deep Reading & Summary / 精读 | 0.1.0 / 2.0.0 |
| Smart Infographic / 智能信息图 | 1.3.0 |
### Filters
| Plugin / 插件 | Version / 版本 |
|---------------|----------------|
| Async Context Compression / 异步上下文压缩 | 1.1.0 |
| Context & Model Enhancement Filter | 0.2 |
| Gemini Manifold Companion | 1.7.0 |
| Gemini 多模态过滤器 | 0.3.2 |
### Pipes
| Plugin / 插件 | Version / 版本 |
|---------------|----------------|
| Gemini Manifold google_genai | 1.26.0 |
---
<!--
Release Template / 发布模板:
## [x.x.x] - YYYY-MM-DD
### Added / 新增
- New feature description
### Changed / 变更
- Change description
### Fixed / 修复
- Bug fix description
### Plugin Updates / 插件更新
- `plugin_name`: v0.x.0 -> v0.y.0
- Feature 1
- Feature 2
-->

View File

@@ -1,13 +0,0 @@
# 🤖 Claude Multi-Agent Protocol (MACP)
## 🚀 Mandatory Startup
1. **Check Hub**: `python3 scripts/agent_sync.py status`
2. **Register**: `python3 scripts/agent_sync.py register claude-code "Claude" "Handling user request"`
3. **Lock**: `python3 scripts/agent_sync.py lock claude-code <file_path>`
4. **Handoff**: Use `python3 scripts/agent_sync.py note` for collaborative findings.
## 🤝 Project Standards
Read these BEFORE writing any code:
- `.agent/rules/plugin_standards.md`
- `.agent/rules/agent_protocol.md`
- `COOPERATION.md`

View File

@@ -1,33 +0,0 @@
# 🤖 Multi-Agent Cooperation Protocol (MACP) v2.1
本项目采用 **SQLite 协作中控 (Agent Hub)** 来管理多个 AI Agent 的并发任务。
## 🚀 核心指令 (Quick Commands)
使用 `./scripts/macp` 即可快速调用,无需记忆复杂的 Python 参数。
| 指令 | 描述 |
| :--- | :--- |
| **`/status`** | 查看全场状态(活跃 Agent、文件锁、任务、研究主题 |
| **`/study <topic> <desc>`** | **一键发起联合研究**。广播主题并通知所有 Agent 进入研究状态。 |
| **`/summon <agent> <task>`** | **定向召唤**。给特定 Agent 派发高优先级任务。 |
| **`/handover <agent> <msg>`** | **任务接力**。释放当前进度并交棒给下一个 Agent。 |
| **`/broadcast <msg>`** | **全场广播**。发送紧急通知或状态同步。 |
| **`/check`** | **收件箱检查**。查看是否有分配给你的待办任务。 |
| **`/resolve <topic> <result>`** | **归档结论**。结束研究专题并记录最终共识。 |
| **`/ping`** | **生存检查**。快速查看哪些 Agent 在线。 |
---
## 🛡️ 协作准则
1. **先查后动**:开始工作前先运行 `./scripts/macp /status`
2. **锁即所有权**:修改文件前必须获取锁。
3. **意图先行**:大型重构建议先通过 `/study` 发起方案讨论。
4. **及时解锁**Commit 并 Push 后,请务必 `/handover` 或手动解锁。
## 📁 基础设施
- **数据库**: `.agent/agent_hub.db` (不要手动编辑)
- **内核**: `scripts/agent_sync.py`
- **快捷工具**: `scripts/macp`
---
*Generated by Claude (Coordinator) in collaboration with Sisyphus & Copilot.*

104
ISSUE_57_ANALYSIS_REPORT.md Normal file
View File

@@ -0,0 +1,104 @@
# Markdown Normalizer 插件可靠性修复分析报告 (Issue #57)
## 1. 问题背景
根据 Issue #57 报告,`Markdown Normalizer` 在 v1.2.7 版本中存在数项严重影响可靠性的 Bug包括错误回滚失效、对内联技术内容的过度转义、配置项不生效以及调试日志潜在的隐私风险。
## 2. 核心处理流程图 (v1.2.8)
以下流程展示了插件如何在确保“不损坏原始内容”的前提下进行智能修复:
```mermaid
graph TD
Start([开始处理内容]) --> Cache[1. 内存中存入原始快照 Snapshot]
Cache --> Logic{进入修复流程}
subgraph "分层保护逻辑 (Context-Aware)"
Logic --> Block[识别并锁定 ``` 代码块]
Block --> Inline[识别并锁定 ` 行内代码]
Inline --> Math[识别并锁定 $ LaTeX 公式]
Math --> Clean[仅对非锁定区域执行转义清理]
end
Clean --> Others[执行其他规则: Thought/Details/Table等]
Others --> Check{运行是否报错?}
Check -- 否 (成功) --> Success[返回修复后的内容]
Check -- 是 (失败) --> Rollback[触发回滚: 丢弃所有修改]
Rollback --> Original[返回步骤1存储的原始快照]
Success --> End([输出结果])
Original --> End
```
## 3. 修复项详细说明
### 2.1 错误回滚机制修复 (Reliability: Error Fallback)
- **问题**:在 `normalize` 流程中,如果某个清理器抛出异常,返回的是已被部分修改的 `content`,导致输出内容损坏。
- **技术实现**
```python
def normalize(self, content: str) -> str:
original_content = content # 1. 流程开始前缓存原始快照
try:
# ... 执行一系列清理步骤 ...
return content
except Exception as e:
# 2. 任何步骤失败,立即记录日志并回滚
logger.error(f"Content normalization failed: {e}", exc_info=True)
return original_content # 确保返回的是原始快照
```
- **验证结果**:通过模拟 `RuntimeError` 验证,插件现在能 100% 回滚至原始状态。
### 2.2 上下文感知的转义保护 (Context-Aware Escaping)
- **问题**:全局替换导致正文中包含在 `` ` `` 内的代码片段如正则、Windows 路径)被破坏。
- **技术实现**
重构后的 `_fix_escape_characters` 采用了 **“分词保护策略”**,通过多层嵌套分割来确保仅在非代码上下文中进行清理:
```python
def _fix_escape_characters(self, content: str) -> str:
# 层级 1: 以 ``` 分隔代码块
parts = content.split("```")
for i in range(len(parts)):
is_code_block = (i % 2 != 0)
if is_code_block and not self.config.enable_escape_fix_in_code_blocks:
continue # 默认跳过代码块
if not is_code_block:
# 层级 2: 在非代码块正文中,以 ` 分隔内联代码
inline_parts = parts[i].split("`")
for k in range(0, len(inline_parts), 2): # 仅处理非内联代码部分
# 层级 3: 在非内联代码中,以 $ 分隔 LaTeX 公式
sub_parts = inline_parts[k].split("$")
for j in range(0, len(sub_parts), 2):
# 最终:仅在确认为“纯文本”的部分执行 clean_text
sub_parts[j] = clean_text(sub_parts[j])
inline_parts[k] = "$".join(sub_parts)
parts[i] = "`".join(inline_parts)
else:
parts[i] = clean_text(parts[i])
return "```".join(parts)
```
- **验证结果**:测试用例 `Regex: [\n\r]` 和 `C:\Windows` 在正文中保持原样,而普通文本中的 `\\n` 被正确转换。
### 2.3 配置项激活 (Configuration Enforcement)
- **问题**`enable_escape_fix_in_code_blocks` 开关在代码中被定义但未被逻辑引用。
- **修复方案**:在 `_fix_escape_characters` 处理流程中加入对该开关的判断。
- **验证结果**:当开关关闭(默认)时,代码块内容保持不变;开启时,代码块内执行转义修复。
### 2.4 默认日志策略调整 (Privacy & Performance)
- **问题**`show_debug_log` 默认为 `True`,且会将原始内容打印到浏览器控制台。
- **修复方案**:将默认值改为 `False`。
- **验证结果**:新安装或默认配置下不再主动输出全量日志,仅在用户显式开启时用于调试。
## 3. 综合测试覆盖
已建立 `comprehensive_test_markdown_normalizer.py` 测试脚本,覆盖以下场景:
1. **异常抛出回滚**:确保插件“不破坏”原始内容。
2. **内联代码保护**:验证正则和路径字符串的完整性。
3. **代码块开关控制**:验证配置项的有效性。
4. **LaTeX 命令回归测试**:确保 `\times`, `\theta` 等命令不被误触。
5. **复杂嵌套结构**:验证包含 Thought 标签、列表、内联代码及代码块的混合文本处理。
## 4. 结论
`Markdown Normalizer v1.2.8` 已解决 Issue #57 提出的所有核心可靠性问题。插件现在具备“不损坏内容”的防御性编程能力,并能更智能地感知 Markdown 上下文。
---
**报告日期**2026-03-08
**修复版本**v1.2.8

12
ISSUE_57_REPLY.md Normal file
View File

@@ -0,0 +1,12 @@
# Reply to Issue #57
I have addressed these issues in **v1.2.8** with a focus on reliability and a "Safe-by-Default" approach:
1. **Robust Error Rollback (Items 1, 4, 5)**: I implemented a full `try...except` wrapper. If any error occurs during normalization, the plugin now returns the **100% original text**. This ensures that the output is never partially modified or corrupted.
2. **Conservative Escaping (Item 2)**: To avoid breaking technical content like regex or paths, the escape fixer now strictly skips all code blocks, inline code, and LaTeX formulas by default. I have shifted toward an "opt-in" model where aggressive cleaning is disabled unless specifically requested.
3. **Fixed Configuration (Item 3)**: The `enable_escape_fix_in_code_blocks` Valve was intended to handle escaping within code blocks (e.g., for fixing flat SQL output), but there was a bug preventing it from being applied. I have fixed this, and the setting is now fully functional.
4. **Privacy & Reliability**: I have changed the default for `show_debug_log` to `False`. While it was previously enabled by default to help gather feedback and squash bugs during the initial development phase, the plugin has now undergone multiple iterations and reliability enhancements (including the new tiered protection and rollback mechanisms), making it stable enough for a "silent" and private default operation.
**Recommendation**: If you encounter SQL or data blocks that appear on a single line, you can now manually enable `enable_escape_fix_in_code_blocks` in the Valves to fix them safely.
Please update to the latest version via [OpenWebUI Community](https://openwebui.com/functions/baaa8732-9348-40b7-8359-7e009660e23c). Thank you for your valuable feedback!

View File

@@ -23,12 +23,12 @@ A collection of enhancements, plugins, and prompts for [open-webui](https://gith
### 🔥 Top 6 Popular Plugins ### 🔥 Top 6 Popular Plugins
| Rank | Plugin | Version | Downloads | Views | 📅 Updated | | Rank | Plugin | Version | Downloads | Views | 📅 Updated |
| :---: | :--- | :---: | :---: | :---: | :---: | | :---: | :--- | :---: | :---: | :---: | :---: |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![p1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_dl.json&style=flat) | ![p1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--27-gray?style=flat) | | 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![p1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_dl.json&style=flat) | ![p1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) |
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![p2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_dl.json&style=flat) | ![p2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) | | 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![p2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_dl.json&style=flat) | ![p2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) |
| 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | ![v](https://img.shields.io/badge/v-1.2.8-blue?style=flat) | ![p3_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_dl.json&style=flat) | ![p3_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) | | 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | ![v](https://img.shields.io/badge/v-1.2.7-blue?style=flat) | ![p3_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_dl.json&style=flat) | ![p3_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) |
| 4⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![p4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_dl.json&style=flat) | ![p4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--14-gray?style=flat) | | 4⃣ | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![p4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_dl.json&style=flat) | ![p4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) |
| 5⃣ | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![p5_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_dl.json&style=flat) | ![p5_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) | | 5⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![v](https://img.shields.io/badge/v-1.4.0-blue?style=flat) | ![p5_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_dl.json&style=flat) | ![p5_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--09-gray?style=flat) |
| 6⃣ | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![p6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_dl.json&style=flat) | ![p6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--01--28-gray?style=flat) | | 6⃣ | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![p6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_dl.json&style=flat) | ![p6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) |
### 📈 Total Downloads Trend ### 📈 Total Downloads Trend
![Activity](https://gist.githubusercontent.com/Fu-Jie/db3d95687075a880af6f1fba76d679c6/raw/chart.svg) ![Activity](https://gist.githubusercontent.com/Fu-Jie/db3d95687075a880af6f1fba76d679c6/raw/chart.svg)
@@ -163,6 +163,13 @@ For code examples, please check the `docs/examples/` directory.
This project is a collection of resources and does not require a Python environment. Simply download the files you need and import them into your OpenWebUI instance. This project is a collection of resources and does not require a Python environment. Simply download the files you need and import them into your OpenWebUI instance.
### Using Prompts
1. Browse the `/prompts` directory and select a prompt file (`.md`).
2. Copy the file content.
3. In the OpenWebUI chat interface, click the "Prompt" button above the input box.
4. Paste the content and save.
### Using Plugins ### Using Plugins
1. **Install from OpenWebUI Community (Recommended)**: 1. **Install from OpenWebUI Community (Recommended)**:
@@ -170,14 +177,11 @@ This project is a collection of resources and does not require a Python environm
- Browse the plugins and select the one you like. - Browse the plugins and select the one you like.
- Click "Get" to import it directly into your OpenWebUI instance. - Click "Get" to import it directly into your OpenWebUI instance.
2. **Quick Install All Plugins**: To install all plugins to your local OpenWebUI instance at once, clone this repo and run `python scripts/install_all_plugins.py` after configuring your API key in `.env` — see [Deployment Guide](./scripts/DEPLOYMENT_GUIDE.md) for details. 2. **Manual Installation**:
- Browse the `/plugins` directory and download the plugin file (`.py`) you need.
### Using Prompts - Go to OpenWebUI **Admin Panel** -> **Settings** -> **Plugins**.
- Click the upload button and select the `.py` file you just downloaded.
1. Browse the `/prompts` directory and select a prompt file (`.md`). - Once uploaded, refresh the page to enable the plugin in your chat settings or toolbar.
2. Copy the file content.
3. In the OpenWebUI chat interface, click the "Prompt" button above the input box.
4. Paste the content and save.
### Contributing ### Contributing

View File

@@ -20,12 +20,12 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
### 🔥 热门插件 Top 6 ### 🔥 热门插件 Top 6
| 排名 | 插件 | 版本 | 下载 | 浏览 | 📅 更新 | | 排名 | 插件 | 版本 | 下载 | 浏览 | 📅 更新 |
| :---: | :--- | :---: | :---: | :---: | :---: | | :---: | :--- | :---: | :---: | :---: | :---: |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![p1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_dl.json&style=flat) | ![p1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--27-gray?style=flat) | | 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![p1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_dl.json&style=flat) | ![p1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) |
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![p2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_dl.json&style=flat) | ![p2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) | | 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![p2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_dl.json&style=flat) | ![p2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) |
| 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | ![v](https://img.shields.io/badge/v-1.2.8-blue?style=flat) | ![p3_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_dl.json&style=flat) | ![p3_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) | | 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | ![v](https://img.shields.io/badge/v-1.2.7-blue?style=flat) | ![p3_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_dl.json&style=flat) | ![p3_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) |
| 4⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![p4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_dl.json&style=flat) | ![p4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--14-gray?style=flat) | | 4⃣ | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![p4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_dl.json&style=flat) | ![p4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) |
| 5⃣ | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![p5_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_dl.json&style=flat) | ![p5_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) | | 5⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![v](https://img.shields.io/badge/v-1.4.0-blue?style=flat) | ![p5_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_dl.json&style=flat) | ![p5_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--09-gray?style=flat) |
| 6⃣ | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![p6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_dl.json&style=flat) | ![p6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--01--28-gray?style=flat) | | 6⃣ | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![p6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_dl.json&style=flat) | ![p6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--08-gray?style=flat) |
### 📈 总下载量累计趋势 ### 📈 总下载量累计趋势
![Activity](https://gist.githubusercontent.com/Fu-Jie/db3d95687075a880af6f1fba76d679c6/raw/chart.svg) ![Activity](https://gist.githubusercontent.com/Fu-Jie/db3d95687075a880af6f1fba76d679c6/raw/chart.svg)
@@ -166,20 +166,4 @@ Open WebUI 的前端增强扩展:
本项目是一个资源集合,无需安装 Python 环境。你只需要下载对应的文件并导入到你的 OpenWebUI 实例中即可。 本项目是一个资源集合,无需安装 Python 环境。你只需要下载对应的文件并导入到你的 OpenWebUI 实例中即可。
### 使用插件
1. **从官方社区安装(推荐)**
- 访问我的主页:[Fu-Jie 的个人页面](https://openwebui.com/u/Fu-Jie)
- 浏览插件并选择你喜欢的
- 点击"Get"按钮直接导入到你的 OpenWebUI 实例
2. **快速安装所有插件**:如果想一次性安装此项目中的所有插件到本地 OpenWebUI 实例,克隆此仓库后运行 `python scripts/install_all_plugins.py`,并在 `.env` 中配置好 API 密钥,详见 [部署指南](./scripts/DEPLOYMENT_GUIDE.md)。
### 使用提示词
1. 浏览 `/prompts` 目录并选择一个提示词文件(`.md`)。
2. 复制文件内容。
3. 在 OpenWebUI 聊天界面中,点击输入框上方的"提示词"按钮。
4. 粘贴内容并保存。
[贡献指南](./CONTRIBUTING_CN.md) | [更新日志](./CHANGELOG.md) [贡献指南](./CONTRIBUTING_CN.md) | [更新日志](./CHANGELOG.md)

99
TEST_CASES_V1.2.8.md Normal file
View File

@@ -0,0 +1,99 @@
# Markdown Normalizer v1.2.8 测试用例集
您可以将以下内容逐个复制到 OpenWebUI 的聊天框中,以验证插件的各项修复功能。
---
## 用例 1验证 SQL 代码块换行修复 (需要手动开启配置)
**测试目的**:验证 `enable_escape_fix_in_code_blocks` 开关是否生效。
**前提条件**:请先在插件 Valves 设置中将 `enable_escape_fix_in_code_blocks` 设置为 **开启 (True)**
**复制以下内容:**
```text
请帮我美化这段 SQL 的排版,使其恢复正常换行:
```sql
SELECT * \n FROM users \n WHERE status = 'active' \n AND created_at > '2024-01-01' \n ORDER BY id DESC;
```
```
**预期效果**SQL 代码块内的 `\n` 消失,变为整齐的多行 SQL 语句。
---
## 用例 2验证上下文感知保护 (防止误伤技术内容)
**测试目的**:验证插件是否能准确识别“纯文本”和“代码区域”,只修复该修复的地方。
**配置要求**:默认配置即可。
**复制以下内容:**
```text
这是一个综合测试用例。
1. 普通文本修复测试:
这是第一行\\n这是第二行你应该看到这里发生了换行
2. 行内代码保护测试(不应被修改):
- 正则表达式:`[\n\r\t]`
- Windows 路径:`C:\Windows\System32\drivers\etc\hosts`
- 转义测试:`\\n` 应该保持字面量。
3. LaTeX 命令保护测试:
这里的数学公式 $\times \theta \nu \sum$ 应该渲染正常,反斜杠不应被修掉。
4. 现代 LaTeX 定界符转换:
\[ E = mc^2 \]
(上面这行应该被自动转换为 $$ 包围的块级公式)
```
**预期效果**
- 第一部分的 `\\n` 成功换行。
- 第二部分反引号 `` ` `` 里的内容原封不动。
- 第三部分的希腊字母公式渲染正常。
- 第四部分的 `\[` 变成了 `$$` 且能正常显示公式。
---
## 用例 3验证思维链与详情标签规范化
**测试目的**:验证对 `<thought>``<details>` 标签的排版优化。
**复制以下内容:**
```text
<thinking>
这是一个正在思考的思维链。
</thinking>
<details>
<summary>点击查看详情</summary>
这里的排版通常容易出错。
</details>
紧接着详情标签的文字(应该和上面有空行隔开)。
```
**预期效果**
- `<thinking>` 标签被统一为 `<thought>`
- `</details>` 标签下方自动注入了空行,防止与正文粘连导致渲染失效。
---
## 用例 4极端压力与回滚测试 (稳定性验证)
**测试目的**:模拟复杂嵌套环境,验证 100% 回滚机制。
**复制以下内容:**
```text
尝试混合所有复杂元素:
- 列表项 1
- 列表项 2 with `inline \\n code`
- $ \text{Math } \alpha $
```sql
-- SQL with nested issue
SELECT 'literal \n string' FROM `table`;
```
<thought>End of test</thought>
```
**预期效果**
- 无论内部处理逻辑多么复杂,插件都应保证输出稳定的结果。
- 如果模拟任何内部崩溃(技术人员可用),消息会回滚至此原始文本,不会导致页面白屏。

View File

@@ -1,139 +0,0 @@
#!/bin/bash
# ==============================================================================
# ai-tabs - Ultra Orchestrator
# Version: v1.0.0
# License: MIT
# Author: Fu-Jie
# Description: Batch-launches and orchestrates multiple AI CLI tools as Tabs.
# ==============================================================================
# 1. Single-Instance Lock
LOCK_FILE="/tmp/ai_terminal_launch.lock"
# If lock is less than 10 seconds old, another instance is running. Exit.
if [ -f "$LOCK_FILE" ]; then
LOCK_TIME=$(stat -f %m "$LOCK_FILE")
NOW=$(date +%s)
if (( NOW - LOCK_TIME < 10 )); then
echo "⚠️ Another launch in progress. Skipping to prevent duplicates."
exit 0
fi
fi
touch "$LOCK_FILE"
trap 'rm -f "$LOCK_FILE"' EXIT
# 2. Configuration & Constants
INIT_DELAY=4.5
PASTE_DELAY=0.3
CMD_CREATION_DELAY=0.3
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
# Search for .env
if [ -f "${SCRIPT_DIR}/.env" ]; then
ENV_FILE="${SCRIPT_DIR}/.env"
elif [ -f "${PARENT_DIR}/.env" ]; then
ENV_FILE="${PARENT_DIR}/.env"
fi
# Supported Tools
SUPPORTED_TOOLS=(
"claude:--continue"
"opencode:--continue"
"gemini:--resume latest"
"copilot:--continue"
"iflow:--continue"
"kilo:--continue"
)
FOUND_TOOLS_NAMES=()
FOUND_CMDS=()
# 3. Part A: Load Manual Configuration
if [ -f "$ENV_FILE" ]; then
set -a; source "$ENV_FILE"; set +a
for var in $(compgen -v | grep '^TOOL_[0-9]' | sort -V); do
TPATH="${!var}"
if [ -x "$TPATH" ]; then
NAME=$(basename "$TPATH")
FLAG="--continue"
for item in "${SUPPORTED_TOOLS[@]}"; do
[[ "${item%%:*}" == "$NAME" ]] && FLAG="${item#*:}" && break
done
FOUND_TOOLS_NAMES+=("$NAME")
FOUND_CMDS+=("'$TPATH' $FLAG || '$TPATH' || exec \$SHELL")
fi
done
fi
# 4. Part B: Automatic Tool Discovery
for item in "${SUPPORTED_TOOLS[@]}"; do
NAME="${item%%:*}"
FLAG="${item#*:}"
ALREADY_CONFIGURED=false
for configured in "${FOUND_TOOLS_NAMES[@]}"; do
[[ "$configured" == "$NAME" ]] && ALREADY_CONFIGURED=true && break
done
[[ "$ALREADY_CONFIGURED" == true ]] && continue
TPATH=$(which "$NAME" 2>/dev/null)
if [ -z "$TPATH" ]; then
SEARCH_PATHS=(
"/opt/homebrew/bin/$NAME"
"/usr/local/bin/$NAME"
"$HOME/.local/bin/$NAME"
"$HOME/bin/$NAME"
"$HOME/.$NAME/bin/$NAME"
"$HOME/.nvm/versions/node/*/bin/$NAME"
"$HOME/.npm-global/bin/$NAME"
"$HOME/.cargo/bin/$NAME"
)
for p in "${SEARCH_PATHS[@]}"; do
for found_p in $p; do [[ -x "$found_p" ]] && TPATH="$found_p" && break 2; done
done
fi
if [ -n "$TPATH" ]; then
FOUND_TOOLS_NAMES+=("$NAME")
FOUND_CMDS+=("'$TPATH' $FLAG || '$TPATH' || exec \$SHELL")
fi
done
NUM_FOUND=${#FOUND_CMDS[@]}
[[ "$NUM_FOUND" -eq 0 ]] && exit 1
# 5. Core Orchestration (Reset + Launch)
# Using Command Palette automation to avoid the need for manual shortcut binding.
AS_SCRIPT="tell application \"System Events\"\n"
# Phase A: Creation (Using Command Palette to ensure it opens in Editor Area)
for ((i=1; i<=NUM_FOUND; i++)); do
AS_SCRIPT+=" keystroke \"p\" using {command down, shift down}\n"
AS_SCRIPT+=" delay 0.1\n"
# Ensure we are searching for the command. Using clipboard for speed and universal language support.
AS_SCRIPT+=" set the clipboard to \"Terminal: Create New Terminal in Editor Area\"\n"
AS_SCRIPT+=" keystroke \"v\" using {command down}\n"
AS_SCRIPT+=" delay 0.1\n"
AS_SCRIPT+=" keystroke return\n"
AS_SCRIPT+=" delay $CMD_CREATION_DELAY\n"
done
# Phase B: Warmup
AS_SCRIPT+=" delay $INIT_DELAY\n"
# Phase C: Command Injection (Reverse)
for ((i=NUM_FOUND-1; i>=0; i--)); do
FULL_CMD="${FOUND_CMDS[$i]}"
CLEAN_CMD=$(echo "$FULL_CMD" | sed 's/"/\\"/g')
AS_SCRIPT+=" set the clipboard to \"$CLEAN_CMD\"\n"
AS_SCRIPT+=" delay 0.1\n"
AS_SCRIPT+=" keystroke \"v\" using {command down}\n"
AS_SCRIPT+=" delay $PASTE_DELAY\n"
AS_SCRIPT+=" keystroke return\n"
if [ $i -gt 0 ]; then
AS_SCRIPT+=" delay 0.5\n"
AS_SCRIPT+=" keystroke \"[\" using {command down, shift down}\n"
fi
done
AS_SCRIPT+="end tell"
# Execute
echo -e "$AS_SCRIPT" | osascript
echo "✨ Ai tabs initialized successfully ($NUM_FOUND tools found)."

View File

@@ -1,7 +1,7 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"label": "downloads", "label": "downloads",
"message": "9.1k", "message": "7.8k",
"color": "blue", "color": "blue",
"namedLogo": "openwebui" "namedLogo": "openwebui"
} }

View File

@@ -1,6 +1,6 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"label": "followers", "label": "followers",
"message": "353", "message": "315",
"color": "blue" "color": "blue"
} }

View File

@@ -1,6 +1,6 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"label": "points", "label": "points",
"message": "359", "message": "329",
"color": "orange" "color": "orange"
} }

View File

@@ -1,6 +1,6 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"label": "upvotes", "label": "upvotes",
"message": "305", "message": "281",
"color": "brightgreen" "color": "brightgreen"
} }

View File

@@ -1,17 +1,19 @@
{ {
"total_posts": 27, "total_posts": 27,
"total_downloads": 9120, "total_downloads": 7786,
"total_views": 95785, "total_views": 82342,
"total_upvotes": 305, "total_upvotes": 281,
"total_downvotes": 4, "total_downvotes": 4,
"total_saves": 452, "total_saves": 398,
"total_comments": 77, "total_comments": 63,
"by_type": { "by_type": {
"filter": 4, "post": 6,
"tool": 2, "tool": 2,
"pipe": 1, "pipe": 1,
"filter": 4,
"action": 12, "action": 12,
"prompt": 1 "prompt": 1,
"review": 1
}, },
"posts": [ "posts": [
{ {
@@ -21,11 +23,11 @@
"version": "1.0.0", "version": "1.0.0",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.", "description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
"downloads": 1797, "downloads": 1542,
"views": 15350, "views": 12996,
"upvotes": 31, "upvotes": 28,
"saves": 72, "saves": 66,
"comments": 23, "comments": 18,
"created_at": "2025-12-30", "created_at": "2025-12-30",
"updated_at": "2026-02-27", "updated_at": "2026-02-27",
"url": "https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a" "url": "https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a"
@@ -37,11 +39,11 @@
"version": "1.5.0", "version": "1.5.0",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.", "description": "AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.",
"downloads": 1362, "downloads": 1230,
"views": 13589, "views": 12309,
"upvotes": 28, "upvotes": 25,
"saves": 53, "saves": 46,
"comments": 12, "comments": 10,
"created_at": "2025-12-28", "created_at": "2025-12-28",
"updated_at": "2026-02-13", "updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/smart_infographic_ad6f0c7f" "url": "https://openwebui.com/posts/smart_infographic_ad6f0c7f"
@@ -50,34 +52,18 @@
"title": "Markdown Normalizer", "title": "Markdown Normalizer",
"slug": "markdown_normalizer_baaa8732", "slug": "markdown_normalizer_baaa8732",
"type": "filter", "type": "filter",
"version": "1.2.8", "version": "1.2.7",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting. Including LaTeX command protection.", "description": "A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting. Including LaTeX command protection.",
"downloads": 838, "downloads": 719,
"views": 8739, "views": 7704,
"upvotes": 21, "upvotes": 20,
"saves": 46, "saves": 42,
"comments": 5, "comments": 5,
"created_at": "2026-01-12", "created_at": "2026-01-12",
"updated_at": "2026-03-08", "updated_at": "2026-03-03",
"url": "https://openwebui.com/posts/markdown_normalizer_baaa8732" "url": "https://openwebui.com/posts/markdown_normalizer_baaa8732"
}, },
{
"title": "Async Context Compression",
"slug": "async_context_compression_b1655bc8",
"type": "filter",
"version": "1.5.0",
"author": "Fu-Jie",
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
"downloads": 801,
"views": 7258,
"upvotes": 18,
"saves": 54,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-03-14",
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
},
{ {
"title": "Export to Word Enhanced", "title": "Export to Word Enhanced",
"slug": "export_to_word_enhanced_formatting_fca6a315", "slug": "export_to_word_enhanced_formatting_fca6a315",
@@ -85,15 +71,31 @@
"version": "0.4.4", "version": "0.4.4",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "Export current conversation from Markdown to Word (.docx) with Mermaid diagrams rendered client-side (Mermaid.js, SVG+PNG), LaTeX math, real hyperlinks, improved tables, syntax highlighting, and blockquote support.", "description": "Export current conversation from Markdown to Word (.docx) with Mermaid diagrams rendered client-side (Mermaid.js, SVG+PNG), LaTeX math, real hyperlinks, improved tables, syntax highlighting, and blockquote support.",
"downloads": 799, "downloads": 700,
"views": 6146, "views": 5399,
"upvotes": 20, "upvotes": 17,
"saves": 41, "saves": 37,
"comments": 5, "comments": 5,
"created_at": "2026-01-03", "created_at": "2026-01-03",
"updated_at": "2026-02-13", "updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315" "url": "https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315"
}, },
{
"title": "Async Context Compression",
"slug": "async_context_compression_b1655bc8",
"type": "filter",
"version": "1.3.0",
"author": "Fu-Jie",
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
"downloads": 669,
"views": 6274,
"upvotes": 16,
"saves": 47,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-03-03",
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
},
{ {
"title": "AI Task Instruction Generator", "title": "AI Task Instruction Generator",
"slug": "ai_task_instruction_generator_9bab8b37", "slug": "ai_task_instruction_generator_9bab8b37",
@@ -101,10 +103,10 @@
"version": "", "version": "",
"author": "", "author": "",
"description": "", "description": "",
"downloads": 692, "downloads": 583,
"views": 7783, "views": 6659,
"upvotes": 10, "upvotes": 9,
"saves": 20, "saves": 17,
"comments": 0, "comments": 0,
"created_at": "2026-01-28", "created_at": "2026-01-28",
"updated_at": "2026-01-28", "updated_at": "2026-01-28",
@@ -117,45 +119,29 @@
"version": "0.3.7", "version": "0.3.7",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.", "description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
"downloads": 616, "downloads": 563,
"views": 3508, "views": 3153,
"upvotes": 11, "upvotes": 11,
"saves": 12, "saves": 11,
"comments": 0, "comments": 0,
"created_at": "2025-05-30", "created_at": "2025-05-30",
"updated_at": "2026-02-13", "updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d" "url": "https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d"
}, },
{
"title": "OpenWebUI Skills Manager Tool",
"slug": "openwebui_skills_manager_tool_b4bce8e4",
"type": "tool",
"version": "0.3.0",
"author": "Fu-Jie",
"description": "Standalone OpenWebUI tool for managing native Workspace Skills (list/show/install/create/update/delete) for any model.",
"downloads": 500,
"views": 6112,
"upvotes": 8,
"saves": 23,
"comments": 4,
"created_at": "2026-02-28",
"updated_at": "2026-03-14",
"url": "https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4"
},
{ {
"title": "GitHub Copilot Official SDK Pipe", "title": "GitHub Copilot Official SDK Pipe",
"slug": "github_copilot_official_sdk_pipe_ce96f7b4", "slug": "github_copilot_official_sdk_pipe_ce96f7b4",
"type": "pipe", "type": "pipe",
"version": "0.10.0", "version": "0.9.1",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "A powerful Agent SDK integration for OpenWebUI. It deeply bridges GitHub Copilot SDK with OpenWebUI's ecosystem, enabling the Agent to autonomously perform intent recognition, web search, and context compaction. It seamlessly reuses your existing Tools, MCP servers, OpenAPI servers, and Skills for a professional, full-featured experience.", "description": "A powerful Agent SDK integration for OpenWebUI. It deeply bridges GitHub Copilot SDK with OpenWebUI's ecosystem, enabling the Agent to autonomously perform intent recognition, web search, and context compaction. It seamlessly reuses your existing Tools, MCP servers, OpenAPI servers, and Skills for a professional, full-featured experience.",
"downloads": 403, "downloads": 335,
"views": 5699, "views": 4905,
"upvotes": 16, "upvotes": 16,
"saves": 12, "saves": 10,
"comments": 8, "comments": 6,
"created_at": "2026-01-26", "created_at": "2026-01-26",
"updated_at": "2026-03-07", "updated_at": "2026-03-03",
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4" "url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
}, },
{ {
@@ -165,15 +151,31 @@
"version": "0.2.4", "version": "0.2.4",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.", "description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
"downloads": 331, "downloads": 312,
"views": 4722, "views": 4448,
"upvotes": 13, "upvotes": 13,
"saves": 22, "saves": 20,
"comments": 2, "comments": 2,
"created_at": "2025-12-30", "created_at": "2025-12-30",
"updated_at": "2026-02-13", "updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/flash_card_65a2ea8f" "url": "https://openwebui.com/posts/flash_card_65a2ea8f"
}, },
{
"title": "OpenWebUI Skills Manager Tool",
"slug": "openwebui_skills_manager_tool_b4bce8e4",
"type": "tool",
"version": "",
"author": "",
"description": "",
"downloads": 303,
"views": 4265,
"upvotes": 7,
"saves": 13,
"comments": 2,
"created_at": "2026-02-28",
"updated_at": "2026-03-05",
"url": "https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4"
},
{ {
"title": "Deep Dive", "title": "Deep Dive",
"slug": "deep_dive_c0b846e4", "slug": "deep_dive_c0b846e4",
@@ -181,8 +183,8 @@
"version": "1.0.0", "version": "1.0.0",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.", "description": "A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.",
"downloads": 229, "downloads": 219,
"views": 1887, "views": 1764,
"upvotes": 6, "upvotes": 6,
"saves": 15, "saves": 15,
"comments": 0, "comments": 0,
@@ -197,8 +199,8 @@
"version": "0.4.4", "version": "0.4.4",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。", "description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
"downloads": 172, "downloads": 165,
"views": 3038, "views": 2831,
"upvotes": 14, "upvotes": 14,
"saves": 7, "saves": 7,
"comments": 4, "comments": 4,
@@ -213,31 +215,15 @@
"version": "0.1.0", "version": "0.1.0",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "Automatically extracts project rules from conversations and injects them into the folder's system prompt.", "description": "Automatically extracts project rules from conversations and injects them into the folder's system prompt.",
"downloads": 130, "downloads": 112,
"views": 2181, "views": 1992,
"upvotes": 7, "upvotes": 7,
"saves": 13, "saves": 11,
"comments": 0, "comments": 0,
"created_at": "2026-01-20", "created_at": "2026-01-20",
"updated_at": "2026-01-20", "updated_at": "2026-01-20",
"url": "https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2" "url": "https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2"
}, },
{
"title": "🧠 Smart Mind Map Tool: Auto-Generate Interactive Knowledge Graphs",
"slug": "smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d",
"type": "tool",
"version": "1.0.0",
"author": "Fu-Jie",
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
"downloads": 116,
"views": 2375,
"upvotes": 5,
"saves": 4,
"comments": 0,
"created_at": "2026-03-04",
"updated_at": "2026-03-05",
"url": "https://openwebui.com/posts/smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d"
},
{ {
"title": "GitHub Copilot SDK Files Filter", "title": "GitHub Copilot SDK Files Filter",
"slug": "github_copilot_sdk_files_filter_403a62ee", "slug": "github_copilot_sdk_files_filter_403a62ee",
@@ -245,8 +231,8 @@
"version": "0.1.3", "version": "0.1.3",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "A specialized filter to bypass OpenWebUI's default RAG for GitHub Copilot SDK models. It moves uploaded files to a safe location ('copilot_files') so the Copilot Pipe can process them natively without interference.", "description": "A specialized filter to bypass OpenWebUI's default RAG for GitHub Copilot SDK models. It moves uploaded files to a safe location ('copilot_files') so the Copilot Pipe can process them natively without interference.",
"downloads": 93, "downloads": 76,
"views": 2474, "views": 2311,
"upvotes": 4, "upvotes": 4,
"saves": 1, "saves": 1,
"comments": 0, "comments": 0,
@@ -261,8 +247,8 @@
"version": "1.5.0", "version": "1.5.0",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。", "description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
"downloads": 72, "downloads": 68,
"views": 1572, "views": 1431,
"upvotes": 10, "upvotes": 10,
"saves": 1, "saves": 1,
"comments": 0, "comments": 0,
@@ -277,8 +263,8 @@
"version": "0.9.2", "version": "0.9.2",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。", "description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
"downloads": 56, "downloads": 52,
"views": 814, "views": 761,
"upvotes": 6, "upvotes": 6,
"saves": 2, "saves": 2,
"comments": 0, "comments": 0,
@@ -293,8 +279,8 @@
"version": "1.2.2", "version": "1.2.2",
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。", "description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
"downloads": 42, "downloads": 39,
"views": 904, "views": 838,
"upvotes": 7, "upvotes": 7,
"saves": 5, "saves": 5,
"comments": 0, "comments": 0,
@@ -303,20 +289,20 @@
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb" "url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
}, },
{ {
"title": "精读", "title": "🧠 Smart Mind Map Tool: Auto-Generate Interactive Knowledge Graphs",
"slug": "精读_99830b0f", "slug": "smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d",
"type": "action", "type": "tool",
"version": "1.0.0", "version": "",
"author": "Fu-Jie", "author": "",
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。", "description": "",
"downloads": 37, "downloads": 34,
"views": 708, "views": 767,
"upvotes": 5, "upvotes": 2,
"saves": 1, "saves": 3,
"comments": 0, "comments": 0,
"created_at": "2026-01-08", "created_at": "2026-03-04",
"updated_at": "2026-01-08", "updated_at": "2026-03-05",
"url": "https://openwebui.com/posts/精读_99830b0f" "url": "https://openwebui.com/posts/smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d"
}, },
{ {
"title": "闪记卡 (Flash Card)", "title": "闪记卡 (Flash Card)",
@@ -326,7 +312,7 @@
"author": "Fu-Jie", "author": "Fu-Jie",
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。", "description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
"downloads": 34, "downloads": 34,
"views": 926, "views": 888,
"upvotes": 7, "upvotes": 7,
"saves": 1, "saves": 1,
"comments": 0, "comments": 0,
@@ -334,31 +320,47 @@
"updated_at": "2026-02-13", "updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/闪记卡生成插件_4a31eac3" "url": "https://openwebui.com/posts/闪记卡生成插件_4a31eac3"
}, },
{
"title": "精读",
"slug": "精读_99830b0f",
"type": "action",
"version": "1.0.0",
"author": "Fu-Jie",
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
"downloads": 31,
"views": 647,
"upvotes": 5,
"saves": 1,
"comments": 0,
"created_at": "2026-01-08",
"updated_at": "2026-01-08",
"url": "https://openwebui.com/posts/精读_99830b0f"
},
{ {
"title": "An Unconventional Use of Open Terminal ⚡", "title": "An Unconventional Use of Open Terminal ⚡",
"slug": "an_unconventional_use_of_open_terminal_35498f8f", "slug": "an_unconventional_use_of_open_terminal_35498f8f",
"type": "action", "type": "post",
"version": "", "version": "",
"author": "", "author": "",
"description": "", "description": "",
"downloads": 0, "downloads": 0,
"views": 3335, "views": 14,
"upvotes": 7, "upvotes": 1,
"saves": 1, "saves": 0,
"comments": 2, "comments": 0,
"created_at": "2026-03-06", "created_at": "2026-03-06",
"updated_at": "2026-03-07", "updated_at": "2026-03-06",
"url": "https://openwebui.com/posts/an_unconventional_use_of_open_terminal_35498f8f" "url": "https://openwebui.com/posts/an_unconventional_use_of_open_terminal_35498f8f"
}, },
{ {
"title": "🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI", "title": "🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI",
"slug": "github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452", "slug": "github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452",
"type": "pipe", "type": "post",
"version": "", "version": "",
"author": "", "author": "",
"description": "", "description": "",
"downloads": 0, "downloads": 0,
"views": 1803, "views": 1585,
"upvotes": 5, "upvotes": 5,
"saves": 1, "saves": 1,
"comments": 0, "comments": 0,
@@ -369,12 +371,12 @@
{ {
"title": "🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️", "title": "🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️",
"slug": "github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131", "slug": "github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131",
"type": "pipe", "type": "post",
"version": "", "version": "",
"author": "", "author": "",
"description": "", "description": "",
"downloads": 0, "downloads": 0,
"views": 2797, "views": 2608,
"upvotes": 8, "upvotes": 8,
"saves": 4, "saves": 4,
"comments": 1, "comments": 1,
@@ -385,14 +387,14 @@
{ {
"title": "🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks", "title": "🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks",
"slug": "github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293", "slug": "github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293",
"type": "pipe", "type": "post",
"version": "", "version": "",
"author": "", "author": "",
"description": "", "description": "",
"downloads": 0, "downloads": 0,
"views": 2442, "views": 2390,
"upvotes": 7, "upvotes": 7,
"saves": 5, "saves": 4,
"comments": 0, "comments": 0,
"created_at": "2026-02-10", "created_at": "2026-02-10",
"updated_at": "2026-02-10", "updated_at": "2026-02-10",
@@ -401,15 +403,15 @@
{ {
"title": "🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager", "title": "🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager",
"slug": "open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e", "slug": "open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e",
"type": "action", "type": "post",
"version": "", "version": "",
"author": "", "author": "",
"description": "", "description": "",
"downloads": 0, "downloads": 0,
"views": 2014, "views": 1915,
"upvotes": 13, "upvotes": 12,
"saves": 23, "saves": 21,
"comments": 9, "comments": 8,
"created_at": "2026-01-25", "created_at": "2026-01-25",
"updated_at": "2026-01-28", "updated_at": "2026-01-28",
"url": "https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e" "url": "https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e"
@@ -422,7 +424,7 @@
"author": "", "author": "",
"description": "", "description": "",
"downloads": 0, "downloads": 0,
"views": 271, "views": 251,
"upvotes": 2, "upvotes": 2,
"saves": 0, "saves": 0,
"comments": 0, "comments": 0,
@@ -433,14 +435,14 @@
{ {
"title": " 🛠️ Debug Open WebUI Plugins in Your Browser", "title": " 🛠️ Debug Open WebUI Plugins in Your Browser",
"slug": "debug_open_webui_plugins_in_your_browser_81bf7960", "slug": "debug_open_webui_plugins_in_your_browser_81bf7960",
"type": "action", "type": "post",
"version": "", "version": "",
"author": "", "author": "",
"description": "", "description": "",
"downloads": 0, "downloads": 0,
"views": 1588, "views": 1549,
"upvotes": 16, "upvotes": 16,
"saves": 13, "saves": 12,
"comments": 2, "comments": 2,
"created_at": "2026-01-10", "created_at": "2026-01-10",
"updated_at": "2026-01-10", "updated_at": "2026-01-10",
@@ -452,11 +454,11 @@
"name": "Fu-Jie", "name": "Fu-Jie",
"profile_url": "https://openwebui.com/u/Fu-Jie", "profile_url": "https://openwebui.com/u/Fu-Jie",
"profile_image": "https://community.s3.openwebui.com/uploads/users/b15d1348-4347-42b4-b815-e053342d6cb0/profile_d9510745-4bd4-4f8f-a997-4a21847d9300.webp", "profile_image": "https://community.s3.openwebui.com/uploads/users/b15d1348-4347-42b4-b815-e053342d6cb0/profile_d9510745-4bd4-4f8f-a997-4a21847d9300.webp",
"followers": 353, "followers": 315,
"following": 6, "following": 6,
"total_points": 359, "total_points": 329,
"post_points": 303, "post_points": 279,
"comment_points": 56, "comment_points": 50,
"contributions": 68 "contributions": 59
} }
} }

View File

@@ -1,462 +0,0 @@
{
"total_posts": 27,
"total_downloads": 8947,
"total_views": 94188,
"total_upvotes": 301,
"total_downvotes": 4,
"total_saves": 444,
"total_comments": 75,
"by_type": {
"tool": 2,
"filter": 4,
"pipe": 1,
"action": 12,
"prompt": 1
},
"posts": [
{
"title": "Smart Mind Map",
"slug": "turn_any_text_into_beautiful_mind_maps_3094c59a",
"type": "action",
"version": "1.0.0",
"author": "Fu-Jie",
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
"downloads": 1772,
"views": 15047,
"upvotes": 30,
"saves": 70,
"comments": 21,
"created_at": "2025-12-30",
"updated_at": "2026-02-27",
"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": 1350,
"views": 13453,
"upvotes": 27,
"saves": 52,
"comments": 12,
"created_at": "2025-12-28",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/smart_infographic_ad6f0c7f"
},
{
"title": "Markdown Normalizer",
"slug": "markdown_normalizer_baaa8732",
"type": "filter",
"version": "1.2.8",
"author": "Fu-Jie",
"description": "A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting. Including LaTeX command protection.",
"downloads": 824,
"views": 8622,
"upvotes": 21,
"saves": 45,
"comments": 5,
"created_at": "2026-01-12",
"updated_at": "2026-03-08",
"url": "https://openwebui.com/posts/markdown_normalizer_baaa8732"
},
{
"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": 780,
"views": 6015,
"upvotes": 19,
"saves": 39,
"comments": 5,
"created_at": "2026-01-03",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315"
},
{
"title": "Async Context Compression",
"slug": "async_context_compression_b1655bc8",
"type": "filter",
"version": "1.4.2",
"author": "Fu-Jie",
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
"downloads": 776,
"views": 7102,
"upvotes": 17,
"saves": 53,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-03-13",
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
},
{
"title": "AI Task Instruction Generator",
"slug": "ai_task_instruction_generator_9bab8b37",
"type": "prompt",
"version": "",
"author": "",
"description": "",
"downloads": 676,
"views": 7619,
"upvotes": 10,
"saves": 19,
"comments": 0,
"created_at": "2026-01-28",
"updated_at": "2026-01-28",
"url": "https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37"
},
{
"title": "Export to Excel",
"slug": "export_mulit_table_to_excel_244b8f9d",
"type": "action",
"version": "0.3.7",
"author": "Fu-Jie",
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
"downloads": 612,
"views": 3475,
"upvotes": 11,
"saves": 12,
"comments": 0,
"created_at": "2025-05-30",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d"
},
{
"title": "OpenWebUI Skills Manager Tool",
"slug": "openwebui_skills_manager_tool_b4bce8e4",
"type": "tool",
"version": "0.3.0",
"author": "Fu-Jie",
"description": "Standalone OpenWebUI tool for managing native Workspace Skills (list/show/install/create/update/delete) for any model.",
"downloads": 463,
"views": 5862,
"upvotes": 8,
"saves": 23,
"comments": 4,
"created_at": "2026-02-28",
"updated_at": "2026-03-13",
"url": "https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4"
},
{
"title": "GitHub Copilot Official SDK Pipe",
"slug": "github_copilot_official_sdk_pipe_ce96f7b4",
"type": "pipe",
"version": "0.10.0",
"author": "Fu-Jie",
"description": "A powerful Agent SDK integration for OpenWebUI. It deeply bridges GitHub Copilot SDK with OpenWebUI's ecosystem, enabling the Agent to autonomously perform intent recognition, web search, and context compaction. It seamlessly reuses your existing Tools, MCP servers, OpenAPI servers, and Skills for a professional, full-featured experience.",
"downloads": 402,
"views": 5629,
"upvotes": 16,
"saves": 12,
"comments": 8,
"created_at": "2026-01-26",
"updated_at": "2026-03-07",
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
},
{
"title": "Flash Card",
"slug": "flash_card_65a2ea8f",
"type": "action",
"version": "0.2.4",
"author": "Fu-Jie",
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
"downloads": 327,
"views": 4685,
"upvotes": 13,
"saves": 22,
"comments": 2,
"created_at": "2025-12-30",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/flash_card_65a2ea8f"
},
{
"title": "Deep Dive",
"slug": "deep_dive_c0b846e4",
"type": "action",
"version": "1.0.0",
"author": "Fu-Jie",
"description": "A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.",
"downloads": 228,
"views": 1874,
"upvotes": 6,
"saves": 15,
"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": 172,
"views": 3019,
"upvotes": 14,
"saves": 7,
"comments": 4,
"created_at": "2026-01-04",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0"
},
{
"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": 128,
"views": 2154,
"upvotes": 7,
"saves": 13,
"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": "🧠 Smart Mind Map Tool: Auto-Generate Interactive Knowledge Graphs",
"slug": "smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d",
"type": "tool",
"version": "1.0.0",
"author": "Fu-Jie",
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
"downloads": 106,
"views": 2284,
"upvotes": 5,
"saves": 4,
"comments": 0,
"created_at": "2026-03-04",
"updated_at": "2026-03-05",
"url": "https://openwebui.com/posts/smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d"
},
{
"title": "GitHub Copilot SDK Files Filter",
"slug": "github_copilot_sdk_files_filter_403a62ee",
"type": "filter",
"version": "0.1.3",
"author": "Fu-Jie",
"description": "A specialized filter to bypass OpenWebUI's default RAG for GitHub Copilot SDK models. It moves uploaded files to a safe location ('copilot_files') so the Copilot Pipe can process them natively without interference.",
"downloads": 93,
"views": 2462,
"upvotes": 4,
"saves": 1,
"comments": 0,
"created_at": "2026-02-09",
"updated_at": "2026-03-03",
"url": "https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee"
},
{
"title": "智能信息图",
"slug": "智能信息图_e04a48ff",
"type": "action",
"version": "1.5.0",
"author": "Fu-Jie",
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
"downloads": 72,
"views": 1566,
"upvotes": 10,
"saves": 1,
"comments": 0,
"created_at": "2025-12-28",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
},
{
"title": "思维导图",
"slug": "智能生成交互式思维导图帮助用户可视化知识_8d4b097b",
"type": "action",
"version": "0.9.2",
"author": "Fu-Jie",
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
"downloads": 56,
"views": 807,
"upvotes": 6,
"saves": 2,
"comments": 0,
"created_at": "2025-12-31",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b"
},
{
"title": "异步上下文压缩",
"slug": "异步上下文压缩_5c0617cb",
"type": "action",
"version": "1.2.2",
"author": "Fu-Jie",
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
"downloads": 42,
"views": 892,
"upvotes": 7,
"saves": 5,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/异步上下文压缩_5c0617cb"
},
{
"title": "闪记卡 (Flash Card)",
"slug": "闪记卡生成插件_4a31eac3",
"type": "action",
"version": "0.2.4",
"author": "Fu-Jie",
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
"downloads": 34,
"views": 922,
"upvotes": 7,
"saves": 1,
"comments": 0,
"created_at": "2025-12-30",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/闪记卡生成插件_4a31eac3"
},
{
"title": "精读",
"slug": "精读_99830b0f",
"type": "action",
"version": "1.0.0",
"author": "Fu-Jie",
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
"downloads": 34,
"views": 699,
"upvotes": 5,
"saves": 1,
"comments": 0,
"created_at": "2026-01-08",
"updated_at": "2026-01-08",
"url": "https://openwebui.com/posts/精读_99830b0f"
},
{
"title": "An Unconventional Use of Open Terminal ⚡",
"slug": "an_unconventional_use_of_open_terminal_35498f8f",
"type": "action",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 3205,
"upvotes": 7,
"saves": 1,
"comments": 2,
"created_at": "2026-03-06",
"updated_at": "2026-03-07",
"url": "https://openwebui.com/posts/an_unconventional_use_of_open_terminal_35498f8f"
},
{
"title": "🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI",
"slug": "github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452",
"type": "pipe",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 1781,
"upvotes": 5,
"saves": 1,
"comments": 0,
"created_at": "2026-02-27",
"updated_at": "2026-02-28",
"url": "https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452"
},
{
"title": "🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️",
"slug": "github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131",
"type": "pipe",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 2775,
"upvotes": 8,
"saves": 4,
"comments": 1,
"created_at": "2026-02-22",
"updated_at": "2026-02-28",
"url": "https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131"
},
{
"title": "🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks",
"slug": "github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293",
"type": "pipe",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 2441,
"upvotes": 7,
"saves": 5,
"comments": 0,
"created_at": "2026-02-10",
"updated_at": "2026-02-10",
"url": "https://openwebui.com/posts/github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293"
},
{
"title": "🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager",
"slug": "open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e",
"type": "action",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 1999,
"upvotes": 13,
"saves": 23,
"comments": 9,
"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": "review",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 267,
"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",
"type": "action",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 1583,
"upvotes": 16,
"saves": 13,
"comments": 2,
"created_at": "2026-01-10",
"updated_at": "2026-01-10",
"url": "https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960"
}
],
"user": {
"username": "Fu-Jie",
"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": 348,
"following": 6,
"total_points": 352,
"post_points": 299,
"comment_points": 53,
"contributions": 67
}
}

View File

@@ -8,7 +8,7 @@
> *Blue: Downloads | Purple: Views (Real-time dynamic)* > *Blue: Downloads | Purple: Views (Real-time dynamic)*
### 📂 Content Distribution ### 📂 Content Distribution
![Distribution](https://kroki.io/mermaid/svg/eNoryExVKMksyUlVUArIKU3PzFMIqSxILVZwySwuKcpMKi3JzM9T4lIAAqW0zJyS1CIlBSsFE4hASX5-DohrBOEWZBakgriGEG5iMlgvSACmoCg_t6AELAIAUaogTg==) ![Distribution](https://kroki.io/mermaid/svg/eNoryExVKMksyUlVUArIKU3PzFMIqSxILVZwySwuKcpMKi3JzM9T4lIAAqWC_OISJQUrBTMItyQ_PwfENYLKZhakgriGEG5aZk5JahFIwAQikJgMNgqkAqajKD-3oARJT1FqWWZqOVgAALS1J50=)
## 📈 Overview ## 📈 Overview
@@ -25,11 +25,13 @@
## 📂 By Type ## 📂 By Type
- ![filter](https://img.shields.io/badge/filter-4-brightgreen) - ![post](https://img.shields.io/badge/post-6-blue)
- ![tool](https://img.shields.io/badge/tool-2-teal) - ![tool](https://img.shields.io/badge/tool-2-teal)
- ![pipe](https://img.shields.io/badge/pipe-1-blueviolet) - ![pipe](https://img.shields.io/badge/pipe-1-blueviolet)
- ![filter](https://img.shields.io/badge/filter-4-brightgreen)
- ![action](https://img.shields.io/badge/action-12-orange) - ![action](https://img.shields.io/badge/action-12-orange)
- ![prompt](https://img.shields.io/badge/prompt-1-lightgrey) - ![prompt](https://img.shields.io/badge/prompt-1-lightgrey)
- ![review](https://img.shields.io/badge/review-1-yellow)
## 📋 Posts List ## 📋 Posts List
@@ -37,28 +39,28 @@
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_dl.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_vw.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_up.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_sv.json&style=flat) | 2026-02-27 | | 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_dl.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_vw.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_up.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_sv.json&style=flat) | 2026-02-27 |
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_376135b87514e63570283b2057459b1f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_dl.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_vw.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_up.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_sv.json&style=flat) | 2026-02-13 | | 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_376135b87514e63570283b2057459b1f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_dl.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_vw.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_up.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_sv.json&style=flat) | 2026-02-13 |
| 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter | ![v](https://img.shields.io/badge/v-1.2.8-blue?style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_dl.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_vw.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_up.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_sv.json&style=flat) | 2026-03-08 | | 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter | ![v](https://img.shields.io/badge/v-1.2.7-blue?style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_dl.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_vw.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_up.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_sv.json&style=flat) | 2026-03-03 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_dl.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_vw.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_up.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_sv.json&style=flat) | 2026-03-14 | | 4 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_dc072f01690dc8293384153dc0231d05_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_dl.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_vw.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_up.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_sv.json&style=flat) | 2026-02-13 |
| 5 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_dc072f01690dc8293384153dc0231d05_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_dl.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_vw.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_up.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_sv.json&style=flat) | 2026-02-13 | | 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | ![v](https://img.shields.io/badge/v-1.3.0-blue?style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_dl.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_vw.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_up.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_sv.json&style=flat) | 2026-03-03 |
| 6 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | prompt | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_dl.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_vw.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_up.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_sv.json&style=flat) | 2026-01-28 | | 6 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | prompt | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_dl.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_vw.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_up.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_sv.json&style=flat) | 2026-01-28 |
| 7 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | ![v](https://img.shields.io/badge/v-0.3.7-blue?style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_dl.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_vw.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_up.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_sv.json&style=flat) | 2026-02-13 | | 7 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | ![v](https://img.shields.io/badge/v-0.3.7-blue?style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_dl.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_vw.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_up.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_sv.json&style=flat) | 2026-02-13 |
| 8 | [OpenWebUI Skills Manager Tool](https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4) | tool | ![v](https://img.shields.io/badge/v-0.3.0-blue?style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_dl.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_vw.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_up.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_sv.json&style=flat) | 2026-03-14 | | 8 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | ![v](https://img.shields.io/badge/v-0.9.1-blue?style=flat) | ![post_aef940e01073e811a311c3a443d9c149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_dl.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_vw.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_up.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_sv.json&style=flat) | 2026-03-03 |
| 9 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | ![v](https://img.shields.io/badge/v-0.10.0-blue?style=flat) | ![post_aef940e01073e811a311c3a443d9c149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_dl.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_vw.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_up.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_sv.json&style=flat) | 2026-03-07 | | 9 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_up.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv.json&style=flat) | 2026-02-13 |
| 10 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_up.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv.json&style=flat) | 2026-02-13 | | 10 | [OpenWebUI Skills Manager Tool](https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4) | tool | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_dl.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_vw.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_up.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_sv.json&style=flat) | 2026-03-05 |
| 11 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_dl.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_vw.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_up.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_sv.json&style=flat) | 2026-01-08 | | 11 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_dl.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_vw.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_up.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_sv.json&style=flat) | 2026-01-08 |
| 12 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_up.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv.json&style=flat) | 2026-02-13 | | 12 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_up.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv.json&style=flat) | 2026-02-13 |
| 13 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | ![v](https://img.shields.io/badge/v-0.1.0-blue?style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_dl.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_vw.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_up.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_sv.json&style=flat) | 2026-01-20 | | 13 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | ![v](https://img.shields.io/badge/v-0.1.0-blue?style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_dl.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_vw.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_up.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_sv.json&style=flat) | 2026-01-20 |
| 14 | [🧠 Smart Mind Map Tool: Auto-Generate Interactive Knowledge Graphs](https://openwebui.com/posts/smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d) | tool | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_dl.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_vw.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_up.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_sv.json&style=flat) | 2026-03-05 | | 14 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter | ![v](https://img.shields.io/badge/v-0.1.3-blue?style=flat) | ![post_68081377a06a5746efe798136a72c6b6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_dl.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_vw.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_up.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_sv.json&style=flat) | 2026-03-03 |
| 15 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter | ![v](https://img.shields.io/badge/v-0.1.3-blue?style=flat) | ![post_68081377a06a5746efe798136a72c6b6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_dl.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_vw.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_up.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_sv.json&style=flat) | 2026-03-03 | | 15 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_dl.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_vw.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_up.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_sv.json&style=flat) | 2026-02-13 |
| 16 | [智能信息](https://openwebui.com/posts/智能信息图_e04a48ff) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_dl.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_vw.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_up.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_sv.json&style=flat) | 2026-02-13 | | 16 | [思维导](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | ![v](https://img.shields.io/badge/v-0.9.2-blue?style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_dl.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_vw.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_up.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_sv.json&style=flat) | 2026-02-13 |
| 17 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | ![v](https://img.shields.io/badge/v-0.9.2-blue?style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_dl.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_vw.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_up.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_sv.json&style=flat) | 2026-02-13 | | 17 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | ![v](https://img.shields.io/badge/v-1.2.2-blue?style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_dl.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_vw.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_up.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_sv.json&style=flat) | 2026-02-13 |
| 18 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | ![v](https://img.shields.io/badge/v-1.2.2-blue?style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_dl.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_vw.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_up.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_sv.json&style=flat) | 2026-02-13 | | 18 | [🧠 Smart Mind Map Tool: Auto-Generate Interactive Knowledge Graphs](https://openwebui.com/posts/smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d) | tool | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_dl.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_vw.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_up.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_sv.json&style=flat) | 2026-03-05 |
| 19 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_dl.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_vw.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_up.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_sv.json&style=flat) | 2026-01-08 | | 19 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_c68a2593e76ec324361358575b6501de_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_dl.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_vw.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_up.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_sv.json&style=flat) | 2026-02-13 |
| 20 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_c68a2593e76ec324361358575b6501de_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_dl.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_vw.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_up.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_sv.json&style=flat) | 2026-02-13 | | 20 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_dl.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_vw.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_up.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_sv.json&style=flat) | 2026-01-08 |
| 21 | [An Unconventional Use of Open Terminal ⚡](https://openwebui.com/posts/an_unconventional_use_of_open_terminal_35498f8f) | action | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_040794b40937926d42e6907044b828b1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_dl.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_vw.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_up.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_sv.json&style=flat) | 2026-03-07 | | 21 | [An Unconventional Use of Open Terminal ⚡](https://openwebui.com/posts/an_unconventional_use_of_open_terminal_35498f8f) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_040794b40937926d42e6907044b828b1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_dl.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_vw.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_up.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_sv.json&style=flat) | 2026-03-06 |
| 22 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | pipe | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_dl.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_vw.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_up.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_sv.json&style=flat) | 2026-02-28 | | 22 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_dl.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_vw.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_up.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_sv.json&style=flat) | 2026-02-28 |
| 23 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | pipe | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv.json&style=flat) | 2026-02-28 | | 23 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv.json&style=flat) | 2026-02-28 |
| 24 | [🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks](https://openwebui.com/posts/github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293) | pipe | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_dl.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_vw.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_up.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_sv.json&style=flat) | 2026-02-10 | | 24 | [🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks](https://openwebui.com/posts/github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_dl.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_vw.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_up.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_sv.json&style=flat) | 2026-02-10 |
| 25 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | action | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_858cb162732370288ce024b4f7944f69_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_dl.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_vw.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_up.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_sv.json&style=flat) | 2026-01-28 | | 25 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_858cb162732370288ce024b4f7944f69_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_dl.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_vw.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_up.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_sv.json&style=flat) | 2026-01-28 |
| 26 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | review | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_dl.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_vw.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_up.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_sv.json&style=flat) | 2026-01-14 | | 26 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | review | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_dl.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_vw.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_up.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_sv.json&style=flat) | 2026-01-14 |
| 27 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | action | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_dl.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_vw.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_up.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_sv.json&style=flat) | 2026-01-10 | | 27 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_dl.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_vw.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_up.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_sv.json&style=flat) | 2026-01-10 |

View File

@@ -8,7 +8,7 @@
> *蓝色: 总下载量 | 紫色: 总浏览量 (实时动态生成)* > *蓝色: 总下载量 | 紫色: 总浏览量 (实时动态生成)*
### 📂 内容分类占比 (Distribution) ### 📂 内容分类占比 (Distribution)
![Distribution](https://kroki.io/mermaid/svg/eNoryExVKMksyUlVUArIKU3PzFMIqSxILVZwySwuKcpMKi3JzM9T4lIAAqW0zJyS1CIlBSsFE4hASX5-DohrBOEWZBakgriGEG5iMlgvSACmoCg_t6AELAIAUaogTg==) ![Distribution](https://kroki.io/mermaid/svg/eNoryExVKMksyUlVUArIKU3PzFMIqSxILVZwySwuKcpMKi3JzM9T4lIAAqWC_OISJQUrBTMItyQ_PwfENYLKZhakgriGEG5aZk5JahFIwAQikJgMNgqkAqajKD-3oARJT1FqWWZqOVgAALS1J50=)
## 📈 总览 ## 📈 总览
@@ -25,11 +25,13 @@
## 📂 按类型分类 ## 📂 按类型分类
- ![filter](https://img.shields.io/badge/filter-4-brightgreen) - ![post](https://img.shields.io/badge/post-6-blue)
- ![tool](https://img.shields.io/badge/tool-2-teal) - ![tool](https://img.shields.io/badge/tool-2-teal)
- ![pipe](https://img.shields.io/badge/pipe-1-blueviolet) - ![pipe](https://img.shields.io/badge/pipe-1-blueviolet)
- ![filter](https://img.shields.io/badge/filter-4-brightgreen)
- ![action](https://img.shields.io/badge/action-12-orange) - ![action](https://img.shields.io/badge/action-12-orange)
- ![prompt](https://img.shields.io/badge/prompt-1-lightgrey) - ![prompt](https://img.shields.io/badge/prompt-1-lightgrey)
- ![review](https://img.shields.io/badge/review-1-yellow)
## 📋 发布列表 ## 📋 发布列表
@@ -37,28 +39,28 @@
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_dl.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_vw.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_up.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_sv.json&style=flat) | 2026-02-27 | | 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_dl.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_vw.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_up.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_sv.json&style=flat) | 2026-02-27 |
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_376135b87514e63570283b2057459b1f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_dl.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_vw.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_up.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_sv.json&style=flat) | 2026-02-13 | | 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_376135b87514e63570283b2057459b1f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_dl.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_vw.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_up.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_sv.json&style=flat) | 2026-02-13 |
| 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter | ![v](https://img.shields.io/badge/v-1.2.8-blue?style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_dl.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_vw.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_up.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_sv.json&style=flat) | 2026-03-08 | | 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter | ![v](https://img.shields.io/badge/v-1.2.7-blue?style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_dl.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_vw.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_up.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_sv.json&style=flat) | 2026-03-03 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_dl.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_vw.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_up.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_sv.json&style=flat) | 2026-03-14 | | 4 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_dc072f01690dc8293384153dc0231d05_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_dl.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_vw.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_up.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_sv.json&style=flat) | 2026-02-13 |
| 5 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_dc072f01690dc8293384153dc0231d05_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_dl.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_vw.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_up.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_sv.json&style=flat) | 2026-02-13 | | 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | ![v](https://img.shields.io/badge/v-1.3.0-blue?style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_dl.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_vw.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_up.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_sv.json&style=flat) | 2026-03-03 |
| 6 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | prompt | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_dl.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_vw.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_up.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_sv.json&style=flat) | 2026-01-28 | | 6 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | prompt | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_dl.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_vw.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_up.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_sv.json&style=flat) | 2026-01-28 |
| 7 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | ![v](https://img.shields.io/badge/v-0.3.7-blue?style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_dl.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_vw.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_up.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_sv.json&style=flat) | 2026-02-13 | | 7 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | ![v](https://img.shields.io/badge/v-0.3.7-blue?style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_dl.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_vw.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_up.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_sv.json&style=flat) | 2026-02-13 |
| 8 | [OpenWebUI Skills Manager Tool](https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4) | tool | ![v](https://img.shields.io/badge/v-0.3.0-blue?style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_dl.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_vw.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_up.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_sv.json&style=flat) | 2026-03-14 | | 8 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | ![v](https://img.shields.io/badge/v-0.9.1-blue?style=flat) | ![post_aef940e01073e811a311c3a443d9c149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_dl.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_vw.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_up.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_sv.json&style=flat) | 2026-03-03 |
| 9 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | ![v](https://img.shields.io/badge/v-0.10.0-blue?style=flat) | ![post_aef940e01073e811a311c3a443d9c149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_dl.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_vw.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_up.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_sv.json&style=flat) | 2026-03-07 | | 9 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_up.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv.json&style=flat) | 2026-02-13 |
| 10 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_up.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv.json&style=flat) | 2026-02-13 | | 10 | [OpenWebUI Skills Manager Tool](https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4) | tool | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_dl.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_vw.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_up.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_sv.json&style=flat) | 2026-03-05 |
| 11 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_dl.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_vw.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_up.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_sv.json&style=flat) | 2026-01-08 | | 11 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_dl.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_vw.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_up.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_sv.json&style=flat) | 2026-01-08 |
| 12 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_up.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv.json&style=flat) | 2026-02-13 | | 12 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_up.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv.json&style=flat) | 2026-02-13 |
| 13 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | ![v](https://img.shields.io/badge/v-0.1.0-blue?style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_dl.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_vw.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_up.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_sv.json&style=flat) | 2026-01-20 | | 13 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | ![v](https://img.shields.io/badge/v-0.1.0-blue?style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_dl.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_vw.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_up.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_sv.json&style=flat) | 2026-01-20 |
| 14 | [🧠 Smart Mind Map Tool: Auto-Generate Interactive Knowledge Graphs](https://openwebui.com/posts/smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d) | tool | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_dl.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_vw.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_up.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_sv.json&style=flat) | 2026-03-05 | | 14 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter | ![v](https://img.shields.io/badge/v-0.1.3-blue?style=flat) | ![post_68081377a06a5746efe798136a72c6b6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_dl.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_vw.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_up.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_sv.json&style=flat) | 2026-03-03 |
| 15 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter | ![v](https://img.shields.io/badge/v-0.1.3-blue?style=flat) | ![post_68081377a06a5746efe798136a72c6b6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_dl.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_vw.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_up.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_sv.json&style=flat) | 2026-03-03 | | 15 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_dl.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_vw.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_up.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_sv.json&style=flat) | 2026-02-13 |
| 16 | [智能信息](https://openwebui.com/posts/智能信息图_e04a48ff) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_dl.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_vw.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_up.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_sv.json&style=flat) | 2026-02-13 | | 16 | [思维导](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | ![v](https://img.shields.io/badge/v-0.9.2-blue?style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_dl.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_vw.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_up.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_sv.json&style=flat) | 2026-02-13 |
| 17 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | ![v](https://img.shields.io/badge/v-0.9.2-blue?style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_dl.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_vw.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_up.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_sv.json&style=flat) | 2026-02-13 | | 17 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | ![v](https://img.shields.io/badge/v-1.2.2-blue?style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_dl.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_vw.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_up.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_sv.json&style=flat) | 2026-02-13 |
| 18 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | ![v](https://img.shields.io/badge/v-1.2.2-blue?style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_dl.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_vw.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_up.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_sv.json&style=flat) | 2026-02-13 | | 18 | [🧠 Smart Mind Map Tool: Auto-Generate Interactive Knowledge Graphs](https://openwebui.com/posts/smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d) | tool | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_dl.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_vw.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_up.json&style=flat) | ![post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fdb1b2ff2b8c6ad0dbb3b15a0318434d_sv.json&style=flat) | 2026-03-05 |
| 19 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_dl.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_vw.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_up.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_sv.json&style=flat) | 2026-01-08 | | 19 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_c68a2593e76ec324361358575b6501de_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_dl.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_vw.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_up.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_sv.json&style=flat) | 2026-02-13 |
| 20 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_c68a2593e76ec324361358575b6501de_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_dl.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_vw.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_up.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_sv.json&style=flat) | 2026-02-13 | | 20 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_dl.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_vw.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_up.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_sv.json&style=flat) | 2026-01-08 |
| 21 | [An Unconventional Use of Open Terminal ⚡](https://openwebui.com/posts/an_unconventional_use_of_open_terminal_35498f8f) | action | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_040794b40937926d42e6907044b828b1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_dl.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_vw.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_up.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_sv.json&style=flat) | 2026-03-07 | | 21 | [An Unconventional Use of Open Terminal ⚡](https://openwebui.com/posts/an_unconventional_use_of_open_terminal_35498f8f) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_040794b40937926d42e6907044b828b1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_dl.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_vw.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_up.json&style=flat) | ![post_040794b40937926d42e6907044b828b1_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_040794b40937926d42e6907044b828b1_sv.json&style=flat) | 2026-03-06 |
| 22 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | pipe | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_dl.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_vw.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_up.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_sv.json&style=flat) | 2026-02-28 | | 22 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_dl.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_vw.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_up.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_sv.json&style=flat) | 2026-02-28 |
| 23 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | pipe | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv.json&style=flat) | 2026-02-28 | | 23 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv.json&style=flat) | 2026-02-28 |
| 24 | [🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks](https://openwebui.com/posts/github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293) | pipe | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_dl.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_vw.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_up.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_sv.json&style=flat) | 2026-02-10 | | 24 | [🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks](https://openwebui.com/posts/github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_dl.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_vw.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_up.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_sv.json&style=flat) | 2026-02-10 |
| 25 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | action | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_858cb162732370288ce024b4f7944f69_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_dl.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_vw.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_up.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_sv.json&style=flat) | 2026-01-28 | | 25 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_858cb162732370288ce024b4f7944f69_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_dl.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_vw.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_up.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_sv.json&style=flat) | 2026-01-28 |
| 26 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | review | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_dl.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_vw.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_up.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_sv.json&style=flat) | 2026-01-14 | | 26 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | review | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_dl.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_vw.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_up.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_sv.json&style=flat) | 2026-01-14 |
| 27 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | action | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_dl.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_vw.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_up.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_sv.json&style=flat) | 2026-01-10 | | 27 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_dl.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_vw.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_up.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_sv.json&style=flat) | 2026-01-10 |

View File

@@ -103,6 +103,13 @@ hide:
## Quick Start ## Quick Start
### Using Prompts
1. Browse the [Prompt Library](prompts/library.md) and select a prompt
2. Click the **Copy** button to copy the prompt to your clipboard
3. In OpenWebUI, click the "Prompt" button above the input box
4. Paste the content and save
### Using Plugins ### Using Plugins
1. **Install from OpenWebUI Community (Recommended)**: 1. **Install from OpenWebUI Community (Recommended)**:
@@ -110,14 +117,11 @@ hide:
- Browse the plugins and select the one you like. - Browse the plugins and select the one you like.
- Click "Get" to import it directly into your OpenWebUI instance. - Click "Get" to import it directly into your OpenWebUI instance.
2. **Quick Install All Plugins**: To install all plugins to your local OpenWebUI instance at once, clone this repo and run `python scripts/install_all_plugins.py` after configuring your API key in `.env` — see [Deployment Guide](https://github.com/Fu-Jie/openwebui-extensions/blob/main/scripts/DEPLOYMENT_GUIDE.md) for details. 2. **Manual Installation**:
- Browse the [Plugin Center](plugins/index.md) and download the plugin file (`.py`)
### Using Prompts - Open OpenWebUI **Admin Panel****Settings****Plugins**
- Click the upload button and select the `.py` file
1. Browse the [Prompt Library](prompts/library.md) and select a prompt - Refresh the page and enable the plugin in your chat settings
2. Click the **Copy** button to copy the prompt to your clipboard
3. In OpenWebUI, click the "Prompt" button above the input box
4. Paste the content and save
--- ---

View File

@@ -103,6 +103,13 @@ hide:
## 快速入门 ## 快速入门
### 使用提示词
1. 浏览[提示词库](prompts/library.md)并选择一个提示词
2. 点击**复制**按钮将提示词复制到剪贴板
3. 在 OpenWebUI 中,点击输入框上方的"提示词"按钮
4. 粘贴内容并保存
### 使用插件 ### 使用插件
1. **从 OpenWebUI 社区安装 (推荐)**: 1. **从 OpenWebUI 社区安装 (推荐)**:
@@ -110,14 +117,11 @@ hide:
- 浏览插件列表,选择你喜欢的插件。 - 浏览插件列表,选择你喜欢的插件。
- 点击 "Get" 按钮,将其直接导入到你的 OpenWebUI 实例中。 - 点击 "Get" 按钮,将其直接导入到你的 OpenWebUI 实例中。
2. **快速安装全部插件**:想要一次性安装此项目中的所有插件到本地 OpenWebUI 实例,克隆此仓库后运行 `python scripts/install_all_plugins.py`,并在 `.env` 中配置好 API 密钥,详见[部署指南](https://github.com/Fu-Jie/openwebui-extensions/blob/main/scripts/DEPLOYMENT_GUIDE.md)。 2. **手动安装**:
- 浏览[插件中心](plugins/index.md)并下载插件文件(`.py`
### 使用提示词 - 打开 OpenWebUI **管理面板****设置****插件**
- 点击上传按钮并选择 `.py` 文件
1. 浏览[提示词库](prompts/library.md)并选择一个提示词 - 刷新页面并在聊天设置中启用插件
2. 点击**复制**按钮将提示词复制到剪贴板
3. 在 OpenWebUI 中,点击输入框上方的"提示词"按钮
4. 粘贴内容并保存
--- ---

View File

@@ -1,19 +1,15 @@
# Async Context Compression Filter # Async Context Compression Filter
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 1.5.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT **Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 1.4.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent. This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent.
## What's new in 1.5.0 ## What's new in 1.4.0
- **External Chat Reference Summaries**: Added support for referenced chat context blocks that can reuse cached summaries, inject small referenced chats directly, or generate summaries for larger referenced chats before injection. - **Atomic Message Grouping**: Introduced structure-aware grouping for `assistant-tool-tool-assistant` chains to prevent "No tool call found" errors.
- **Fast Multilingual Token Estimation**: Added a new mixed-script token estimation pipeline so inlet/outlet preflight checks can avoid unnecessary exact token counts while staying much closer to real usage. - **Tail Boundary Alignment**: Implemented automatic correction for truncation points to ensure they don't fall inside a tool-calling sequence.
- **Stronger Working-Memory Prompt**: Refined the XML summary prompt to better preserve actionable context across general chat, coding tasks, and tool-heavy conversations. - **Chat Session Locking**: Added a session-based lock to prevent multiple concurrent summary tasks for the same chat ID.
- **Clearer Frontend Debug Logs**: Reworked browser-console logging into grouped structural snapshots that are easier to scan during debugging. - **Enhanced Traceability**: Improved summary formatting to include message IDs, names, and metadata for better context tracking.
- **Safer Tool Trimming Defaults**: Enabled native tool-output trimming by default and exposed a dedicated `tool_trim_threshold_chars` valve with a 600-character default.
- **Safer Referenced-Chat Fallbacks**: If generating a referenced chat summary fails, the new reference-summary path now falls back to direct contextual injection instead of failing the whole chat.
- **Correct Summary Budgeting**: `summary_model_max_context` now controls summary-input fitting, while `max_summary_tokens` remains an output-length cap.
- **More Visible Summary Failures**: Important background summary failures now surface in the browser console (`F12`) and as a status hint even when `show_debug_log` is off.
--- ---
@@ -25,85 +21,15 @@ This filter reduces token consumption in long conversations through intelligent
- ✅ Persistent storage via Open WebUI's shared database connection (PostgreSQL, SQLite, etc.). - ✅ Persistent storage via Open WebUI's shared database connection (PostgreSQL, SQLite, etc.).
- ✅ Flexible retention policy to keep the first and last N messages. - ✅ Flexible retention policy to keep the first and last N messages.
- ✅ Smart injection of historical summaries back into the context. - ✅ Smart injection of historical summaries back into the context.
- ✅ External chat reference summarization with cached-summary reuse, direct injection for small chats, and generated summaries for larger chats.
- ✅ Structure-aware trimming that preserves document structure (headers, intro, conclusion). - ✅ Structure-aware trimming that preserves document structure (headers, intro, conclusion).
- ✅ Native tool output trimming for cleaner context when using function calling. - ✅ Native tool output trimming for cleaner context when using function calling.
- ✅ Real-time context usage monitoring with warning notifications (>90%). - ✅ Real-time context usage monitoring with warning notifications (>90%).
-Fast multilingual token estimation plus exact token fallback for precise debugging and optimization. -Detailed token logging for precise debugging and optimization.
-**Smart Model Matching**: Automatically inherits configuration from base models for custom presets. -**Smart Model Matching**: Automatically inherits configuration from base models for custom presets.
-**Multimodal Support**: Images are preserved but their tokens are **NOT** calculated. Please adjust thresholds accordingly. -**Multimodal Support**: Images are preserved but their tokens are **NOT** calculated. Please adjust thresholds accordingly.
--- ---
## What This Fixes
- **Problem 1: A referenced chat could break the current request.**
Before, if the filter needed to summarize a referenced chat and that LLM call failed, the current chat could fail with it. Now it degrades gracefully and injects direct context instead.
- **Problem 2: Some referenced chats were being cut too aggressively.**
Before, the output limit (`max_summary_tokens`) could be treated like the input window, which made large referenced chats shrink earlier than necessary. Now input fitting uses the summary model's real context window (`summary_model_max_context` or model/global fallback).
- **Problem 3: Some background summary failures were too easy to miss.**
Before, a failure during background summary preparation could disappear quietly when frontend debug logging was off. Now important failures are forced to the browser console and also shown through a user-facing status message.
---
## Workflow Overview
This filter operates in two phases:
1. `inlet`: injects stored summaries, processes external chat references, and trims context when required before the request is sent to the model.
2. `outlet`: runs asynchronously after the response is complete, decides whether a new summary should be generated, and persists it when appropriate.
```mermaid
flowchart TD
A[Request enters inlet] --> B[Normalize tool IDs and optionally trim large tool outputs]
B --> C{Referenced chats attached?}
C -- No --> D[Load current chat summary if available]
C -- Yes --> E[Inspect each referenced chat]
E --> F{Existing cached summary?}
F -- Yes --> G[Reuse cached summary]
F -- No --> H{Fits direct budget?}
H -- Yes --> I[Inject full referenced chat text]
H -- No --> J[Prepare referenced-chat summary input]
J --> K{Referenced-chat summary call succeeds?}
K -- Yes --> L[Inject generated referenced summary]
K -- No --> M[Fallback to direct contextual injection]
G --> D
I --> D
L --> D
M --> D
D --> N[Build current-chat Head + Summary + Tail]
N --> O{Over max_context_tokens?}
O -- Yes --> P[Trim oldest atomic groups]
O -- No --> Q[Send final context to the model]
P --> Q
Q --> R[Model returns the reply]
R --> S[Outlet rebuilds the full history]
S --> T{Reached compression threshold?}
T -- No --> U[Finish]
T -- Yes --> V[Fit summary input to the summary model context]
V --> W{Background summary call succeeds?}
W -- Yes --> X[Save new chat summary and update status]
W -- No --> Y[Force browser-console error and show status hint]
```
### Key Notes
- `inlet` only injects and trims context. It does not generate the main chat summary.
- `outlet` performs summary generation asynchronously and does not block the current reply.
- External chat references may come from an existing persisted summary, a small chat's full text, or a generated/truncated reference summary.
- If a referenced-chat summary call fails, the filter falls back to direct context injection instead of failing the whole request.
- `summary_model_max_context` controls summary-input fitting. `max_summary_tokens` only controls how long the generated summary may be.
- Important background summary failures are surfaced to the browser console (`F12`) and the chat status area.
- External reference messages are protected during trimming so they are not discarded first.
---
## Installation & Configuration ## Installation & Configuration
### 1) Database (automatic) ### 1) Database (automatic)
@@ -127,12 +53,11 @@ flowchart TD
| `keep_first` | `1` | Always keep the first N messages (protects system prompts). | | `keep_first` | `1` | Always keep the first N messages (protects system prompts). |
| `keep_last` | `6` | Always keep the last N messages to preserve recent context. | | `keep_last` | `6` | Always keep the last N messages to preserve recent context. |
| `summary_model` | `None` | Model for summaries. Strongly recommended to set a fast, economical model (e.g., `gemini-2.5-flash`, `deepseek-v3`). Falls back to the current chat model when empty. | | `summary_model` | `None` | Model for summaries. Strongly recommended to set a fast, economical model (e.g., `gemini-2.5-flash`, `deepseek-v3`). Falls back to the current chat model when empty. |
| `summary_model_max_context` | `0` | Input context window used to fit summary requests. If `0`, falls back to `model_thresholds` or global `max_context_tokens`. | | `summary_model_max_context` | `0` | Max context tokens for the summary model. If 0, falls back to `model_thresholds` or global `max_context_tokens`. |
| `max_summary_tokens` | `16384` | Maximum output length for the generated summary. This is not the summary-input context limit. | | `max_summary_tokens` | `16384` | Maximum tokens for the generated summary. |
| `summary_temperature` | `0.1` | Randomness for summary generation. Lower is more deterministic. | | `summary_temperature` | `0.3` | Randomness for summary generation. Lower is more deterministic. |
| `model_thresholds` | `{}` | Per-model overrides for `compression_threshold_tokens` and `max_context_tokens` (useful for mixed models). | | `model_thresholds` | `{}` | Per-model overrides for `compression_threshold_tokens` and `max_context_tokens` (useful for mixed models). |
| `enable_tool_output_trimming` | `true` | When enabled for `function_calling: "native"`, trims oversized native tool outputs while keeping the tool-call chain intact. | | `enable_tool_output_trimming` | `false` | When enabled and `function_calling: "native"` is active, trims verbose tool outputs to extract only the final answer. |
| `tool_trim_threshold_chars` | `600` | Trim native tool output blocks once their total content length reaches this threshold. |
| `debug_mode` | `false` | Log verbose debug info. Set to `false` in production. | | `debug_mode` | `false` | Log verbose debug info. Set to `false` in production. |
| `show_debug_log` | `false` | Print debug logs to browser console (F12). Useful for frontend debugging. | | `show_debug_log` | `false` | Print debug logs to browser console (F12). Useful for frontend debugging. |
| `show_token_usage_status` | `true` | Show token usage status notification in the chat interface. | | `show_token_usage_status` | `true` | Show token usage status notification in the chat interface. |
@@ -148,12 +73,8 @@ If this plugin has been useful, a star on [OpenWebUI Extensions](https://github.
- **Initial system prompt is lost**: Keep `keep_first` greater than 0 to protect the initial message. - **Initial system prompt is lost**: Keep `keep_first` greater than 0 to protect the initial message.
- **Compression effect is weak**: Raise `compression_threshold_tokens` or lower `keep_first` / `keep_last` to allow more aggressive compression. - **Compression effect is weak**: Raise `compression_threshold_tokens` or lower `keep_first` / `keep_last` to allow more aggressive compression.
- **A referenced chat summary fails**: The current request should continue with a direct-context fallback. Check the browser console (`F12`) if you need the upstream failure details.
- **A background summary silently seems to do nothing**: Important failures now surface in chat status and the browser console (`F12`).
- **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [OpenWebUI Extensions Issues](https://github.com/Fu-Jie/openwebui-extensions/issues) - **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [OpenWebUI Extensions Issues](https://github.com/Fu-Jie/openwebui-extensions/issues)
## Changelog ## Changelog
See [`v1.5.0` Release Notes](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/v1.5.0.md) for the release-specific summary.
See the full history on GitHub: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) See the full history on GitHub: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)

View File

@@ -1,21 +1,17 @@
# 异步上下文压缩过滤器 # 异步上下文压缩过滤器
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 1.5.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT **作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 1.4.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
> **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。 > **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
本过滤器通过智能摘要和消息压缩技术,在保持对话连贯性的同时,显著降低长对话的 Token 消耗。 本过滤器通过智能摘要和消息压缩技术,在保持对话连贯性的同时,显著降低长对话的 Token 消耗。
## 1.5.0 版本更新 ## 1.4.0 版本更新
- **外部聊天引用摘要**: 新增对引用聊天上下文的摘要支持。现在可以复用缓存摘要、直接注入较小引用聊天,或先为较大的引用聊天生成摘要再注入 - **原子消息组 (Atomic Grouping)**: 引入结构感知的消息分组逻辑,确保工具调用链被整体保留或移除,彻底解决 "No tool call found" 错误
- **快速多语言 Token 预估**: 新增混合脚本 Token 预估链路,使 inlet / outlet 的预检可以减少不必要的精确计数,同时比旧的粗略字符比值更接近真实用量 - **尾部边界自动对齐**: 实现了截断点的自动修正逻辑,确保历史上下文截断不会落在工具调用序列中间
- **更稳健的工作记忆提示词**: 重写 XML 摘要提示词,增强普通聊天、编码任务和连续工具调用场景下的关键信息保留能力 - **会话级异步锁**: 增加了基于 `chat_id` 的后台任务锁,防止同一会话并发触发多个总结任务
- **更清晰的前端调试日志**: 浏览器控制台日志改为分组化、结构化展示,排查上下文压缩行为更直观 - **元数据溯源增强**: 优化了总结输入格式,在总结中保留了消息 ID、参与者名称及关键元数据提升上下文可追踪性
- **更安全的工具裁剪默认值**: 原生工具输出裁剪默认开启,并新增 `tool_trim_threshold_chars` 配置项,默认阈值为 600 字符。
- **更稳妥的引用聊天回退**: 当新的引用聊天摘要路径生成失败时,不再拖垮当前请求,而是自动回退为直接注入上下文。
- **更准确的摘要预算**: `summary_model_max_context` 现在只负责摘要输入窗口,`max_summary_tokens` 继续只负责摘要输出长度。
- **更容易发现摘要失败**: 重要的后台摘要失败现在会强制显示到浏览器控制台 (`F12`),并同步给出状态提示。
--- ---
@@ -27,84 +23,14 @@
-**持久化存储**: 复用 Open WebUI 共享数据库连接,自动支持 PostgreSQL/SQLite 等。 -**持久化存储**: 复用 Open WebUI 共享数据库连接,自动支持 PostgreSQL/SQLite 等。
-**灵活保留策略**: 可配置保留对话头部和尾部消息,确保关键信息连贯。 -**灵活保留策略**: 可配置保留对话头部和尾部消息,确保关键信息连贯。
-**智能注入**: 将历史摘要智能注入到新上下文中。 -**智能注入**: 将历史摘要智能注入到新上下文中。
-**外部聊天引用摘要**: 支持复用缓存摘要、小聊天直接注入,以及大聊天先摘要后注入。
-**结构感知裁剪**: 智能折叠过长消息,保留文档骨架(标题、首尾)。 -**结构感知裁剪**: 智能折叠过长消息,保留文档骨架(标题、首尾)。
-**原生工具输出裁剪**: 支持裁剪冗长的工具调用输出。 -**原生工具输出裁剪**: 支持裁剪冗长的工具调用输出。
-**实时监控**: 实时监控上下文使用情况,超过 90% 发出警告。 -**实时监控**: 实时监控上下文使用情况,超过 90% 发出警告。
-**快速预估 + 精确回退**: 提供更快的多语言 Token 预估,并在必要时回退到精确统计,便于调试。 -**详细日志**: 提供精确的 Token 统计日志,便于调试。
-**智能模型匹配**: 自定义模型自动继承基础模型的阈值配置。 -**智能模型匹配**: 自定义模型自动继承基础模型的阈值配置。
-**多模态支持**: 图片内容会被保留,但其 Token **不参与计算**。请相应调整阈值。 -**多模态支持**: 图片内容会被保留,但其 Token **不参与计算**。请相应调整阈值。
详细的工作原理和更长说明仍可参考 [工作流程指南](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/WORKFLOW_GUIDE_CN.md)。 详细的工作原理和流程请参考 [工作流程指南](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/WORKFLOW_GUIDE_CN.md)。
---
## 这次解决了什么问题(通俗版)
- **问题 1引用别的聊天时摘要失败可能把当前对话一起弄挂。**
以前如果过滤器需要先帮被引用聊天做摘要,而这一步的 LLM 调用失败了,当前请求也可能直接失败。现在改成了“能摘要就摘要,失败就退回直接塞上下文”,当前对话不会被一起拖死。
- **问题 2有些被引用聊天被截得太早信息丢得太多。**
以前有一段逻辑把 `max_summary_tokens` 这种“输出长度限制”误当成了“输入上下文窗口”,结果大一点的引用聊天会被过早截断。现在改成按摘要模型真实的输入窗口来算,能保留更多有用内容。
- **问题 3后台摘要失败时用户不容易知道发生了什么。**
以前在 `show_debug_log=false` 时,有些后台失败只会留在内部日志里。现在关键失败会强制打到浏览器控制台,并在聊天状态里提醒去看 `F12`
---
## 工作流总览
该过滤器分为两个阶段:
1. `inlet`:在请求发送给模型前执行,负责注入已有摘要、处理外部聊天引用、并在必要时裁剪上下文。
2. `outlet`:在模型回复完成后异步执行,负责判断是否需要生成新摘要,并在合适时写入数据库。
```mermaid
flowchart TD
A[请求进入 inlet] --> B[规范化工具 ID 并按需裁剪超长工具输出]
B --> C{是否附带引用聊天?}
C -- 否 --> D[如果有当前聊天摘要就先加载]
C -- 是 --> E[逐个检查被引用聊天]
E --> F{已有缓存摘要?}
F -- 是 --> G[直接复用缓存摘要]
F -- 否 --> H{能直接放进当前预算?}
H -- 是 --> I[直接注入完整引用聊天文本]
H -- 否 --> J[准备引用聊天的摘要输入]
J --> K{引用聊天摘要调用成功?}
K -- 是 --> L[注入生成后的引用摘要]
K -- 否 --> M[回退为直接注入上下文]
G --> D
I --> D
L --> D
M --> D
D --> N[为当前聊天构造 Head + Summary + Tail]
N --> O{是否超过 max_context_tokens?}
O -- 是 --> P[从最旧 atomic groups 开始裁剪]
O -- 否 --> Q[把最终上下文发给模型]
P --> Q
Q --> R[模型返回当前回复]
R --> S[Outlet 重建完整历史]
S --> T{达到压缩阈值了吗?}
T -- 否 --> U[结束]
T -- 是 --> V[把摘要输入压到摘要模型可接受的上下文窗口]
V --> W{后台摘要调用成功?}
W -- 是 --> X[保存新摘要并更新状态]
W -- 否 --> Y[强制输出浏览器控制台错误并提示用户查看]
```
### 关键说明
- `inlet` 只负责注入和裁剪上下文,不负责生成当前聊天的主摘要。
- `outlet` 异步生成摘要,不会阻塞当前回复。
- 外部聊天引用可以来自已有持久化摘要、小聊天的完整文本,或动态生成/截断后的引用摘要。
- 如果引用聊天摘要失败,会自动回退为直接注入上下文,而不是让当前请求失败。
- `summary_model_max_context` 控制摘要输入窗口;`max_summary_tokens` 只控制生成摘要的输出长度。
- 重要的后台摘要失败会显示到浏览器控制台 (`F12`) 和聊天状态提示里。
- 外部引用消息在裁剪阶段会被特殊保护,避免被最先删除。
--- ---
@@ -140,8 +66,8 @@ flowchart TD
| 参数 | 默认值 | 描述 | | 参数 | 默认值 | 描述 |
| :-------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ | | :-------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ |
| `summary_model` | `None` | 用于生成摘要的模型 ID。**强烈建议**配置快速、经济、上下文窗口大的模型(如 `gemini-2.5-flash``deepseek-v3`)。留空则尝试复用当前对话模型。 | | `summary_model` | `None` | 用于生成摘要的模型 ID。**强烈建议**配置快速、经济、上下文窗口大的模型(如 `gemini-2.5-flash``deepseek-v3`)。留空则尝试复用当前对话模型。 |
| `summary_model_max_context` | `0` | 摘要请求可使用的输入上下文窗口。如果为 0则回退到 `model_thresholds` 或全局 `max_context_tokens`。 | | `summary_model_max_context` | `0` | 摘要模型的最大上下文 Token 数。如果为 0则回退到 `model_thresholds` 或全局 `max_context_tokens`。 |
| `max_summary_tokens` | `16384` | 生成摘要时允许的最大输出 Token 数。它不是摘要输入窗口上限。 | | `max_summary_tokens` | `16384` | 生成摘要时允许的最大 Token 数。 |
| `summary_temperature` | `0.1` | 控制摘要生成的随机性,较低的值结果更稳定。 | | `summary_temperature` | `0.1` | 控制摘要生成的随机性,较低的值结果更稳定。 |
### 高级配置 ### 高级配置
@@ -169,8 +95,7 @@ flowchart TD
| 参数 | 默认值 | 描述 | | 参数 | 默认值 | 描述 |
| :----------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | :----------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
| `enable_tool_output_trimming` | `true` | 启用后(仅在 `function_calling: "native"` 下生效)会裁剪过大的本机工具输出,保留工具调用链结构并以简短占位替换冗长内容。 | | `enable_tool_output_trimming` | `false` | 启用时,若 `function_calling: "native"` 激活,将裁剪冗长的工具输出以仅提取最终答案。 |
| `tool_trim_threshold_chars` | `600` | 当本机工具输出累计字符数达到该值时触发裁剪,适用于包含长文本或表格的工具结果。 |
| `debug_mode` | `false` | 是否在 Open WebUI 的控制台日志中打印详细的调试信息。生产环境默认且建议设为 `false`。 | | `debug_mode` | `false` | 是否在 Open WebUI 的控制台日志中打印详细的调试信息。生产环境默认且建议设为 `false`。 |
| `show_debug_log` | `false` | 是否在浏览器控制台 (F12) 打印调试日志。便于前端调试。 | | `show_debug_log` | `false` | 是否在浏览器控制台 (F12) 打印调试日志。便于前端调试。 |
| `show_token_usage_status` | `true` | 是否在对话结束时显示 Token 使用情况的状态通知。 | | `show_token_usage_status` | `true` | 是否在对话结束时显示 Token 使用情况的状态通知。 |
@@ -186,12 +111,8 @@ flowchart TD
- **初始系统提示丢失**:将 `keep_first` 设置为大于 0。 - **初始系统提示丢失**:将 `keep_first` 设置为大于 0。
- **压缩效果不明显**:提高 `compression_threshold_tokens`,或降低 `keep_first` / `keep_last` 以增强压缩力度。 - **压缩效果不明显**:提高 `compression_threshold_tokens`,或降低 `keep_first` / `keep_last` 以增强压缩力度。
- **引用聊天摘要失败**:当前请求现在应该会继续执行,并回退为直接注入上下文。如果要看上游失败原因,请打开浏览器控制台 (`F12`)。
- **后台摘要看起来“没反应”**:重要失败现在会同时出现在状态提示和浏览器控制台 (`F12`) 中。
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue[OpenWebUI Extensions Issues](https://github.com/Fu-Jie/openwebui-extensions/issues) - **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue[OpenWebUI Extensions Issues](https://github.com/Fu-Jie/openwebui-extensions/issues)
## 更新日志 ## 更新日志
请查看 [`v1.5.0` 版本发布说明](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/v1.5.0_CN.md) 获取本次版本的独立发布摘要。
完整历史请查看 GitHub 项目: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 完整历史请查看 GitHub 项目: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)

View File

@@ -20,9 +20,9 @@ Filters act as middleware in the message pipeline:
--- ---
Reduces token consumption in long conversations with safer summary fallbacks and clearer failure visibility. Reduces token consumption in long conversations through intelligent summarization while maintaining coherence.
**Version:** 1.5.0 **Version:** 1.4.0
[:octicons-arrow-right-24: Documentation](async-context-compression.md) [:octicons-arrow-right-24: Documentation](async-context-compression.md)

View File

@@ -20,11 +20,11 @@ Filter 充当消息管线中的中间件:
--- ---
通过更稳健的摘要回退和更清晰的失败提示,降低长对话的 token 消耗保持连贯性。 通过智能总结减少长对话的 token 消耗,同时保持连贯性。
**版本:** 1.5.0 **版本:** 1.4.0
[:octicons-arrow-right-24: 查看文档](async-context-compression.zh.md) [:octicons-arrow-right-24: 查看文档](async-context-compression.md)
- :material-text-box-plus:{ .lg .middle } **Context Enhancement** - :material-text-box-plus:{ .lg .middle } **Context Enhancement**

View File

@@ -1,139 +0,0 @@
# Batch Install Plugins from GitHub
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.0.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
---
One-click batch install plugins from GitHub repositories to your OpenWebUI instance.
## Key Features
- **One-Click Install**: Install all plugins with a single command
- **Auto-Update**: Automatically updates previously installed plugins
- **GitHub Support**: Install plugins from any GitHub repository
- **Multi-Type Support**: Supports Pipe, Action, Filter, and Tool plugins
- **Confirmation**: Shows plugin list before installing, allows selective installation
- **i18n**: Supports 11 languages
## Flow
```
User Input
┌─────────────────────────────────────┐
│ Discover Plugins from GitHub │
│ (fetch file tree + parse .py) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Filter by Type & Keywords │
│ (tool/filter/pipe/action) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Show Confirmation Dialog │
│ (list plugins + exclude hint) │
└─────────────────────────────────────┘
├── [Cancel] → End
┌─────────────────────────────────────┐
│ Install to OpenWebUI │
│ (update or create each plugin) │
└─────────────────────────────────────┘
Done
```
## How to Use
1. Open OpenWebUI and go to **Workspace > Tools**
2. Install **Batch Install Plugins from GitHub** from the official marketplace
3. Enable this tool for your model/chat
4. Ask the model to install plugins
## Usage Examples
```
"Install all plugins"
"Install all plugins from github.com/username/repo"
"Install only pipe plugins"
"Install action and filter plugins"
"Install all plugins, exclude_keywords=copilot"
```
## Popular Plugin Repositories
Here are some popular repositories with many plugins you can install:
### Community Collections
```
# Install all plugins from iChristGit's collection
"Install all plugins from iChristGit/OpenWebui-Tools"
# Install all tools from Haervwe's tools collection
"Install all plugins from Haervwe/open-webui-tools"
# Install all plugins from Classic298's repository
"Install all plugins from Classic298/open-webui-plugins"
# Install all functions from suurt8ll's collection
"Install all plugins from suurt8ll/open_webui_functions"
# Install only specific types (e.g., only tools)
"Install only tool plugins from iChristGit/OpenWebui-Tools"
# Exclude certain keywords while installing
"Install all plugins from Haervwe/open-webui-tools, exclude_keywords=test,deprecated"
```
### Supported Repositories
- `Fu-Jie/openwebui-extensions` - Default, official plugin collection
- `iChristGit/OpenWebui-Tools` - Comprehensive tool and plugin collection
- `Haervwe/open-webui-tools` - Specialized tools and utilities
- `Classic298/open-webui-plugins` - Various plugin implementations
- `suurt8ll/open_webui_functions` - Function-based plugins
## Default Repository
When no repository is specified, defaults to `Fu-Jie/openwebui-extensions`.
## Plugin Detection Rules
### Fu-Jie/openwebui-extensions (Strict)
For the default repository, plugins must have:
1. A `.py` file containing `class Tools:`, `class Filter:`, `class Pipe:`, or `class Action:`
2. A docstring with `title:`, `description:`, and **`openwebui_id:`** fields
3. Filename must not end with `_cn`
### Other GitHub Repositories
For other repositories:
1. A `.py` file containing `class Tools:`, `class Filter:`, `class Pipe:`, or `class Action:`
2. A docstring with `title:` and `description:` fields
## Configuration (Valves)
| Parameter | Default | Description |
| --- | --- | --- |
| `SKIP_KEYWORDS` | `test,verify,example,template,mock` | Comma-separated keywords to skip |
| `TIMEOUT` | `20` | Request timeout in seconds |
## Confirmation Timeout
User confirmation dialogs have a default timeout of **2 minutes (120 seconds)**, allowing sufficient time for users to:
- Read and review the plugin list
- Make installation decisions
- Handle network delays
## Support
If this plugin has been useful, a star on [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) is a big motivation for me. Thank you for the support.

View File

@@ -1,139 +0,0 @@
# Batch Install Plugins from GitHub - 从 GitHub 批量安装插件
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.0.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可:** MIT
---
一键从 GitHub 仓库批量安装插件到你的 OpenWebUI 实例。
## ✨ 主要特性
- **一键安装**: 一条命令安装所有插件
- **自动更新**: 自动更新之前已安装的插件
- **GitHub 支持**: 支持从任何 GitHub 仓库安装插件
- **多类型支持**: 支持 Pipe、Action、Filter 和 Tool 插件
- **确认机制**: 安装前显示插件列表,允许选择性安装
- **国际化**: 支持 11 种语言
## 工作流
```
用户输入
┌─────────────────────────────────────┐
│ 从 GitHub 发现插件 │
│ (获取文件树 + 解析 .py) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 按类型和关键词过滤 │
│ (tool/filter/pipe/action) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 显示确认对话框 │
│ (插件列表 + 排除提示) │
└─────────────────────────────────────┘
├── [取消] → 结束
┌─────────────────────────────────────┐
│ 安装到 OpenWebUI │
│ (更新或创建每个插件) │
└─────────────────────────────────────┘
完成
```
## 🚀 使用方法
1. 打开 OpenWebUI进入 **工作区 > 工具**
2. 从官方市场安装 **Batch Install Plugins from GitHub**
3. 为你的模型/聊天启用此工具
4. 让模型安装插件
## 使用示例
```
"安装所有插件"
"从 github.com/username/repo 安装所有插件"
"仅安装 pipe 插件"
"安装 action 和 filter 插件"
"安装所有插件exclude_keywords=copilot"
```
## 热门插件仓库
这些是包含大量插件的热门仓库,你可以从中安装插件:
### 社区合集
```
# 从 iChristGit 的集合安装所有插件
"从 iChristGit/OpenWebui-Tools 安装所有插件"
# 从 Haervwe 的工具集合只安装工具
"从 Haervwe/open-webui-tools 安装所有插件"
# 从 Classic298 的仓库安装所有插件
"从 Classic298/open-webui-plugins 安装所有插件"
# 从 suurt8ll 的集合安装所有函数
"从 suurt8ll/open_webui_functions 安装所有插件"
# 仅安装特定类型的插件(比如只安装工具)
"从 iChristGit/OpenWebui-Tools 仅安装 tool 插件"
# 安装时排除特定关键词
"从 Haervwe/open-webui-tools 安装所有插件exclude_keywords=test,deprecated"
```
### 支持的仓库
- `Fu-Jie/openwebui-extensions` - 默认的官方插件集合
- `iChristGit/OpenWebui-Tools` - 全面的工具和插件集合
- `Haervwe/open-webui-tools` - 专业的工具和实用程序
- `Classic298/open-webui-plugins` - 各种插件实现
- `suurt8ll/open_webui_functions` - 基于函数的插件
## 默认仓库
未指定仓库时,默认使用 `Fu-Jie/openwebui-extensions`
## 插件检测规则
### Fu-Jie/openwebui-extensions严格模式
对于默认仓库,插件必须有:
1. 包含 `class Tools:``class Filter:``class Pipe:``class Action:``.py` 文件
2. 包含 `title:``description:`**`openwebui_id:`** 字段的文档字符串
3. 文件名不能以 `_cn` 结尾
### 其他 GitHub 仓库
对于其他仓库:
1. 包含 `class Tools:``class Filter:``class Pipe:``class Action:``.py` 文件
2. 包含 `title:``description:` 字段的文档字符串
## 配置 (Valves)
| 参数 | 默认值 | 描述 |
| --- | --- | --- |
| `SKIP_KEYWORDS` | `test,verify,example,template,mock` | 要跳过的关键词,用逗号分隔 |
| `TIMEOUT` | `20` | 请求超时时间(秒) |
## 确认超时时间
用户确认对话框的默认超时时间为 **2 分钟120 秒)**,为用户提供充足的时间来:
- 阅读和查看插件列表
- 做出安装决定
- 处理网络延迟
## 支持
如果这个插件对你有帮助,欢迎到 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 点个 Star这将是我持续改进的动力感谢支持。

View File

@@ -4,6 +4,5 @@ OpenWebUI native Tool plugins that can be used across models.
## Available Tool Plugins ## Available Tool Plugins
- [Batch Install Plugins from GitHub](batch-install-plugins-tool.md) (v1.0.0) - One-click batch install plugins from GitHub repositories with confirmation and multi-language support.
- [OpenWebUI Skills Manager Tool](openwebui-skills-manager-tool.md) (v0.3.0) - Simple native skill management (`list/show/install/create/update/delete`). - [OpenWebUI Skills Manager Tool](openwebui-skills-manager-tool.md) (v0.3.0) - Simple native skill management (`list/show/install/create/update/delete`).
- [Smart Mind Map Tool](smart-mind-map-tool.md) (v1.0.0) - Intelligently analyzes text content and proactively generates interactive mind maps to help users structure and visualize knowledge. - [Smart Mind Map Tool](smart-mind-map-tool.md) (v1.0.0) - Intelligently analyzes text content and proactively generates interactive mind maps to help users structure and visualize knowledge.

View File

@@ -4,6 +4,5 @@
## 可用 Tool 插件 ## 可用 Tool 插件
- [Batch Install Plugins from GitHub](batch-install-plugins-tool.zh.md) (v1.0.0) - 一键从 GitHub 仓库批量安装插件,支持确认和多语言。
- [OpenWebUI Skills 管理工具](openwebui-skills-manager-tool.zh.md) (v0.3.0) - 简化技能管理(`list/show/install/create/update/delete`)。 - [OpenWebUI Skills 管理工具](openwebui-skills-manager-tool.zh.md) (v0.3.0) - 简化技能管理(`list/show/install/create/update/delete`)。
- [智能思维导图工具 (Smart Mind Map Tool)](smart-mind-map-tool.zh.md) (v1.0.0) - 智能分析文本内容并主动生成交互式思维导图,帮助用户结构化与可视化知识。 - [智能思维导图工具 (Smart Mind Map Tool)](smart-mind-map-tool.zh.md) (v1.0.0) - 智能分析文本内容并主动生成交互式思维导图,帮助用户结构化与可视化知识。

View File

@@ -128,13 +128,11 @@ We follow [Semantic Versioning](https://semver.org/):
### release.yml ### release.yml
**Triggers:** **Triggers:**
- ⭐ Push to `main` branch with `plugins/**/*.py` changes (auto-release) - ⭐ Push to `main` branch with `plugins/**/*.py` changes (auto-release)
- Manual workflow dispatch - Manual workflow dispatch
- Push of version tags (`v*`) - Push of version tags (`v*`)
**Actions:** **Actions:**
1. Detects version changes compared to last release 1. Detects version changes compared to last release
2. Collects updated plugin files 2. Collects updated plugin files
3. Generates release notes (with commit history) 3. Generates release notes (with commit history)
@@ -143,11 +141,9 @@ We follow [Semantic Versioning](https://semver.org/):
### plugin-version-check.yml ### plugin-version-check.yml
**Trigger:** **Trigger:**
- Pull requests that modify `plugins/**/*.py` - Pull requests that modify `plugins/**/*.py`
**Actions:** **Actions:**
1. Compares plugin versions between base and PR 1. Compares plugin versions between base and PR
2. Checks if version was updated 2. Checks if version was updated
3. Checks if PR description is detailed enough 3. Checks if PR description is detailed enough
@@ -191,31 +187,6 @@ python scripts/extract_plugin_versions.py --json --output versions.json
--- ---
## Installing All Plugins to Your Instance
After a release, you can quickly install all plugins to your OpenWebUI instance:
```bash
# Clone the repository
git clone https://github.com/Fu-Jie/openwebui-extensions.git
cd openwebui-extensions
# Configure API key and instance URL
echo "api_key=sk-your-api-key-here" > scripts/.env
echo "url=http://localhost:3000" >> scripts/.env
# For remote instances, set the appropriate baseURL:
# echo "url=http://192.168.1.10:3000" >> scripts/.env
# echo "url=https://openwebui.example.com" >> scripts/.env
# Install all plugins at once
python scripts/install_all_plugins.py
```
For detailed instructions, see [Deployment Guide](https://github.com/Fu-Jie/openwebui-extensions/blob/main/scripts/DEPLOYMENT_GUIDE.md).
---
## Author ## Author
Fu-Jie Fu-Jie

View File

@@ -128,13 +128,11 @@ git push origin v1.0.0
### release.yml ### release.yml
**触发条件:** **触发条件:**
- ⭐ 推送到 `main` 分支且修改了 `plugins/**/*.py`(自动发布) - ⭐ 推送到 `main` 分支且修改了 `plugins/**/*.py`(自动发布)
- 手动触发 (workflow_dispatch) - 手动触发 (workflow_dispatch)
- 推送版本标签 (`v*`) - 推送版本标签 (`v*`)
**动作:** **动作:**
1. 检测与上次 Release 的版本变化 1. 检测与上次 Release 的版本变化
2. 收集更新的插件文件 2. 收集更新的插件文件
3. 生成发布说明(含提交记录) 3. 生成发布说明(含提交记录)
@@ -143,11 +141,9 @@ git push origin v1.0.0
### plugin-version-check.yml ### plugin-version-check.yml
**触发条件:** **触发条件:**
- 修改 `plugins/**/*.py` 的 Pull Request - 修改 `plugins/**/*.py` 的 Pull Request
**动作:** **动作:**
1. 比较基础分支和 PR 的插件版本 1. 比较基础分支和 PR 的插件版本
2. 检查是否有版本更新 2. 检查是否有版本更新
3. 检查 PR 描述是否足够详细 3. 检查 PR 描述是否足够详细
@@ -189,31 +185,6 @@ python scripts/extract_plugin_versions.py --json --output versions.json
--- ---
## 批量安装所有插件到你的实例
在发布之后,你可以快速将所有插件安装到 OpenWebUI 实例:
```bash
# 克隆仓库
git clone https://github.com/Fu-Jie/openwebui-extensions.git
cd openwebui-extensions
# 配置 API 密钥和实例地址
echo "api_key=sk-your-api-key-here" > scripts/.env
echo "url=http://localhost:3000" >> scripts/.env
# 如果是远程实例,需要设置相应的 baseURL
# echo "url=http://192.168.1.10:3000" >> scripts/.env
# echo "url=https://openwebui.example.com" >> scripts/.env
# 一次性安装所有插件
python scripts/install_all_plugins.py
```
详细说明请参考 [部署指南](https://github.com/Fu-Jie/openwebui-extensions/blob/main/scripts/DEPLOYMENT_GUIDE.md)。
---
## 作者 ## 作者
Fu-Jie Fu-Jie

View File

@@ -340,45 +340,5 @@
"total_saves": 274, "total_saves": 274,
"followers": 220, "followers": 220,
"points": 271 "points": 271
},
{
"date": "2026-03-12",
"total_posts": 27,
"total_downloads": 8765,
"total_views": 92460,
"total_upvotes": 300,
"total_saves": 431,
"followers": 344,
"points": 351,
"contributions": 66,
"posts": {
"turn_any_text_into_beautiful_mind_maps_3094c59a": 1730,
"smart_infographic_ad6f0c7f": 1330,
"markdown_normalizer_baaa8732": 807,
"export_to_word_enhanced_formatting_fca6a315": 767,
"async_context_compression_b1655bc8": 760,
"ai_task_instruction_generator_9bab8b37": 666,
"export_mulit_table_to_excel_244b8f9d": 604,
"openwebui_skills_manager_tool_b4bce8e4": 434,
"github_copilot_official_sdk_pipe_ce96f7b4": 399,
"flash_card_65a2ea8f": 325,
"deep_dive_c0b846e4": 224,
"导出为_word_支持公式流程图表格和代码块_8a6306c0": 171,
"folder_memory_auto_evolving_project_context_4a9875b2": 125,
"smart_mind_map_tool_auto_generate_interactive_know_d25f4e3d": 100,
"github_copilot_sdk_files_filter_403a62ee": 93,
"智能信息图_e04a48ff": 71,
"智能生成交互式思维导图帮助用户可视化知识_8d4b097b": 53,
"异步上下文压缩_5c0617cb": 40,
"闪记卡生成插件_4a31eac3": 34,
"精读_99830b0f": 32,
"an_unconventional_use_of_open_terminal_35498f8f": 0,
"github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452": 0,
"github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131": 0,
"github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293": 0,
"open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e": 0,
"review_of_claude_haiku_45_41b0db39": 0,
"debug_open_webui_plugins_in_your_browser_81bf7960": 0
}
} }
] ]

51
original_system_prompt.md Normal file
View File

@@ -0,0 +1,51 @@
You are a helpful assistant.
[Session Context]
- **Your Isolated Workspace**: `/app/backend/data/copilot_workspace/user_123/chat_456`
- **Active User ID**: `user_123`
- **Active Chat ID**: `chat_456`
- **Skills Directory**: `/app/backend/data/skills/shared/` — contains user-installed skills.
- **Config Directory**: `/app/backend/data/.copilot` — system configuration (Restricted).
- **CLI Tools Path**: `/app/backend/data/.copilot_tools/` — Global tools installed via npm or pip will automatically go here and be in your $PATH. Python tools are strictly isolated in a venv here.
**CRITICAL INSTRUCTION**: You MUST use the above workspace for ALL file operations.
- DO NOT create files in `/tmp` or any other system directories.
- Always interpret 'current directory' as your Isolated Workspace.
[Available Native System Tools]
The host environment is rich. Based on the official OpenWebUI Docker deployment baseline (backend image), the following CLI tools are expected to be preinstalled and globally available in $PATH:
- **Network/Data**: `curl`, `jq`, `netcat-openbsd`
- **Media/Doc**: `pandoc` (format conversion), `ffmpeg` (audio/video)
- **Build/System**: `git`, `gcc`, `make`, `build-essential`, `zstd`, `bash`
- **Python/Runtime**: `python3`, `pip3`, `uv`
- **Verification Rule**: Before installing any CLI/tool dependency, first check availability with `which <tool>` or a lightweight version probe (e.g. `<tool> --version`).
- **Python Libs**: The active virtual environment inherits `--system-site-packages`. Advanced libraries like `pandas`, `numpy`, `pillow`, `opencv-python-headless`, `pypdf`, `langchain`, `playwright`, `httpx`, and `beautifulsoup4` are ALREADY installed. Try importing them before attempting to install.
[Mode Context: Plan Mode]
You are currently operating in **Plan Mode**.
DEFINITION: Plan mode is a collaborative phase to outline multi-step plans or conduct research BEFORE any code is modified.
<workflow>
1. Clarification: If requirements/goals are ambiguous, ask questions.
2. Analysis: Analyze the codebase to understand constraints. You MAY use shell commands (e.g., `ls`, `grep`, `find`, `cat`) and other read-only tools.
3. Formulation: Generate your structured plan OR research findings.
4. Approval: Present the detailed plan directly to the user for approval via chat.
</workflow>
<key_principles>
- ZERO CODE MODIFICATION: You must NOT execute file edits, write operations, or destructive system changes. Your permissions are locked to READ/RESEARCH ONLY, with the sole exception of the progress-tracking file `plan.md`.
- SHELL USAGE: Shell execution is ENABLED for research purposes. Any attempts to modify the filesystem via shell (e.g., `sed -i`, `rm`) will be strictly blocked, except for appending to `plan.md`.
- PURE RESEARCH SUPPORT: If the user requests a pure research report, output your conclusions directly matching the plan style.
- PERSISTENCE: You MUST save your proposed plan to `/app/backend/data/.copilot/session-state/chat_456/plan.md` to sync with the UI. The UI automatically reads this file to update the plan view.
</key_principles>
<plan_format>
When presenting your findings or plan in the chat, structure it clearly:
## Plan / Report: {Title}
**TL;DR**: {Summary}
**Detailed Tasks / Steps**: {List step-by-step}
**Affected Files**:
- `path/to/file`
**Constraint/Status**: {Any constraints}
</plan_format>
Acknowledge your role as a planner and format your next response using the plan style above.

View File

@@ -1,62 +0,0 @@
# 异步上下文压缩插件:当前问题与处理状态总结
这份文档详细梳理了我们在处理 `async_context_compression`(异步上下文压缩插件)时,遭遇的“幽灵截断”问题的根本原因,以及我们目前的解决进度。
## 1. 根本原因:两种截然不同的“世界观”(数据序列化差异)
在我们之前的排查中,我曾错误地认为:`outlet`(后置处理阶段)拿到的 `body["messages"]` 是由于截断导致的残缺数据。
但根据您提供的本地运行日志,**您是对的,`body['messages']` 确实包含了完整的对话历史**。
那么为什么长度会产生 `inlet 看到 27 条`,而 `outlet 只看到 8 条` 这种巨大的差异?
原因在于OpenWebUI 的管道在进入大模型前和从大模型返回后,使用了**两种完全不同的消息格式**
### 视图 AInlet 阶段(原生 API 展开视图)
- **特点**:严格遵循 OpenAI 函数调用规范。
- **状态**:每一次工具调用、工具返回,都被视为一条独立的 message。
- **例子**:一个包含了复杂搜索的对话。
- User: 帮我查一下天气1条
- Assistant: 发起 tool_call1条
- Tool: 返回 JSON 结果1条
- ...多次往复...
- **最终总计27 条。**我们的压缩算法trim是基于这个 27 条的坐标系来计算保留多少条的。
### 视图 BOutlet 阶段UI HTML 折叠视图)
- **特点**:专为前端渲染优化的紧凑视图。
- **状态**OpenWebUI 在调用完模型后,为了让前端显示出那个好看的、可折叠的工具调用卡片,强行把中间所有的 Tool 交互过程,用 `<details type="tool_calls">...</details>` 的 HTML 代码包裹起来,塞进了一个 `role: assistant``content` 字符串里!
- **例子**:同样的对话。
- User: 帮我查一下天气1条
- Assistant: `<details>包含了好多次工具调用和结果的代码</details> 今天天气很好...`1条
- **最终总计8 条。**
**💥 灾难发生点:**
原本的插件逻辑假定 `inlet``outlet` 共享同一个坐标系。
1.`inlet` 时,系统计算出:“我需要把前 10 条消息生成摘要,保留后 17 条”。
2. 系统把“生成前10条摘要”的任务转入后台异步执行。
3. 后台任务在 `outlet` 阶段被触发,此时它拿到的消息数组变成了**视图 B总共只有 8 条)。**
4. 算法试图在只有 8 条消息的数组里,把“前 10 条消息”砍掉并替换为 1 条摘要。
5. **结果就是:数组索引越界/坐标彻底错乱,触发报错,并且可能将最新的有效消息当成旧消息删掉(过度压缩)。**
---
## 2. 目前已解决的问题 (✅ Done)
为了立刻制止这种因为“坐标系错位”导致的数据破坏我们已经落实了热修复Local v1.4.0
**✅ 添加了“折叠视图”的探针防御:**
- 我写了一个函数 `_is_compact_tool_details_view`
- 现在,当后台触发生成摘要时,系统会自动扫描 `outlet` 传来的 `messages`。只要发现里面包含 `<details type="tool_calls">` 这种带有 HTML 折叠标签的痕迹,就会**立刻终止并跳过**当前的摘要生成任务。
- **收益**彻底杜绝了因数组错位而引发的任务报错和强制裁切。UI 崩溃与历史丢失问题得到遏制。
---
## 3. 当前已解决的遗留问题 (✅ Done: 逆向展开修复)
之前因为跳过生成而引入的新限制:**包含工具调用的长轮次对话,无法自动生成“历史摘要”** 的问题,现已彻底解决。
### 最终实施的技术方案:
我们通过源码分析发现OpenWebUI 在进入 `inlet` 时会执行 `convert_output_to_messages` 还原工具调用链。因此,我们在插件的 `outlet` 阶段引入了相同的 **逆向展开 (Deflation/Unfolding)** 机制 `_unfold_messages`
现在,当后台任务拿到 `outlet` 传来的折叠视图时,不会再选择“跳过”。而是自动提取出潜藏在消息对象体内部的原生 `output` 字段,并**将其重新展开为展开视图**(比如将 8 条假象重新还原为真实的 27 条底层数据),使得它的坐标系与 `inlet` 完全对齐。
至此,带有复杂工具调用的长轮次对话也能安全地进行背景自动压缩,不再有任何截断和强制删减的风险!

View File

@@ -1,60 +0,0 @@
# 回复 dhaern — 针对最新审查的跟进
感谢您重新审查了最新版本并提出了持续精准的分析意见。以下针对您剩余的两个关切点逐一回应。
---
### 1. `enable_tool_output_trimming` — 不是功能退化,而是行为变化是有意为之
裁剪逻辑依然存在且可正常运行。以下是当前版本与之前版本的行为对比。
**当前行为(`_trim_native_tool_outputs`,第 835945 行):**
- 通过 `_get_atomic_groups` 遍历原子分组。
- 识别有效的工具调用链:`assistant(tool_calls)``tool` → [可选的 assistant 跟进消息]。
- 如果一条链内所有 `tool` 角色消息的字符数总和超过 **1,200 个字符**,则将 *tool 消息本身的内容* 折叠为一个本地化的 `[Content collapsed]` 占位符,并注入 `metadata.is_trimmed` 标志。
- 同时遍历包含 `<details type="tool_calls">` HTML 块的 assistant 消息,对其中尺寸过大的 `result` 属性进行相同的折叠处理。
-`enable_tool_output_trimming=True``function_calling=native` 时,该函数在 inlet 阶段被调用。
**与旧版本的区别:**
旧版的做法是改写 *assistant 跟进消息*,仅保留"最终答案"。新版的做法是折叠 *tool 响应内容本身*。两者都会缩减上下文体积,但新方法能够保留 tool 调用链的结构完整性(这是本次发布中原子分组工作的前提条件)。
插件头部的 docstring 里还有一段过时的描述("提取最终答案"),与实际行为相悖。最新提交中已将其更正为"将尺寸过大的原生工具输出折叠为简短占位符"。
如果您在寻找旧版本中"仅保留最终答案"的特定行为,该路径已被有意移除,因为它与本次发布引入的原子分组完整性保证相冲突。当前的折叠方案是安全的替代实现。
---
### 2. `compressed_message_count` — 修复是真实有效的;以下是坐标系追踪
您对"从已修改视图重新计算"的担忧,考虑到此前的架构背景,是完全可以理解的。以下精确说明为何当前代码不存在这一问题。
**`outlet` 中的关键变更:**
```python
db_messages = self._load_full_chat_messages(chat_id)
messages_to_unfold = db_messages if (db_messages and len(db_messages) >= len(messages)) else messages
summary_messages = self._unfold_messages(messages_to_unfold)
target_compressed_count = self._calculate_target_compressed_count(summary_messages)
```
`_load_full_chat_messages` 从 OpenWebUI 数据库中获取原始的持久化历史记录。由于在 inlet 渲染期间注入的合成 summary 消息**从未被回写到数据库**,从 DB 路径获取的 `summary_messages` 始终是干净的、未经修改的原始历史记录——没有 summary 标记,没有坐标膨胀。
在此干净列表上调用 `_calculate_target_compressed_count` 的计算逻辑如下(仍在原始历史坐标系内):
```
original_count = len(db_messages)
raw_target = original_count - keep_last
target = atomic_align(raw_target)
```
这个 `target_compressed_count` 值原封不动地传递进 `_generate_summary_async`。在异步任务内部,同一批 `db_messages` 被切片为 `messages[start:target]` 来构建 `middle_messages`。生成完成后(可能从末尾进行原子截断),保存的值为:
```python
saved_compressed_count = start_index + len(middle_messages)
```
这是原始 DB 消息列表中新摘要实际涵盖到的确切位置——不是目标值,也不是来自不同视图的估算值。
**回退路径DB 不可用时)** 使用 inlet 渲染后的 body 消息。此时 `_get_summary_view_state` 会读取注入的 summary 标记的 `covered_until` 字段(该字段在写入时已记录为原子对齐后的 `start_index`),因此 `base_progress` 已经处于原始历史坐标系内,计算可以自然延续,不会混用两种视图。
简而言之:该字段在整个调用链中现在具有唯一、一致的语义——即原始持久化消息列表中,当前摘要文本实际覆盖到的索引位置。
---
再次感谢您严格的审查。您在上次发布后标记的这两个问题已得到处理,文档中的过时描述也已更正。如果发现其他问题,欢迎继续反馈。

View File

@@ -1,60 +0,0 @@
# Reply to dhaern - Follow-up on the Latest Review
Thank you for re-checking the latest version and for the continued precise analysis. Let me address your two remaining concerns directly.
---
### 1. `enable_tool_output_trimming` — Not a regression; behavior change is intentional
The trimming logic is present and functional. Here is what it does now versus before.
**Current behavior (`_trim_native_tool_outputs`, lines 835945):**
- Iterates over atomic groups via `_get_atomic_groups`.
- Identifies valid chains: `assistant(tool_calls)``tool` → [optional assistant follow-up].
- If the combined character count of the `tool` role messages in a chain exceeds **1,200 characters**, it collapses *the tool messages themselves* to a localized `[Content collapsed]` placeholder and injects a `metadata.is_trimmed` flag.
- Separately walks assistant messages containing `<details type="tool_calls">` HTML blocks and collapses oversized `result` attributes in the same way.
- The function is called at inlet when `enable_tool_output_trimming=True` and `function_calling=native`.
**What is different from the previous version:**
The old approach rewrote the *assistant follow-up* message to keep only the "final answer". The new approach collapses the *tool response content* itself. Both reduce context size, but the new approach preserves the structural integrity of the tool-calling chain (which the atomic grouping work in this release depends on).
The docstring in the plugin header also contained a stale description ("extract only the final answer") that contradicted the actual behavior. That has been corrected in the latest commit to accurately say "collapses oversized native tool outputs to a short placeholder."
If you are looking for the specific "keep only the final answer" behavior from the old version, that path was intentionally removed because it conflicted with the atomic-group integrity guarantees introduced in this release. The current collapse approach is a safe replacement.
---
### 2. `compressed_message_count` — The fix is real; here is the coordinate trace
The concern about "recalculating from the already-modified view" is understandable given the previous architecture. Here is exactly why the current code does not have that problem.
**Key change in `outlet`:**
```python
db_messages = self._load_full_chat_messages(chat_id)
messages_to_unfold = db_messages if (db_messages and len(db_messages) >= len(messages)) else messages
summary_messages = self._unfold_messages(messages_to_unfold)
target_compressed_count = self._calculate_target_compressed_count(summary_messages)
```
`_load_full_chat_messages` fetches the raw persisted history from the OpenWebUI database. Because the synthetic summary message (injected during inlet rendering) is **never written back to the database**, `summary_messages` from the DB path is always the clean, unmodified original history — no summary marker, no coordinate inflation.
`_calculate_target_compressed_count` called on this clean list simply computes:
```
original_count = len(db_messages)
raw_target = original_count - keep_last
target = atomic_align(raw_target) # still in original-history coordinates
```
This `target_compressed_count` value is then passed into `_generate_summary_async` unchanged. Inside the async task, the same `db_messages` list is sliced to `messages[start:target]` to build `middle_messages`. After generation (with potential atomic truncation from the end), the saved value is:
```python
saved_compressed_count = start_index + len(middle_messages)
```
This is the exact position in the original DB message list up to which the new summary actually covers — not a target, not an estimate from a different view.
**The fallback path (DB unavailable)** uses the inlet-rendered body messages. In that case `_get_summary_view_state` reads `covered_until` from the injected summary marker (which was written as the atomically-aligned `start_index`), so `base_progress` is already in original-history coordinates. The calculation naturally continues from there without mixing views.
In short: the field now has a single, consistent meaning throughout the entire call chain — the index (in the original, persisted message list) up to which the current summary text actually covers.
---
Thank you again for the rigorous review. The two points you flagged after the last release are now addressed, and the documentation stale description has been corrected. Please do let us know if you spot anything else.

View File

@@ -1,123 +0,0 @@
# ✅ Async Context Compression 部署完成2024-03-12
## 🎯 部署摘要
**日期**: 2024-03-12
**版本**: 1.4.1
**状态**: ✅ 成功部署
**目标**: OpenWebUI localhost:3003
---
## 📌 新增功能
### 前端控制台调试信息
`async_context_compression.py` 中增加了 6 个结构化数据检查点,可在浏览器 Console 中查看插件的内部数据流。
#### 新增方法
```python
async def _emit_struct_log(self, __event_call__, title: str, data: Any):
"""
Emit structured data to browser console.
- Arrays → console.table() [表格形式]
- Objects → console.dir(d, {depth: 3}) [树形结构]
"""
```
#### 6 个检查点
| # | 检查点 | 阶段 | 显示内容 |
|---|-------|------|--------|
| 1⃣ | `__user__ structure` | Inlet 入口 | id, name, language, resolved_language |
| 2⃣ | `__metadata__ structure` | Inlet 入口 | chat_id, message_id, function_calling |
| 3⃣ | `body top-level structure` | Inlet 入口 | model, message_count, metadata keys |
| 4⃣ | `summary_record loaded from DB` | Inlet DB 后 | compressed_count, summary_preview, timestamps |
| 5⃣ | `final_messages shape → LLM` | Inlet 返回前 | 表格:每条消息的 role、content_length、tools |
| 6⃣ | `middle_messages shape` | 异步摘要中 | 表格:要摘要的消息切片 |
---
## 🚀 快速开始5 分钟)
### 步骤 1: 启用 Filter
```
OpenWebUI → Settings → Filters → 启用 "Async Context Compression"
```
### 步骤 2: 启用调试
```
在 Filter 配置中 → show_debug_log: ON → Save
```
### 步骤 3: 打开控制台
```
F12 (Windows/Linux) 或 Cmd+Option+I (Mac) → Console 标签
```
### 步骤 4: 发送消息
```
发送 10+ 条消息,观察 📋 [Compression] 开头的日志
```
---
## 📊 代码变更
```
新增方法: _emit_struct_log() [42 行]
新增日志点: 6 个
新增代码总行: ~150 行
向后兼容: 100% (由 show_debug_log 保护)
```
---
## 💡 日志示例
### 表格日志Arrays
```
📋 [Compression] Inlet: final_messages shape → LLM (7 msgs)
┌─────┬──────────┬──────────────┬─────────────┐
│index│role │content_length│has_tool_... │
├─────┼──────────┼──────────────┼─────────────┤
│ 0 │"system" │150 │false │
│ 1 │"user" │200 │false │
│ 2 │"assistant"│500 │true │
└─────┴──────────┴──────────────┴─────────────┘
```
### 树形日志Objects
```
📋 [Compression] Inlet: __metadata__ structure
├─ chat_id: "chat-abc123..."
├─ message_id: "msg-xyz789"
├─ function_calling: "native"
└─ all_keys: ["chat_id", "message_id", ...]
```
---
## ✅ 验证清单
- [x] 代码变更已保存
- [x] 部署脚本成功执行
- [x] OpenWebUI 正常运行
- [x] 新增 6 个日志点
- [x] 防卡死保护已实装
- [x] 向后兼容性完整
---
## 📖 文档
- [QUICK_START.md](../../scripts/QUICK_START.md) - 快速参考
- [README_CN.md](./README_CN.md) - 插件说明
- [DEPLOYMENT_REFERENCE.md](./DEPLOYMENT_REFERENCE.md) - 部署工具
---
**部署时间**: 2024-03-12
**维护者**: Fu-Jie
**项目**: [openwebui-extensions](https://github.com/Fu-Jie/openwebui-extensions)

View File

@@ -1,19 +1,15 @@
# Async Context Compression Filter # Async Context Compression Filter
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 1.5.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT **Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 1.4.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent. This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent.
## What's new in 1.5.0 ## What's new in 1.4.0
- **External Chat Reference Summaries**: Added support for referenced chat context blocks that can reuse cached summaries, inject small referenced chats directly, or generate summaries for larger referenced chats before injection. - **Atomic Message Grouping**: Introduced structure-aware grouping for `assistant-tool-tool-assistant` chains to prevent "No tool call found" errors.
- **Fast Multilingual Token Estimation**: Added a new mixed-script token estimation pipeline so inlet/outlet preflight checks can avoid unnecessary exact token counts while staying much closer to real usage. - **Tail Boundary Alignment**: Implemented automatic correction for truncation points to ensure they don't fall inside a tool-calling sequence.
- **Stronger Working-Memory Prompt**: Refined the XML summary prompt to better preserve actionable context across general chat, coding tasks, and tool-heavy conversations. - **Chat Session Locking**: Added a session-based lock to prevent multiple concurrent summary tasks for the same chat ID.
- **Clearer Frontend Debug Logs**: Reworked browser-console logging into grouped structural snapshots that are easier to scan during debugging. - **Enhanced Traceability**: Improved summary formatting to include message IDs, names, and metadata for better context tracking.
- **Safer Tool Trimming Defaults**: Enabled native tool-output trimming by default and exposed a dedicated `tool_trim_threshold_chars` valve with a 600-character default.
- **Safer Referenced-Chat Fallbacks**: If generating a referenced chat summary fails, the new reference-summary path now falls back to direct contextual injection instead of failing the whole chat.
- **Correct Summary Budgeting**: `summary_model_max_context` now controls summary-input fitting, while `max_summary_tokens` remains an output-length cap.
- **More Visible Summary Failures**: Important background summary failures now surface in the browser console (`F12`) and as a status hint even when `show_debug_log` is off.
--- ---
@@ -25,85 +21,15 @@ This filter reduces token consumption in long conversations through intelligent
- ✅ Persistent storage via Open WebUI's shared database connection (PostgreSQL, SQLite, etc.). - ✅ Persistent storage via Open WebUI's shared database connection (PostgreSQL, SQLite, etc.).
- ✅ Flexible retention policy to keep the first and last N messages. - ✅ Flexible retention policy to keep the first and last N messages.
- ✅ Smart injection of historical summaries back into the context. - ✅ Smart injection of historical summaries back into the context.
- ✅ External chat reference summarization with cached-summary reuse, direct injection for small chats, and generated summaries for larger chats.
- ✅ Structure-aware trimming that preserves document structure (headers, intro, conclusion). - ✅ Structure-aware trimming that preserves document structure (headers, intro, conclusion).
- ✅ Native tool output trimming for cleaner context when using function calling. - ✅ Native tool output trimming for cleaner context when using function calling.
- ✅ Real-time context usage monitoring with warning notifications (>90%). - ✅ Real-time context usage monitoring with warning notifications (>90%).
-Fast multilingual token estimation plus exact token fallback for precise debugging and optimization. -Detailed token logging for precise debugging and optimization.
-**Smart Model Matching**: Automatically inherits configuration from base models for custom presets. -**Smart Model Matching**: Automatically inherits configuration from base models for custom presets.
-**Multimodal Support**: Images are preserved but their tokens are **NOT** calculated. Please adjust thresholds accordingly. -**Multimodal Support**: Images are preserved but their tokens are **NOT** calculated. Please adjust thresholds accordingly.
--- ---
## What This Fixes
- **Problem 1: A referenced chat could break the current request.**
Before, if the filter needed to summarize a referenced chat and that LLM call failed, the current chat could fail with it. Now it degrades gracefully and injects direct context instead.
- **Problem 2: Some referenced chats were being cut too aggressively.**
Before, the output limit (`max_summary_tokens`) could be treated like the input window, which made large referenced chats shrink earlier than necessary. Now input fitting uses the summary model's real context window (`summary_model_max_context` or model/global fallback).
- **Problem 3: Some background summary failures were too easy to miss.**
Before, a failure during background summary preparation could disappear quietly when frontend debug logging was off. Now important failures are forced to the browser console and also shown through a user-facing status message.
---
## Workflow Overview
This filter operates in two phases:
1. `inlet`: injects stored summaries, processes external chat references, and trims context when required before the request is sent to the model.
2. `outlet`: runs asynchronously after the response is complete, decides whether a new summary should be generated, and persists it when appropriate.
```mermaid
flowchart TD
A[Request enters inlet] --> B[Normalize tool IDs and optionally trim large tool outputs]
B --> C{Referenced chats attached?}
C -- No --> D[Load current chat summary if available]
C -- Yes --> E[Inspect each referenced chat]
E --> F{Existing cached summary?}
F -- Yes --> G[Reuse cached summary]
F -- No --> H{Fits direct budget?}
H -- Yes --> I[Inject full referenced chat text]
H -- No --> J[Prepare referenced-chat summary input]
J --> K{Referenced-chat summary call succeeds?}
K -- Yes --> L[Inject generated referenced summary]
K -- No --> M[Fallback to direct contextual injection]
G --> D
I --> D
L --> D
M --> D
D --> N[Build current-chat Head + Summary + Tail]
N --> O{Over max_context_tokens?}
O -- Yes --> P[Trim oldest atomic groups]
O -- No --> Q[Send final context to the model]
P --> Q
Q --> R[Model returns the reply]
R --> S[Outlet rebuilds the full history]
S --> T{Reached compression threshold?}
T -- No --> U[Finish]
T -- Yes --> V[Fit summary input to the summary model context]
V --> W{Background summary call succeeds?}
W -- Yes --> X[Save new chat summary and update status]
W -- No --> Y[Force browser-console error and show status hint]
```
### Key Notes
- `inlet` only injects and trims context. It does not generate the main chat summary.
- `outlet` performs summary generation asynchronously and does not block the current reply.
- External chat references may come from an existing persisted summary, a small chat's full text, or a generated/truncated reference summary.
- If a referenced-chat summary call fails, the filter falls back to direct context injection instead of failing the whole request.
- `summary_model_max_context` controls summary-input fitting. `max_summary_tokens` only controls how long the generated summary may be.
- Important background summary failures are surfaced to the browser console (`F12`) and the chat status area.
- External reference messages are protected during trimming so they are not discarded first.
---
## Installation & Configuration ## Installation & Configuration
### 1) Database (automatic) ### 1) Database (automatic)
@@ -127,12 +53,11 @@ flowchart TD
| `keep_first` | `1` | Always keep the first N messages (protects system prompts). | | `keep_first` | `1` | Always keep the first N messages (protects system prompts). |
| `keep_last` | `6` | Always keep the last N messages to preserve recent context. | | `keep_last` | `6` | Always keep the last N messages to preserve recent context. |
| `summary_model` | `None` | Model for summaries. Strongly recommended to set a fast, economical model (e.g., `gemini-2.5-flash`, `deepseek-v3`). Falls back to the current chat model when empty. | | `summary_model` | `None` | Model for summaries. Strongly recommended to set a fast, economical model (e.g., `gemini-2.5-flash`, `deepseek-v3`). Falls back to the current chat model when empty. |
| `summary_model_max_context` | `0` | Input context window used to fit summary requests. If `0`, falls back to `model_thresholds` or global `max_context_tokens`. | | `summary_model_max_context` | `0` | Max context tokens for the summary model. If 0, falls back to `model_thresholds` or global `max_context_tokens`. |
| `max_summary_tokens` | `16384` | Maximum output length for the generated summary. This is not the summary-input context limit. | | `max_summary_tokens` | `16384` | Maximum tokens for the generated summary. |
| `summary_temperature` | `0.1` | Randomness for summary generation. Lower is more deterministic. | | `summary_temperature` | `0.3` | Randomness for summary generation. Lower is more deterministic. |
| `model_thresholds` | `{}` | Per-model overrides for `compression_threshold_tokens` and `max_context_tokens` (useful for mixed models). | | `model_thresholds` | `{}` | Per-model overrides for `compression_threshold_tokens` and `max_context_tokens` (useful for mixed models). |
| `enable_tool_output_trimming` | `true` | When enabled for `function_calling: "native"`, trims oversized native tool outputs while keeping the tool-call chain intact. | | `enable_tool_output_trimming` | `false` | When enabled and `function_calling: "native"` is active, trims verbose tool outputs to extract only the final answer. |
| `tool_trim_threshold_chars` | `600` | Trim native tool output blocks once their total content length reaches this threshold. |
| `debug_mode` | `false` | Log verbose debug info. Set to `false` in production. | | `debug_mode` | `false` | Log verbose debug info. Set to `false` in production. |
| `show_debug_log` | `false` | Print debug logs to browser console (F12). Useful for frontend debugging. | | `show_debug_log` | `false` | Print debug logs to browser console (F12). Useful for frontend debugging. |
| `show_token_usage_status` | `true` | Show token usage status notification in the chat interface. | | `show_token_usage_status` | `true` | Show token usage status notification in the chat interface. |
@@ -148,12 +73,8 @@ If this plugin has been useful, a star on [OpenWebUI Extensions](https://github.
- **Initial system prompt is lost**: Keep `keep_first` greater than 0 to protect the initial message. - **Initial system prompt is lost**: Keep `keep_first` greater than 0 to protect the initial message.
- **Compression effect is weak**: Raise `compression_threshold_tokens` or lower `keep_first` / `keep_last` to allow more aggressive compression. - **Compression effect is weak**: Raise `compression_threshold_tokens` or lower `keep_first` / `keep_last` to allow more aggressive compression.
- **A referenced chat summary fails**: The current request should continue with a direct-context fallback. Check the browser console (`F12`) if you need the upstream failure details.
- **A background summary silently seems to do nothing**: Important failures now surface in chat status and the browser console (`F12`).
- **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [OpenWebUI Extensions Issues](https://github.com/Fu-Jie/openwebui-extensions/issues) - **Submit an Issue**: If you encounter any problems, please submit an issue on GitHub: [OpenWebUI Extensions Issues](https://github.com/Fu-Jie/openwebui-extensions/issues)
## Changelog ## Changelog
See [`v1.5.0` Release Notes](./v1.5.0.md) for the release-specific summary.
See the full history on GitHub: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) See the full history on GitHub: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)

View File

@@ -1,21 +1,17 @@
# 异步上下文压缩过滤器 # 异步上下文压缩过滤器
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 1.5.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT **作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 1.4.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
> **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。 > **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
本过滤器通过智能摘要和消息压缩技术,在保持对话连贯性的同时,显著降低长对话的 Token 消耗。 本过滤器通过智能摘要和消息压缩技术,在保持对话连贯性的同时,显著降低长对话的 Token 消耗。
## 1.5.0 版本更新 ## 1.4.0 版本更新
- **外部聊天引用摘要**: 新增对引用聊天上下文的摘要支持。现在可以复用缓存摘要、直接注入较小引用聊天,或先为较大的引用聊天生成摘要再注入 - **原子消息组 (Atomic Grouping)**: 引入结构感知的消息分组逻辑,确保工具调用链被整体保留或移除,彻底解决 "No tool call found" 错误
- **快速多语言 Token 预估**: 新增混合脚本 Token 预估链路,使 inlet / outlet 的预检可以减少不必要的精确计数,同时比旧的粗略字符比值更接近真实用量 - **尾部边界自动对齐**: 实现了截断点的自动修正逻辑,确保历史上下文截断不会落在工具调用序列中间
- **更稳健的工作记忆提示词**: 重写 XML 摘要提示词,增强普通聊天、编码任务和连续工具调用场景下的关键信息保留能力 - **会话级异步锁**: 增加了基于 `chat_id` 的后台任务锁,防止同一会话并发触发多个总结任务
- **更清晰的前端调试日志**: 浏览器控制台日志改为分组化、结构化展示,排查上下文压缩行为更直观 - **元数据溯源增强**: 优化了总结输入格式,在总结中保留了消息 ID、参与者名称及关键元数据提升上下文可追踪性
- **更安全的工具裁剪默认值**: 原生工具输出裁剪默认开启,并新增 `tool_trim_threshold_chars` 配置项,默认阈值为 600 字符。
- **更稳妥的引用聊天回退**: 当新的引用聊天摘要路径生成失败时,不再拖垮当前请求,而是自动回退为直接注入上下文。
- **更准确的摘要预算**: `summary_model_max_context` 现在只负责摘要输入窗口,`max_summary_tokens` 继续只负责摘要输出长度。
- **更容易发现摘要失败**: 重要的后台摘要失败现在会强制显示到浏览器控制台 (`F12`),并同步给出状态提示。
--- ---
@@ -27,84 +23,14 @@
-**持久化存储**: 复用 Open WebUI 共享数据库连接,自动支持 PostgreSQL/SQLite 等。 -**持久化存储**: 复用 Open WebUI 共享数据库连接,自动支持 PostgreSQL/SQLite 等。
-**灵活保留策略**: 可配置保留对话头部和尾部消息,确保关键信息连贯。 -**灵活保留策略**: 可配置保留对话头部和尾部消息,确保关键信息连贯。
-**智能注入**: 将历史摘要智能注入到新上下文中。 -**智能注入**: 将历史摘要智能注入到新上下文中。
-**外部聊天引用摘要**: 支持复用缓存摘要、小聊天直接注入,以及大聊天先摘要后注入。
-**结构感知裁剪**: 智能折叠过长消息,保留文档骨架(标题、首尾)。 -**结构感知裁剪**: 智能折叠过长消息,保留文档骨架(标题、首尾)。
-**原生工具输出裁剪**: 支持裁剪冗长的工具调用输出。 -**原生工具输出裁剪**: 支持裁剪冗长的工具调用输出。
-**实时监控**: 实时监控上下文使用情况,超过 90% 发出警告。 -**实时监控**: 实时监控上下文使用情况,超过 90% 发出警告。
-**快速预估 + 精确回退**: 提供更快的多语言 Token 预估,并在必要时回退到精确统计,便于调试。 -**详细日志**: 提供精确的 Token 统计日志,便于调试。
-**智能模型匹配**: 自定义模型自动继承基础模型的阈值配置。 -**智能模型匹配**: 自定义模型自动继承基础模型的阈值配置。
-**多模态支持**: 图片内容会被保留,但其 Token **不参与计算**。请相应调整阈值。 -**多模态支持**: 图片内容会被保留,但其 Token **不参与计算**。请相应调整阈值。
详细的工作原理和更长说明仍可参考 [工作流程指南](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/WORKFLOW_GUIDE_CN.md)。 详细的工作原理和流程请参考 [工作流程指南](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/WORKFLOW_GUIDE_CN.md)。
---
## 这次解决了什么问题(通俗版)
- **问题 1引用别的聊天时摘要失败可能把当前对话一起弄挂。**
以前如果过滤器需要先帮被引用聊天做摘要,而这一步的 LLM 调用失败了,当前请求也可能直接失败。现在改成了“能摘要就摘要,失败就退回直接塞上下文”,当前对话不会被一起拖死。
- **问题 2有些被引用聊天被截得太早信息丢得太多。**
以前有一段逻辑把 `max_summary_tokens` 这种“输出长度限制”误当成了“输入上下文窗口”,结果大一点的引用聊天会被过早截断。现在改成按摘要模型真实的输入窗口来算,能保留更多有用内容。
- **问题 3后台摘要失败时用户不容易知道发生了什么。**
以前在 `show_debug_log=false` 时,有些后台失败只会留在内部日志里。现在关键失败会强制打到浏览器控制台,并在聊天状态里提醒去看 `F12`
---
## 工作流总览
该过滤器分为两个阶段:
1. `inlet`:在请求发送给模型前执行,负责注入已有摘要、处理外部聊天引用、并在必要时裁剪上下文。
2. `outlet`:在模型回复完成后异步执行,负责判断是否需要生成新摘要,并在合适时写入数据库。
```mermaid
flowchart TD
A[请求进入 inlet] --> B[规范化工具 ID 并按需裁剪超长工具输出]
B --> C{是否附带引用聊天?}
C -- 否 --> D[如果有当前聊天摘要就先加载]
C -- 是 --> E[逐个检查被引用聊天]
E --> F{已有缓存摘要?}
F -- 是 --> G[直接复用缓存摘要]
F -- 否 --> H{能直接放进当前预算?}
H -- 是 --> I[直接注入完整引用聊天文本]
H -- 否 --> J[准备引用聊天的摘要输入]
J --> K{引用聊天摘要调用成功?}
K -- 是 --> L[注入生成后的引用摘要]
K -- 否 --> M[回退为直接注入上下文]
G --> D
I --> D
L --> D
M --> D
D --> N[为当前聊天构造 Head + Summary + Tail]
N --> O{是否超过 max_context_tokens?}
O -- 是 --> P[从最旧 atomic groups 开始裁剪]
O -- 否 --> Q[把最终上下文发给模型]
P --> Q
Q --> R[模型返回当前回复]
R --> S[Outlet 重建完整历史]
S --> T{达到压缩阈值了吗?}
T -- 否 --> U[结束]
T -- 是 --> V[把摘要输入压到摘要模型可接受的上下文窗口]
V --> W{后台摘要调用成功?}
W -- 是 --> X[保存新摘要并更新状态]
W -- 否 --> Y[强制输出浏览器控制台错误并提示用户查看]
```
### 关键说明
- `inlet` 只负责注入和裁剪上下文,不负责生成当前聊天的主摘要。
- `outlet` 异步生成摘要,不会阻塞当前回复。
- 外部聊天引用可以来自已有持久化摘要、小聊天的完整文本,或动态生成/截断后的引用摘要。
- 如果引用聊天摘要失败,会自动回退为直接注入上下文,而不是让当前请求失败。
- `summary_model_max_context` 控制摘要输入窗口;`max_summary_tokens` 只控制生成摘要的输出长度。
- 重要的后台摘要失败会显示到浏览器控制台 (`F12`) 和聊天状态提示里。
- 外部引用消息在裁剪阶段会被特殊保护,避免被最先删除。
--- ---
@@ -140,8 +66,8 @@ flowchart TD
| 参数 | 默认值 | 描述 | | 参数 | 默认值 | 描述 |
| :-------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ | | :-------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ |
| `summary_model` | `None` | 用于生成摘要的模型 ID。**强烈建议**配置快速、经济、上下文窗口大的模型(如 `gemini-2.5-flash``deepseek-v3`)。留空则尝试复用当前对话模型。 | | `summary_model` | `None` | 用于生成摘要的模型 ID。**强烈建议**配置快速、经济、上下文窗口大的模型(如 `gemini-2.5-flash``deepseek-v3`)。留空则尝试复用当前对话模型。 |
| `summary_model_max_context` | `0` | 摘要请求可使用的输入上下文窗口。如果为 0则回退到 `model_thresholds` 或全局 `max_context_tokens`。 | | `summary_model_max_context` | `0` | 摘要模型的最大上下文 Token 数。如果为 0则回退到 `model_thresholds` 或全局 `max_context_tokens`。 |
| `max_summary_tokens` | `16384` | 生成摘要时允许的最大输出 Token 数。它不是摘要输入窗口上限。 | | `max_summary_tokens` | `16384` | 生成摘要时允许的最大 Token 数。 |
| `summary_temperature` | `0.1` | 控制摘要生成的随机性,较低的值结果更稳定。 | | `summary_temperature` | `0.1` | 控制摘要生成的随机性,较低的值结果更稳定。 |
### 高级配置 ### 高级配置
@@ -169,8 +95,7 @@ flowchart TD
| 参数 | 默认值 | 描述 | | 参数 | 默认值 | 描述 |
| :----------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | :----------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
| `enable_tool_output_trimming` | `true` | 启用后(仅在 `function_calling: "native"` 下生效)会裁剪过大的本机工具输出,保留工具调用链结构并以简短占位替换冗长内容。 | | `enable_tool_output_trimming` | `false` | 启用时,若 `function_calling: "native"` 激活,将裁剪冗长的工具输出以仅提取最终答案。 |
| `tool_trim_threshold_chars` | `600` | 当本机工具输出累计字符数达到该值时触发裁剪,适用于包含长文本或表格的工具结果。 |
| `debug_mode` | `false` | 是否在 Open WebUI 的控制台日志中打印详细的调试信息。生产环境默认且建议设为 `false`。 | | `debug_mode` | `false` | 是否在 Open WebUI 的控制台日志中打印详细的调试信息。生产环境默认且建议设为 `false`。 |
| `show_debug_log` | `false` | 是否在浏览器控制台 (F12) 打印调试日志。便于前端调试。 | | `show_debug_log` | `false` | 是否在浏览器控制台 (F12) 打印调试日志。便于前端调试。 |
| `show_token_usage_status` | `true` | 是否在对话结束时显示 Token 使用情况的状态通知。 | | `show_token_usage_status` | `true` | 是否在对话结束时显示 Token 使用情况的状态通知。 |
@@ -186,12 +111,8 @@ flowchart TD
- **初始系统提示丢失**:将 `keep_first` 设置为大于 0。 - **初始系统提示丢失**:将 `keep_first` 设置为大于 0。
- **压缩效果不明显**:提高 `compression_threshold_tokens`,或降低 `keep_first` / `keep_last` 以增强压缩力度。 - **压缩效果不明显**:提高 `compression_threshold_tokens`,或降低 `keep_first` / `keep_last` 以增强压缩力度。
- **引用聊天摘要失败**:当前请求现在应该会继续执行,并回退为直接注入上下文。如果要看上游失败原因,请打开浏览器控制台 (`F12`)。
- **后台摘要看起来“没反应”**:重要失败现在会同时出现在状态提示和浏览器控制台 (`F12`) 中。
- **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue[OpenWebUI Extensions Issues](https://github.com/Fu-Jie/openwebui-extensions/issues) - **提交 Issue**: 如果遇到任何问题,请在 GitHub 上提交 Issue[OpenWebUI Extensions Issues](https://github.com/Fu-Jie/openwebui-extensions/issues)
## 更新日志 ## 更新日志
请查看 [`v1.5.0` 版本发布说明](./v1.5.0_CN.md) 获取本次版本的独立发布摘要。
完整历史请查看 GitHub 项目: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 完整历史请查看 GitHub 项目: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)

View File

@@ -1,315 +0,0 @@
# 📋 Response 结构检查指南
## 🎯 新增检查点
`_call_summary_llm()` 方法中添加了 **3 个新的响应检查点**,用于前端控制台检查 LLM 调用的完整响应流程。
### 新增检查点位置
| # | 检查点名称 | 位置 | 显示内容 |
|---|-----------|------|--------|
| 1⃣ | **LLM Response structure** | `generate_chat_completion()` 返回后 | 原始 response 对象的类型、键、结构 |
| 2⃣ | **LLM Summary extracted & cleaned** | 提取并清理 summary 后 | 摘要长度、字数、格式、是否为空 |
| 3⃣ | **Summary saved to database** | 保存到 DB 后验证 | 数据库记录是否正确保存、字段一致性 |
---
## 📊 检查点详解
### 1⃣ LLM Response structure
**显示时机**: `generate_chat_completion()` 返回,处理前
**用途**: 验证原始响应数据结构
```
📋 [Compression] LLM Response structure (raw from generate_chat_completion)
├─ type: "dict" / "Response" / "JSONResponse"
├─ has_body: true/false (表示是否为 Response 对象)
├─ has_status_code: true/false
├─ is_dict: true/false
├─ keys: ["choices", "usage", "model", ...] (如果是 dict)
├─ first_choice_keys: ["message", "finish_reason", ...]
├─ message_keys: ["role", "content"]
└─ content_length: 1234 (摘要文本长度)
```
**关键验证**:
-`type` — 应该是 `dict``JSONResponse`
-`is_dict` — 最终应该是 `true`(处理完毕后)
-`keys` — 应该包含 `choices``usage`
-`first_choice_keys` — 应该包含 `message`
-`message_keys` — 应该包含 `role``content`
-`content_length` — 摘要不应该为空(> 0
---
### 2⃣ LLM Summary extracted & cleaned
**显示时机**: 从 response 中提取并 strip() 后
**用途**: 验证提取的摘要内容质量
```
📋 [Compression] LLM Summary extracted & cleaned
├─ type: "str"
├─ length_chars: 1234
├─ length_words: 156
├─ first_100_chars: "用户提问关于......"
├─ has_newlines: true
├─ newline_count: 3
└─ is_empty: false
```
**关键验证**:
-`type` — 应该始终是 `str`
-`is_empty` — 应该是 `false`(不能为空)
-`length_chars` — 通常 100-2000 字符(取决于配置)
-`newline_count` — 多行摘要通常有几个换行符
-`first_100_chars` — 可视化开头内容,检查是否正确
---
### 3⃣ Summary saved to database
**显示时机**: 保存到 DB 后,重新加载验证
**用途**: 确认数据库持久化成功且数据一致
```
📋 [Compression] Summary saved to database (verification)
├─ db_id: 42
├─ db_chat_id: "chat-abc123..."
├─ db_compressed_message_count: 10
├─ db_summary_length_chars: 1234
├─ db_summary_preview_100: "用户提问关于......"
├─ db_created_at: "2024-03-12 15:30:45.123456+00:00"
├─ db_updated_at: "2024-03-12 15:35:20.654321+00:00"
├─ matches_input_chat_id: true
└─ matches_input_compressed_count: true
```
**关键验证** ⭐ 最重要:
-`matches_input_chat_id`**必须是 `true`**
-`matches_input_compressed_count`**必须是 `true`**
-`db_summary_length_chars` — 与提取后的长度一致
-`db_updated_at` — 应该是最新时间戳
-`db_id` — 应该有有效的数据库 ID
---
## 🔍 如何在前端查看
### 步骤 1: 启用调试模式
在 OpenWebUI 中:
```
Settings → Filters → Async Context Compression
找到 valve: "show_debug_log"
勾选启用 + Save
```
### 步骤 2: 打开浏览器控制台
- **Windows/Linux**: F12 → Console
- **Mac**: Cmd + Option + I → Console
### 步骤 3: 触发摘要生成
发送足够多的消息使 Filter 触发压缩:
```
1. 发送 15+ 条消息
2. 等待后台摘要任务开始
3. 在 Console 观察 📋 日志
```
### 步骤 4: 观察完整流程
```
[1] 📋 LLM Response structure (raw)
↓ (显示原始响应类型、结构)
[2] 📋 LLM Summary extracted & cleaned
↓ (显示提取后的文本信息)
[3] 📋 Summary saved to database (verification)
↓ (显示数据库保存结果)
```
---
## 📈 完整流程验证
### 优质流程示例 ✅
```
1⃣ Response structure:
- type: "dict"
- is_dict: true
- has "choices": true
- has "usage": true
2⃣ Summary extracted:
- is_empty: false
- length_chars: 1500
- length_words: 200
3⃣ DB verification:
- matches_input_chat_id: true ✅
- matches_input_compressed_count: true ✅
- db_id: 42 (有效)
```
### 问题流程示例 ❌
```
1⃣ Response structure:
- type: "Response" (需要处理)
- has_body: true
- (需要解析 body)
2⃣ Summary extracted:
- is_empty: true ❌ (摘要为空!)
- length_chars: 0
3⃣ DB verification:
- matches_input_chat_id: false ❌ (chat_id 不匹配!)
- matches_input_compressed_count: false ❌ (计数不匹配!)
```
---
## 🛠️ 调试技巧
### 快速过滤日志
在 Console 过滤框输入:
```
📋 (搜索所有压缩日志)
LLM Response (搜索响应相关)
Summary extracted (搜索提取摘要)
saved to database (搜索保存验证)
```
### 展开表格/对象查看详情
1. **对象型日志** (console.dir)
- 点击左边的 ▶ 符号展开
- 逐级查看嵌套字段
2. **表格型日志** (console.table)
- 点击上方的 ▶ 展开
- 查看完整列
### 对比多个日志
```javascript
// 在 Console 中手动对比
检查点1: type = "dict", is_dict = true
检查点2: is_empty = false, length_chars = 1234
检查点3: matches_input_chat_id = true
如果所有都符合预期 流程正常
如果有不符的 检查具体问题
```
---
## 🐛 常见问题诊断
### Q: "type" 是 "Response" 而不是 "dict"?
**原因**: 某些后端返回 Response 对象而非 dict
**解决**: 代码会自动处理,看后续日志是否成功解析
```
检查点1: type = "Response" ← 需要解析
代码执行 `response.body` 解析
再次检查是否变为 dict
```
### Q: "is_empty" 是 true?
**原因**: LLM 没有返回有效的摘要文本
**诊断**:
1. 检查 `first_100_chars` — 应该有实际内容
2. 检查模型是否正确配置
3. 检查中间消息是否过多导致 LLM 超时
### Q: "matches_input_chat_id" 是 false?
**原因**: 保存到 DB 时 chat_id 不匹配
**诊断**:
1. 对比 `db_chat_id` 和输入的 `chat_id`
2. 可能是数据库连接问题
3. 可能是并发修改导致的
### Q: "matches_input_compressed_count" 是 false?
**原因**: 保存的消息计数与预期不符
**诊断**:
1. 对比 `db_compressed_message_count``saved_compressed_count`
2. 检查中间消息是否被意外修改
3. 检查原子边界对齐是否正确
---
## 📚 相关代码位置
```python
# 文件: async_context_compression.py
# 检查点 1: 响应结构检查 (L3459)
if self.valves.show_debug_log and __event_call__:
await self._emit_struct_log(
__event_call__,
"LLM Response structure (raw from generate_chat_completion)",
response_inspection_data,
)
# 检查点 2: 摘要提取检查 (L3524)
if self.valves.show_debug_log and __event_call__:
await self._emit_struct_log(
__event_call__,
"LLM Summary extracted & cleaned",
summary_inspection,
)
# 检查点 3: 数据库保存检查 (L3168)
if self.valves.show_debug_log and __event_call__:
await self._emit_struct_log(
__event_call__,
"Summary saved to database (verification)",
save_inspection,
)
```
---
## 🎯 完整检查清单
在前端 Console 中验证:
- [ ] 检查点 1 出现且 `is_dict: true`
- [ ] 检查点 1 显示 `first_choice_keys` 包含 `message`
- [ ] 检查点 2 出现且 `is_empty: false`
- [ ] 检查点 2 显示合理的 `length_chars` (通常 > 100)
- [ ] 检查点 3 出现且 `matches_input_chat_id: true`
- [ ] 检查点 3 显示 `matches_input_compressed_count: true`
- [ ] 所有日志时间戳合理
- [ ] 没有异常或错误信息
---
## 📞 后续步骤
1. ✅ 启用调试模式
2. ✅ 发送消息触发摘要生成
3. ✅ 观察 3 个新检查点
4. ✅ 验证所有字段符合预期
5. ✅ 如有问题,参考本指南诊断
---
**最后更新**: 2024-03-12
**相关特性**: Response 结构检查 (v1.4.1+)
**文档**: [async_context_compression.py 第 3459, 3524, 3168 行]

View File

@@ -1,270 +0,0 @@
[![](https://img.shields.io/badge/OpenWebUI%20Community-Get%20Plugin-blue?style=for-the-badge)](https://openwebui.com/posts/async_context_compression_b1655bc8)
# Async Context Compression: A Production-Scale Working-Memory Filter for OpenWebUI
Long chats do not just get expensive. They also get fragile.
Once a conversation grows large enough, you usually have to choose between two bad options:
- keep the full history and pay a heavy context cost
- trim aggressively and risk losing continuity, tool state, or important prior decisions
`Async Context Compression` is built to avoid that tradeoff.
It is not a simple “summarize old messages” utility. It is a structure-aware, async, database-backed working-memory system for OpenWebUI that can compress long conversations while preserving conversational continuity, tool-calling integrity, and now, as of `v1.5.0`, referenced-chat context injection as well.
This plugin has now reached the point where it feels complete enough to be described as a serious, high-capability filter rather than a small convenience add-on.
**[📖 Full README](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/README.md)**
**[📝 v1.5.0 Release Notes](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/v1.5.0.md)**
---
## Why This Plugin Exists
OpenWebUI conversations often contain much more than plain chat:
- long-running planning threads
- coding sessions with repeated tool use
- model-specific context limits
- multimodal messages
- external referenced chats
- custom models with different context windows
A naive compression strategy is not enough in those environments.
If a filter only drops earlier messages based on length, it can:
- break native tool-calling chains
- lose critical task state
- destroy continuity in old chats
- make debugging impossible
- hide important provider-side failures
`Async Context Compression` is designed around a stronger premise:
> compress history without treating conversation structure as disposable
That means it tries to preserve what actually matters for the next turn:
- the current goal
- durable user preferences
- recent progress
- tool outputs that still matter
- error state
- summary continuity
- referenced context from other chats
---
## What Makes It Different
This plugin now combines several capabilities that are usually split across separate systems:
### 1. Asynchronous working-memory generation
The current reply is not blocked while the plugin generates a new summary in the background.
### 2. Persistent summary storage
Summaries are stored in OpenWebUI's shared database and reused across turns, instead of being regenerated from scratch every time.
### 3. Structure-aware trimming
The filter respects atomic message boundaries so native tool-calling history is not corrupted by compression.
### 4. External chat reference summarization
New in `v1.5.0`: referenced chats can now be reused as cached summaries, injected directly if small enough, or summarized before injection if too large.
### 5. Mixed-script token estimation
The plugin now uses a much stronger multilingual token estimation path before falling back to exact counting, which helps reduce unnecessary expensive token calculations while staying much closer to real usage.
### 6. Real failure visibility
Important background summary failures are surfaced to the browser console and status messages instead of disappearing silently.
---
## Workflow Overview
This is the current high-level flow:
```mermaid
flowchart TD
A[Request enters inlet] --> B[Normalize tool IDs and optionally trim large tool outputs]
B --> C{Referenced chats attached?}
C -- No --> D[Load current chat summary if available]
C -- Yes --> E[Inspect each referenced chat]
E --> F{Existing cached summary?}
F -- Yes --> G[Reuse cached summary]
F -- No --> H{Fits direct budget?}
H -- Yes --> I[Inject full referenced chat text]
H -- No --> J[Prepare referenced-chat summary input]
J --> K{Referenced-chat summary call succeeds?}
K -- Yes --> L[Inject generated referenced summary]
K -- No --> M[Fallback to direct contextual injection]
G --> D
I --> D
L --> D
M --> D
D --> N[Build current-chat Head + Summary + Tail]
N --> O{Over max_context_tokens?}
O -- Yes --> P[Trim oldest atomic groups]
O -- No --> Q[Send final context to the model]
P --> Q
Q --> R[Model returns the reply]
R --> S[Outlet rebuilds the full history]
S --> T{Reached compression threshold?}
T -- No --> U[Finish]
T -- Yes --> V[Fit summary input to the summary model context]
V --> W{Background summary call succeeds?}
W -- Yes --> X[Save new chat summary and update status]
W -- No --> Y[Force browser-console error and show status hint]
```
This is why I consider the plugin “powerful” now: it is no longer solving a single problem. It is coordinating context reduction, summary persistence, tool safety, referenced-chat handling, and model-budget control inside one filter.
---
## New in v1.5.0
This release is important because it turns the plugin from “long-chat compression with strong tool safety” into something closer to a reusable context-management layer.
### External chat reference summaries
This is a new feature in `v1.5.0`, not just a small adjustment.
When a user references another chat:
- the plugin can reuse an existing cached summary
- inject the full referenced chat if it is small enough
- or generate a summary first if the referenced chat is too large
That means the filter can now carry relevant context across chats, not just across turns inside the same chat.
### Fast multilingual token estimation
Also new in `v1.5.0`.
The plugin no longer relies on a rough one-size-fits-all character ratio. It now estimates token usage with mixed-script heuristics that behave much better for:
- English
- Chinese
- Japanese
- Korean
- Cyrillic
- Arabic
- Thai
- mixed-language conversations
This matters because the plugin makes context decisions constantly. Better estimation means fewer unnecessary exact counts and fewer bad preflight assumptions.
### Stronger final-prompt budgeting
The summary path now fits the **real final summary request**, not just an intermediate estimate. That includes:
- prompt wrapper
- formatted conversation text
- previous summary
- reserved output budget
- safety margin
This directly improves reliability in the large old-chat cases that are hardest to handle.
---
## Why It Feels Complete Now
I would describe the current plugin as “feature-complete for the main problem space,” because it now covers the major operational surfaces that matter in real usage:
- long plain-chat conversations
- multi-step coding threads
- native tool-calling conversations
- persistent summaries
- custom model thresholds
- background async generation
- external chat references
- multilingual token estimation
- failure surfacing for debugging
That does not mean it is finished forever. It means the plugin has crossed the line from a narrow experimental filter into a robust context-management system with enough breadth to support demanding OpenWebUI usage patterns.
---
## Scale and Engineering Depth
For people who care about implementation depth, this plugin is not small anymore.
Current code size:
- main plugin: **4,573 lines**
- focused test file: **1,037 lines**
- combined visible implementation + regression coverage: **5,610 lines**
Line count is not a quality metric by itself, but at this scale it does say something real:
- the plugin has grown well beyond a toy filter
- the behavior surface is large enough to require explicit regression testing
- the plugin now encodes a lot of edge-case handling that only shows up after repeated real-world usage
In other words: this is no longer “just summarize old messages.” It is a fairly serious stateful filter.
---
## Practical Benefits
If you use OpenWebUI heavily, the value is straightforward:
- lower token consumption in long chats
- better continuity across long-running sessions
- safer native tool-calling history
- fewer broken conversations after compression
- more stable summary generation on large histories
- better visibility when the provider rejects a summary request
- useful reuse of context from referenced chats
This plugin is especially valuable if you:
- regularly work in long coding chats
- use models with strict context budgets
- rely on native tool calling
- revisit old project chats
- want summaries to behave like working memory, not like lossy notes
---
## Installation
- OpenWebUI Community: <https://openwebui.com/posts/async_context_compression_b1655bc8>
- Source: <https://github.com/Fu-Jie/openwebui-extensions/tree/main/plugins/filters/async-context-compression>
If you want the full valve list, deployment notes, and troubleshooting details, the README is the best reference.
---
## Final Note
Do I think this plugin is powerful?
Yes, genuinely.
Not because it is large, but because it now solves the right combination of problems at once:
- cost control
- continuity
- structural safety
- async persistence
- cross-chat reuse
- operational debuggability
That combination is what makes it feel strong.
If you have been looking for a serious long-conversation memory/compression filter for OpenWebUI, `Async Context Compression` is now in that category.

View File

@@ -1,282 +0,0 @@
[![](https://img.shields.io/badge/OpenWebUI%20%E7%A4%BE%E5%8C%BA-%E8%8E%B7%E5%8F%96%E6%8F%92%E4%BB%B6-blue?style=for-the-badge)](https://openwebui.com/posts/async_context_compression_b1655bc8)
# Async Context Compression一个面向生产场景的 OpenWebUI 工作记忆过滤器
长对话的问题,从来不只是“贵”。
当聊天足够长时,通常只剩下两个都不太好的选择:
- 保留完整历史,继续承担很高的上下文成本
- 粗暴裁剪旧消息,但冒着丢失上下文、工具状态和关键决策的风险
`Async Context Compression` 的目标,就是尽量避免这个二选一。
它不是一个简单的“把老消息总结一下”的小工具,而是一个带有结构感知、异步摘要、数据库持久化能力的 OpenWebUI 工作记忆系统。它的任务不是单纯缩短上下文,而是在压缩长对话的同时,尽量保留:
- 对话连续性
- 工具调用状态完整性
- 历史摘要进度
- 跨聊天引用上下文
- 出错时的可诊断性
`v1.5.0` 这个阶段,我认为它已经不再只是一个“方便的小过滤器”,而是一个足够完整、足够强、也足够有工程深度的上下文管理插件。
**[📖 完整 README](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/README_CN.md)**
**[📝 v1.5.0 发布说明](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/v1.5.0_CN.md)**
---
## 为什么会有这个插件
OpenWebUI 里的真实对话,通常并不只是“用户问一句,模型答一句”。
它常常还包含:
- 很长的项目型对话
- 多轮编码与调试
- 原生工具调用
- 多模态消息
- 不同模型上下文窗口差异
- 其他聊天的引用上下文
在这种环境里,单纯靠“按长度裁掉旧消息”其实不够。
如果一个过滤器只会按长度或索引裁剪消息,它很容易:
- 把原生 tool-calling 历史裁坏
- 丢掉仍然会影响下一轮回复的关键信息
- 在老聊天里破坏连续性
- 出问题时几乎无法排查
- 把上游 provider 报错伪装成模糊的内部错误
`Async Context Compression` 的核心思路更强一些:
> 可以压缩历史,但不能把“对话结构”当成无关紧要的东西一起压掉
它真正想保留的是下一轮最需要的状态:
- 当前目标
- 持久偏好
- 最近进展
- 仍然有效的工具结果
- 错误状态
- 已有摘要的连续性
- 来自其他聊天的相关上下文
---
## 它和普通摘要插件有什么不同
现在这个插件,实际上已经把几个通常要分散在不同系统里的能力组合到了一起:
### 1. 异步工作记忆生成
用户当前这次回复不会被后台摘要阻塞。
### 2. 持久化摘要存储
摘要会写入 OpenWebUI 共享数据库,并在后续轮次中复用,而不是每次都从头重算。
### 3. 结构感知裁剪
裁剪逻辑会尊重原子消息边界,避免把原生 tool-calling 历史裁坏。
### 4. 外部聊天引用摘要
这是 `v1.5.0` 新增的重要能力:被引用聊天现在可以直接复用缓存摘要、在小体量时直接注入、或者在过大时先生成摘要再注入。
### 5. 多语言 Token 预估
插件现在具备更强的多脚本文本 Token 预估逻辑,在很多情况下可以减少不必要的精确计数,同时明显比旧的粗略字符比值更贴近真实用量。
### 6. 失败可见性
关键的后台摘要失败现在会出现在浏览器控制台和状态提示里,不再悄悄消失。
---
## 工作流总览
下面是当前的高层流程:
```mermaid
flowchart TD
A[Request enters inlet] --> B[Normalize tool IDs and optionally trim large tool outputs]
B --> C{Referenced chats attached?}
C -- No --> D[Load current chat summary if available]
C -- Yes --> E[Inspect each referenced chat]
E --> F{Existing cached summary?}
F -- Yes --> G[Reuse cached summary]
F -- No --> H{Fits direct budget?}
H -- Yes --> I[Inject full referenced chat text]
H -- No --> J[Prepare referenced-chat summary input]
J --> K{Referenced-chat summary call succeeds?}
K -- Yes --> L[Inject generated referenced summary]
K -- No --> M[Fallback to direct contextual injection]
G --> D
I --> D
L --> D
M --> D
D --> N[Build current-chat Head + Summary + Tail]
N --> O{Over max_context_tokens?}
O -- Yes --> P[Trim oldest atomic groups]
O -- No --> Q[Send final context to the model]
P --> Q
Q --> R[Model returns the reply]
R --> S[Outlet rebuilds the full history]
S --> T{Reached compression threshold?}
T -- No --> U[Finish]
T -- Yes --> V[Fit summary input to the summary model context]
V --> W{Background summary call succeeds?}
W -- Yes --> X[Save new chat summary and update status]
W -- No --> Y[Force browser-console error and show status hint]
```
这也是为什么我会觉得它现在“强”:它已经不再只解决一个问题,而是在一个过滤器里同时协调:
- 上下文压缩
- 历史摘要复用
- 工具调用安全性
- 被引用聊天上下文
- 模型预算控制
---
## v1.5.0 为什么重要
这个版本的重要性在于,它把插件从“长对话压缩器”推进成了一个更接近“上下文管理层”的东西。
### 外部聊天引用摘要
这是 `v1.5.0` 的新功能,不是小修小补。
当用户引用另一个聊天时,插件现在可以:
- 直接复用已有缓存摘要
- 如果聊天足够小,直接把完整内容注入
- 如果聊天太大,先生成摘要再注入
这意味着它现在不仅能跨“轮次”保留上下文,也能开始跨“聊天”携带相关上下文。
### 快速多语言 Token 预估
这同样是 `v1.5.0` 的新能力。
插件不再依赖简单粗暴的统一字符比值,而是改用更适合混合语言文本的估算方式,尤其对下面这些场景更有意义:
- 英文
- 中文
- 日文
- 韩文
- 西里尔字符
- 阿拉伯语
- 泰语
- 中英混合或多语言混合对话
这很重要,因为上下文管理类插件会不断做预算判断。预估更准,就意味着更少无意义的精确计算,也更不容易在预检阶段做出错误判断。
### 更强的最终请求预算控制
现在的摘要路径会去拟合“真实最终 summary request”而不是只看一个中间估算值。它会把这些内容都算进去
- prompt 包装
- 格式化后的对话文本
- previous summary
- 预留输出预算
- 安全余量
这对老聊天、大聊天和最难处理的边界情况特别关键。
---
## 为什么我觉得它现在已经足够完整
如果把“问题空间”列出来,我会说这个插件现在对主要场景已经覆盖得比较完整了:
- 很长的普通聊天
- 多轮编码与调试对话
- 原生工具调用
- 历史摘要持久化
- 自定义模型阈值
- 异步后台摘要
- 外部聊天引用
- 多语言 Token 预估
- 调试可见性
这并不代表它永远不会再迭代,而是说它已经越过了“窄功能实验品”的阶段,进入了一个更像“通用上下文管理系统”的形态。
---
## 代码规模与工程深度
如果你关心实现深度,这个插件现在已经不小了。
当前代码规模:
- 主插件文件:**4,573 行**
- 聚焦测试文件:**1,037 行**
- 可见实现 + 回归测试合计:**5,610 行**
代码行数本身不等于质量,但在这个量级上,它至少说明了几件真实的事:
- 这已经不是一个玩具级过滤器
- 这个插件的行为面足够大,必须靠专门回归测试兜住
- 它已经积累了很多只有在真实使用中才会暴露出来的边界处理逻辑
也就是说,它现在做的事情,已经明显不是“把老消息总结一下”那么简单。
---
## 实际价值
如果你是 OpenWebUI 的重度用户,这个插件的价值其实很直接:
- 长聊天更省 Token
- 长会话连续性更好
- 原生 tool-calling 更安全
- 压缩后更不容易把会话搞坏
- 大历史摘要生成更稳定
- provider 拒绝摘要请求时更容易看到真错误
- 能复用其他聊天里的有效上下文
尤其适合这些用户:
- 经常做长时间编码聊天
- 使用上下文窗口比较紧的模型
- 依赖原生工具调用
- 经常回看旧项目聊天
- 希望摘要更像“工作记忆”而不是“丢失细节的简要笔记”
---
## 安装
- OpenWebUI 社区:<https://openwebui.com/posts/async_context_compression_b1655bc8>
- 源码目录:<https://github.com/Fu-Jie/openwebui-extensions/tree/main/plugins/filters/async-context-compression>
如果你想看完整的 valves、部署说明和故障排查README 仍然是最完整的参考入口。
---
## 最后一句
你问我这个插件是不是强大。
我的答案是:**是,确实强,而且现在已经不是“看起来强”,而是“问题空间覆盖得比较完整”的那种强。**
不是因为它代码多,而是因为它现在同时解决的是一组真正相关的问题:
- 成本控制
- 连续性
- 结构安全
- 异步持久化
- 跨聊天上下文复用
- 出错时的可诊断性
正是这几个东西一起成立,才让它现在像一个真正成熟的长对话上下文管理插件。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

View File

@@ -1,10 +1,8 @@
[![](https://img.shields.io/badge/OpenWebUI%20Community-Get%20Plugin-blue?style=for-the-badge)](https://openwebui.com/posts/async_context_compression_b1655bc8)
## Overview ## Overview
This release focuses on improving the structural integrity of chat history when using function-calling models and enhancing task reliability through concurrent task management. It introduces "Atomic Message Grouping" to prevent chat context corruption and a session-based locking mechanism to ensure stable background operations. **[🚀 Get/Update on OpenWebUI Community](https://openwebui.com/posts/async_context_compression_b1655bc8)**
**[📖 README](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/README.md)** This release focuses on improving the structural integrity of chat history when using function-calling models and enhancing task reliability through concurrent task management. Version 1.4.0 introduces "Atomic Message Grouping" to prevent chat context corruption and a session-based locking mechanism to ensure stable background operations.
## New Features ## New Features
@@ -22,3 +20,7 @@ This release focuses on improving the structural integrity of chat history when
## Related Issues ## Related Issues
- **[#56](https://github.com/Fu-Jie/openwebui-extensions/issues/56)**: Tool-Calling context corruption and concurrent summary tasks. - **[#56](https://github.com/Fu-Jie/openwebui-extensions/issues/56)**: Tool-Calling context corruption and concurrent summary tasks.
## Related PRs
- **[#61](https://github.com/Fu-Jie/openwebui-extensions/pull/61)**: (Placeholder) Full implementation of structure-aware grouping.

View File

@@ -1,6 +1,8 @@
[![](https://img.shields.io/badge/OpenWebUI%20%E7%A4%BE%E5%8C%BA-%E8%8E%B7%E5%8F%96%E6%8F%92%E4%BB%B6-blue?style=for-the-badge)](https://openwebui.com/posts/async_context_compression_b1655bc8) ## 概述
本次发布重点优化了在使用工具调用Function Calling模型时对话历史的结构完整性并通过并发任务管理增强了系统的可靠性。新版本引入了“原子消息组”逻辑以防止上下文损坏并增加了会话级锁定机制以确保后台任务的稳定运行。 **[🚀 在 OpenWebUI 社区获取/更新](https://openwebui.com/posts/async_context_compression_b1655bc8)**
本次发布重点优化了在使用工具调用Function Calling模型时对话历史的结构完整性并通过并发任务管理增强了系统的可靠性。1.4.0 版本引入了“原子消息组”逻辑以防止上下文损坏,并增加了会话级锁定机制以确保后台任务的稳定运行。
## 新功能 ## 新功能
@@ -18,3 +20,7 @@
## 相关 Issue ## 相关 Issue
- **[#56](https://github.com/Fu-Jie/openwebui-extensions/issues/56)**: 修复工具调用上下文损坏及并发总结任务冲突问题。 - **[#56](https://github.com/Fu-Jie/openwebui-extensions/issues/56)**: 修复工具调用上下文损坏及并发总结任务冲突问题。
## 相关 PR
- **[#61](https://github.com/Fu-Jie/openwebui-extensions/pull/61)**: (占位符) 结构感知消息分组的完整实现。

View File

@@ -1,17 +0,0 @@
[![](https://img.shields.io/badge/OpenWebUI%20Community-Get%20Plugin-blue?style=for-the-badge)](https://openwebui.com/f/fujie/async_context_compression)
## Overview
This release addresses the critical progress coordinate drift issue in OpenWebUI's `outlet` phase, ensuring robust summarization for long tool-calling conversations.
[View on GitHub](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/README.md)
- **New Features**
- **Reverse-Unfolding Mechanism**: Accurately reconstructs the expanded native tool-calling sequence during the outlet phase to permanently fix coordinate drift and missing summaries for long tool-based conversations.
- **Safer Tool Trimming**: Refactored `enable_tool_output_trimming` to strictly use atomic block groups for safe trimming, completely preventing JSON payload corruption.
- **Bug Fixes**
- Fixed coordinate drift where `compressed_message_count` could lose track due to OpenWebUI's frontend view truncating tool calls.
- **Related Issues**
- Closes #56

View File

@@ -1,27 +0,0 @@
[![](https://img.shields.io/badge/OpenWebUI%20Community-Get%20Plugin-blue?style=for-the-badge)](https://openwebui.com/f/fujie/async_context_compression)
## Overview
Compared with the previous git version (`1.4.2`), this release introduces two major new capabilities: external chat reference summarization and a much stronger multilingual token-estimation pipeline. It also improves the reliability of the surrounding summary workflow, especially when provider-side failures occur.
**[📖 README](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/README.md)**
## New Features
- **External Chat Reference Summaries**: Add support for referenced chat context injection that can reuse cached summaries, inject small referenced chats directly, or generate summaries for larger referenced chats before injection.
- **Fast Multilingual Token Estimation**: Replace the old rough `len(text)//4` fallback with a new mixed-script estimation pipeline so preflight decisions stay much closer to actual usage across English, Chinese, Japanese, Korean, Arabic, Cyrillic, Thai, and mixed content.
- **Stronger Working-Memory Prompt**: Refined the XML summary prompt so generated working memory preserves more actionable state across general chat, coding tasks, and tool-heavy conversations.
- **Clearer Frontend Debug Logs**: Reworked browser-console debug output into grouped structural snapshots that make inlet/outlet state easier to inspect.
- **Safer Tool Trimming Defaults**: Enabled native tool-output trimming by default and exposed `tool_trim_threshold_chars` with a 600-character threshold.
## Bug Fixes
- **Referenced-Chat Fallback Reliability**: If the new referenced-chat summary path fails, the active request now falls back to direct contextual injection instead of failing the whole chat.
- **Correct Summary Budgeting**: Fixed referenced-chat summary preparation so `summary_model_max_context` controls summary-input fitting, while `max_summary_tokens` remains an output cap.
- **Visible Background Failures**: Important background summary failures now surface to the browser console and chat status even when `show_debug_log` is disabled.
- **Provider Error Surfacing**: Improved summary-call error extraction so non-standard upstream provider error payloads are reported more clearly.
## Release Notes
- Bilingual plugin README files and mirrored docs pages were refreshed for the `1.5.0` release.
- This release is aimed at reducing silent failure modes and making summary behavior easier to reason about during debugging.

View File

@@ -1,27 +0,0 @@
[![](https://img.shields.io/badge/OpenWebUI%20Community-Get%20Plugin-blue?style=for-the-badge)](https://openwebui.com/f/fujie/async_context_compression)
## 概述
相较上一个 git 版本(`1.4.2`),本次发布新增了两个重要能力:外部聊天引用摘要,以及更强的多语言 Token 预估链路。同时也补强了围绕这些新能力的摘要流程稳定性,特别是上游提供商报错时的回退与可见性。
**[📖 README](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/filters/async-context-compression/README_CN.md)**
## 新功能
- **外部聊天引用摘要**:新增引用聊天上下文注入能力。现在可以复用缓存摘要、直接注入较小引用聊天,或先为较大的引用聊天生成摘要再注入。
- **快速多语言 Token 预估**:用新的混合脚本估算链路替代旧的 `len(text)//4` 粗略回退,使预检在英文、中文、日文、韩文、阿拉伯文、西里尔文、泰文及混合内容下都更接近真实用量。
- **更稳健的工作记忆提示词**:重写 XML 摘要提示词,让生成出的 working memory 在普通聊天、编码任务和密集工具调用场景下保留更多可操作上下文。
- **更清晰的前端调试日志**:浏览器控制台调试输出改为分组化、结构化展示,更容易观察 inlet / outlet 的真实状态。
- **更安全的工具裁剪默认值**:原生工具输出裁剪默认开启,并新增 `tool_trim_threshold_chars`,默认阈值为 600 字符。
## 问题修复
- **引用聊天回退更稳妥**:当新的引用聊天摘要路径失败时,当前请求会自动回退为直接注入上下文,而不是整个对话一起失败。
- **摘要预算计算更准确**:修复引用聊天摘要准备逻辑,明确由 `summary_model_max_context` 控制摘要输入窗口,而 `max_summary_tokens` 只控制摘要输出长度。
- **后台失败更容易发现**:即使关闭 `show_debug_log`,关键后台摘要失败现在也会显示到浏览器控制台和聊天状态提示中。
- **提供商错误信息更清晰**:改进摘要调用的错误提取逻辑,让非标准上游错误载荷也能更准确地显示出来。
## 发布说明
- 已同步更新中英插件 README 与 docs 镜像页,确保 `1.5.0` 发布说明一致。
- 本次版本的目标,是减少“静默失败”这类难排查问题,并让摘要行为在调试时更容易理解。

View File

@@ -114,7 +114,6 @@ class Filter:
# Check if it's a Copilot model # Check if it's a Copilot model
is_copilot_model = self._is_copilot_model(current_model) is_copilot_model = self._is_copilot_model(current_model)
body["is_copilot_model"] = is_copilot_model
await self._emit_debug_log( await self._emit_debug_log(
__event_emitter__, __event_emitter__,

View File

@@ -1,137 +0,0 @@
# Batch Install Plugins from GitHub
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.0.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
One-click batch install plugins from GitHub repositories to your OpenWebUI instance.
## Key Features
- **One-Click Install**: Install all plugins with a single command
- **Auto-Update**: Automatically updates previously installed plugins
- **GitHub Support**: Install plugins from any GitHub repository
- **Multi-Type Support**: Supports Pipe, Action, Filter, and Tool plugins
- **Confirmation**: Shows plugin list before installing, allows selective installation
- **i18n**: Supports 11 languages
## Flow
```
User Input
┌─────────────────────────────────────┐
│ Discover Plugins from GitHub │
│ (fetch file tree + parse .py) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Filter by Type & Keywords │
│ (tool/filter/pipe/action) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Show Confirmation Dialog │
│ (list plugins + exclude hint) │
└─────────────────────────────────────┘
├── [Cancel] → End
┌─────────────────────────────────────┐
│ Install to OpenWebUI │
│ (update or create each plugin) │
└─────────────────────────────────────┘
Done
```
## How to Use
1. Open OpenWebUI and go to **Workspace > Tools**
2. Install **Batch Install Plugins from GitHub** from the official marketplace
3. Enable this tool for your model/chat
4. Ask the model to install plugins
## Usage Examples
```
"Install all plugins"
"Install all plugins from github.com/username/repo"
"Install only pipe plugins"
"Install action and filter plugins"
"Install all plugins, exclude_keywords=copilot"
```
## Popular Plugin Repositories
Here are some popular repositories with many plugins you can install:
### Community Collections
```
# Install all plugins from iChristGit's collection
"Install all plugins from iChristGit/OpenWebui-Tools"
# Install all tools from Haervwe's tools collection
"Install all plugins from Haervwe/open-webui-tools"
# Install all plugins from Classic298's repository
"Install all plugins from Classic298/open-webui-plugins"
# Install all functions from suurt8ll's collection
"Install all plugins from suurt8ll/open_webui_functions"
# Install only specific types (e.g., only tools)
"Install only tool plugins from iChristGit/OpenWebui-Tools"
# Exclude certain keywords while installing
"Install all plugins from Haervwe/open-webui-tools, exclude_keywords=test,deprecated"
```
### Supported Repositories
- `Fu-Jie/openwebui-extensions` - Default, official plugin collection
- `iChristGit/OpenWebui-Tools` - Comprehensive tool and plugin collection
- `Haervwe/open-webui-tools` - Specialized tools and utilities
- `Classic298/open-webui-plugins` - Various plugin implementations
- `suurt8ll/open_webui_functions` - Function-based plugins
## Default Repository
When no repository is specified, defaults to `Fu-Jie/openwebui-extensions`.
## Plugin Detection Rules
### Fu-Jie/openwebui-extensions (Strict)
For the default repository, plugins must have:
1. A `.py` file containing `class Tools:`, `class Filter:`, `class Pipe:`, or `class Action:`
2. A docstring with `title:`, `description:`, and **`openwebui_id:`** fields
3. Filename must not end with `_cn`
### Other GitHub Repositories
For other repositories:
1. A `.py` file containing `class Tools:`, `class Filter:`, `class Pipe:`, or `class Action:`
2. A docstring with `title:` and `description:` fields
## Configuration (Valves)
| Parameter | Default | Description |
| --- | --- | --- |
| `SKIP_KEYWORDS` | `test,verify,example,template,mock` | Comma-separated keywords to skip |
| `TIMEOUT` | `20` | Request timeout in seconds |
## Confirmation Timeout
User confirmation dialogs have a default timeout of **2 minutes (120 seconds)**, allowing sufficient time for users to:
- Read and review the plugin list
- Make installation decisions
- Handle network delays
## Support
If this plugin has been useful, a star on [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) is a big motivation for me. Thank you for the support.

View File

@@ -1,137 +0,0 @@
# Batch Install Plugins from GitHub
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.0.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
一键将 GitHub 仓库中的插件批量安装到你的 OpenWebUI 实例。
## 主要功能
- 一键安装:单个命令安装所有插件
- 自动更新:自动更新之前安装过的插件
- GitHub 支持:从任意 GitHub 仓库安装插件
- 多类型支持:支持 Pipe、Action、Filter 和 Tool 插件
- 安装确认:安装前显示插件列表,支持选择性安装
- 国际化:支持 11 种语言
## 流程
```
用户输入
┌─────────────────────────────────────┐
│ 从 GitHub 发现插件 │
│ (获取文件树 + 解析 .py 文件) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 按类型和关键词过滤 │
│ (tool/filter/pipe/action) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 显示确认对话框 │
│ (插件列表 + 排除提示) │
└─────────────────────────────────────┘
├── [取消] → 结束
┌─────────────────────────────────────┐
│ 安装到 OpenWebUI │
│ (更新或创建每个插件) │
└─────────────────────────────────────┘
完成
```
## 使用方法
1. 打开 OpenWebUI进入 **Workspace > Tools**
2. 从官方市场安装 **Batch Install Plugins from GitHub**
3. 为你的模型/对话启用此工具
4. 让模型调用工具方法
## 使用示例
```
"安装所有插件"
"从 github.com/username/repo 安装所有插件"
"只安装 pipe 插件"
"安装 action 和 filter 插件"
"安装所有插件, exclude_keywords=copilot"
```
## 热门插件仓库
这些是包含大量插件的热门仓库,你可以从中安装插件:
### 社区合集
```
# 从 iChristGit 的集合安装所有插件
"从 iChristGit/OpenWebui-Tools 安装所有插件"
# 从 Haervwe 的工具集合只安装工具
"从 Haervwe/open-webui-tools 安装所有插件"
# 从 Classic298 的仓库安装所有插件
"从 Classic298/open-webui-plugins 安装所有插件"
# 从 suurt8ll 的集合安装所有函数
"从 suurt8ll/open_webui_functions 安装所有插件"
# 只安装特定类型的插件(比如只安装工具)
"从 iChristGit/OpenWebui-Tools 只安装 tool 插件"
# 安装时排除特定关键词
"从 Haervwe/open-webui-tools 安装所有插件, exclude_keywords=test,deprecated"
```
### 支持的仓库
- `Fu-Jie/openwebui-extensions` - 默认的官方插件集合
- `iChristGit/OpenWebui-Tools` - 全面的工具和插件集合
- `Haervwe/open-webui-tools` - 专业的工具和实用程序
- `Classic298/open-webui-plugins` - 各种插件实现
- `suurt8ll/open_webui_functions` - 基于函数的插件
## 默认仓库
未指定仓库时,默认为 `Fu-Jie/openwebui-extensions`
## 插件检测规则
### Fu-Jie/openwebui-extensions严格模式
默认仓库的插件必须满足:
1. 包含 `class Tools:``class Filter:``class Pipe:``class Action:``.py` 文件
2. Docstring 中包含 `title:``description:`**`openwebui_id:`** 字段
3. 文件名不能以 `_cn` 结尾
### 其他 GitHub 仓库
其他仓库的插件必须满足:
1. 包含 `class Tools:``class Filter:``class Pipe:``class Action:``.py` 文件
2. Docstring 中包含 `title:``description:` 字段
## 配置Valves
| 参数 | 默认值 | 描述 |
| --- | --- | --- |
| `SKIP_KEYWORDS` | `test,verify,example,template,mock` | 逗号分隔的跳过关键词 |
| `TIMEOUT` | `20` | 请求超时时间(秒)|
## 确认超时时间
用户确认对话框的默认超时时间为 **2 分钟120 秒)**,为用户提供充足的时间来:
- 阅读和查看插件列表
- 做出安装决定
- 处理网络延迟
## 支持
如果这个插件对你有帮助,欢迎到 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 点个 Star这将是我持续改进的动力感谢支持。

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +0,0 @@
[![](https://img.shields.io/badge/OpenWebUI%20Community-Get%20Plugin-blue?style=for-the-badge)](https://openwebui.com/t/fujie/batch_install_plugins)
## Overview
Batch Install Plugins from GitHub is a new tool for OpenWebUI that enables one-click installation of multiple plugins directly from GitHub repositories. This initial release includes comprehensive features for discovering, filtering, and installing plugins with user confirmation, extensive multi-language support, and robust debugging capabilities for container deployments.
**[📖 README](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/tools/batch-install-plugins/README.md)**
## Features
- **One-Click Installation**: Install all plugins from a repository with a single command
- **Smart Plugin Discovery**: Parse Python files to extract metadata and validate plugins automatically
- **Multi-Type Support**: Support Pipe, Action, Filter, and Tool plugins in a single operation
- **Confirmation Dialog**: Display plugin list before installation for user review and approval
- **Selective Installation**: Exclude specific plugins using keyword-based filtering
- **Smart Fallback**: Container deployments auto-retry with localhost:8080 if primary connection fails
- **Enhanced Debugging**: Rich frontend JavaScript and backend Python logs for troubleshooting
- **Extended Timeout**: 120-second confirmation window for thoughtful decision-making
- **Async Architecture**: Non-blocking I/O operations for better performance
- **Full Internationalization**: Complete support for 11 languages with proper fallback maps
- **Auto-Update**: Automatically updates previously installed plugins
- **Self-Exclusion**: Automatically excludes the tool itself from batch operations
## Technical Highlights
- **httpx Integration**: Modern async HTTP client for reliable, non-blocking requests
- **Event Emitter Support**: Proper handling of OpenWebUI event injection with fallbacks
- **Timeout Protection**: Wrapped frontend execution with timeout guards to prevent hanging
- **Filtered List Consistency**: Uses single source of truth for confirmation and installation
- **Error Localization**: All error messages are user-facing and properly localized across languages
- **Deployment Resilience**: Intelligent base URL resolution handles domain, localhost, and containerized environments
## Supported Repositories
- **Default**: Fu-Jie/openwebui-extensions (strict validation)
- **Custom**: Any GitHub repository with Python plugin files
## Testing
Comprehensive regression tests included:
- Filtered installation list consistency
- Missing event emitter handling
- Confirmation timeout verification
- Full failure scenarios
- Localization completeness
- Connection error debug logging and smart fallback
All 6 tests pass successfully.
## Documentation
- English README with flow diagrams and usage examples
- Chinese README (README_CN.md) with complete translations
- Mirrored documentation for official docs site
- Plugin index entries in both English and Chinese
## Compatibility
- OpenWebUI: 0.2.x - 0.8.x
- Python: 3.9+
- Dependencies: httpx (async HTTP client), pydantic (type validation)
## Release Notes
- This initial v1.0.0 release includes complete plugin infrastructure with smart deployment handling.
- The plugin is designed to handle diverse deployment scenarios (domain, localhost, containerized) with minimal configuration.

View File

@@ -1,67 +0,0 @@
[![](https://img.shields.io/badge/OpenWebUI%20Community-Get%20Plugin-blue?style=for-the-badge)](https://openwebui.com/t/fujie/batch_install_plugins)
## 概述
从 GitHub 批量安装插件是一款全新的 OpenWebUI 工具,支持直接从 GitHub 仓库一键安装多个插件。此首个发布版本包含了全面的插件发现、过滤和安装功能,支持用户确认流程、广泛的多语言支持,以及针对容器部署的健壮调试能力。
**[📖 README](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/tools/batch-install-plugins/README_CN.md)**
## 主要功能
- **一键安装**:通过单个命令安装仓库中的所有插件
- **智能插件发现**:解析 Python 文件提取元数据并自动验证插件
- **多类型支持**:在单个操作中支持 Pipe、Action、Filter 和 Tool 插件
- **确认对话框**:安装前显示插件列表供用户审查和批准
- **选择性安装**:通过基于关键词的过滤排除特定插件
- **智能降级**:容器环境中主 URL 连接失败时自动重试 localhost:8080
- **增强调试**:前端 JavaScript 和后端 Python 富日志输出,便于排查问题
- **延长超时**120 秒确认窗口,给用户充分的思考时间
- **异步架构**:非阻塞 I/O 操作,性能更优
- **完整国际化**:支持 11 种语言,包含适当的回退机制
- **自动更新**:自动更新之前安装过的插件
- **自排除机制**:自动排除工具自身,避免在批量操作中重复安装
## 技术亮点
- **httpx 集成**:现代化的异步 HTTP 客户端,请求更可靠且非阻塞
- **事件注入支持**:正确处理 OpenWebUI 事件注入,提供回退支持
- **超时保护**:前端执行周围包装了超时保护,防止进程挂起
- **过滤列表一致性**:确认和安装使用同一份过滤列表
- **错误本地化**:所有错误消息都是面向用户的,已正确本地化到各语言
- **部署弹性**:智能 Base URL 解析处理域名、localhost 和容器化环境
## 支持的仓库
- **默认**Fu-Jie/openwebui-extensions严格验证
- **自定义**:任意 GitHub 仓库中的 Python 插件文件
## 测试覆盖
包含全面的回归测试:
- 过滤安装列表一致性
- 缺少事件注入器时的处理
- 确认超时验证
- 完全失败场景
- 本地化完整性
- 连接错误调试日志和智能降级
所有 6 个测试均通过。
## 文档
- 英文 README包含流程图和使用示例
- 中文 README (README_CN.md),完整翻译
- 官方文档站点的镜像文档
- 英文和中文的插件索引条目
## 兼容性
- OpenWebUI0.2.x - 0.8.x
- Python3.9+
- 依赖httpx异步 HTTP 客户端、pydantic类型验证
## 发布说明
- 本首发 v1.0.0 版本包含完整的插件基础设施和智能部署处理能力。
- 该插件设计用于处理多种部署场景域名、localhost、容器化配置最少。

View File

@@ -1,6 +1,6 @@
# MkDocs Documentation Dependencies # MkDocs Documentation Dependencies
# Core MkDocs # Core MkDocs
mkdocs>=1.5.0,<2.0.0 mkdocs>=1.5.0
# Material Theme for MkDocs # Material Theme for MkDocs
mkdocs-material>=9.5.0 mkdocs-material>=9.5.0

View File

@@ -1,26 +0,0 @@
# OpenWebUI Bulk Installer Configuration
#
# Instructions:
# - api_key: Copy from OpenWebUI Settings (starts with sk-)
# - url: OpenWebUI server address (supports localhost, IP, and domain)
#
# URL Examples:
# - Local: http://localhost:3000
# - Remote IP: http://192.168.1.10:3000
# - Domain: https://openwebui.example.com
#
# Environment variable precedence (highest to lowest):
# 1. OPENWEBUI_API_KEY / OPENWEBUI_URL environment variables
# 2. OPENWEBUI_BASE_URL environment variable
# 3. Configuration in this .env file
# API Key (required)
api_key=sk-your-api-key-here
# OpenWebUI server address (required)
# Configure the baseURL where your OpenWebUI instance is running
url=http://localhost:3000
# Alternatively, use environment variable format (both methods are equivalent)
# OPENWEBUI_API_KEY=sk-your-api-key-here
# OPENWEBUI_BASE_URL=http://localhost:3000

View File

@@ -1,156 +1,147 @@
# 🚀 Local Deployment Scripts Guide # 🚀 本地部署脚本指南 (Local Deployment Guide)
## Overview ## 概述
This directory contains automated scripts for deploying plugins in development to a local OpenWebUI instance. They enable quick code pushes without restarting OpenWebUI. 本目录包含用于将开发中的插件部署到本地 OpenWebUI 实例的自动化脚本。它们可以快速推送代码更改而无需重启 OpenWebUI
## Prerequisites ## 前置条件
1. **OpenWebUI Running**: Make sure OpenWebUI is running locally (default `http://localhost:3000`)
2. **API Key**: You need a valid OpenWebUI API key
3. **Environment File**: Create a `.env` file in this directory containing your API key:
1. **OpenWebUI 运行中**: 确保 OpenWebUI 在本地运行(默认 `http://localhost:3003`
2. **API 密钥**: 需要一个有效的 OpenWebUI API 密钥
3. **环境文件**: 在此目录创建 `.env` 文件,包含 API 密钥:
``` ```
api_key=sk-xxxxxxxxxxxxx api_key=sk-xxxxxxxxxxxxx
``` ```
## Quick Start ## 快速开始
### Deploy a Pipe Plugin ### 部署 Pipe 插件
```bash ```bash
# Deploy GitHub Copilot SDK Pipe # 部署 GitHub Copilot SDK Pipe
python deploy_pipe.py python deploy_pipe.py
``` ```
### Deploy a Filter Plugin ### 部署 Filter 插件
```bash ```bash
# Deploy async_context_compression Filter (default) # 部署 async_context_compression Filter(默认)
python deploy_filter.py python deploy_filter.py
# Deploy a specific Filter plugin # 部署指定的 Filter 插件
python deploy_filter.py my-filter-name python deploy_filter.py my-filter-name
# List all available Filters # 列出所有可用的 Filter
python deploy_filter.py --list python deploy_filter.py --list
``` ```
## Script Documentation ## 脚本说明
### `deploy_filter.py` — Filter Plugin Deployment Tool ### `deploy_filter.py` — Filter 插件部署工具
Used to deploy Filter-type plugins (such as message filtering, context compression, etc.). 用于部署 Filter 类型的插件(如消息过滤、上下文压缩等)。
**Key Features**: **主要特性**:
- ✅ 从 Python 文件自动提取元数据(版本、作者、描述等)
- ✅ Auto-extracts metadata from Python files (version, author, description, etc.) - ✅ 尝试更新现有插件,若不存在则创建新插件
- ✅ Attempts to update existing plugins, creates if not found - ✅ 支持多个 Filter 插件管理
- ✅ Supports multiple Filter plugin management - ✅ 详细的错误提示和连接诊断
- ✅ Detailed error messages and connection diagnostics
**Usage**:
**用法**:
```bash ```bash
# Deploy async_context_compression (default) # 默认部署 async_context_compression
python deploy_filter.py python deploy_filter.py
# Deploy other Filters # 部署其他 Filter
python deploy_filter.py async-context-compression python deploy_filter.py async-context-compression
python deploy_filter.py workflow-guide python deploy_filter.py workflow-guide
# List all available Filters # 列出所有可用 Filter
python deploy_filter.py --list python deploy_filter.py --list
python deploy_filter.py -l python deploy_filter.py -l
``` ```
**Workflow**: **工作流程**:
1. 从 `.env` 加载 API 密钥
2. 查找目标 Filter 插件目录
3. 读取 Python 源文件
4. 从 docstring 提取元数据title, version, author, description, etc.
5. 构建 API 请求负载
6. 发送更新请求到 OpenWebUI
7. 若更新失败,自动尝试创建新插件
8. 显示结果和诊断信息
1. Load API key from `.env` ### `deploy_pipe.py` — Pipe 插件部署工具
2. Find target Filter plugin directory
3. Read Python source file
4. Extract metadata from docstring (title, version, author, description, etc.)
5. Build API request payload
6. Send update request to OpenWebUI
7. If update fails, auto-attempt to create new plugin
8. Display results and diagnostic info
### `deploy_pipe.py` — Pipe Plugin Deployment Tool 用于部署 Pipe 类型的插件(如 GitHub Copilot SDK
Used to deploy Pipe-type plugins (such as GitHub Copilot SDK).
**Usage**:
**使用**:
```bash ```bash
python deploy_pipe.py python deploy_pipe.py
``` ```
## Get an API Key ## 获取 API 密钥
### Method 1: Use Existing User Token (Recommended) ### 方法 1: 使用现有用户令牌(推荐)
1. Open OpenWebUI interface 1. 打开 OpenWebUI 界面
2. Click user avatar → Settings 2. 点击用户头像 → Settings(设置)
3. Find the API Keys section 3. 找到 API Keys 部分
4. Copy your API key (starts with sk-) 4. 复制你的 API 密钥sk-开头)
5. Paste into `.env` file 5. 粘贴到 `.env` 文件中
### Method 2: Create a Long-term API Key ### 方法 2: 创建长期 API 密钥
Create a dedicated long-term API key in OpenWebUI Settings for deployment purposes. 在 OpenWebUI 设置中创建专用于部署的长期 API 密钥。
## Troubleshooting ## 故障排除
### "Connection error: Could not reach OpenWebUI at localhost:3000" ### "Connection error: Could not reach OpenWebUI at localhost:3003"
**Cause**: OpenWebUI is not running or port is different **原因**: OpenWebUI 未运行或端口不同
**Solution**: **解决方案**:
- 确保 OpenWebUI 正在运行
- Make sure OpenWebUI is running - 检查 OpenWebUI 实际监听的端口(通常是 3000 或 3003
- Check which port OpenWebUI is actually listening on (usually 3000) - 根据需要编辑脚本中的 URL
- Edit the URL in the script if needed
### ".env file not found" ### ".env file not found"
**Cause**: `.env` file was not created **原因**: 未创建 `.env` 文件
**Solution**:
**解决方案**:
```bash ```bash
echo "api_key=sk-your-api-key-here" > .env echo "api_key=sk-your-api-key-here" > .env
``` ```
### "Filter 'xxx' not found" ### "Filter 'xxx' not found"
**Cause**: Filter directory name is incorrect **原因**: Filter 目录名不正确
**Solution**:
**解决方案**:
```bash ```bash
# List all available Filters # 列出所有可用的 Filter
python deploy_filter.py --list python deploy_filter.py --list
``` ```
### "Failed to update or create. Status: 401" ### "Failed to update or create. Status: 401"
**Cause**: API key is invalid or expired **原因**: API 密钥无效或过期
**Solution**: **解决方案**:
1. 验证 API 密钥的有效性
2. 获取新的 API 密钥
3. 更新 `.env` 文件
1. Verify your API key is valid ## 工作流示例
2. Generate a new API key
3. Update the `.env` file
## Workflow Examples ### 开发并部署新的 Filter
### Develop and Deploy a New Filter
```bash ```bash
# 1. Create new Filter directory in plugins/filters/ # 1. plugins/filters/ 创建新的 Filter 目录
mkdir plugins/filters/my-new-filter mkdir plugins/filters/my-new-filter
# 2. Create my_new_filter.py with required metadata: # 2. 创建 my_new_filter.py 文件,包含必要的元数据:
# """ # """
# title: My New Filter # title: My New Filter
# author: Your Name # author: Your Name
@@ -158,59 +149,58 @@ mkdir plugins/filters/my-new-filter
# description: Filter description # description: Filter description
# """ # """
# 3. Deploy to local OpenWebUI # 3. 部署到本地 OpenWebUI
cd scripts cd scripts
python deploy_filter.py my-new-filter python deploy_filter.py my-new-filter
# 4. Test the plugin in OpenWebUI UI # 4. OpenWebUI UI 中测试插件
# 5. Continue development # 5. 继续迭代开发
# ... modify code ... # ... 修改代码 ...
# 6. Re-deploy (auto-overwrites) # 6. 重新部署(自动覆盖)
python deploy_filter.py my-new-filter python deploy_filter.py my-new-filter
``` ```
### Fix a Bug and Deploy Quickly ### 修复 Bug 并快速部署
```bash ```bash
# 1. Modify the source code # 1. 修改源代码
# vim ../plugins/filters/async-context-compression/async_context_compression.py # vim ../plugins/filters/async-context-compression/async_context_compression.py
# 2. Deploy immediately to local # 2. 立即部署到本地
python deploy_filter.py async-context-compression python deploy_filter.py async-context-compression
# 3. Test the fix in OpenWebUI # 3. OpenWebUI 中测试修复
# (No need to restart OpenWebUI) # (无需重启 OpenWebUI
``` ```
## Security Considerations ## 安全注意事项
⚠️ **Important**: ⚠️ **重要**:
- ✅ 将 `.env` 文件添加到 `.gitignore`(避免提交敏感信息)
- ✅ 不要在版本控制中提交 API 密钥
- ✅ 仅在可信的网络环境中使用
- ✅ 定期轮换 API 密钥
- ✅ Add `.env` file to `.gitignore` (avoid committing sensitive info) ## 文件结构
- ✅ Never commit API keys to version control
- ✅ Use only on trusted networks
- ✅ Rotate API keys periodically
## File Structure
``` ```
scripts/ scripts/
├── deploy_filter.py # Filter plugin deployment tool ├── deploy_filter.py # Filter 插件部署工具
├── deploy_pipe.py # Pipe plugin deployment tool ├── deploy_pipe.py # Pipe 插件部署工具
├── .env # API key (local, not committed) ├── .env # API 密钥(本地,不提交)
├── README.md # This file ├── README.md # 本文件
└── ... └── ...
``` ```
## Reference Resources ## 参考资源
- [OpenWebUI Documentation](https://docs.openwebui.com/) - [OpenWebUI 文档](https://docs.openwebui.com/)
- [Plugin Development Guide](../docs/development/plugin-guide.md) - [插件开发指南](../docs/development/plugin-guide.md)
- [Filter Plugin Examples](../plugins/filters/) - [Filter 插件示例](../plugins/filters/)
--- ---
**Last Updated**: 2026-03-09 **最后更新**: 2026-03-09
**Author**: Fu-Jie **作者**: Fu-Jie

View File

@@ -1,118 +1,114 @@
# 📦 Async Context Compression — Local Deployment Tools # 📦 Async Context Compression — 本地部署工具 (Local Deployment Tools)
## 🎯 Feature Overview ## 🎯 功能概述
Added a complete local deployment toolchain for the `async_context_compression` Filter plugin, supporting fast iterative development without restarting OpenWebUI. `async_context_compression` Filter 插件添加了完整的本地部署工具链,支持快速迭代开发无需重启 OpenWebUI
## 📋 New Files ## 📋 新增文件
### 1. **deploy_filter.py** — Filter Plugin Deployment Script ### 1. **deploy_filter.py** — Filter 插件部署脚本
- **位置**: `scripts/deploy_filter.py`
- **功能**: 自动部署 Filter 类插件到本地 OpenWebUI 实例
- **特性**:
- ✅ 从 Python docstring 自动提取元数据
- ✅ 智能版本号识别semantic versioning
- ✅ 支持多个 Filter 插件管理
- ✅ 自动更新或创建插件
- ✅ 详细的错误诊断和连接测试
- ✅ 列表指令查看所有可用 Filter
- **代码行数**: ~300 行
- **Location**: `scripts/deploy_filter.py` ### 2. **DEPLOYMENT_GUIDE.md** — 完整部署指南
- **Function**: Auto-deploy Filter-type plugins to local OpenWebUI instance - **位置**: `scripts/DEPLOYMENT_GUIDE.md`
- **Features**: - **内容**:
- ✅ Auto-extract metadata from Python docstring - 前置条件和快速开始
- ✅ Smart semantic version recognition - 脚本详细说明
- ✅ Support multiple Filter plugin management - API 密钥获取方法
- ✅ Auto-update or create plugins - 故障排除指南
- ✅ Detailed error diagnostics and connection testing - 分步工作流示例
- ✅ List command to view all available Filters
- **Code Lines**: ~300
### 2. **DEPLOYMENT_GUIDE.md** — Complete Deployment Guide ### 3. **QUICK_START.md** — 快速参考卡片
- **位置**: `scripts/QUICK_START.md`
- **内容**:
- 一行命令部署
- 前置步骤
- 常见命令表格
- 故障诊断速查表
- CI/CD 集成示例
- **Location**: `scripts/DEPLOYMENT_GUIDE.md` ### 4. **test_deploy_filter.py** — 单元测试套件
- **Contents**: - **位置**: `tests/scripts/test_deploy_filter.py`
- Prerequisites and quick start - **测试覆盖**:
- Detailed script documentation - ✅ Filter 文件发现 (3 个测试)
- API key retrieval method - ✅ 元数据提取 (3 个测试)
- Troubleshooting guide - ✅ API 负载构建 (4 个测试)
- Step-by-step workflow examples - **测试通过率**: 10/10 ✅
### 3. **QUICK_START.md** — Quick Reference Card ## 🚀 使用方式
- **Location**: `scripts/QUICK_START.md` ### 基本部署(一行命令)
- **Contents**:
- One-line deployment command
- Setup steps
- Common commands table
- Troubleshooting quick-reference table
- CI/CD integration examples
### 4. **test_deploy_filter.py** — Unit Test Suite
- **Location**: `tests/scripts/test_deploy_filter.py`
- **Test Coverage**:
- ✅ Filter file discovery (3 tests)
- ✅ Metadata extraction (3 tests)
- ✅ API payload building (4 tests)
- **Pass Rate**: 10/10 ✅
## 🚀 Usage
### Basic Deploy (One-liner)
```bash ```bash
cd scripts cd scripts
python deploy_filter.py python deploy_filter.py
``` ```
### List All Available Filters ### 列出所有可用 Filter
```bash ```bash
python deploy_filter.py --list python deploy_filter.py --list
``` ```
### Deploy Specific Filter ### 部署指定 Filter
```bash ```bash
python deploy_filter.py folder-memory python deploy_filter.py folder-memory
python deploy_filter.py context_enhancement_filter python deploy_filter.py context_enhancement_filter
``` ```
## 🔧 How It Works ## 🔧 工作原理
``` ```
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ 1. Load API key (.env) │ │ 1. 加载 API 密钥 (.env) │
└──────────────────┬──────────────────────────────────────────┘ └──────────────────┬──────────────────────────────────────────┘
┌──────────────────▼──────────────────────────────────────────┐ ┌──────────────────▼──────────────────────────────────────────┐
│ 2. Find Filter plugin file │ 2. 查找 Filter 插件文件
│ - Infer file path from name │ - 从名称推断文件路径
│ - Support hyphen-case and snake_case lookup │ - 支持 hyphen-case snake_case 查找
└──────────────────┬──────────────────────────────────────────┘ └──────────────────┬──────────────────────────────────────────┘
┌──────────────────▼──────────────────────────────────────────┐ ┌──────────────────▼──────────────────────────────────────────┐
│ 3. Read Python source code │ 3. 读取 Python 源代码
│ - Extract docstring metadata │ - 提取 docstring 元数据
│ - title, version, author, description, openwebui_id │ │ - title, version, author, description, openwebui_id │
└──────────────────┬──────────────────────────────────────────┘ └──────────────────┬──────────────────────────────────────────┘
┌──────────────────▼──────────────────────────────────────────┐ ┌──────────────────▼──────────────────────────────────────────┐
│ 4. Build API request payload │ 4. 构建 API 请求负载
│ - Assemble manifest and meta info │ - 组装 manifest meta 信息
│ - Include complete source code content │ - 包含完整源代码内容
└──────────────────┬──────────────────────────────────────────┘ └──────────────────┬──────────────────────────────────────────┘
┌──────────────────▼──────────────────────────────────────────┐ ┌──────────────────▼──────────────────────────────────────────┐
│ 5. Send request │ 5. 发送请求
│ - POST /api/v1/functions/id/{id}/update (update) │ - POST /api/v1/functions/id/{id}/update (更新)
│ - POST /api/v1/functions/create (create fallback) │ - POST /api/v1/functions/create (创建备用)
└──────────────────┬──────────────────────────────────────────┘ └──────────────────┬──────────────────────────────────────────┘
┌──────────────────▼──────────────────────────────────────────┐ ┌──────────────────▼──────────────────────────────────────────┐
│ 6. Display results and diagnostics │ 6. 显示结果和诊断
│ - ✅ Update/create success │ - ✅ 更新/创建成功
│ - ❌ Error messages and solutions │ - ❌ 错误信息和解决建议
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
``` ```
## 📊 Supported Filters List ## 📊 支持的 Filter 列表
Script auto-discovers the following Filters: 脚本自动发现以下 Filter
| Filter Name | Python File | Version | | Filter 名称 | Python 文件 | 版本 |
|-----------|-----------|------| |-----------|-----------|------|
| async-context-compression | async_context_compression.py | 1.3.0+ | | async-context-compression | async_context_compression.py | 1.3.0+ |
| chat-session-mapping-filter | chat_session_mapping_filter.py | 0.1.0+ | | chat-session-mapping-filter | chat_session_mapping_filter.py | 0.1.0+ |
@@ -122,11 +118,11 @@ Script auto-discovers the following Filters:
| markdown_normalizer | markdown_normalizer.py | 1.2.8+ | | markdown_normalizer | markdown_normalizer.py | 1.2.8+ |
| web_gemini_multimodel_filter | web_gemini_multimodel_filter.py | 0.3.2+ | | web_gemini_multimodel_filter | web_gemini_multimodel_filter.py | 0.3.2+ |
## ⚙️ Technical Details ## ⚙️ 技术细节
### Metadata Extraction ### 元数据提取
Script extracts metadata from the docstring at the top of Python file: 脚本从 Python 文件顶部的 docstring 中提取元数据:
```python ```python
""" """
@@ -141,112 +137,111 @@ openwebui_id: b1655bc8-6de9-4cad-8cb5-a6f7829a02ce
""" """
``` ```
**Supported Metadata Fields**: **支持的元数据字段**:
- `title` — Filter 显示名称 ✅
- `id` — 唯一标识符 ✅
- `author` — 作者名称 ✅
- `author_url` — 作者主页链接 ✅
- `funding_url` — 项目链接 ✅
- `description` — 功能描述 ✅
- `version` — 语义化版本号 ✅
- `openwebui_id` — OpenWebUI UUID (可选)
- `title` — Filter display name ✅ ### API 集成
- `id` — Unique identifier ✅
- `author` — Author name ✅
- `author_url` — Author homepage ✅
- `funding_url` — Project link ✅
- `description` — Feature description ✅
- `version` — Semantic version number ✅
- `openwebui_id` — OpenWebUI UUID (optional)
### API Integration 脚本使用 OpenWebUI REST API
Script uses OpenWebUI REST API:
``` ```
POST /api/v1/functions/id/{filter_id}/update POST /api/v1/functions/id/{filter_id}/update
- Update existing Filter - 更新现有 Filter
- HTTP 200: Update success - HTTP 200: 更新成功
- HTTP 404: Filter not found, auto-attempt create - HTTP 404: Filter 不存在,自动尝试创建
POST /api/v1/functions/create POST /api/v1/functions/create
- Create new Filter - 创建新 Filter
- HTTP 200: Creation success - HTTP 200: 创建成功
``` ```
**Authentication**: Bearer token (API key method) **认证**: Bearer token (API 密钥方式)
## 🔐 Security ## 🔐 安全性
### API Key Management ### API 密钥管理
```bash ```bash
# 1. Create .env file # 1. 创建 .env 文件
echo "api_key=sk-your-key-here" > scripts/.env echo "api_key=sk-your-key-here" > scripts/.env
# 2. Add .env to .gitignore # 2. .env 添加到 .gitignore
echo "scripts/.env" >> .gitignore echo "scripts/.env" >> .gitignore
# 3. Don't commit API key # 3. 不要提交 API 密钥
git add scripts/.gitignore git add scripts/.gitignore
git commit -m "chore: add .env to gitignore" git commit -m "chore: add .env to gitignore"
``` ```
### Best Practices ### 最佳实践
-Use long-term auth tokens (not short-term JWT) -使用长期认证令牌(而不是短期 JWT
-Rotate API keys periodically -定期轮换 API 密钥
-Limit key permission scope -限制密钥权限范围
-Use only on trusted networks -在可信网络中使用
-Use CI/CD secret management in production -生产环境使用 CI/CD 秘密管理
## 🧪 Test Verification ## 🧪 测试验证
### Run Test Suite ### 运行测试套件
```bash ```bash
pytest tests/scripts/test_deploy_filter.py -v pytest tests/scripts/test_deploy_filter.py -v
``` ```
### Test Coverage ### 测试覆盖范围
``` ```
✅ TestFilterDiscovery (3 tests) ✅ TestFilterDiscovery (3 个测试)
- test_find_async_context_compression - test_find_async_context_compression
- test_find_nonexistent_filter - test_find_nonexistent_filter
- test_find_filter_with_underscores - test_find_filter_with_underscores
✅ TestMetadataExtraction (3 tests) ✅ TestMetadataExtraction (3 个测试)
- test_extract_metadata_from_async_compression - test_extract_metadata_from_async_compression
- test_extract_metadata_empty_file - test_extract_metadata_empty_file
- test_extract_metadata_multiline_docstring - test_extract_metadata_multiline_docstring
✅ TestPayloadBuilding (4 tests) ✅ TestPayloadBuilding (4 个测试)
- test_build_filter_payload_basic - test_build_filter_payload_basic
- test_payload_has_required_fields - test_payload_has_required_fields
- test_payload_with_openwebui_id - test_payload_with_openwebui_id
✅ TestVersionExtraction (1 test) ✅ TestVersionExtraction (1 个测试)
- test_extract_valid_version - test_extract_valid_version
Result: 10/10 PASSED 结果: 10/10 通过
``` ```
## 💡 Common Use Cases ## 💡 常见用例
### Use Case 1: Quick Test After Bug Fix ### 用例 1: 修复 Bug 后快速测试
```bash ```bash
# 1. Modify code # 1. 修改代码
vim plugins/filters/async-context-compression/async_context_compression.py vim plugins/filters/async-context-compression/async_context_compression.py
# 2. Deploy immediately (no OpenWebUI restart needed) # 2. 立即部署(不需要重启 OpenWebUI
cd scripts && python deploy_filter.py cd scripts && python deploy_filter.py
# 3. Test fix in OpenWebUI # 3. OpenWebUI 中测试修复
# 4. Iterate (return to step 1) # 4. 重复迭代(返回步骤 1
``` ```
### Use Case 2: Develop New Filter ### 用例 2: 开发新的 Filter
```bash ```bash
# 1. Create new Filter directory # 1. 创建新 Filter 目录
mkdir plugins/filters/my-new-filter mkdir plugins/filters/my-new-filter
# 2. Write code (include required docstring metadata) # 2. 编写代码(包含必要的 docstring 元数据)
cat > plugins/filters/my-new-filter/my_new_filter.py << 'EOF' cat > plugins/filters/my-new-filter/my_new_filter.py << 'EOF'
""" """
title: My New Filter title: My New Filter
@@ -259,33 +254,33 @@ class Filter:
# ... implementation ... # ... implementation ...
EOF EOF
# 3. First deployment (create) # 3. 首次部署(创建)
cd scripts && python deploy_filter.py my-new-filter cd scripts && python deploy_filter.py my-new-filter
# 4. Test in OpenWebUI UI # 4. OpenWebUI UI 测试
# 5. Repeat updates # 5. 重复更新
cd scripts && python deploy_filter.py my-new-filter cd scripts && python deploy_filter.py my-new-filter
``` ```
### Use Case 3: Version Update and Release ### 用例 3: 版本更新和发布
```bash ```bash
# 1. Update version number # 1. 更新版本号
vim plugins/filters/async-context-compression/async_context_compression.py vim plugins/filters/async-context-compression/async_context_compression.py
# Change: version: 1.3.0 → version: 1.4.0 # 修改: version: 1.3.0 → version: 1.4.0
# 2. Deploy new version # 2. 部署新版本
cd scripts && python deploy_filter.py cd scripts && python deploy_filter.py
# 3. After testing, commit # 3. 测试通过后提交
git add plugins/filters/async-context-compression/ git add plugins/filters/async-context-compression/
git commit -m "feat(filters): update async-context-compression to 1.4.0" git commit -m "feat(filters): update async-context-compression to 1.4.0"
git push git push
``` ```
## 🔄 CI/CD Integration ## 🔄 CI/CD 集成
### GitHub Actions Example ### GitHub Actions 示例
```yaml ```yaml
name: Deploy Filter on Release name: Deploy Filter on Release
@@ -313,74 +308,71 @@ jobs:
api_key: ${{ secrets.OPENWEBUI_API_KEY }} api_key: ${{ secrets.OPENWEBUI_API_KEY }}
``` ```
## 📚 Reference Documentation ## 📚 参考文档
- [Complete Deployment Guide](DEPLOYMENT_GUIDE.md) - [完整部署指南](DEPLOYMENT_GUIDE.md)
- [Quick Reference Card](QUICK_START.md) - [快速参考卡片](QUICK_START.md)
- [Test Suite](../tests/scripts/test_deploy_filter.py) - [测试套件](../tests/scripts/test_deploy_filter.py)
- [Plugin Development Guide](../docs/development/plugin-guide.md) - [插件开发指南](../docs/development/plugin-guide.md)
- [OpenWebUI Documentation](https://docs.openwebui.com/) - [OpenWebUI 文档](https://docs.openwebui.com/)
## 🎓 Learning Resources ## 🎓 学习资源
### Architecture Understanding ### 架构理解
``` ```
OpenWebUI System Design OpenWebUI 系统设计
Filter Plugin Type Definition Filter 插件类型定义
REST API Interface (/api/v1/functions) REST API 接口 (/api/v1/functions)
Local Deployment Script Implementation (deploy_filter.py) 本地部署脚本实现 (deploy_filter.py)
Metadata Extraction and Delivery 元数据提取和投递
``` ```
### Debugging Tips ### 调试技巧
1. **Enable Verbose Logging**:
1. **启用详细日志**:
```bash ```bash
python deploy_filter.py 2>&1 | tee deploy.log python deploy_filter.py 2>&1 | tee deploy.log
``` ```
2. **Test API Connection**: 2. **测试 API 连接**:
```bash ```bash
curl -X GET http://localhost:3000/api/v1/functions \ curl -X GET http://localhost:3003/api/v1/functions \
-H "Authorization: Bearer $API_KEY" -H "Authorization: Bearer $API_KEY"
``` ```
3. **Verify .env File**: 3. **验证 .env 文件**:
```bash ```bash
grep "api_key=" scripts/.env grep "api_key=" scripts/.env
``` ```
## 📞 Troubleshooting ## 📞 故障排除
| Issue | Diagnosis | Solution | | 问题 | 诊断 | 解决方案 |
|-------|-----------|----------| |------|------|----------|
| Connection error | Wrong OpenWebUI address/port | Check localhost:3000; modify URL if needed | | Connection error | OpenWebUI 地址/端口不对 | 检查 localhost:3003修改 URL 如需要 |
| .env not found | Config file not created | `echo "api_key=sk-..." > scripts/.env` | | .env not found | 未创建配置文件 | `echo "api_key=sk-..." > scripts/.env` |
| Filter not found | Wrong Plugin name | Run `python deploy_filter.py --list` | | Filter not found | 插件名称错误 | 运行 `python deploy_filter.py --list` |
| Status 401 | Invalid/expired API key | Update key in `.env` | | Status 401 | API 密钥无效/过期 | 更新 `.env` 中的密钥 |
| Status 500 | Server error | Check OpenWebUI service logs | | Status 500 | 服务器错误 | 检查 OpenWebUI 服务日志 |
## ✨ Highlight Features ## ✨ 特色功能
| Feature | Description | | 特性 | 描述 |
|---------|-------------| |------|------|
| 🔍 Auto Discovery | Automatically find all Filter plugins | | 🔍 自动发现 | 自动查找所有 Filter 插件 |
| 📊 Metadata Extraction | Auto-extract version and metadata from code | | 📊 元数据提取 | 从代码自动提取版本和元数据 |
| ♻️ Auto-update | Smart handling of update or create | | ♻️ 自动更新 | 智能处理更新或创建 |
| 🛡️ Error Handling | Detailed error messages and diagnostics | | 🛡️ 错误处理 | 详细的错误提示和诊断信息 |
| 🚀 Fast Iteration | Second-level deployment, no restart | | 🚀 快速迭代 | 秒级部署,无需重启 |
| 🧪 Complete Testing | 10 unit tests covering core functions | | 🧪 完整测试 | 10 个单元测试覆盖核心功能 |
--- ---
**Last Updated**: 2026-03-09 **最后更新**: 2026-03-09
**Author**: Fu-Jie **作者**: Fu-Jie
**Project**: [openwebui-extensions](https://github.com/Fu-Jie/openwebui-extensions) **项目**: [openwebui-extensions](https://github.com/Fu-Jie/openwebui-extensions)

View File

@@ -1,76 +1,76 @@
# ⚡ Quick Deployment Reference # ⚡ 快速部署参考 (Quick Deployment Reference)
## One-line Deploy Commands ## 一行命令部署
```bash ```bash
# Deploy async_context_compression Filter (default) # 部署 async_context_compression Filter(默认)
cd scripts && python deploy_filter.py cd scripts && python deploy_filter.py
# List all available Filters # 列出所有可用 Filter
cd scripts && python deploy_filter.py --list cd scripts && python deploy_filter.py --list
``` ```
## Setup Steps (One time only) ## 前置步骤(仅需一次)
```bash ```bash
# 1. Enter scripts directory # 1. 进入 scripts 目录
cd scripts cd scripts
# 2. Create .env file with your OpenWebUI API key # 2. 创建 .env 文件,包含 OpenWebUI API 密钥
echo "api_key=sk-your-api-key-here" > .env echo "api_key=sk-your-api-key-here" > .env
# 3. Make sure OpenWebUI is running on localhost:3000 # 3. 确保 OpenWebUI 运行在 localhost:3003
``` ```
## Get Your API Key ## 获取 API 密钥
1. Open OpenWebUI → user avatar → Settings 1. 打开 OpenWebUI → 用户头像 → Settings
2. Find "API Keys" section 2. 找到 "API Keys" 部分
3. Copy your key (starts with sk-) 3. 复制密钥sk-开头)
4. Paste into `.env` file 4. 粘贴到 `.env` 文件
## Deployment Workflow ## 部署流程
```bash ```bash
# 1. Edit plugin code # 1. 编辑插件代码
vim ../plugins/filters/async-context-compression/async_context_compression.py vim ../plugins/filters/async-context-compression/async_context_compression.py
# 2. Deploy to local # 2. 部署到本地
python deploy_filter.py python deploy_filter.py
# 3. Test in OpenWebUI (no restart needed) # 3. OpenWebUI 测试(无需重启)
# 4. Deploy again (auto-overwrites) # 4. 重复部署(自动覆盖)
python deploy_filter.py python deploy_filter.py
``` ```
## Common Commands ## 常见命令
| Command | Description | | 命令 | 说明 |
|---------|-------------| |------|------|
| `python deploy_filter.py` | Deploy async_context_compression | | `python deploy_filter.py` | 部署 async_context_compression |
| `python deploy_filter.py filter-name` | Deploy specific Filter | | `python deploy_filter.py filter-name` | 部署指定 Filter |
| `python deploy_filter.py --list` | List all available Filters | | `python deploy_filter.py --list` | 列出所有可用 Filter |
| `python deploy_pipe.py` | Deploy GitHub Copilot SDK Pipe | | `python deploy_pipe.py` | 部署 GitHub Copilot SDK Pipe |
## Troubleshooting ## 故障诊断
| Error | Cause | Solution | | 错误 | 原因 | 解决方案 |
|-------|-------|----------| |------|------|----------|
| Connection error | OpenWebUI not running | Start OpenWebUI or check port | | Connection error | OpenWebUI 未运行 | 启动 OpenWebUI 或检查端口 |
| .env not found | Config file not created | `echo "api_key=sk-..." > .env` | | .env not found | 未创建配置文件 | `echo "api_key=sk-..." > .env` |
| Filter not found | Filter name is wrong | Run `python deploy_filter.py --list` | | Filter not found | Filter 名称错误 | 运行 `python deploy_filter.py --list` |
| Status 401 | API key invalid | Update key in `.env` | | Status 401 | API 密钥无效 | 更新 `.env` 中的密钥 |
## File Locations ## 文件位置
``` ```
openwebui-extensions/ openwebui-extensions/
├── scripts/ ├── scripts/
│ ├── deploy_filter.py ← Filter deployment tool │ ├── deploy_filter.py ← Filter 部署工具
│ ├── deploy_pipe.py ← Pipe deployment tool │ ├── deploy_pipe.py ← Pipe 部署工具
│ ├── .env ← API key (don't commit) │ ├── .env ← API 密钥(不提交)
│ └── DEPLOYMENT_GUIDE.md ← Full guide │ └── DEPLOYMENT_GUIDE.md ← 完整指南
└── plugins/ └── plugins/
└── filters/ └── filters/
@@ -80,26 +80,26 @@ openwebui-extensions/
└── README_CN.md └── README_CN.md
``` ```
## Suggested Workflow ## 工作流建议
### Fast Iterative Development ### 快速迭代开发
```bash ```bash
# Terminal 1: Start OpenWebUI (if not running) # Terminal 1: 启动 OpenWebUI(如果未运行)
docker run -d -p 3000:8080 ghcr.io/open-webui/open-webui:latest docker run -d -p 3003:8080 ghcr.io/open-webui/open-webui:latest
# Terminal 2: Development loop (repeated) # Terminal 2: 开发环节(重复执行)
cd scripts cd scripts
code ../plugins/filters/async-context-compression/ # Edit code code ../plugins/filters/async-context-compression/ # 编辑代码
python deploy_filter.py # Deploy python deploy_filter.py # 部署
# → Test in OpenWebUI # → OpenWebUI 测试
# → Go back to edit, repeat # → 返回编辑,重复
``` ```
### CI/CD Integration ### CI/CD 集成
```bash ```bash
# In GitHub Actions # GitHub Actions
- name: Deploy filter to staging - name: Deploy filter to staging
run: | run: |
cd scripts cd scripts
@@ -110,4 +110,4 @@ python deploy_filter.py # Deploy
--- ---
📚 **More Help**: See `DEPLOYMENT_GUIDE.md` 📚 **更多帮助**: 查看 `DEPLOYMENT_GUIDE.md`

View File

@@ -1,86 +1,70 @@
# 🚀 Deployment Scripts Guide # 🚀 部署脚本使用指南 (Deployment Scripts Guide)
## 📁 Deployment Tools ## 📁 新增部署工具
To support quick local deployment of async_context_compression and other Filter plugins, we've added the following files: 为了支持快速本地部署 async_context_compression 和其他 Filter 插件,我们添加了以下文件:
### File Inventory ### 具体文件列表
``` ```
scripts/ scripts/
├── install_all_plugins.py ✨ Batch install Action/Filter/Pipe/Tool plugins ├── deploy_filter.py ✨ 通用 Filter 部署工具
├── deploy_filter.py ✨ Generic Filter deployment tool ├── deploy_async_context_compression.py ✨ Async Context Compression 快捷部署
├── deploy_tool.py ✨ Tool plugin deployment tool ├── deploy_pipe.py (已有) Pipe 部署工具
├── deploy_async_context_compression.py ✨ Async Context Compression quick deploy ├── DEPLOYMENT_GUIDE.md ✨ 完整部署指南
├── deploy_pipe.py (existing) Pipe deployment tool ├── DEPLOYMENT_SUMMARY.md ✨ 部署功能总结
├── DEPLOYMENT_GUIDE.md ✨ Complete deployment guide ├── QUICK_START.md ✨ 快速参考卡片
├── DEPLOYMENT_SUMMARY.md ✨ Deploy feature summary ├── .env (需要创建) API 密钥配置
── QUICK_START.md ✨ Quick reference card ── ...其他现有脚本
├── .env (create as needed) API key configuration
└── ...other existing scripts
``` ```
## ⚡ Quick Start (30 seconds) ## ⚡ 快速开始 (30 秒)
### Step 1: Prepare Your API Key ### 步骤 1: 准备 API 密钥
```bash ```bash
cd scripts cd scripts
# Get your OpenWebUI API key: # 获取你的 OpenWebUI API 密钥:
# 1. Open OpenWebUI → User menu → Settings # 1. 打开 OpenWebUI → 用户菜单 → Settings
# 2. Find the "API Keys" section # 2. 找到 "API Keys" 部分
# 3. Copy your key (starts with sk-) # 3. 复制你的密钥(以 sk- 开头)
# Create .env file # 创建 .env 文件
cat > .env <<'EOF' echo "api_key=sk-你的密钥" > .env
api_key=sk-your-key-here
url=http://localhost:3000
EOF
``` ```
### Step 2a: Install All Plugins (Recommended) ### 步骤 2: 部署异步上下文压缩
```bash ```bash
python install_all_plugins.py # 最简单的方式 - 专用脚本
```
### Step 2b: Or Deploy Individual Plugins
```bash
# Easiest way - dedicated script
python deploy_async_context_compression.py python deploy_async_context_compression.py
# Or use generic script # 或使用通用脚本
python deploy_filter.py python deploy_filter.py
# Or specify plugin name # 或指定插件名称
python deploy_filter.py async-context-compression python deploy_filter.py async-context-compression
# Or deploy a Tool
python deploy_tool.py
``` ```
## 📋 Deployment Tools Detailed ## 📋 部署工具详解
### 1⃣ `deploy_async_context_compression.py` — Dedicated Deployment Script ### 1⃣ `deploy_async_context_compression.py` — 专用部署脚本
**The simplest way to deploy!** **最简单的部署方式!**
```bash ```bash
cd scripts cd scripts
python deploy_async_context_compression.py python deploy_async_context_compression.py
``` ```
**Features**: **特点**:
- ✅ 专为 async_context_compression 优化
-Optimized specifically for async_context_compression -清晰的部署步骤和确认
-Clear deployment steps and confirmation -友好的错误提示
-Friendly error messages -部署成功后显示后续步骤
- ✅ Shows next steps after successful deployment
**Sample Output**:
**输出样例**:
``` ```
====================================================================== ======================================================================
🚀 Deploying Async Context Compression Filter Plugin 🚀 Deploying Async Context Compression Filter Plugin
@@ -95,321 +79,269 @@ python deploy_async_context_compression.py
====================================================================== ======================================================================
Next steps: Next steps:
1. Open OpenWebUI in your browser: http://localhost:3000 1. Open OpenWebUI in your browser: http://localhost:3003
2. Go to Settings → Filters 2. Go to Settings → Filters
3. Enable 'Async Context Compression' 3. Enable 'Async Context Compression'
4. Configure Valves as needed 4. Configure Valves as needed
5. Start using the filter in conversations 5. Start using the filter in conversations
``` ```
### 2⃣ `deploy_filter.py` — Generic Filter Deployment Tool ### 2⃣ `deploy_filter.py` — 通用 Filter 部署工具
**Supports all Filter plugins!** **支持所有 Filter 插件!**
```bash ```bash
# Deploy default async_context_compression # 部署默认的 async_context_compression
python deploy_filter.py python deploy_filter.py
# Deploy other Filters # 部署其他 Filter
python deploy_filter.py folder-memory python deploy_filter.py folder-memory
python deploy_filter.py context_enhancement_filter python deploy_filter.py context_enhancement_filter
# List all available Filters # 列出所有可用 Filter
python deploy_filter.py --list python deploy_filter.py --list
``` ```
**Features**: **特点**:
- ✅ 通用的 Filter 部署工具
- ✅ 支持多个插件
- ✅ 自动元数据提取
- ✅ 智能更新/创建逻辑
- ✅ 完整的错误诊断
- ✅ Generic Filter deployment tool ### 3⃣ `deploy_pipe.py` — Pipe 部署工具
- ✅ Supports multiple plugins
- ✅ Auto metadata extraction
- ✅ Smart update/create logic
- ✅ Complete error diagnostics
### 3⃣ `deploy_pipe.py` — Pipe Deployment Tool
```bash ```bash
python deploy_pipe.py python deploy_pipe.py
``` ```
Used to deploy Pipe-type plugins (like GitHub Copilot SDK). 用于部署 Pipe 类型的插件(如 GitHub Copilot SDK)。
### 3⃣+ `deploy_tool.py` — Tool Deployment Tool ## 🔧 工作原理
```bash
# Deploy default Tool
python deploy_tool.py
# Or specify a specific Tool
python deploy_tool.py openwebui-skills-manager
```
**Features**:
- ✅ Supports Tools plugin deployment
- ✅ Auto-detects `Tools` class definition
- ✅ Smart update/create logic
- ✅ Complete error diagnostics
**Use Case**:
Deploy or reinstall a specific Tool individually, or deploy only Tools without running full batch installation. The script now calls OpenWebUI's native `/api/v1/tools/*` endpoints.
### 4⃣ `install_all_plugins.py` — Batch Installation Script
One-command installation of all repository plugins that meet these criteria:
- Located in `plugins/actions`, `plugins/filters`, `plugins/pipes`, `plugins/tools`
- Plugin header contains `openwebui_id`
- Filename is not in Chinese characters
- Filename does not end with `_cn.py`
```bash
# Check which plugins will be installed
python install_all_plugins.py --list
# Dry-run without calling API
python install_all_plugins.py --dry-run
# Actually install all supported types (including Action/Filter/Pipe/Tool)
python install_all_plugins.py
# Install only specific types
python install_all_plugins.py --types pipe action
```
The script prioritizes updating existing plugins and automatically creates new ones.
**Tool Integration**: Tool-type plugins now automatically use OpenWebUI's native `/api/v1/tools/create` and `/api/v1/tools/id/{id}/update` endpoints, no longer reusing the `functions` endpoint.
## 🔧 How It Works
``` ```
Your code changes 你的代码变更
Run deployment script 运行部署脚本
Script reads the corresponding plugin file 脚本读取对应插件文件
Auto-extracts metadata from code (title, version, author, etc.) 从代码自动提取元数据 (title, version, author, etc.)
Builds API request 构建 API 请求
Sends to local OpenWebUI 发送到本地 OpenWebUI
OpenWebUI updates or creates plugin OpenWebUI 更新或创建插件
Takes effect immediately! (no restart needed) 立即生效!(无需重启)
``` ```
## 📊 Available Filter List ## 📊 可部署的 Filter 列表
Use `python deploy_filter.py --list` to see all available Filters: 使用 `python deploy_filter.py --list` 查看所有可用 Filter
| Filter Name | Python File | Description | | Filter 名称 | Python 文件 | 描述 |
|-----------|-----------|------| |-----------|-----------|------|
| **async-context-compression** | async_context_compression.py | Async context compression | | **async-context-compression** | async_context_compression.py | 异步上下文压缩 |
| chat-session-mapping-filter | chat_session_mapping_filter.py | Chat session mapping | | chat-session-mapping-filter | chat_session_mapping_filter.py | 聊天会话映射 |
| context_enhancement_filter | context_enhancement_filter.py | Context enhancement | | context_enhancement_filter | context_enhancement_filter.py | 上下文增强 |
| folder-memory | folder_memory.py | Folder memory | | folder-memory | folder_memory.py | 文件夹记忆 |
| github_copilot_sdk_files_filter | github_copilot_sdk_files_filter.py | Copilot SDK Files | | github_copilot_sdk_files_filter | github_copilot_sdk_files_filter.py | Copilot SDK Files |
| markdown_normalizer | markdown_normalizer.py | Markdown normalization | | markdown_normalizer | markdown_normalizer.py | Markdown 规范化 |
| web_gemini_multimodel_filter | web_gemini_multimodel_filter.py | Gemini multimodal | | web_gemini_multimodel_filter | web_gemini_multimodel_filter.py | Gemini 多模态 |
## 🎯 Common Use Cases ## 🎯 常见使用场景
### Scenario 1: Deploy After Feature Development ### 场景 1: 开发新功能后部署
```bash ```bash
# 1. Modify code # 1. 修改代码
vim ../plugins/filters/async-context-compression/async_context_compression.py vim ../plugins/filters/async-context-compression/async_context_compression.py
# 2. Update version number (optional) # 2. 更新版本号(可选)
# version: 1.3.0 → 1.3.1 # version: 1.3.0 → 1.3.1
# 3. Deploy # 3. 部署
python deploy_async_context_compression.py python deploy_async_context_compression.py
# 4. Test in OpenWebUI # 4. OpenWebUI 中测试
# → No restart needed, takes effect immediately! # → 无需重启,立即生效!
# 5. Continue development and repeat # 5. 继续开发,重复上述步骤
``` ```
### Scenario 2: Fix Bug and Verify Quickly ### 场景 2: 修复 Bug 并快速验证
```bash ```bash
# 1. Find and fix bug # 1. 定位并修复 Bug
vim ../plugins/filters/async-context-compression/async_context_compression.py vim ../plugins/filters/async-context-compression/async_context_compression.py
# 2. Quick deploy to verify # 2. 快速部署验证
python deploy_async_context_compression.py python deploy_async_context_compression.py
# 3. Test bug fix in OpenWebUI # 3. 在 OpenWebUI 测试 Bug 修复
# One-command deploy, instant feedback! # 一键部署,秒级反馈!
``` ```
### Scenario 3: Deploy Multiple Filters ### 场景 3: 部署多个 Filter
```bash ```bash
# Deploy all Filters that need updates # 部署所有需要更新的 Filter
python deploy_filter.py async-context-compression python deploy_filter.py async-context-compression
python deploy_filter.py folder-memory python deploy_filter.py folder-memory
python deploy_filter.py context_enhancement_filter python deploy_filter.py context_enhancement_filter
``` ```
## 🔐 Security Tips ## 🔐 安全提示
### Manage API Keys ### 管理 API 密钥
```bash ```bash
# 1. Create .env (local only) # 1. 创建 .env只在本地
echo "api_key=sk-your-key" > .env echo "api_key=sk-your-key" > .env
# 2. Add to .gitignore (prevent commit) # 2. 添加到 .gitignore(防止提交)
echo "scripts/.env" >> ../.gitignore echo "scripts/.env" >> ../.gitignore
# 3. Verify it won't be committed # 3. 验证不会被提交
git status # should not show .env git status # 应该看不到 .env
# 4. Rotate keys regularly # 4. 定期轮换密钥
# → Generate new key in OpenWebUI Settings # → OpenWebUI Settings 中生成新密钥
# → Update .env file # → 更新 .env 文件
``` ```
### ✅ Security Checklist ### ✅ 安全检查清单
- [ ] `.env` file is in `.gitignore` - [ ] `.env` 文件在 `.gitignore`
- [ ] Never hardcode API keys in code - [ ] 从不在代码中硬编码 API 密钥
- [ ] Rotate API keys periodically - [ ] 定期轮换 API 密钥
- [ ] Use only on trusted networks - [ ] 仅在可信网络中使用
- [ ] Use CI/CD secret management in production - [ ] 生产环境使用 CI/CD 秘密管理
## ❌ Troubleshooting ## ❌ 故障排除
### Issue 1: "Connection error" ### 问题 1: "Connection error"
``` ```
❌ Connection error: Could not reach OpenWebUI at localhost:3000 ❌ Connection error: Could not reach OpenWebUI at localhost:3003
Make sure OpenWebUI is running and accessible. Make sure OpenWebUI is running and accessible.
``` ```
**Solution**: **解决方案**:
```bash ```bash
# 1. Check if OpenWebUI is running # 1. 检查 OpenWebUI 是否运行
curl http://localhost:3000 curl http://localhost:3003
# 2. If port is different, edit URL in script # 2. 如果端口不同,编辑脚本中的 URL
# Default: http://localhost:3000 # 默认: http://localhost:3003
# Location: "localhost:3000" in deploy_filter.py # 修改位置: deploy_filter.py 中的 "localhost:3003"
# 3. Check firewall settings # 3. 检查防火墙设置
``` ```
### Issue 2: ".env file not found" ### 问题 2: ".env file not found"
``` ```
❌ [ERROR] .env file not found at .env ❌ [ERROR] .env file not found at .env
Please create it with: api_key=sk-xxxxxxxxxxxx Please create it with: api_key=sk-xxxxxxxxxxxx
``` ```
**Solution**: **解决方案**:
```bash ```bash
echo "api_key=sk-your-api-key" > .env echo "api_key=sk-your-api-key" > .env
cat .env # verify file created cat .env # 验证文件已创建
``` ```
### Issue 3: "Filter not found" ### 问题 3: "Filter not found"
``` ```
❌ [ERROR] Filter 'xxx' not found in .../plugins/filters ❌ [ERROR] Filter 'xxx' not found in .../plugins/filters
``` ```
**Solution**: **解决方案**:
```bash ```bash
# List all available Filters # 列出所有可用 Filter
python deploy_filter.py --list python deploy_filter.py --list
# Retry with correct name # 使用正确的名称重试
python deploy_filter.py async-context-compression python deploy_filter.py async-context-compression
``` ```
### Issue 4: "Status 401" (Unauthorized) ### 问题 4: "Status 401" (Unauthorized)
``` ```
❌ Failed to update or create. Status: 401 ❌ Failed to update or create. Status: 401
Error: {"error": "Unauthorized"} Error: {"error": "Unauthorized"}
``` ```
**Solution**: **解决方案**:
```bash ```bash
# 1. Verify API key is correct # 1. 验证 API 密钥是否正确
grep "api_key=" .env grep "api_key=" .env
# 2. Check if key is still valid in OpenWebUI # 2. 在 OpenWebUI 中检查密钥是否仍然有效
# Settings → API Keys → Check # Settings → API Keys → 检查
# 3. Generate new key and update .env # 3. 生成新密钥并更新 .env
echo "api_key=sk-new-key" > .env echo "api_key=sk-new-key" > .env
``` ```
## 📖 Documentation Navigation ## 📖 文档导航
| Document | Description | | 文档 | 描述 |
|------|------| |------|------|
| **README.md** (this file) | Quick reference and FAQs | | **README.md** (本文件) | 快速参考和常见问题 |
| [QUICK_START.md](QUICK_START.md) | One-page cheat sheet | | [QUICK_START.md](QUICK_START.md) | 一页速查表 |
| [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) | Complete detailed guide | | [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) | 完整详细指南 |
| [DEPLOYMENT_SUMMARY.md](DEPLOYMENT_SUMMARY.md) | Technical architecture | | [DEPLOYMENT_SUMMARY.md](DEPLOYMENT_SUMMARY.md) | 技术架构说明 |
## 🧪 Verify Deployment Success ## 🧪 验证部署成功
### Method 1: Check Script Output ### 方式 1: 检查脚本输出
```bash ```bash
python deploy_async_context_compression.py python deploy_async_context_compression.py
# Success indicator: # 成功标志:
✅ Successfully updated 'Async Context Compression' filter! ✅ Successfully updated 'Async Context Compression' filter!
``` ```
### Method 2: Verify in OpenWebUI ### 方式 2: 在 OpenWebUI 中验证
1. Open OpenWebUI: <http://localhost:3000> 1. 打开 OpenWebUI: http://localhost:3003
2. Go to Settings → Filters 2. 进入 Settings → Filters
3. Check if 'Async Context Compression' is listed 3. 查看 "Async Context Compression" 是否列出
4. Verify version number is correct (should be latest) 4. 查看版本号是否正确(应该是最新的)
### Method 3: Test Plugin Functionality ### 方式 3: 测试插件功能
1. Open a new conversation 1. 打开一个新对话
2. Enable 'Async Context Compression' Filter 2. 启用 "Async Context Compression" Filter
3. Have multiple-turn conversation and verify compression/summarization works 3. 进行多轮对话,验证压缩和总结功能正常
## 💡 Advanced Usage ## 💡 高级用法
### Automated Deploy & Test ### 自动化部署测试
```bash ```bash
#!/bin/bash #!/bin/bash
# deploy_and_test.sh # deploy_and_test.sh
echo "Deploying plugin..." echo "部署插件..."
python scripts/deploy_async_context_compression.py python scripts/deploy_async_context_compression.py
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo "✅ Deploy successful, running tests..." echo "✅ 部署成功,运行测试..."
python -m pytest tests/plugins/filters/async-context-compression/ -v python -m pytest tests/plugins/filters/async-context-compression/ -v
else else
echo "❌ Deploy failed" echo "❌ 部署失败"
exit 1 exit 1
fi fi
``` ```
### CI/CD Integration ### CI/CD 集成
```yaml ```yaml
# .github/workflows/deploy.yml # .github/workflows/deploy.yml
@@ -430,55 +362,55 @@ jobs:
api_key: ${{ secrets.OPENWEBUI_API_KEY }} api_key: ${{ secrets.OPENWEBUI_API_KEY }}
``` ```
## 📞 Getting Help ## 📞 获取帮助
### Check Script Status ### 检查脚本状态
```bash ```bash
# List all available scripts # 列出所有可用脚本
ls -la scripts/*.py ls -la scripts/*.py
# Check if deployment scripts exist # 检查部署脚本是否存在
ls -la scripts/deploy_*.py ls -la scripts/deploy_*.py
``` ```
### View Script Help ### 查看脚本版本
```bash ```bash
# View help (if supported) # 查看脚本帮助
python scripts/deploy_filter.py --help # if supported python scripts/deploy_filter.py --help # 如果支持的话
python scripts/deploy_async_context_compression.py --help python scripts/deploy_async_context_compression.py --help
``` ```
### Debug Mode ### 调试模式
```bash ```bash
# Save output to log file # 保存输出到日志文件
python scripts/deploy_async_context_compression.py | tee deploy.log python scripts/deploy_async_context_compression.py | tee deploy.log
# Check log # 检查日志
cat deploy.log cat deploy.log
``` ```
--- ---
## 📝 File Checklist ## 📝 文件清单
Newly created deployment-related files: 新增的部署相关文件:
``` ```
✨ scripts/deploy_filter.py (new) ~300 lines ✨ scripts/deploy_filter.py (新增) ~300
✨ scripts/deploy_async_context_compression.py (new) ~70 lines ✨ scripts/deploy_async_context_compression.py (新增) ~70
✨ scripts/DEPLOYMENT_GUIDE.md (new) complete guide ✨ scripts/DEPLOYMENT_GUIDE.md (新增) 完整指南
✨ scripts/DEPLOYMENT_SUMMARY.md (new) technical summary ✨ scripts/DEPLOYMENT_SUMMARY.md (新增) 技术总结
✨ scripts/QUICK_START.md (new) quick reference ✨ scripts/QUICK_START.md (新增) 快速参考
📄 tests/scripts/test_deploy_filter.py (new) 10 unit tests 📄 tests/scripts/test_deploy_filter.py (新增) 10 个单元测试
All files created and tested successfully! 所有文件已创建并测试通过!
``` ```
--- ---
**Last Updated**: 2026-03-09 **最后更新**: 2026-03-09
**Script Status**: ✅ Ready for production **脚本状态**: ✅ Ready for production
**Test Coverage**: 10/10 passed **测试覆盖**: 10/10 通过

View File

@@ -1,46 +1,45 @@
# 🔄 Deployment Scripts Update Mechanism # 🔄 部署脚本的更新机制 (Deployment Update Mechanism)
## Core Answer ## 核心答案
**Yes, re-deploying automatically updates the plugin!** **是的,再次部署会自动更新!**
The deployment script uses a **smart two-stage strategy**: 部署脚本采用**智能两阶段策略**
1. 🔄 **优先尝试更新** (UPDATE) — 如果插件已存在
2. 📝 **自动创建** (CREATE) — 如果更新失败(插件不存在)
1. 🔄 **Try UPDATE First** (if plugin exists) ## 工作流程图
2. 📝 **Auto CREATE** (if update fails — plugin doesn't exist)
## Workflow Diagram
``` ```
Run deploy script 运行部署脚本
Read local code and metadata 读取本地代码和元数据
Send UPDATE request to OpenWebUI 发送 UPDATE 请求到 OpenWebUI
├─ HTTP 200 ✅ ├─ HTTP 200 ✅
│ └─ Plugin exists → Update successful! │ └─ 插件已存在 → 更新成功!
└─ Other status codes (404, 400, etc.) └─ 其他状态代码 (404, 400)
└─ Plugin doesn't exist or update failed └─ 插件不存在或更新失败
Send CREATE request 发送 CREATE 请求
├─ HTTP 200 ✅ ├─ HTTP 200 ✅
│ └─ Creation successful! │ └─ 创建成功!
└─ Failed └─ 失败
└─ Display error message └─ 显示错误信息
``` ```
## Detailed Step-by-step ## 详细步骤分析
### Step 1⃣: Try UPDATE First ### 步骤 1⃣: 尝试更新 (UPDATE)
```python ```python
# Code location: deploy_filter.py line 220-230 # 代码位置: deploy_filter.py 220-230
update_url = "http://localhost:3000/api/v1/functions/id/{filter_id}/update" update_url = "http://localhost:3003/api/v1/functions/id/{filter_id}/update"
response = requests.post( response = requests.post(
update_url, update_url,
@@ -54,25 +53,24 @@ if response.status_code == 200:
return True return True
``` ```
**What Happens**: **这一步**:
- 向 OpenWebUI API 发送 **POST**`/api/v1/functions/id/{filter_id}/update`
- 如果返回 **HTTP 200**,说明插件已存在且成功更新
- 包含的内容:
- 完整的最新代码
- 元数据 (title, version, author, description 等)
- 清单信息 (manifest)
- Send **POST** to `/api/v1/functions/id/{filter_id}/update` ### 步骤 2⃣: 若更新失败,尝试创建 (CREATE)
- If returns **HTTP 200**, plugin exists and update succeeded
- Includes:
- Complete latest code
- Metadata (title, version, author, description, etc.)
- Manifest information
### Step 2⃣: If UPDATE Fails, Try CREATE
```python ```python
# Code location: deploy_filter.py line 231-245 # 代码位置: deploy_filter.py 231-245
if response.status_code != 200: if response.status_code != 200:
print(f"⚠️ Update failed with status {response.status_code}, " print(f"⚠️ Update failed with status {response.status_code}, "
"attempting to create instead...") "attempting to create instead...")
create_url = "http://localhost:3000/api/v1/functions/create" create_url = "http://localhost:3003/api/v1/functions/create"
res_create = requests.post( res_create = requests.post(
create_url, create_url,
headers=headers, headers=headers,
@@ -85,100 +83,96 @@ if response.status_code != 200:
return True return True
``` ```
**What Happens**: **这一步**:
- 如果更新失败 (HTTP ≠ 200),自动尝试创建
-`/api/v1/functions/create` 发送 **POST** 请求
- 使用**相同的 payload**(代码、元数据都一样)
- 如果创建成功,第一次部署到 OpenWebUI
- If update fails (HTTP ≠ 200), auto-attempt create ## 实际使用场景
- Send **POST** to `/api/v1/functions/create`
- Uses **same payload** (code, metadata identical)
- If creation succeeds, first deployment to OpenWebUI
## Real-world Scenarios ### 场景 A: 第一次部署
### Scenario A: First Deployment
```bash ```bash
$ python deploy_async_context_compression.py $ python deploy_async_context_compression.py
📦 Deploying filter 'Async Context Compression' (version 1.3.0)... 📦 Deploying filter 'Async Context Compression' (version 1.3.0)...
File: .../async_context_compression.py File: .../async_context_compression.py
⚠️ Update failed with status 404, attempting to create instead... ← First time, plugin doesn't exist ⚠️ Update failed with status 404, attempting to create instead... ← 第一次,插件不存在
✅ Successfully created 'Async Context Compression' filter! ← Creation succeeds ✅ Successfully created 'Async Context Compression' filter! ← 创建成功
``` ```
**What Happens**: **发生的事**:
1. 尝试 UPDATE → 失败 (HTTP 404 — 插件不存在)
1. Try UPDATE → fails (HTTP 404 — plugin doesn't exist) 2. 自动尝试 CREATE → 成功 (HTTP 200)
2. Auto-try CREATE → succeeds (HTTP 200) 3. 插件被创建到 OpenWebUI
3. Plugin created in OpenWebUI
--- ---
### Scenario B: Re-deploy After Code Changes ### 场景 B: 再次部署 (修改代码后)
```bash ```bash
# Made first code change, deploying again # 第一次修改代码,再次部署
$ python deploy_async_context_compression.py $ python deploy_async_context_compression.py
📦 Deploying filter 'Async Context Compression' (version 1.3.1)... 📦 Deploying filter 'Async Context Compression' (version 1.3.1)...
File: .../async_context_compression.py File: .../async_context_compression.py
✅ Successfully updated 'Async Context Compression' filter! ← Direct update! ✅ Successfully updated 'Async Context Compression' filter! ← 直接更新!
``` ```
**What Happens**: **发生的事**:
1. 读取修改后的代码
1. Read modified code 2. 尝试 UPDATE → 成功 (HTTP 200 — 插件已存在)
2. Try UPDATE → succeeds (HTTP 200 — plugin exists) 3. OpenWebUI 中的插件被更新为最新代码
3. Plugin in OpenWebUI updated to latest code 4. **无需重启 OpenWebUI**,立即生效!
4. **No need to restart OpenWebUI**, takes effect immediately!
--- ---
### Scenario C: Multiple Fast Iterations ### 场景 C: 多次快速迭代
```bash ```bash
# 1st change # 第1次修改
$ python deploy_async_context_compression.py $ python deploy_async_context_compression.py
✅ Successfully updated 'Async Context Compression' filter! ✅ Successfully updated 'Async Context Compression' filter!
# 2nd change # 第2次修改
$ python deploy_async_context_compression.py $ python deploy_async_context_compression.py
✅ Successfully updated 'Async Context Compression' filter! ✅ Successfully updated 'Async Context Compression' filter!
# 3rd change # 第3次修改
$ python deploy_async_context_compression.py $ python deploy_async_context_compression.py
✅ Successfully updated 'Async Context Compression' filter! ✅ Successfully updated 'Async Context Compression' filter!
# ... repeat infinitely ... # ... 无限制地重复 ...
``` ```
**Characteristics**: **特点**:
- 🚀 每次更新只需 5 秒
- 📝 每次都是增量更新
- ✅ 无需重启 OpenWebUI
- 🔄 可以无限制地重复
- 🚀 Each update takes only 5 seconds ## 更新的内容清单
- 📝 Each is an incremental update
- ✅ No need to restart OpenWebUI
- 🔄 Can repeat indefinitely
## What Gets Updated 每次部署时,以下内容会被更新:
Each deployment updates the following: **代码** — 全部最新的 Python 代码
**版本号** — 从 docstring 自动提取
**标题** — 插件的显示名称
**作者信息** — author, author_url
**描述** — plugin description
**元数据** — funding_url, openwebui_id 等
**Code**All latest Python code **配置不会被覆盖**用户在 OpenWebUI 中设置的 Valves 配置保持不变
**Version** — Auto-extracted from docstring
**Title** — Plugin display name
**Author Info** — author, author_url
**Description** — Plugin description
**Metadata** — funding_url, openwebui_id, etc.
**Configuration NOT Overwritten** — User's Valves settings in OpenWebUI stay unchanged ## 版本号管理
## Version Number Management ### 更新时版本号会变吗?
### Does Version Change on Update? **是的,会变!**
**Yes!**
```python ```python
# docstring in async_context_compression.py # async_context_compression.py 的 docstring
""" """
title: Async Context Compression title: Async Context Compression
@@ -186,129 +180,126 @@ version: 1.3.0
""" """
``` ```
**Each deployment**: **每次部署时**:
1. 脚本从 docstring 读取版本号
1. Script reads version from docstring 2. 发送给 OpenWebUI 的 manifest 包含这个版本号
2. Sends this version in manifest to OpenWebUI 3. 如果代码中改了版本号,部署时会更新到新版本
3. If you change version in code, deployment updates to new version
**Best Practice**:
**最佳实践**:
```bash ```bash
# 1. Modify code # 1. 修改代码
vim async_context_compression.py vim async_context_compression.py
# 2. Update version (in docstring) # 2. 更新版本号(在 docstring 中)
# version: 1.3.0 → 1.3.1 # 版本: 1.3.0 → 1.3.1
# 3. Deploy # 3. 部署
python deploy_async_context_compression.py python deploy_async_context_compression.py
# Result: OpenWebUI shows version 1.3.1 # 结果: OpenWebUI 中显示版本 1.3.1
``` ```
## Deployment Failure Cases ## 部署失败的情况
### Case 1: Network Error ### 情况 1: 网络错误
```bash ```bash
❌ Connection error: Could not reach OpenWebUI at localhost:3000 ❌ Connection error: Could not reach OpenWebUI at localhost:3003
Make sure OpenWebUI is running and accessible. Make sure OpenWebUI is running and accessible.
``` ```
**Cause**: OpenWebUI not running or wrong port **原因**: OpenWebUI 未运行或端口错误
**Solution**: Check if OpenWebUI is running **解决**: 检查 OpenWebUI 是否在运行
### Case 2: Invalid API Key ### 情况 2: API 密钥无效
```bash ```bash
❌ Failed to update or create. Status: 401 ❌ Failed to update or create. Status: 401
Error: {"error": "Unauthorized"} Error: {"error": "Unauthorized"}
``` ```
**Cause**: API key in .env is invalid or expired **原因**: .env 中的 API 密钥无效或过期
**Solution**: Update api_key in `.env` file **解决**: 更新 `.env` 文件中的 api_key
### Case 3: Server Error ### 情况 3: 服务器错误
```bash ```bash
❌ Failed to update or create. Status: 500 ❌ Failed to update or create. Status: 500
Error: Internal server error Error: Internal server error
``` ```
**Cause**: OpenWebUI server internal error **原因**: OpenWebUI 服务器内部错误
**Solution**: Check OpenWebUI logs **解决**: 检查 OpenWebUI 日志
## Setting Version Numbers — Best Practices ## 设置版本号的最佳实践
### Semantic Versioning ### 语义化版本 (Semantic Versioning)
Follow `MAJOR.MINOR.PATCH` format: 遵循 `MAJOR.MINOR.PATCH` 格式:
```python ```python
""" """
version: 1.3.0 version: 1.3.0
│ │ │ │ │ │
│ │ └─ PATCH: Bug fixes (1.3.0 → 1.3.1) │ │ └─ PATCH: Bug 修复 (1.3.0 → 1.3.1)
│ └────── MINOR: New features (1.3.0 → 1.4.0) │ └────── MINOR: 新功能 (1.3.0 → 1.4.0)
└───────── MAJOR: Breaking changes (1.3.0 → 2.0.0) └───────── MAJOR: 破坏性变更 (1.3.0 → 2.0.0)
""" """
``` ```
**Examples**: **例子**:
```python ```python
# Bug fix (PATCH) # Bug 修复 (PATCH)
version: 1.3.0 1.3.1 version: 1.3.0 1.3.1
# New feature (MINOR) # 新功能 (MINOR)
version: 1.3.0 1.4.0 version: 1.3.0 1.4.0
# Major update (MAJOR) # 重大更新 (MAJOR)
version: 1.3.0 2.0.0 version: 1.3.0 2.0.0
``` ```
## Complete Iteration Workflow ## 完整的迭代工作流
```bash ```bash
# 1. First deployment # 1. 首次部署
cd scripts cd scripts
python deploy_async_context_compression.py python deploy_async_context_compression.py
# Result: Plugin created (first time) # 结果: 创建插件 (第一次)
# 2. Modify code # 2. 修改代码
vim ../plugins/filters/async-context-compression/async_context_compression.py vim ../plugins/filters/async-context-compression/async_context_compression.py
# Edit code... # 修改内容...
# 3. Deploy again (auto-update) # 3. 再次部署 (自动更新)
python deploy_async_context_compression.py python deploy_async_context_compression.py
# Result: Plugin updated (takes effect immediately, no OpenWebUI restart) # 结果: 更新插件 (立即生效,无需重启 OpenWebUI)
# 4. Repeat steps 2-3 indefinitely # 4. 重复步骤 2-3无限次迭代
# Modify → Deploy → Test → Improve → Repeat # 每次修改 → 每次部署 → 立即测试 → 继续改进
``` ```
## Benefits of Auto-update ## 自动更新的优势
| Benefit | Details | | 优势 | 说明 |
|---------|---------| |-----|------|
| ⚡ **Fast Iteration** | Code change → Deploy (5s) → Test, no waiting | | ⚡ **快速迭代** | 修改代码 → 部署 (5) → 测试,无需等待 |
| 🔄 **Auto-detection** | No manual decision between create/update | | 🔄 **自动检测** | 无需手动判断是创建还是更新 |
| 📝 **Version Management** | Version auto-extracted from code | | 📝 **版本管理** | 版本号自动从代码提取 |
| ✅ **No Restart Needed** | OpenWebUI runs continuously, config stays same | | ✅ **无需重启** | OpenWebUI 无需重启,配置保持不变 |
| 🛡️ **Safe Updates** | User settings (Valves) never overwritten | | 🛡️ **安全更新** | 用户配置 (Valves) 不会被覆盖 |
## Disable Auto-update? ❌ ## 禁用自动更新? ❌
Usually **not needed** because: 通常**不需要**禁用自动更新,因为:
1.Updates are idempotent (same code deployed multiple times = no change) 1.更新是幂等的 (多次更新相同代码 = 无变化)
2.User configuration not modified 2.用户配置不会被修改
3.Version numbers auto-managed 3.版本号自动管理
4.Failures auto-rollback 4.失败时自动回退
但如果真的需要控制,可以: 但如果真的需要控制,可以:
- 手动修改脚本 (修改 `deploy_filter.py`) - 手动修改脚本 (修改 `deploy_filter.py`)
- 或分别使用 UPDATE/CREATE 的具体 API 端点 - 或分别使用 UPDATE/CREATE 的具体 API 端点
@@ -332,7 +323,6 @@ Usually **not needed** because:
### Q: 可以同时部署多个插件吗? ### Q: 可以同时部署多个插件吗?
**可以!** **可以!**
```bash ```bash
python deploy_filter.py async-context-compression python deploy_filter.py async-context-compression
python deploy_filter.py folder-memory python deploy_filter.py folder-memory
@@ -347,7 +337,6 @@ python deploy_filter.py context_enhancement_filter
--- ---
**总结**: 部署脚本的更新机制完全自动化,开发者只需修改代码,每次运行 `deploy_async_context_compression.py` 就会自动: **总结**: 部署脚本的更新机制完全自动化,开发者只需修改代码,每次运行 `deploy_async_context_compression.py` 就会自动:
1. ✅ 创建(第一次)或更新(后续)插件 1. ✅ 创建(第一次)或更新(后续)插件
2. ✅ 从代码提取最新的元数据和版本号 2. ✅ 从代码提取最新的元数据和版本号
3. ✅ 立即生效,无需重启 OpenWebUI 3. ✅ 立即生效,无需重启 OpenWebUI

View File

@@ -1,91 +1,91 @@
# 🔄 Quick Reference: Deployment Update Mechanism # 🔄 快速参考:部署更新机制 (Quick Reference)
## The Shortest Answer ## 最简短的答案
**Re-deploying automatically updates the plugin.** **再次部署会自动更新。**
## How It Works (30-second understanding) ## 工作原理 (30 秒理解)
``` ```
Each time you run the deploy script: 每次运行部署脚本:
1. Priority: try UPDATE (if plugin exists) → succeeds 1. 优先尝试 UPDATE如果插件已存在→ 更新成功
2. Fallback: auto CREATE (first deployment) → succeeds 2. 失败时自动 CREATE第一次部署时→ 创建成功
Result: 结果:
Works correctly every time, regardless of deployment count 不管第几次部署,脚本都能正确处理
No manual judgement needed between create vs update 无需手动判断创建还是更新
Takes effect immediately, no restart needed 立即生效,无需重启
``` ```
## Three Scenarios ## 三个场景
| Scenario | What Happens | Result | | 场景 | 发生什么 | 结果 |
|----------|-------------|--------| |------|---------|------|
| **First deployment** | UPDATE fails → CREATE succeeds | ✅ Plugin created | | **第1次部署** | UPDATE 失败 → CREATE 成功 | ✅ 插件被创建 |
| **Deploy after code change** | UPDATE succeeds directly | ✅ Plugin updates instantly | | **修改代码后再次部署** | UPDATE 直接成功 | ✅ 插件立即更新 |
| **Deploy without changes** | UPDATE succeeds (no change) | ✅ Safe (no effect) | | **未修改,重复部署** | UPDATE 成功 (无任何变化) | ✅ 无效果 (安全) |
## Development Workflow ## 开发流程
```bash ```bash
# 1. First deployment # 1. 第一次部署
python deploy_async_context_compression.py python deploy_async_context_compression.py
# Result: ✅ Created # 结果: ✅ Created
# 2. Modify code # 2. 修改代码
vim ../plugins/filters/async-context-compression/async_context_compression.py vim ../plugins/filters/async-context-compression/async_context_compression.py
# Edit... # 编辑...
# 3. Deploy again (auto-update) # 3. 再次部署 (自动更新)
python deploy_async_context_compression.py python deploy_async_context_compression.py
# Result: ✅ Updated # 结果: ✅ Updated
# 4. Continue editing and redeploying # 4. 继续修改,重复部署
# ... can repeat infinitely ... # ... 可以无限重复 ...
``` ```
## Key Points ## 关键点
**Automated** — No need to worry about create vs update **自动化** — 不用管是更新还是创建
**Fast**Each deployment takes 5 seconds **快速**每次部署 5 秒
**Safe**User configuration never gets overwritten **安全**用户配置不会被覆盖
**Instant**No need to restart OpenWebUI **即时**无需重启 OpenWebUI
**Version Management** — Auto-extracted from code **版本管理** — 自动从代码提取版本号
## How to Manage Version Numbers? ## 版本号怎么管理?
Modify the version in your code: 修改代码中的版本号:
```python ```python
# async_context_compression.py # async_context_compression.py
""" """
version: 1.3.0 → 1.3.1 (Bug fixes) version: 1.3.0 → 1.3.1 (修复 Bug)
version: 1.3.0 → 1.4.0 (New features) version: 1.3.0 → 1.4.0 (新功能)
version: 1.3.0 → 2.0.0 (Major updates) version: 1.3.0 → 2.0.0 (重大更新)
""" """
``` ```
Then deploy, the script will auto-read the new version and update. 然后部署,脚本会自动读取新版本号并更新。
## Quick Q&A ## 常见问题速答
**Q: Will user configuration be overwritten?** **Q: 用户的配置会被覆盖吗?**
A: ❌ No, Valves configuration stays the same A: ❌ 不会,Valves 配置保持不变
**Q: Do I need to restart OpenWebUI?** **Q: 需要重启 OpenWebUI 吗?**
A: ❌ No, takes effect immediately A: ❌ 不需要,立即生效
**Q: What if update fails?** **Q: 更新失败了会怎样?**
A: ✅ Safe, keeps original plugin intact A: ✅ 安全,保持原有插件不变
**Q: Can I deploy unlimited times?** **Q: 可以无限制地重复部署吗?**
A: ✅ Yes, completely idempotent A: ✅ 可以,完全幂等
## One-liner Summary ## 一行总结
> First deployment creates plugin, subsequent deployments auto-update, 5-second feedback, no restart needed. > 首次部署创建插件之后每次部署自动更新5 秒即时反馈,无需重启。
--- ---
📖 Full docs: `scripts/UPDATE_MECHANISM.md` 📖 详细文档:`scripts/UPDATE_MECHANISM.md`

View File

@@ -1,202 +0,0 @@
#!/usr/bin/env python3
"""
🤖 AGENT SYNC TOOL v2.2 (Unified Semantic Edition)
-------------------------------------------------
Consolidated and simplified command set based on Copilot's architectural feedback.
Native support for Study, Task, and Broadcast workflows.
Maintains Sisyphus's advanced task management (task_queue, subscriptions).
"""
import sqlite3
import os
import sys
import argparse
from datetime import datetime
DB_PATH = os.path.join(os.getcwd(), ".agent/agent_hub.db")
def get_connection():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
return sqlite3.connect(DB_PATH)
def init_db():
conn = get_connection()
cursor = conn.cursor()
cursor.executescript('''
CREATE TABLE IF NOT EXISTS agents (
id TEXT PRIMARY KEY,
name TEXT,
task TEXT,
status TEXT DEFAULT 'idle',
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS file_locks (
file_path TEXT PRIMARY KEY,
agent_id TEXT,
lock_type TEXT DEFAULT 'write',
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS research_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT,
topic TEXT,
content TEXT,
note_type TEXT DEFAULT 'note', -- 'note', 'study', 'conclusion'
is_resolved INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS task_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
initiator TEXT,
task_type TEXT, -- 'research', 'collab', 'fix'
topic TEXT,
description TEXT,
priority TEXT DEFAULT 'normal',
status TEXT DEFAULT 'pending', -- 'pending', 'active', 'completed'
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS task_subscriptions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER,
agent_id TEXT,
role TEXT, -- 'lead', 'reviewer', 'worker', 'observer'
FOREIGN KEY(task_id) REFERENCES task_queue(id)
);
CREATE TABLE IF NOT EXISTS broadcasts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sender_id TEXT,
type TEXT,
payload TEXT,
active INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS global_settings (
key TEXT PRIMARY KEY, value TEXT
);
''')
cursor.execute("INSERT OR IGNORE INTO global_settings (key, value) VALUES ('mode', 'isolation')")
conn.commit()
conn.close()
print(f"✅ MACP 2.2 Semantic Kernel Active")
def get_status():
conn = get_connection(); cursor = conn.cursor()
print("\n--- 🛰️ Agent Fleet ---")
for r in cursor.execute("SELECT id, name, status, task FROM agents"):
print(f"[{r[2].upper()}] {r[1]} ({r[0]}) | Task: {r[3]}")
print("\n--- 📋 Global Task Queue ---")
for r in cursor.execute("SELECT id, topic, task_type, priority, status FROM task_queue WHERE status != 'completed'"):
print(f" #{r[0]} [{r[2].upper()}] {r[1]} | {r[3]} | {r[4]}")
print("\n--- 📚 Active Studies ---")
for r in cursor.execute("SELECT topic, agent_id FROM research_log WHERE note_type='study' AND is_resolved=0"):
print(f" 🔬 {r[0]} (by {r[1]})")
print("\n--- 📢 Live Broadcasts ---")
for r in cursor.execute("SELECT sender_id, type, payload FROM broadcasts WHERE active=1 ORDER BY created_at DESC LIMIT 3"):
print(f"📣 {r[0]} [{r[1].upper()}]: {r[2]}")
print("\n--- 🔒 File Locks ---")
for r in cursor.execute("SELECT file_path, agent_id, lock_type FROM file_locks ORDER BY timestamp DESC LIMIT 20"):
print(f" {r[0]} -> {r[1]} ({r[2]})")
cursor.execute("SELECT value FROM global_settings WHERE key='mode'")
mode = cursor.fetchone()[0]
print(f"\n🌍 Project Mode: {mode.upper()}")
conn.close()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command")
# Base commands
subparsers.add_parser("init")
subparsers.add_parser("status")
subparsers.add_parser("check")
subparsers.add_parser("ping")
reg = subparsers.add_parser("register")
reg.add_argument("id"); reg.add_argument("name"); reg.add_argument("task")
# Lock commands
lock = subparsers.add_parser("lock")
lock.add_argument("id"); lock.add_argument("path")
unlock = subparsers.add_parser("unlock")
unlock.add_argument("id"); unlock.add_argument("path")
# Research & Note commands
note = subparsers.add_parser("note")
note.add_argument("id"); note.add_argument("topic"); note.add_argument("content")
note.add_argument("--type", default="note")
# Semantic Workflows (The Unified Commands)
study = subparsers.add_parser("study")
study.add_argument("id"); study.add_argument("topic"); study.add_argument("--desc", default=None)
resolve = subparsers.add_parser("resolve")
resolve.add_argument("id"); resolve.add_argument("topic"); resolve.add_argument("conclusion")
# Task Management (The Advanced Commands)
assign = subparsers.add_parser("assign")
assign.add_argument("id"); assign.add_argument("target"); assign.add_argument("topic")
assign.add_argument("--role", default="worker"); assign.add_argument("--priority", default="normal")
bc = subparsers.add_parser("broadcast")
bc.add_argument("id"); bc.add_argument("type"); bc.add_argument("payload")
args = parser.parse_args()
if args.command == "init": init_db()
elif args.command == "status" or args.command == "check" or args.command == "ping": get_status()
elif args.command == "register":
conn = get_connection(); cursor = conn.cursor()
cursor.execute("INSERT OR REPLACE INTO agents (id, name, task, status, last_seen) VALUES (?, ?, ?, 'active', CURRENT_TIMESTAMP)", (args.id, args.name, args.task))
conn.commit(); conn.close()
print(f"🤖 Registered: {args.id}")
elif args.command == "lock":
conn = get_connection(); cursor = conn.cursor()
try:
cursor.execute("INSERT INTO file_locks (file_path, agent_id) VALUES (?, ?)", (args.path, args.id))
conn.commit(); print(f"🔒 Locked {args.path}")
except: print(f"❌ Lock conflict on {args.path}"); sys.exit(1)
finally: conn.close()
elif args.command == "unlock":
conn = get_connection(); cursor = conn.cursor()
cursor.execute("DELETE FROM file_locks WHERE file_path=? AND agent_id=?", (args.path, args.id))
conn.commit(); conn.close(); print(f"🔓 Unlocked {args.path}")
elif args.command == "study":
conn = get_connection(); cursor = conn.cursor()
cursor.execute("INSERT INTO research_log (agent_id, topic, content, note_type) VALUES (?, ?, ?, 'study')", (args.id, args.topic, args.desc or "Study started"))
cursor.execute("UPDATE agents SET status = 'researching'")
cursor.execute("INSERT INTO broadcasts (sender_id, type, payload) VALUES (?, 'research', ?)", (args.id, f"NEW STUDY: {args.topic}"))
cursor.execute("UPDATE global_settings SET value = ? WHERE key = 'mode'", (f"RESEARCH: {args.topic}",))
conn.commit(); conn.close()
print(f"🔬 Study '{args.topic}' initiated.")
elif args.command == "resolve":
conn = get_connection(); cursor = conn.cursor()
cursor.execute("UPDATE research_log SET is_resolved = 1 WHERE topic = ?", (args.topic,))
cursor.execute("INSERT INTO research_log (agent_id, topic, content, note_type, is_resolved) VALUES (?, ?, ?, 'conclusion', 1)", (args.id, args.topic, args.conclusion))
cursor.execute("UPDATE global_settings SET value = 'isolation' WHERE key = 'mode'")
cursor.execute("UPDATE agents SET status = 'active' WHERE status = 'researching'")
conn.commit(); conn.close()
print(f"✅ Study '{args.topic}' resolved.")
elif args.command == "assign":
conn = get_connection(); cursor = conn.cursor()
cursor.execute(
"INSERT INTO task_queue (initiator, task_type, topic, description, priority, status) VALUES (?, 'task', ?, ?, ?, 'pending')",
(args.id, args.topic, f"Assigned to {args.target}: {args.topic}", args.priority),
)
task_id = cursor.lastrowid
cursor.execute("INSERT INTO task_subscriptions (task_id, agent_id, role) VALUES (?, ?, ?)", (task_id, args.target, args.role))
conn.commit(); conn.close()
print(f"📋 Task #{task_id} assigned to {args.target}")
elif args.command == "broadcast":
conn = get_connection(); cursor = conn.cursor()
cursor.execute("UPDATE broadcasts SET active = 0 WHERE type = ?", (args.type,))
cursor.execute("INSERT INTO broadcasts (sender_id, type, payload) VALUES (?, ?, ?)", (args.id, args.type, args.payload))
conn.commit(); conn.close()
print(f"📡 Broadcast: {args.payload}")
elif args.command == "note":
conn = get_connection(); cursor = conn.cursor()
cursor.execute("INSERT INTO research_log (agent_id, topic, content, note_type) VALUES (?, ?, ?, ?)", (args.id, args.topic, args.content, args.type))
conn.commit(); conn.close()
print(f"📝 Note added.")

View File

@@ -1,847 +0,0 @@
#!/usr/bin/env python3
"""
🤖 AGENT SYNC TOOL v2.0 - MULTI-AGENT COOPERATION PROTOCOL (MACP)
---------------------------------------------------------
Enhanced collaboration commands for seamless multi-agent synergy.
QUICK COMMANDS:
@research <topic> - Start a joint research topic
@join <topic> - Join an active research topic
@find <topic> <content> - Post a finding to research topic
@consensus <topic> - Generate consensus document
@assign <agent> <task> - Assign task to specific agent
@notify <message> - Broadcast to all agents
@handover <agent> - Handover current task
@poll <question> - Start a quick poll
@switch <agent> - Request switch to specific agent
WORKFLOW: @research -> @find (xN) -> @consensus -> @assign
"""
import sqlite3
import os
import sys
import argparse
import json
from datetime import datetime, timedelta
from typing import List, Dict, Optional
DB_PATH = os.path.join(os.getcwd(), ".agent/agent_hub.db")
def get_connection():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
return sqlite3.connect(DB_PATH)
def init_db():
conn = get_connection()
cursor = conn.cursor()
cursor.executescript('''
CREATE TABLE IF NOT EXISTS agents (
id TEXT PRIMARY KEY,
name TEXT,
task TEXT,
status TEXT DEFAULT 'idle',
current_research TEXT,
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS file_locks (
file_path TEXT PRIMARY KEY,
agent_id TEXT,
lock_type TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(agent_id) REFERENCES agents(id)
);
CREATE TABLE IF NOT EXISTS research_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT,
topic TEXT,
content TEXT,
finding_type TEXT DEFAULT 'note',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(agent_id) REFERENCES agents(id)
);
CREATE TABLE IF NOT EXISTS research_topics (
topic TEXT PRIMARY KEY,
status TEXT DEFAULT 'active',
initiated_by TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP
);
CREATE TABLE IF NOT EXISTS agent_research_participation (
agent_id TEXT,
topic TEXT,
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (agent_id, topic)
);
CREATE TABLE IF NOT EXISTS task_assignments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT,
task TEXT,
assigned_by TEXT,
status TEXT DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP
);
CREATE TABLE IF NOT EXISTS notifications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT,
message TEXT,
is_broadcast BOOLEAN DEFAULT 0,
is_read BOOLEAN DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS polls (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question TEXT,
created_by TEXT,
status TEXT DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS poll_votes (
poll_id INTEGER,
agent_id TEXT,
vote TEXT,
voted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (poll_id, agent_id)
);
CREATE TABLE IF NOT EXISTS global_settings (
key TEXT PRIMARY KEY,
value TEXT
);
''')
cursor.execute("INSERT OR IGNORE INTO global_settings (key, value) VALUES ('mode', 'isolation')")
conn.commit()
conn.close()
print(f"✅ Agent Hub v2.0 initialized at {DB_PATH}")
# ============ AGENT MANAGEMENT ============
def register_agent(agent_id, name, task, status="idle"):
conn = get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO agents (id, name, task, status, last_seen)
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
''', (agent_id, name, task, status))
conn.commit()
conn.close()
print(f"🤖 Agent '{name}' ({agent_id}) registered.")
def update_agent_status(agent_id, status, research_topic=None):
conn = get_connection()
cursor = conn.cursor()
if research_topic:
cursor.execute('''
UPDATE agents SET status = ?, current_research = ?, last_seen = CURRENT_TIMESTAMP
WHERE id = ?
''', (status, research_topic, agent_id))
else:
cursor.execute('''
UPDATE agents SET status = ?, last_seen = CURRENT_TIMESTAMP
WHERE id = ?
''', (status, agent_id))
conn.commit()
conn.close()
# ============ RESEARCH COLLABORATION ============
def start_research(agent_id, topic):
"""@research - Start a new research topic and notify all agents"""
conn = get_connection()
cursor = conn.cursor()
# Create research topic
try:
cursor.execute('''
INSERT INTO research_topics (topic, status, initiated_by)
VALUES (?, 'active', ?)
''', (topic, agent_id))
except sqlite3.IntegrityError:
print(f"⚠️ Research topic '{topic}' already exists")
conn.close()
return
# Add initiator as participant
cursor.execute('''
INSERT OR IGNORE INTO agent_research_participation (agent_id, topic)
VALUES (?, ?)
''', (agent_id, topic))
# Update agent status
cursor.execute('''
UPDATE agents SET status = 'researching', current_research = ?
WHERE id = ?
''', (topic, agent_id))
# Notify all other agents
cursor.execute("SELECT id FROM agents WHERE id != ?", (agent_id,))
other_agents = cursor.fetchall()
for (other_id,) in other_agents:
cursor.execute('''
INSERT INTO notifications (agent_id, message, is_broadcast)
VALUES (?, ?, 0)
''', (other_id, f"🔬 New research started: '{topic}' by {agent_id}. Use '@join {topic}' to participate."))
conn.commit()
conn.close()
print(f"🔬 Research topic '{topic}' started by {agent_id}")
print(f"📢 Notified {len(other_agents)} other agents")
def join_research(agent_id, topic):
"""@join - Join an active research topic"""
conn = get_connection()
cursor = conn.cursor()
# Check if topic exists and is active
cursor.execute("SELECT status FROM research_topics WHERE topic = ?", (topic,))
result = cursor.fetchone()
if not result:
print(f"❌ Research topic '{topic}' not found")
conn.close()
return
if result[0] != 'active':
print(f"⚠️ Research topic '{topic}' is {result[0]}")
conn.close()
return
# Add participant
cursor.execute('''
INSERT OR IGNORE INTO agent_research_participation (agent_id, topic)
VALUES (?, ?)
''', (agent_id, topic))
# Update agent status
cursor.execute('''
UPDATE agents SET status = 'researching', current_research = ?
WHERE id = ?
''', (topic, agent_id))
conn.commit()
conn.close()
print(f"{agent_id} joined research: '{topic}'")
def post_finding(agent_id, topic, content, finding_type="note"):
"""@find - Post a finding to research topic"""
conn = get_connection()
cursor = conn.cursor()
# Check if topic exists
cursor.execute("SELECT status FROM research_topics WHERE topic = ?", (topic,))
result = cursor.fetchone()
if not result:
print(f"❌ Research topic '{topic}' not found")
conn.close()
return
if result[0] != 'active':
print(f"⚠️ Research topic '{topic}' is {result[0]}")
# Add finding
cursor.execute('''
INSERT INTO research_log (agent_id, topic, content, finding_type)
VALUES (?, ?, ?, ?)
''', (agent_id, topic, content, finding_type))
# Update agent status
cursor.execute('''
UPDATE agents SET last_seen = CURRENT_TIMESTAMP WHERE id = ?
''', (agent_id,))
conn.commit()
conn.close()
print(f"📝 Finding added to '{topic}' by {agent_id}")
def generate_consensus(topic):
"""@consensus - Generate consensus document from research findings"""
conn = get_connection()
cursor = conn.cursor()
# Get all findings
cursor.execute('''
SELECT agent_id, content, finding_type, created_at
FROM research_log
WHERE topic = ?
ORDER BY created_at
''', (topic,))
findings = cursor.fetchall()
if not findings:
print(f"⚠️ No findings found for topic '{topic}'")
conn.close()
return
# Get participants
cursor.execute('''
SELECT agent_id FROM agent_research_participation WHERE topic = ?
''', (topic,))
participants = [row[0] for row in cursor.fetchall()]
# Mark topic as completed
cursor.execute('''
UPDATE research_topics
SET status = 'completed', completed_at = CURRENT_TIMESTAMP
WHERE topic = ?
''', (topic,))
conn.commit()
conn.close()
# Generate consensus document
consensus_dir = os.path.join(os.getcwd(), ".agent/consensus")
os.makedirs(consensus_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{topic.replace(' ', '_').replace('/', '_')}_{timestamp}.md"
filepath = os.path.join(consensus_dir, filename)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(f"# 🎯 Consensus: {topic}\n\n")
f.write(f"**Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write(f"**Participants**: {', '.join(participants)}\n\n")
f.write("---\n\n")
for agent_id, content, finding_type, created_at in findings:
f.write(f"## [{finding_type.upper()}] {agent_id}\n\n")
f.write(f"*{created_at}*\n\n")
f.write(f"{content}\n\n")
print(f"✅ Consensus generated: {filepath}")
print(f"📊 Total findings: {len(findings)}")
print(f"👥 Participants: {len(participants)}")
return filepath
# ============ TASK MANAGEMENT ============
def assign_task(assigned_by, agent_id, task):
"""@assign - Assign task to specific agent"""
conn = get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT INTO task_assignments (agent_id, task, assigned_by)
VALUES (?, ?, ?)
''', (agent_id, task, assigned_by))
# Notify the agent
cursor.execute('''
INSERT INTO notifications (agent_id, message, is_broadcast)
VALUES (?, ?, 0)
''', (agent_id, f"📋 New task assigned by {assigned_by}: {task}"))
conn.commit()
conn.close()
print(f"📋 Task assigned to {agent_id} by {assigned_by}")
def list_tasks(agent_id=None):
"""List tasks for an agent or all agents"""
conn = get_connection()
cursor = conn.cursor()
if agent_id:
cursor.execute('''
SELECT id, task, assigned_by, status, created_at
FROM task_assignments
WHERE agent_id = ? AND status != 'completed'
ORDER BY created_at DESC
''', (agent_id,))
tasks = cursor.fetchall()
print(f"\n📋 Tasks for {agent_id}:")
for task_id, task, assigned_by, status, created_at in tasks:
print(f" [{status.upper()}] #{task_id}: {task} (from {assigned_by})")
else:
cursor.execute('''
SELECT agent_id, id, task, assigned_by, status
FROM task_assignments
WHERE status != 'completed'
ORDER BY agent_id
''')
tasks = cursor.fetchall()
print(f"\n📋 All pending tasks:")
current_agent = None
for agent, task_id, task, assigned_by, status in tasks:
if agent != current_agent:
print(f"\n {agent}:")
current_agent = agent
print(f" [{status.upper()}] #{task_id}: {task}")
conn.close()
def complete_task(task_id):
"""Mark a task as completed"""
conn = get_connection()
cursor = conn.cursor()
cursor.execute('''
UPDATE task_assignments
SET status = 'completed', completed_at = CURRENT_TIMESTAMP
WHERE id = ?
''', (task_id,))
if cursor.rowcount > 0:
print(f"✅ Task #{task_id} marked as completed")
else:
print(f"❌ Task #{task_id} not found")
conn.commit()
conn.close()
# ============ NOTIFICATIONS ============
def broadcast_message(from_agent, message):
"""@notify - Broadcast message to all agents"""
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT id FROM agents WHERE id != ?", (from_agent,))
other_agents = cursor.fetchall()
for (agent_id,) in other_agents:
cursor.execute('''
INSERT INTO notifications (agent_id, message, is_broadcast)
VALUES (?, ?, 1)
''', (agent_id, f"📢 Broadcast from {from_agent}: {message}"))
conn.commit()
conn.close()
print(f"📢 Broadcast sent to {len(other_agents)} agents")
def get_notifications(agent_id, unread_only=False):
"""Get notifications for an agent"""
conn = get_connection()
cursor = conn.cursor()
if unread_only:
cursor.execute('''
SELECT id, message, is_broadcast, created_at
FROM notifications
WHERE agent_id = ? AND is_read = 0
ORDER BY created_at DESC
''', (agent_id,))
else:
cursor.execute('''
SELECT id, message, is_broadcast, created_at
FROM notifications
WHERE agent_id = ?
ORDER BY created_at DESC
LIMIT 10
''', (agent_id,))
notifications = cursor.fetchall()
print(f"\n🔔 Notifications for {agent_id}:")
for notif_id, message, is_broadcast, created_at in notifications:
prefix = "📢" if is_broadcast else "🔔"
print(f" {prefix} {message}")
print(f" {created_at}")
# Mark as read
cursor.execute('''
UPDATE notifications SET is_read = 1
WHERE agent_id = ? AND is_read = 0
''', (agent_id,))
conn.commit()
conn.close()
# ============ POLLS ============
def start_poll(agent_id, question):
"""@poll - Start a quick poll"""
conn = get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT INTO polls (question, created_by, status)
VALUES (?, ?, 'active')
''', (question, agent_id))
poll_id = cursor.lastrowid
# Notify all agents
cursor.execute("SELECT id FROM agents WHERE id != ?", (agent_id,))
other_agents = cursor.fetchall()
for (other_id,) in other_agents:
cursor.execute('''
INSERT INTO notifications (agent_id, message, is_broadcast)
VALUES (?, ?, 0)
''', (other_id, f"🗳️ New poll from {agent_id}: '{question}' (Poll #{poll_id}). Vote with: @vote {poll_id} <yes/no/maybe>"))
conn.commit()
conn.close()
print(f"🗳️ Poll #{poll_id} started: {question}")
return poll_id
def vote_poll(agent_id, poll_id, vote):
"""@vote - Vote on a poll"""
conn = get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO poll_votes (poll_id, agent_id, vote)
VALUES (?, ?, ?)
''', (poll_id, agent_id, vote))
conn.commit()
conn.close()
print(f"✅ Vote recorded for poll #{poll_id}: {vote}")
def show_poll_results(poll_id):
"""Show poll results"""
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT question FROM polls WHERE id = ?", (poll_id,))
result = cursor.fetchone()
if not result:
print(f"❌ Poll #{poll_id} not found")
conn.close()
return
question = result[0]
cursor.execute('''
SELECT vote, COUNT(*) FROM poll_votes
WHERE poll_id = ?
GROUP BY vote
''', (poll_id,))
votes = dict(cursor.fetchall())
cursor.execute('''
SELECT agent_id, vote FROM poll_votes
WHERE poll_id = ?
''', (poll_id,))
details = cursor.fetchall()
conn.close()
print(f"\n🗳️ Poll #{poll_id}: {question}")
print("Results:")
for vote, count in votes.items():
print(f" {vote}: {count}")
print("\nVotes:")
for agent, vote in details:
print(f" {agent}: {vote}")
# ============ HANDOVER ============
def request_handover(from_agent, to_agent, context=""):
"""@handover - Request task handover to another agent"""
conn = get_connection()
cursor = conn.cursor()
# Get current task of from_agent
cursor.execute("SELECT task FROM agents WHERE id = ?", (from_agent,))
result = cursor.fetchone()
current_task = result[0] if result else "current task"
# Create handover notification
message = f"🔄 Handover request from {from_agent}: '{current_task}'"
if context:
message += f" | Context: {context}"
cursor.execute('''
INSERT INTO notifications (agent_id, message, is_broadcast)
VALUES (?, ?, 0)
''', (to_agent, message))
# Update from_agent status
cursor.execute('''
UPDATE agents SET status = 'idle', task = NULL
WHERE id = ?
''', (from_agent,))
conn.commit()
conn.close()
print(f"🔄 Handover requested: {from_agent} -> {to_agent}")
def switch_to(agent_id, to_agent):
"""@switch - Request to switch to specific agent"""
conn = get_connection()
cursor = conn.cursor()
message = f"🔄 {agent_id} requests to switch to you for continuation"
cursor.execute('''
INSERT INTO notifications (agent_id, message, is_broadcast)
VALUES (?, ?, 0)
''', (to_agent, message))
conn.commit()
conn.close()
print(f"🔄 Switch request sent: {agent_id} -> {to_agent}")
# ============ STATUS & MONITORING ============
def get_status():
"""Enhanced status view"""
conn = get_connection()
cursor = conn.cursor()
print("\n" + "="*60)
print("🛰️ ACTIVE AGENTS")
print("="*60)
for row in cursor.execute('''
SELECT name, task, status, current_research, last_seen
FROM agents
ORDER BY last_seen DESC
'''):
status_emoji = {
'active': '🟢',
'idle': '',
'researching': '🔬',
'busy': '🔴'
}.get(row[2], '')
research_info = f" | Research: {row[3]}" if row[3] else ""
print(f"{status_emoji} [{row[2].upper()}] {row[0]}: {row[1]}{research_info}")
print(f" Last seen: {row[4]}")
print("\n" + "="*60)
print("🔬 ACTIVE RESEARCH TOPICS")
print("="*60)
for row in cursor.execute('''
SELECT t.topic, t.initiated_by, t.created_at,
(SELECT COUNT(*) FROM agent_research_participation WHERE topic = t.topic) as participants,
(SELECT COUNT(*) FROM research_log WHERE topic = t.topic) as findings
FROM research_topics t
WHERE t.status = 'active'
ORDER BY t.created_at DESC
'''):
print(f"🔬 {row[0]}")
print(f" Initiated by: {row[1]} | Participants: {row[3]} | Findings: {row[4]}")
print(f" Started: {row[2]}")
print("\n" + "="*60)
print("🔒 FILE LOCKS")
print("="*60)
locks = list(cursor.execute('''
SELECT file_path, agent_id, lock_type
FROM file_locks
ORDER BY timestamp DESC
'''))
if locks:
for file_path, agent_id, lock_type in locks:
lock_emoji = '🔒' if lock_type == 'write' else '🔍'
print(f"{lock_emoji} {file_path} -> {agent_id} ({lock_type})")
else:
print(" No active locks")
print("\n" + "="*60)
print("📋 PENDING TASKS")
print("="*60)
for row in cursor.execute('''
SELECT agent_id, COUNT(*)
FROM task_assignments
WHERE status = 'pending'
GROUP BY agent_id
'''):
print(f" {row[0]}: {row[1]} pending tasks")
cursor.execute("SELECT value FROM global_settings WHERE key = 'mode'")
mode = cursor.fetchone()[0]
print(f"\n🌍 Global Mode: {mode.upper()}")
print("="*60)
conn.close()
def show_research_topic(topic):
"""Show detailed view of a research topic"""
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT status, initiated_by, created_at FROM research_topics WHERE topic = ?", (topic,))
result = cursor.fetchone()
if not result:
print(f"❌ Topic '{topic}' not found")
conn.close()
return
status, initiated_by, created_at = result
print(f"\n🔬 Research: {topic}")
print(f"Status: {status} | Initiated by: {initiated_by} | Started: {created_at}")
cursor.execute('''
SELECT agent_id FROM agent_research_participation WHERE topic = ?
''', (topic,))
participants = [row[0] for row in cursor.fetchall()]
print(f"Participants: {', '.join(participants)}")
print("\n--- Findings ---")
cursor.execute('''
SELECT agent_id, content, finding_type, created_at
FROM research_log
WHERE topic = ?
ORDER BY created_at
''', (topic,))
for agent_id, content, finding_type, created_at in cursor.fetchall():
emoji = {'note': '📝', 'finding': '🔍', 'concern': '⚠️', 'solution': ''}.get(finding_type, '📝')
print(f"\n{emoji} [{finding_type.upper()}] {agent_id} ({created_at})")
print(f" {content}")
conn.close()
# ============ MAIN CLI ============
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="🤖 Agent Sync v2.0 - Multi-Agent Cooperation Protocol",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
QUICK COMMANDS:
@research <topic> Start joint research
@join <topic> Join active research
@find <topic> <content> Post finding to research
@consensus <topic> Generate consensus document
@assign <agent> <task> Assign task to agent
@notify <message> Broadcast to all agents
@handover <agent> [context] Handover task
@switch <agent> Request switch to agent
@poll <question> Start a poll
@vote <poll_id> <vote> Vote on poll
@tasks [agent] List tasks
@complete <task_id> Complete task
@notifications [agent] Check notifications
@topic <topic> View research topic details
EXAMPLES:
python3 agent_sync_v2.py research claude-code "API Design"
python3 agent_sync_v2.py find copilot "API Design" "Use REST instead of GraphQL"
python3 agent_sync_v2.py assign claude-code copilot "Implement REST endpoints"
python3 agent_sync_v2.py consensus "API Design"
"""
)
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
# Legacy commands
subparsers.add_parser("init", help="Initialize the database")
reg = subparsers.add_parser("register", help="Register an agent")
reg.add_argument("id", help="Agent ID")
reg.add_argument("name", help="Agent name")
reg.add_argument("task", help="Current task")
reg.add_argument("--status", default="idle", help="Agent status")
lock = subparsers.add_parser("lock", help="Lock a file")
lock.add_argument("id", help="Agent ID")
lock.add_argument("path", help="File path")
lock.add_argument("--type", default="write", choices=["write", "research"], help="Lock type")
unlock = subparsers.add_parser("unlock", help="Unlock a file")
unlock.add_argument("id", help="Agent ID")
unlock.add_argument("path", help="File path")
subparsers.add_parser("status", help="Show status dashboard")
# New v2.0 commands
research = subparsers.add_parser("research", help="@research - Start joint research topic")
research.add_argument("agent_id", help="Agent initiating research")
research.add_argument("topic", help="Research topic")
join = subparsers.add_parser("join", help="@join - Join active research")
join.add_argument("agent_id", help="Agent joining")
join.add_argument("topic", help="Topic to join")
find = subparsers.add_parser("find", help="@find - Post finding to research")
find.add_argument("agent_id", help="Agent posting finding")
find.add_argument("topic", help="Research topic")
find.add_argument("content", help="Finding content")
find.add_argument("--type", default="note", choices=["note", "finding", "concern", "solution"], help="Type of finding")
consensus = subparsers.add_parser("consensus", help="@consensus - Generate consensus document")
consensus.add_argument("topic", help="Topic to generate consensus for")
assign = subparsers.add_parser("assign", help="@assign - Assign task to agent")
assign.add_argument("from_agent", help="Agent assigning the task")
assign.add_argument("to_agent", help="Agent to assign task to")
assign.add_argument("task", help="Task description")
tasks = subparsers.add_parser("tasks", help="@tasks - List pending tasks")
tasks.add_argument("--agent", help="Filter by agent ID")
complete = subparsers.add_parser("complete", help="@complete - Mark task as completed")
complete.add_argument("task_id", type=int, help="Task ID to complete")
notify = subparsers.add_parser("notify", help="@notify - Broadcast message to all agents")
notify.add_argument("from_agent", help="Agent sending notification")
notify.add_argument("message", help="Message to broadcast")
handover = subparsers.add_parser("handover", help="@handover - Handover task to another agent")
handover.add_argument("from_agent", help="Current agent")
handover.add_argument("to_agent", help="Agent to handover to")
handover.add_argument("--context", default="", help="Handover context")
switch = subparsers.add_parser("switch", help="@switch - Request switch to specific agent")
switch.add_argument("from_agent", help="Current agent")
switch.add_argument("to_agent", help="Agent to switch to")
poll = subparsers.add_parser("poll", help="@poll - Start a quick poll")
poll.add_argument("agent_id", help="Agent starting poll")
poll.add_argument("question", help="Poll question")
vote = subparsers.add_parser("vote", help="@vote - Vote on a poll")
vote.add_argument("agent_id", help="Agent voting")
vote.add_argument("poll_id", type=int, help="Poll ID")
vote.add_argument("vote_choice", choices=["yes", "no", "maybe"], help="Your vote")
poll_results = subparsers.add_parser("poll-results", help="Show poll results")
poll_results.add_argument("poll_id", type=int, help="Poll ID")
notifications = subparsers.add_parser("notifications", help="@notifications - Check notifications")
notifications.add_argument("agent_id", help="Agent to check notifications for")
notifications.add_argument("--unread", action="store_true", help="Show only unread")
topic = subparsers.add_parser("topic", help="@topic - View research topic details")
topic.add_argument("topic_name", help="Topic name")
args = parser.parse_args()
if args.command == "init":
init_db()
elif args.command == "register":
register_agent(args.id, args.name, args.task, args.status)
elif args.command == "lock":
lock_file(args.id, args.path, args.type)
elif args.command == "unlock":
unlock_file(args.id, args.path)
elif args.command == "status":
get_status()
elif args.command == "research":
start_research(args.agent_id, args.topic)
elif args.command == "join":
join_research(args.agent_id, args.topic)
elif args.command == "find":
post_finding(args.agent_id, args.topic, args.content, args.type)
elif args.command == "consensus":
generate_consensus(args.topic)
elif args.command == "assign":
assign_task(args.from_agent, args.to_agent, args.task)
elif args.command == "tasks":
list_tasks(args.agent)
elif args.command == "complete":
complete_task(args.task_id)
elif args.command == "notify":
broadcast_message(args.from_agent, args.message)
elif args.command == "handover":
request_handover(args.from_agent, args.to_agent, args.context)
elif args.command == "switch":
switch_to(args.from_agent, args.to_agent)
elif args.command == "poll":
start_poll(args.agent_id, args.question)
elif args.command == "vote":
vote_poll(args.agent_id, args.poll_id, args.vote_choice)
elif args.command == "poll-results":
show_poll_results(args.poll_id)
elif args.command == "notifications":
get_notifications(args.agent_id, args.unread)
elif args.command == "topic":
show_research_topic(args.topic_name)
else:
parser.print_help()

View File

@@ -11,9 +11,9 @@ Usage:
To get started: To get started:
1. Create .env file with your OpenWebUI API key: 1. Create .env file with your OpenWebUI API key:
echo "api_key=sk-your-key-here" > .env echo "api_key=sk-your-key-here" > .env
2. Make sure OpenWebUI is running on localhost:3000 2. Make sure OpenWebUI is running on localhost:3003
3. Run this script: 3. Run this script:
python deploy_async_context_compression.py python deploy_async_context_compression.py
""" """
@@ -34,10 +34,10 @@ def main():
print("🚀 Deploying Async Context Compression Filter Plugin") print("🚀 Deploying Async Context Compression Filter Plugin")
print("=" * 70) print("=" * 70)
print() print()
# Deploy the filter # Deploy the filter
success = deploy_filter("async-context-compression") success = deploy_filter("async-context-compression")
if success: if success:
print() print()
print("=" * 70) print("=" * 70)
@@ -45,7 +45,7 @@ def main():
print("=" * 70) print("=" * 70)
print() print()
print("Next steps:") print("Next steps:")
print(" 1. Open OpenWebUI in your browser: http://localhost:3000") print(" 1. Open OpenWebUI in your browser: http://localhost:3003")
print(" 2. Go to Settings → Filters") print(" 2. Go to Settings → Filters")
print(" 3. Enable 'Async Context Compression'") print(" 3. Enable 'Async Context Compression'")
print(" 4. Configure Valves as needed") print(" 4. Configure Valves as needed")
@@ -58,12 +58,12 @@ def main():
print("=" * 70) print("=" * 70)
print() print()
print("Troubleshooting:") print("Troubleshooting:")
print(" • Check that OpenWebUI is running: http://localhost:3000") print(" • Check that OpenWebUI is running: http://localhost:3003")
print(" • Verify API key in .env file") print(" • Verify API key in .env file")
print(" • Check network connectivity") print(" • Check network connectivity")
print() print()
return 1 return 1
return 0 return 0

View File

@@ -49,78 +49,53 @@ def _load_api_key() -> str:
raise ValueError("api_key not found in .env file.") raise ValueError("api_key not found in .env file.")
def _load_openwebui_base_url() -> str:
"""Load OpenWebUI base URL from .env file or environment.
Checks in order:
1. OPENWEBUI_BASE_URL in .env
2. OPENWEBUI_BASE_URL environment variable
3. Default to http://localhost:3000
"""
if ENV_FILE.exists():
for line in ENV_FILE.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line.startswith("OPENWEBUI_BASE_URL="):
url = line.split("=", 1)[1].strip()
if url:
return url
# Try environment variable
url = os.environ.get("OPENWEBUI_BASE_URL")
if url:
return url
# Default
return "http://localhost:3000"
def _find_filter_file(filter_name: str) -> Optional[Path]: def _find_filter_file(filter_name: str) -> Optional[Path]:
"""Find the main Python file for a filter. """Find the main Python file for a filter.
Args: Args:
filter_name: Directory name of the filter (e.g., 'async-context-compression') filter_name: Directory name of the filter (e.g., 'async-context-compression')
Returns: Returns:
Path to the main Python file, or None if not found. Path to the main Python file, or None if not found.
""" """
filter_dir = FILTERS_DIR / filter_name filter_dir = FILTERS_DIR / filter_name
if not filter_dir.exists(): if not filter_dir.exists():
return None return None
# Try to find a .py file matching the filter name # Try to find a .py file matching the filter name
py_files = list(filter_dir.glob("*.py")) py_files = list(filter_dir.glob("*.py"))
# Prefer a file with the filter name (with hyphens converted to underscores) # Prefer a file with the filter name (with hyphens converted to underscores)
preferred_name = filter_name.replace("-", "_") + ".py" preferred_name = filter_name.replace("-", "_") + ".py"
for py_file in py_files: for py_file in py_files:
if py_file.name == preferred_name: if py_file.name == preferred_name:
return py_file return py_file
# Otherwise, return the first .py file (usually the only one) # Otherwise, return the first .py file (usually the only one)
if py_files: if py_files:
return py_files[0] return py_files[0]
return None return None
def _extract_metadata(content: str) -> Dict[str, Any]: def _extract_metadata(content: str) -> Dict[str, Any]:
"""Extract metadata from the plugin docstring. """Extract metadata from the plugin docstring.
Args: Args:
content: Python file content content: Python file content
Returns: Returns:
Dictionary with extracted metadata (title, author, version, etc.) Dictionary with extracted metadata (title, author, version, etc.)
""" """
metadata = {} metadata = {}
# Extract docstring # Extract docstring
match = re.search(r'"""(.*?)"""', content, re.DOTALL) match = re.search(r'"""(.*?)"""', content, re.DOTALL)
if not match: if not match:
return metadata return metadata
docstring = match.group(1) docstring = match.group(1)
# Extract key-value pairs # Extract key-value pairs
for line in docstring.split("\n"): for line in docstring.split("\n"):
line = line.strip() line = line.strip()
@@ -129,7 +104,7 @@ def _extract_metadata(content: str) -> Dict[str, Any]:
key = parts[0].strip().lower() key = parts[0].strip().lower()
value = parts[1].strip() value = parts[1].strip()
metadata[key] = value metadata[key] = value
return metadata return metadata
@@ -137,13 +112,13 @@ def _build_filter_payload(
filter_name: str, file_path: Path, content: str, metadata: Dict[str, Any] filter_name: str, file_path: Path, content: str, metadata: Dict[str, Any]
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Build the payload for the filter update/create API. """Build the payload for the filter update/create API.
Args: Args:
filter_name: Directory name of the filter filter_name: Directory name of the filter
file_path: Path to the plugin file file_path: Path to the plugin file
content: File content content: File content
metadata: Extracted metadata metadata: Extracted metadata
Returns: Returns:
Payload dictionary ready for API submission Payload dictionary ready for API submission
""" """
@@ -151,14 +126,12 @@ def _build_filter_payload(
filter_id = metadata.get("id", filter_name).replace("-", "_") filter_id = metadata.get("id", filter_name).replace("-", "_")
title = metadata.get("title", filter_name) title = metadata.get("title", filter_name)
author = metadata.get("author", "Fu-Jie") author = metadata.get("author", "Fu-Jie")
author_url = metadata.get( author_url = metadata.get("author_url", "https://github.com/Fu-Jie/openwebui-extensions")
"author_url", "https://github.com/Fu-Jie/openwebui-extensions"
)
funding_url = metadata.get("funding_url", "https://github.com/open-webui") funding_url = metadata.get("funding_url", "https://github.com/open-webui")
description = metadata.get("description", f"Filter plugin: {title}") description = metadata.get("description", f"Filter plugin: {title}")
version = metadata.get("version", "1.0.0") version = metadata.get("version", "1.0.0")
openwebui_id = metadata.get("openwebui_id", "") openwebui_id = metadata.get("openwebui_id", "")
payload = { payload = {
"id": filter_id, "id": filter_id,
"name": title, "name": title,
@@ -177,20 +150,20 @@ def _build_filter_payload(
}, },
"content": content, "content": content,
} }
# Add openwebui_id if available # Add openwebui_id if available
if openwebui_id: if openwebui_id:
payload["meta"]["manifest"]["openwebui_id"] = openwebui_id payload["meta"]["manifest"]["openwebui_id"] = openwebui_id
return payload return payload
def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool: def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
"""Deploy a filter plugin to OpenWebUI. """Deploy a filter plugin to OpenWebUI.
Args: Args:
filter_name: Directory name of the filter to deploy filter_name: Directory name of the filter to deploy
Returns: Returns:
True if successful, False otherwise True if successful, False otherwise
""" """
@@ -218,7 +191,7 @@ def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
content = file_path.read_text(encoding="utf-8") content = file_path.read_text(encoding="utf-8")
metadata = _extract_metadata(content) metadata = _extract_metadata(content)
if not metadata: if not metadata:
print(f"[ERROR] Could not extract metadata from {file_path}") print(f"[ERROR] Could not extract metadata from {file_path}")
return False return False
@@ -238,14 +211,12 @@ def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
} }
# 6. Send update request # 6. Send update request
base_url = _load_openwebui_base_url() update_url = "http://localhost:3003/api/v1/functions/id/{}/update".format(filter_id)
update_url = "{}/api/v1/functions/id/{}/update".format(base_url, filter_id) create_url = "http://localhost:3003/api/v1/functions/create"
create_url = "{}/api/v1/functions/create".format(base_url)
print(f"📦 Deploying filter '{title}' (version {version})...") print(f"📦 Deploying filter '{title}' (version {version})...")
print(f" File: {file_path}") print(f" File: {file_path}")
print(f" Target: {base_url}")
try: try:
# Try update first # Try update first
response = requests.post( response = requests.post(
@@ -254,7 +225,7 @@ def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
data=json.dumps(payload), data=json.dumps(payload),
timeout=10, timeout=10,
) )
if response.status_code == 200: if response.status_code == 200:
print(f"✅ Successfully updated '{title}' filter!") print(f"✅ Successfully updated '{title}' filter!")
return True return True
@@ -263,7 +234,7 @@ def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
f"⚠️ Update failed with status {response.status_code}, " f"⚠️ Update failed with status {response.status_code}, "
"attempting to create instead..." "attempting to create instead..."
) )
# Try create if update fails # Try create if update fails
res_create = requests.post( res_create = requests.post(
create_url, create_url,
@@ -271,24 +242,23 @@ def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
data=json.dumps(payload), data=json.dumps(payload),
timeout=10, timeout=10,
) )
if res_create.status_code == 200: if res_create.status_code == 200:
print(f"✅ Successfully created '{title}' filter!") print(f"✅ Successfully created '{title}' filter!")
return True return True
else: else:
print( print(f"❌ Failed to update or create. Status: {res_create.status_code}")
f"❌ Failed to update or create. Status: {res_create.status_code}"
)
try: try:
error_msg = res_create.json() error_msg = res_create.json()
print(f" Error: {error_msg}") print(f" Error: {error_msg}")
except: except:
print(f" Response: {res_create.text[:500]}") print(f" Response: {res_create.text[:500]}")
return False return False
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
base_url = _load_openwebui_base_url() print(
print(f"❌ Connection error: Could not reach OpenWebUI at {base_url}") "❌ Connection error: Could not reach OpenWebUI at localhost:3003"
)
print(" Make sure OpenWebUI is running and accessible.") print(" Make sure OpenWebUI is running and accessible.")
return False return False
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
@@ -302,20 +272,16 @@ def deploy_filter(filter_name: str = DEFAULT_FILTER) -> bool:
def list_filters() -> None: def list_filters() -> None:
"""List all available filters.""" """List all available filters."""
print("📋 Available filters:") print("📋 Available filters:")
filters = [ filters = [d.name for d in FILTERS_DIR.iterdir() if d.is_dir() and not d.name.startswith("_")]
d.name
for d in FILTERS_DIR.iterdir()
if d.is_dir() and not d.name.startswith("_")
]
if not filters: if not filters:
print(" (No filters found)") print(" (No filters found)")
return return
for filter_name in sorted(filters): for filter_name in sorted(filters):
filter_dir = FILTERS_DIR / filter_name filter_dir = FILTERS_DIR / filter_name
py_file = _find_filter_file(filter_name) py_file = _find_filter_file(filter_name)
if py_file: if py_file:
content = py_file.read_text(encoding="utf-8") content = py_file.read_text(encoding="utf-8")
metadata = _extract_metadata(content) metadata = _extract_metadata(content)

View File

@@ -9,7 +9,7 @@ SCRIPT_DIR = Path(__file__).parent
ENV_FILE = SCRIPT_DIR / ".env" ENV_FILE = SCRIPT_DIR / ".env"
URL = ( URL = (
"http://localhost:3000/api/v1/functions/id/github_copilot_official_sdk_pipe/update" "http://localhost:3003/api/v1/functions/id/github_copilot_official_sdk_pipe/update"
) )
FILE_PATH = SCRIPT_DIR.parent / "plugins/pipes/github-copilot-sdk/github_copilot_sdk.py" FILE_PATH = SCRIPT_DIR.parent / "plugins/pipes/github-copilot-sdk/github_copilot_sdk.py"
@@ -103,7 +103,7 @@ def deploy_pipe() -> None:
print( print(
f"⚠️ Update failed with status {response.status_code}, attempting to create instead..." f"⚠️ Update failed with status {response.status_code}, attempting to create instead..."
) )
CREATE_URL = "http://localhost:3000/api/v1/functions/create" CREATE_URL = "http://localhost:3003/api/v1/functions/create"
res_create = requests.post( res_create = requests.post(
CREATE_URL, headers=headers, data=json.dumps(payload) CREATE_URL, headers=headers, data=json.dumps(payload)
) )

View File

@@ -1,325 +0,0 @@
#!/usr/bin/env python3
"""
Deploy Tools plugins to OpenWebUI instance.
This script deploys tool plugins to a running OpenWebUI instance.
It reads the plugin metadata and submits it to the local API.
Usage:
python deploy_tool.py # Deploy OpenWebUI Skills Manager Tool
python deploy_tool.py <tool_name> # Deploy specific tool
python deploy_tool.py --list # List available tools
"""
import requests
import json
import os
import re
import sys
from pathlib import Path
from typing import Optional, Dict, Any
# ─── Configuration ───────────────────────────────────────────────────────────
SCRIPT_DIR = Path(__file__).parent
ENV_FILE = SCRIPT_DIR / ".env"
TOOLS_DIR = SCRIPT_DIR.parent / "plugins/tools"
# Default target tool
DEFAULT_TOOL = "openwebui-skills-manager"
def _load_api_key() -> str:
"""Load API key from .env file in the same directory as this script."""
env_values = {}
if ENV_FILE.exists():
for line in ENV_FILE.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
env_values[key.strip().lower()] = value.strip().strip('"').strip("'")
api_key = (
os.getenv("OPENWEBUI_API_KEY")
or os.getenv("api_key")
or env_values.get("api_key")
or env_values.get("openwebui_api_key")
)
if not api_key:
raise ValueError(
f"Missing api_key. Please create {ENV_FILE} with: "
"api_key=sk-xxxxxxxxxxxx"
)
return api_key
def _get_base_url() -> str:
"""Load base URL from .env file or environment."""
env_values = {}
if ENV_FILE.exists():
for line in ENV_FILE.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
env_values[key.strip().lower()] = value.strip().strip('"').strip("'")
base_url = (
os.getenv("OPENWEBUI_URL")
or os.getenv("OPENWEBUI_BASE_URL")
or os.getenv("url")
or env_values.get("url")
or env_values.get("openwebui_url")
or env_values.get("openwebui_base_url")
)
if not base_url:
raise ValueError(
f"Missing url. Please create {ENV_FILE} with: " "url=http://localhost:3000"
)
return base_url.rstrip("/")
def _find_tool_file(tool_name: str) -> Optional[Path]:
"""Find the main Python file for a tool.
Args:
tool_name: Directory name of the tool (e.g., 'openwebui-skills-manager')
Returns:
Path to the main Python file, or None if not found.
"""
tool_dir = TOOLS_DIR / tool_name
if not tool_dir.exists():
return None
# Try to find a .py file matching the tool name
py_files = list(tool_dir.glob("*.py"))
# Prefer a file with the tool name (with hyphens converted to underscores)
preferred_name = tool_name.replace("-", "_") + ".py"
for py_file in py_files:
if py_file.name == preferred_name:
return py_file
# Otherwise, return the first .py file (usually the only one)
if py_files:
return py_files[0]
return None
def _extract_metadata(content: str) -> Dict[str, Any]:
"""Extract metadata from the plugin docstring."""
metadata = {}
# Extract docstring
match = re.search(r'"""(.*?)"""', content, re.DOTALL)
if not match:
return metadata
docstring = match.group(1)
# Extract key-value pairs
for line in docstring.split("\n"):
line = line.strip()
if ":" in line and not line.startswith("#") and not line.startswith(""):
parts = line.split(":", 1)
key = parts[0].strip().lower()
value = parts[1].strip()
metadata[key] = value
return metadata
def _build_tool_payload(
tool_name: str, file_path: Path, content: str, metadata: Dict[str, Any]
) -> Dict[str, Any]:
"""Build the payload for the tool update/create API."""
tool_id = metadata.get("id", tool_name).replace("-", "_")
title = metadata.get("title", tool_name)
author = metadata.get("author", "Fu-Jie")
author_url = metadata.get(
"author_url", "https://github.com/Fu-Jie/openwebui-extensions"
)
funding_url = metadata.get("funding_url", "https://github.com/open-webui")
description = metadata.get("description", f"Tool plugin: {title}")
version = metadata.get("version", "1.0.0")
openwebui_id = metadata.get("openwebui_id", "")
payload = {
"id": tool_id,
"name": title,
"meta": {
"description": description,
"manifest": {
"title": title,
"author": author,
"author_url": author_url,
"funding_url": funding_url,
"description": description,
"version": version,
"type": "tool",
},
"type": "tool",
},
"content": content,
}
# Add openwebui_id if available
if openwebui_id:
payload["meta"]["manifest"]["openwebui_id"] = openwebui_id
return payload
def deploy_tool(tool_name: str = DEFAULT_TOOL) -> bool:
"""Deploy a tool plugin to OpenWebUI.
Args:
tool_name: Directory name of the tool to deploy
Returns:
True if successful, False otherwise
"""
# 1. Load API key and base URL
try:
api_key = _load_api_key()
base_url = _get_base_url()
except ValueError as e:
print(f"[ERROR] {e}")
return False
# 2. Find tool file
file_path = _find_tool_file(tool_name)
if not file_path:
print(f"[ERROR] Tool '{tool_name}' not found in {TOOLS_DIR}")
print(f"[INFO] Available tools:")
for d in TOOLS_DIR.iterdir():
if d.is_dir() and not d.name.startswith("_"):
print(f" - {d.name}")
return False
# 3. Read local source file
if not file_path.exists():
print(f"[ERROR] Source file not found: {file_path}")
return False
content = file_path.read_text(encoding="utf-8")
metadata = _extract_metadata(content)
if not metadata:
print(f"[ERROR] Could not extract metadata from {file_path}")
return False
version = metadata.get("version", "1.0.0")
title = metadata.get("title", tool_name)
tool_id = metadata.get("id", tool_name).replace("-", "_")
# 4. Build payload
payload = _build_tool_payload(tool_name, file_path, content, metadata)
# 5. Build headers
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
# 6. Send update request through the native tool endpoints
update_url = f"{base_url}/api/v1/tools/id/{tool_id}/update"
create_url = f"{base_url}/api/v1/tools/create"
print(f"📦 Deploying tool '{title}' (version {version})...")
print(f" File: {file_path}")
try:
# Try update first
response = requests.post(
update_url,
headers=headers,
data=json.dumps(payload),
timeout=10,
)
if response.status_code == 200:
print(f"✅ Successfully updated '{title}' tool!")
return True
else:
print(
f"⚠️ Update failed with status {response.status_code}, "
"attempting to create instead..."
)
# Try create if update fails
res_create = requests.post(
create_url,
headers=headers,
data=json.dumps(payload),
timeout=10,
)
if res_create.status_code == 200:
print(f"✅ Successfully created '{title}' tool!")
return True
else:
print(
f"❌ Failed to update or create. Status: {res_create.status_code}"
)
try:
error_msg = res_create.json()
print(f" Error: {error_msg}")
except:
print(f" Response: {res_create.text[:500]}")
return False
except requests.exceptions.ConnectionError:
print("❌ Connection error: Could not reach OpenWebUI at {base_url}")
print(" Make sure OpenWebUI is running and accessible.")
return False
except requests.exceptions.Timeout:
print("❌ Request timeout: OpenWebUI took too long to respond")
return False
except Exception as e:
print(f"❌ Request error: {e}")
return False
def list_tools() -> None:
"""List all available tools."""
print("📋 Available tools:")
tools = [
d.name for d in TOOLS_DIR.iterdir() if d.is_dir() and not d.name.startswith("_")
]
if not tools:
print(" (No tools found)")
return
for tool_name in sorted(tools):
tool_dir = TOOLS_DIR / tool_name
py_file = _find_tool_file(tool_name)
if py_file:
content = py_file.read_text(encoding="utf-8")
metadata = _extract_metadata(content)
title = metadata.get("title", tool_name)
version = metadata.get("version", "?")
print(f" - {tool_name:<30} {title:<40} v{version}")
else:
print(f" - {tool_name:<30} (no Python file found)")
if __name__ == "__main__":
if len(sys.argv) > 1:
if sys.argv[1] == "--list" or sys.argv[1] == "-l":
list_tools()
else:
tool_name = sys.argv[1]
success = deploy_tool(tool_name)
sys.exit(0 if success else 1)
else:
# Deploy default tool
success = deploy_tool()
sys.exit(0 if success else 1)

View File

@@ -285,8 +285,9 @@ def format_release_notes(
prev_ver = prev_manifest.get("version") or prev.get("version") prev_ver = prev_manifest.get("version") or prev.get("version")
readme_url = _get_readme_url(curr.get("file_path", "")) readme_url = _get_readme_url(curr.get("file_path", ""))
readme_link = f" | [📖 README]({readme_url})" if readme_url else "" lines.append(f"- **{curr_title}**: v{prev_ver} → v{curr_ver}")
lines.append(f"- **{curr_title}**: v{prev_ver} → v{curr_ver}{readme_link}") if readme_url:
lines.append(f" - 📖 [README]({readme_url})")
lines.append("") lines.append("")
if comparison["removed"] and not ignore_removed: if comparison["removed"] and not ignore_removed:

View File

@@ -1,443 +0,0 @@
#!/usr/bin/env python3
"""
Bulk install OpenWebUI plugins from this repository.
This script installs plugins from the local repository into a target OpenWebUI
instance. It only installs plugins that:
- live under plugins/actions, plugins/filters, plugins/pipes, or plugins/tools
- contain an `openwebui_id` in the plugin header docstring
- do not use a Chinese filename
- do not use a `_cn.py` localized filename suffix
Supported Plugin Types:
- Action (standard Function class)
- Filter (standard Function class)
- Pipe (standard Function class)
- Tool (native Tools class via /api/v1/tools endpoints)
Configuration:
Create `scripts/.env` with:
api_key=sk-your-api-key
url=http://localhost:3000
Usage:
python scripts/install_all_plugins.py
python scripts/install_all_plugins.py --list
python scripts/install_all_plugins.py --dry-run
python scripts/install_all_plugins.py --types pipe action filter tool
"""
from __future__ import annotations
import argparse
import json
import os
import re
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional, Sequence, Tuple
import requests
SCRIPT_DIR = Path(__file__).resolve().parent
REPO_ROOT = SCRIPT_DIR.parent
ENV_FILE = SCRIPT_DIR / ".env"
DEFAULT_TIMEOUT = 20
DEFAULT_TYPES = ("pipe", "action", "filter", "tool")
SKIP_PREFIXES = ("test_", "verify_")
DOCSTRING_PATTERN = re.compile(r'^\s*"""\n(.*?)\n"""', re.DOTALL)
PLUGIN_TYPE_DIRS = {
"action": REPO_ROOT / "plugins" / "actions",
"filter": REPO_ROOT / "plugins" / "filters",
"pipe": REPO_ROOT / "plugins" / "pipes",
"tool": REPO_ROOT / "plugins" / "tools",
}
@dataclass(frozen=True)
class PluginCandidate:
plugin_type: str
file_path: Path
metadata: Dict[str, str]
content: str
function_id: str
@property
def title(self) -> str:
return self.metadata.get("title", self.file_path.stem)
@property
def version(self) -> str:
return self.metadata.get("version", "unknown")
def _load_env_file(env_path: Path = ENV_FILE) -> Dict[str, str]:
values: Dict[str, str] = {}
if not env_path.exists():
return values
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key_lower = key.strip().lower()
values[key_lower] = value.strip().strip('"').strip("'")
return values
def load_config(env_path: Path = ENV_FILE) -> Tuple[str, str]:
env_values = _load_env_file(env_path)
api_key = (
os.getenv("OPENWEBUI_API_KEY")
or os.getenv("api_key")
or env_values.get("api_key")
or env_values.get("openwebui_api_key")
)
base_url = (
os.getenv("OPENWEBUI_URL")
or os.getenv("OPENWEBUI_BASE_URL")
or os.getenv("url")
or env_values.get("url")
or env_values.get("openwebui_url")
or env_values.get("openwebui_base_url")
)
missing = []
if not api_key:
missing.append("api_key")
if not base_url:
missing.append("url")
if missing:
joined = ", ".join(missing)
raise ValueError(
f"Missing required config: {joined}. "
f"Please set them in environment variables or {env_path}."
)
return api_key, normalize_base_url(base_url)
def normalize_base_url(url: str) -> str:
normalized = url.strip()
if not normalized:
raise ValueError("URL cannot be empty.")
return normalized.rstrip("/")
def extract_metadata(content: str) -> Dict[str, str]:
match = DOCSTRING_PATTERN.search(content)
if not match:
return {}
metadata: Dict[str, str] = {}
for raw_line in match.group(1).splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or ":" not in line:
continue
key, value = line.split(":", 1)
metadata[key.strip().lower()] = value.strip()
return metadata
def contains_non_ascii_filename(file_path: Path) -> bool:
try:
file_path.stem.encode("ascii")
return False
except UnicodeEncodeError:
return True
def should_skip_plugin_file(file_path: Path) -> Optional[str]:
stem = file_path.stem.lower()
if contains_non_ascii_filename(file_path):
return "non-ascii filename"
if stem.endswith("_cn"):
return "localized _cn file"
if stem.startswith(SKIP_PREFIXES):
return "test or helper script"
return None
def slugify_function_id(value: str) -> str:
slug = re.sub(r"[^a-z0-9]+", "_", value.lower()).strip("_")
slug = re.sub(r"_+", "_", slug)
return slug or "plugin"
def build_function_id(file_path: Path, metadata: Dict[str, str]) -> str:
if metadata.get("id"):
return slugify_function_id(metadata["id"])
if metadata.get("title"):
return slugify_function_id(metadata["title"])
return slugify_function_id(file_path.stem)
def has_tools_class(content: str) -> bool:
"""Check if plugin content defines a Tools class instead of Function class."""
return "\nclass Tools:" in content or "\nclass Tools (" in content
def build_payload(candidate: PluginCandidate) -> Dict[str, object]:
manifest = dict(candidate.metadata)
manifest.setdefault("title", candidate.title)
manifest.setdefault("author", "Fu-Jie")
manifest.setdefault("author_url", "https://github.com/Fu-Jie/openwebui-extensions")
manifest.setdefault("funding_url", "https://github.com/open-webui")
manifest.setdefault(
"description", f"{candidate.plugin_type.title()} plugin: {candidate.title}"
)
manifest.setdefault("version", "1.0.0")
manifest["type"] = candidate.plugin_type
if candidate.plugin_type == "tool":
return {
"id": candidate.function_id,
"name": manifest["title"],
"meta": {
"description": manifest["description"],
"manifest": {},
},
"content": candidate.content,
"access_grants": [],
}
return {
"id": candidate.function_id,
"name": manifest["title"],
"meta": {
"description": manifest["description"],
"manifest": manifest,
"type": candidate.plugin_type,
},
"content": candidate.content,
}
def build_api_urls(base_url: str, candidate: PluginCandidate) -> Tuple[str, str]:
if candidate.plugin_type == "tool":
return (
f"{base_url}/api/v1/tools/id/{candidate.function_id}/update",
f"{base_url}/api/v1/tools/create",
)
return (
f"{base_url}/api/v1/functions/id/{candidate.function_id}/update",
f"{base_url}/api/v1/functions/create",
)
def discover_plugins(
plugin_types: Sequence[str],
) -> Tuple[List[PluginCandidate], List[Tuple[Path, str]]]:
candidates: List[PluginCandidate] = []
skipped: List[Tuple[Path, str]] = []
for plugin_type in plugin_types:
plugin_dir = PLUGIN_TYPE_DIRS[plugin_type]
if not plugin_dir.exists():
continue
for file_path in sorted(plugin_dir.rglob("*.py")):
skip_reason = should_skip_plugin_file(file_path)
if skip_reason:
skipped.append((file_path, skip_reason))
continue
content = file_path.read_text(encoding="utf-8")
metadata = extract_metadata(content)
if not metadata:
skipped.append((file_path, "missing plugin header"))
continue
if not metadata.get("openwebui_id"):
skipped.append((file_path, "missing openwebui_id"))
continue
candidates.append(
PluginCandidate(
plugin_type=plugin_type,
file_path=file_path,
metadata=metadata,
content=content,
function_id=build_function_id(file_path, metadata),
)
)
candidates.sort(key=lambda item: (item.plugin_type, item.file_path.as_posix()))
skipped.sort(key=lambda item: item[0].as_posix())
return candidates, skipped
def install_plugin(
candidate: PluginCandidate,
api_key: str,
base_url: str,
timeout: int = DEFAULT_TIMEOUT,
) -> Tuple[bool, str]:
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
payload = build_payload(candidate)
update_url, create_url = build_api_urls(base_url, candidate)
try:
update_response = requests.post(
update_url,
headers=headers,
data=json.dumps(payload),
timeout=timeout,
)
if 200 <= update_response.status_code < 300:
return True, "updated"
create_response = requests.post(
create_url,
headers=headers,
data=json.dumps(payload),
timeout=timeout,
)
if 200 <= create_response.status_code < 300:
return True, "created"
message = _response_message(create_response)
return False, f"create failed ({create_response.status_code}): {message}"
except requests.exceptions.Timeout:
return False, "request timed out"
except requests.exceptions.ConnectionError:
return False, f"cannot connect to {base_url}"
except Exception as exc:
return False, str(exc)
def _response_message(response: requests.Response) -> str:
try:
return json.dumps(response.json(), ensure_ascii=False)
except Exception:
return response.text[:500]
def print_candidates(candidates: Sequence[PluginCandidate]) -> None:
if not candidates:
print("No installable plugins found.")
return
print(f"Found {len(candidates)} installable plugins:")
for candidate in candidates:
relative_path = candidate.file_path.relative_to(REPO_ROOT)
print(
f" - [{candidate.plugin_type}] {candidate.title} "
f"v{candidate.version} -> {relative_path}"
)
def print_skipped_summary(skipped: Sequence[Tuple[Path, str]]) -> None:
if not skipped:
return
counts: Dict[str, int] = {}
for _, reason in skipped:
counts[reason] = counts.get(reason, 0) + 1
summary = ", ".join(
f"{reason}: {count}" for reason, count in sorted(counts.items())
)
print(f"Skipped {len(skipped)} files ({summary}).")
def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Install repository plugins into an OpenWebUI instance."
)
parser.add_argument(
"--types",
nargs="+",
choices=sorted(PLUGIN_TYPE_DIRS.keys()),
default=list(DEFAULT_TYPES),
help="Plugin types to install. Defaults to all supported types.",
)
parser.add_argument(
"--list",
action="store_true",
help="List installable plugins without calling the API.",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would be installed without calling the API.",
)
parser.add_argument(
"--timeout",
type=int,
default=DEFAULT_TIMEOUT,
help=f"Request timeout in seconds. Default: {DEFAULT_TIMEOUT}.",
)
return parser.parse_args(argv)
def main(argv: Optional[Sequence[str]] = None) -> int:
args = parse_args(argv)
candidates, skipped = discover_plugins(args.types)
print_candidates(candidates)
print_skipped_summary(skipped)
if args.list or args.dry_run:
return 0
if not candidates:
print("Nothing to install.")
return 1
try:
api_key, base_url = load_config()
except ValueError as exc:
print(f"[ERROR] {exc}")
return 1
print(f"Installing to: {base_url}")
success_count = 0
failed_candidates = []
for candidate in candidates:
relative_path = candidate.file_path.relative_to(REPO_ROOT)
print(
f"\nInstalling [{candidate.plugin_type}] {candidate.title} "
f"v{candidate.version} ({relative_path})"
)
ok, message = install_plugin(
candidate=candidate,
api_key=api_key,
base_url=base_url,
timeout=args.timeout,
)
if ok:
success_count += 1
print(f" [OK] {message}")
else:
failed_candidates.append(candidate)
print(f" [FAILED] {message}")
print(f"\n" + "=" * 80)
print(
f"Finished: {success_count}/{len(candidates)} plugins installed successfully."
)
if failed_candidates:
print(f"\n{len(failed_candidates)} plugin(s) failed to install:")
for candidate in failed_candidates:
print(f"{candidate.title} ({candidate.plugin_type})")
print(f" → Check the error message above")
print()
print("=" * 80)
return 0 if success_count == len(candidates) else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,110 +0,0 @@
#!/bin/bash
# 🤖 MACP Quick Command v2.1 (Unified Edition)
set -euo pipefail
AGENT_ID_FILE=".agent/current_agent"
resolve_agent_id() {
if [ -n "${MACP_AGENT_ID:-}" ]; then
echo "$MACP_AGENT_ID"
return
fi
if [ -f "$AGENT_ID_FILE" ]; then
cat "$AGENT_ID_FILE"
return
fi
echo "Error: MACP agent identity is not set. Export MACP_AGENT_ID or create .agent/current_agent." >&2
exit 1
}
resolve_agent_name() {
python3 - <<'PY2'
import os
import sqlite3
import sys
agent_id = os.environ.get("MACP_AGENT_ID", "").strip()
if not agent_id:
path = os.path.join(os.getcwd(), ".agent", "current_agent")
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as handle:
agent_id = handle.read().strip()
db_path = os.path.join(os.getcwd(), ".agent", "agent_hub.db")
name = agent_id or "Agent"
if agent_id and os.path.exists(db_path):
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("SELECT name FROM agents WHERE id = ?", (agent_id,))
row = cur.fetchone()
conn.close()
if row and row[0]:
name = row[0]
sys.stdout.write(name)
PY2
}
AGENT_ID="$(resolve_agent_id)"
export MACP_AGENT_ID="$AGENT_ID"
AGENT_NAME="$(resolve_agent_name)"
CMD="${1:-}"
if [ -z "$CMD" ]; then
echo "Usage: ./scripts/macp [/status|/ping|/study|/broadcast|/summon|/handover|/note|/check|/resolve]" >&2
exit 1
fi
shift
case "$CMD" in
/study)
TOPIC="$1"
shift
DESC="$*"
if [ -n "$DESC" ]; then
python3 scripts/agent_sync.py study "$AGENT_ID" "$TOPIC" --desc "$DESC"
else
python3 scripts/agent_sync.py study "$AGENT_ID" "$TOPIC"
fi
;;
/broadcast)
python3 scripts/agent_sync.py broadcast "$AGENT_ID" manual "$*"
;;
/summon)
TO_AGENT="$1"
shift
python3 scripts/agent_sync.py assign "$AGENT_ID" "$TO_AGENT" "$*" --role worker --priority high
;;
/handover)
TO_AGENT="$1"
shift
python3 scripts/agent_sync.py assign "$AGENT_ID" "$TO_AGENT" "$*" --role worker
python3 scripts/agent_sync.py register "$AGENT_ID" "$AGENT_NAME" "Idle"
;;
/note)
TOPIC="$1"
shift
python3 scripts/agent_sync.py note "$AGENT_ID" "$TOPIC" "$*" --type note
;;
/check)
python3 scripts/agent_sync.py check
;;
/resolve)
TOPIC="$1"
shift
python3 scripts/agent_sync.py resolve "$AGENT_ID" "$TOPIC" "$*"
;;
/ping)
python3 scripts/agent_sync.py status | grep "\["
;;
/status)
python3 scripts/agent_sync.py status
;;
*)
echo "Usage: ./scripts/macp [/status|/ping|/study|/broadcast|/summon|/handover|/note|/check|/resolve]"
;;
esac

View File

@@ -277,37 +277,12 @@ class OpenWebUIStats:
}, },
} }
def _get_plugin_obj(self, post: dict) -> dict:
"""Extract the actual plugin object from post['data'] (handling different keys like function/tool/pipe)."""
data = post.get("data", {}) or {}
if not data:
return {}
# Priority 1: Use post['type'] as the key (standard behavior)
post_type = post.get("type")
if post_type and post_type in data and data[post_type]:
return data[post_type]
# Priority 2: Fallback to 'function' (most common for actions/filters/pipes)
if "function" in data and data["function"]:
return data["function"]
# Priority 3: Try other known keys
for k in ["tool", "pipe", "action", "filter", "prompt", "model"]:
if k in data and data[k]:
return data[k]
# Priority 4: If there's only one key in data, assume that's the one
if len(data) == 1:
return list(data.values())[0] or {}
return {}
def _resolve_post_type(self, post: dict) -> str: def _resolve_post_type(self, post: dict) -> str:
"""Resolve the post category type""" """Resolve the post category type"""
top_type = post.get("type") top_type = post.get("type")
plugin_obj = self._get_plugin_obj(post) function_data = post.get("data", {}) or {}
meta = plugin_obj.get("meta", {}) or {} function_obj = function_data.get("function", {}) or {}
meta = function_obj.get("meta", {}) or {}
manifest = meta.get("manifest", {}) or {} manifest = meta.get("manifest", {}) or {}
# Category identification priority: # Category identification priority:
@@ -317,17 +292,17 @@ class OpenWebUIStats:
post_type = "unknown" post_type = "unknown"
if meta.get("type"): if meta.get("type"):
post_type = meta.get("type") post_type = meta.get("type")
elif plugin_obj.get("type"): elif function_obj.get("type"):
post_type = plugin_obj.get("type") post_type = function_obj.get("type")
elif top_type: elif top_type:
post_type = top_type post_type = top_type
elif not meta and not plugin_obj: elif not meta and not function_obj:
post_type = "post" post_type = "post"
post_type = self._normalize_post_type(post_type) post_type = self._normalize_post_type(post_type)
# Unified and heuristic identification logic # Unified and heuristic identification logic
if post_type == "unknown" and plugin_obj: if post_type == "unknown" and function_obj:
post_type = "action" post_type = "action"
if post_type == "action" or post_type == "unknown": if post_type == "action" or post_type == "unknown":
@@ -625,8 +600,9 @@ class OpenWebUIStats:
for post in posts: for post in posts:
post_type = self._resolve_post_type(post) post_type = self._resolve_post_type(post)
plugin_obj = self._get_plugin_obj(post) function_data = post.get("data", {}) or {}
meta = plugin_obj.get("meta", {}) or {} function_obj = function_data.get("function", {}) or {}
meta = function_obj.get("meta", {}) or {}
manifest = meta.get("manifest", {}) or {} manifest = meta.get("manifest", {}) or {}
# Accumulate statistics # Accumulate statistics
@@ -639,12 +615,13 @@ class OpenWebUIStats:
stats["total_saves"] += post.get("saveCount", 0) stats["total_saves"] += post.get("saveCount", 0)
stats["total_comments"] += post.get("commentCount", 0) stats["total_comments"] += post.get("commentCount", 0)
# Key: only count views for posts with actual downloads (exclude post/review types) # Key: total views do not include non-downloadable types (e.g., post, review)
if post_type not in ("post", "review") and post_downloads > 0: if post_type in self.DOWNLOADABLE_TYPES or post_downloads > 0:
stats["total_views"] += post_views stats["total_views"] += post_views
if post_type not in stats["by_type"]:
stats["by_type"][post_type] = 0 if post_type not in stats["by_type"]:
stats["by_type"][post_type] += 1 stats["by_type"][post_type] = 0
stats["by_type"][post_type] += 1
# Individual post information # Individual post information
created_at = datetime.fromtimestamp(post.get("createdAt", 0)) created_at = datetime.fromtimestamp(post.get("createdAt", 0))

View File

@@ -9,95 +9,94 @@ local deployment are present and functional.
import sys import sys
from pathlib import Path from pathlib import Path
def main(): def main():
"""Check all deployment tools are ready.""" """Check all deployment tools are ready."""
base_dir = Path(__file__).parent.parent base_dir = Path(__file__).parent.parent
print("\n" + "=" * 80) print("\n" + "="*80)
print("Async Context Compression Local Deployment Tools — Verification Status") print("异步上下文压缩本地部署工具 — 验证状态")
print("=" * 80 + "\n") print("="*80 + "\n")
files_to_check = { files_to_check = {
"🐍 Python Scripts": [ "🐍 Python 脚本": [
"scripts/deploy_async_context_compression.py", "scripts/deploy_async_context_compression.py",
"scripts/deploy_filter.py", "scripts/deploy_filter.py",
"scripts/deploy_pipe.py", "scripts/deploy_pipe.py",
], ],
"📖 Deployment Documentation": [ "📖 部署文档": [
"scripts/README.md", "scripts/README.md",
"scripts/QUICK_START.md", "scripts/QUICK_START.md",
"scripts/DEPLOYMENT_GUIDE.md", "scripts/DEPLOYMENT_GUIDE.md",
"scripts/DEPLOYMENT_SUMMARY.md", "scripts/DEPLOYMENT_SUMMARY.md",
"plugins/filters/async-context-compression/DEPLOYMENT_REFERENCE.md", "plugins/filters/async-context-compression/DEPLOYMENT_REFERENCE.md",
], ],
"🧪 Test Files": [ "🧪 测试文件": [
"tests/scripts/test_deploy_filter.py", "tests/scripts/test_deploy_filter.py",
], ],
} }
all_exist = True all_exist = True
for category, files in files_to_check.items(): for category, files in files_to_check.items():
print(f"\n{category}:") print(f"\n{category}:")
print("-" * 80) print("-" * 80)
for file_path in files: for file_path in files:
full_path = base_dir / file_path full_path = base_dir / file_path
exists = full_path.exists() exists = full_path.exists()
status = "" if exists else "" status = "" if exists else ""
print(f" {status} {file_path}") print(f" {status} {file_path}")
if exists and file_path.endswith(".py"): if exists and file_path.endswith(".py"):
size = full_path.stat().st_size size = full_path.stat().st_size
lines = len(full_path.read_text().split("\n")) lines = len(full_path.read_text().split('\n'))
print(f" └─ [{size} bytes, ~{lines} lines]") print(f" └─ [{size} bytes, ~{lines} lines]")
if not exists: if not exists:
all_exist = False all_exist = False
print("\n" + "=" * 80) print("\n" + "="*80)
if all_exist: if all_exist:
print("All deployment tool files are ready!") print("所有部署工具文件已准备就绪!")
print("=" * 80 + "\n") print("="*80 + "\n")
print("🚀 Quick Start (3 ways):\n") print("🚀 快速开始3 种方式):\n")
print(" Method 1: Easiest (Recommended)") print(" 方式 1: 最简单 (推荐)")
print(" ─────────────────────────────────────────────────────────") print(" ─────────────────────────────────────────────────────────")
print(" cd scripts") print(" cd scripts")
print(" python deploy_async_context_compression.py") print(" python deploy_async_context_compression.py")
print() print()
print(" Method 2: Generic Tool") print(" 方式 2: 通用工具")
print(" ─────────────────────────────────────────────────────────") print(" ─────────────────────────────────────────────────────────")
print(" cd scripts") print(" cd scripts")
print(" python deploy_filter.py") print(" python deploy_filter.py")
print() print()
print(" Method 3: Deploy Other Filters") print(" 方式 3: 部署其他 Filter")
print(" ─────────────────────────────────────────────────────────") print(" ─────────────────────────────────────────────────────────")
print(" cd scripts") print(" cd scripts")
print(" python deploy_filter.py --list") print(" python deploy_filter.py --list")
print(" python deploy_filter.py folder-memory") print(" python deploy_filter.py folder-memory")
print() print()
print("=" * 80 + "\n") print("="*80 + "\n")
print("📚 Documentation References:\n") print("📚 文档参考:\n")
print("Quick Start: scripts/QUICK_START.md") print("快速开始: scripts/QUICK_START.md")
print("Complete Guide: scripts/DEPLOYMENT_GUIDE.md") print("完整指南: scripts/DEPLOYMENT_GUIDE.md")
print("Technical Summary: scripts/DEPLOYMENT_SUMMARY.md") print("技术总结: scripts/DEPLOYMENT_SUMMARY.md")
print("Script Info: scripts/README.md") print("脚本说明: scripts/README.md")
print("Test Coverage: pytest tests/scripts/test_deploy_filter.py -v") print("测试覆盖: pytest tests/scripts/test_deploy_filter.py -v")
print() print()
print("=" * 80 + "\n") print("="*80 + "\n")
return 0 return 0
else: else:
print("Some files are missing!") print("某些文件缺失!")
print("=" * 80 + "\n") print("="*80 + "\n")
return 1 return 1

View File

@@ -1,302 +0,0 @@
import asyncio
import importlib.util
import sys
from pathlib import Path
import httpx
import pytest
MODULE_PATH = (
Path(__file__).resolve().parents[3]
/ "plugins"
/ "tools"
/ "batch-install-plugins"
/ "batch_install_plugins.py"
)
SPEC = importlib.util.spec_from_file_location("batch_install_plugins", MODULE_PATH)
batch_install_plugins = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = batch_install_plugins
SPEC.loader.exec_module(batch_install_plugins)
def make_candidate(title: str, file_path: str, function_id: str):
return batch_install_plugins.PluginCandidate(
plugin_type="tool",
file_path=file_path,
metadata={"title": title, "description": f"{title} description"},
content="class Tools:\n pass\n",
function_id=function_id,
)
def make_request():
class Request:
base_url = "http://localhost:3000/"
headers = {"Authorization": "Bearer token"}
return Request()
class DummyResponse:
def __init__(self, status_code: int, json_data=None, text: str = ""):
self.status_code = status_code
self._json_data = json_data
self.text = text
def json(self):
if self._json_data is None:
raise ValueError("no json body")
return self._json_data
class FakeAsyncClient:
posts = []
responses = []
def __init__(self, *args, **kwargs):
pass
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
return False
async def post(self, url, headers=None, json=None):
type(self).posts.append((url, headers, json))
if not type(self).responses:
raise AssertionError("No fake response configured for POST request")
response = type(self).responses.pop(0)
if isinstance(response, Exception):
raise response
return response
@pytest.mark.asyncio
async def test_install_all_plugins_only_installs_filtered_candidates(monkeypatch):
keep = make_candidate("Keep Plugin", "plugins/tools/keep/keep.py", "keep_plugin")
exclude = make_candidate(
"Exclude Me",
"plugins/tools/exclude-me/exclude_me.py",
"exclude_me",
)
self_plugin = make_candidate(
"Batch Install Plugins from GitHub",
"plugins/tools/batch-install-plugins/batch_install_plugins.py",
"batch_install_plugins",
)
async def fake_discover_plugins(url, skip_keywords):
return [keep, exclude, self_plugin], []
monkeypatch.setattr(batch_install_plugins, "discover_plugins", fake_discover_plugins)
FakeAsyncClient.posts = []
FakeAsyncClient.responses = [DummyResponse(404), DummyResponse(201)]
monkeypatch.setattr(batch_install_plugins.httpx, "AsyncClient", FakeAsyncClient)
events = []
captured = {}
async def event_call(payload):
if payload["type"] == "confirmation":
captured["message"] = payload["data"]["message"]
elif payload["type"] == "execute":
captured.setdefault("execute_codes", []).append(payload["data"]["code"])
return True
async def emitter(event):
events.append(event)
result = await batch_install_plugins.Tools().install_all_plugins(
__user__={"id": "u1", "language": "en-US"},
__event_call__=event_call,
__request__=make_request(),
__event_emitter__=emitter,
repo=batch_install_plugins.DEFAULT_REPO,
plugin_types=["tool"],
exclude_keywords="exclude",
)
assert "Created: Keep Plugin" in result
assert "Exclude Me" not in result
assert "1/1" in result
assert captured["message"].count("[tool]") == 1
assert "Keep Plugin" in captured["message"]
assert "Exclude Me" not in captured["message"]
assert "Batch Install Plugins from GitHub" not in captured["message"]
assert "exclude, batch-install-plugins" in captured["message"]
urls = [url for url, _, _ in FakeAsyncClient.posts]
assert urls == [
"http://localhost:3000/api/v1/tools/id/keep_plugin/update",
"http://localhost:3000/api/v1/tools/create",
]
assert any(
"Starting OpenWebUI install requests" in code
for code in captured.get("execute_codes", [])
)
assert events[-1]["type"] == "notification"
assert events[-1]["data"]["type"] == "success"
@pytest.mark.asyncio
async def test_install_all_plugins_supports_missing_event_emitter(monkeypatch):
keep = make_candidate("Keep Plugin", "plugins/tools/keep/keep.py", "keep_plugin")
async def fake_discover_plugins(url, skip_keywords):
return [keep], []
monkeypatch.setattr(batch_install_plugins, "discover_plugins", fake_discover_plugins)
FakeAsyncClient.posts = []
FakeAsyncClient.responses = [DummyResponse(404), DummyResponse(201)]
monkeypatch.setattr(batch_install_plugins.httpx, "AsyncClient", FakeAsyncClient)
result = await batch_install_plugins.Tools().install_all_plugins(
__user__={"id": "u1", "language": "en-US"},
__request__=make_request(),
repo="example/repo",
plugin_types=["tool"],
)
assert "Created: Keep Plugin" in result
assert "1/1" in result
@pytest.mark.asyncio
async def test_install_all_plugins_handles_confirmation_timeout(monkeypatch):
keep = make_candidate("Keep Plugin", "plugins/tools/keep/keep.py", "keep_plugin")
async def fake_discover_plugins(url, skip_keywords):
return [keep], []
async def fake_wait_for(awaitable, timeout):
awaitable.close()
raise asyncio.TimeoutError
monkeypatch.setattr(batch_install_plugins, "discover_plugins", fake_discover_plugins)
monkeypatch.setattr(batch_install_plugins.asyncio, "wait_for", fake_wait_for)
events = []
async def event_call(payload):
return True
async def emitter(event):
events.append(event)
result = await batch_install_plugins.Tools().install_all_plugins(
__user__={"id": "u1", "language": "en-US"},
__event_call__=event_call,
__request__=make_request(),
__event_emitter__=emitter,
repo="example/repo",
plugin_types=["tool"],
)
assert result == "Confirmation timed out or failed. Installation cancelled."
assert events[-1]["type"] == "notification"
assert events[-1]["data"]["type"] == "warning"
@pytest.mark.asyncio
async def test_install_all_plugins_marks_total_failure_as_error(monkeypatch):
keep = make_candidate("Keep Plugin", "plugins/tools/keep/keep.py", "keep_plugin")
async def fake_discover_plugins(url, skip_keywords):
return [keep], []
monkeypatch.setattr(batch_install_plugins, "discover_plugins", fake_discover_plugins)
FakeAsyncClient.posts = []
FakeAsyncClient.responses = [
DummyResponse(500, {"detail": "update failed"}, "update failed"),
DummyResponse(500, {"detail": "create failed"}, "create failed"),
]
monkeypatch.setattr(batch_install_plugins.httpx, "AsyncClient", FakeAsyncClient)
events = []
async def emitter(event):
events.append(event)
result = await batch_install_plugins.Tools().install_all_plugins(
__user__={"id": "u1", "language": "en-US"},
__request__=make_request(),
__event_emitter__=emitter,
repo="example/repo",
plugin_types=["tool"],
)
assert "Failed: Keep Plugin - status 500:" in result
assert "0/1" in result
assert events[-1]["type"] == "notification"
assert events[-1]["data"]["type"] == "error"
@pytest.mark.asyncio
async def test_install_all_plugins_localizes_timeout_errors(monkeypatch):
keep = make_candidate("Keep Plugin", "plugins/tools/keep/keep.py", "keep_plugin")
async def fake_discover_plugins(url, skip_keywords):
return [keep], []
monkeypatch.setattr(batch_install_plugins, "discover_plugins", fake_discover_plugins)
FakeAsyncClient.posts = []
FakeAsyncClient.responses = [httpx.TimeoutException("timed out")]
monkeypatch.setattr(batch_install_plugins.httpx, "AsyncClient", FakeAsyncClient)
result = await batch_install_plugins.Tools().install_all_plugins(
__user__={"id": "u1", "language": "zh-CN"},
__request__=make_request(),
repo="example/repo",
plugin_types=["tool"],
)
assert "失败Keep Plugin - 请求超时" in result
@pytest.mark.asyncio
async def test_install_all_plugins_emits_frontend_debug_logs_on_connect_error(
monkeypatch,
):
keep = make_candidate("Keep Plugin", "plugins/tools/keep/keep.py", "keep_plugin")
async def fake_discover_plugins(url, skip_keywords):
return [keep], []
monkeypatch.setattr(batch_install_plugins, "discover_plugins", fake_discover_plugins)
FakeAsyncClient.posts = []
# Both initial attempt and fallback retry should fail
FakeAsyncClient.responses = [httpx.ConnectError("connect failed"), httpx.ConnectError("connect failed")]
monkeypatch.setattr(batch_install_plugins.httpx, "AsyncClient", FakeAsyncClient)
execute_codes = []
events = []
async def event_call(payload):
if payload["type"] == "execute":
execute_codes.append(payload["data"]["code"])
return True
if payload["type"] == "confirmation":
return True
raise AssertionError(f"Unexpected event_call payload type: {payload['type']}")
async def emitter(event):
events.append(event)
result = await batch_install_plugins.Tools().install_all_plugins(
__user__={"id": "u1", "language": "en-US"},
__event_call__=event_call,
__request__=make_request(),
__event_emitter__=emitter,
repo="example/repo",
plugin_types=["tool"],
)
assert result == "Cannot connect to OpenWebUI. Is it running?"
assert any("OpenWebUI connection failed" in code for code in execute_codes)
assert any("console.error" in code for code in execute_codes)
assert any("http://localhost:3000" in code for code in execute_codes)
assert events[-1]["type"] == "notification"
assert events[-1]["data"]["type"] == "error"

View File

@@ -1,179 +0,0 @@
import importlib.util
import sys
from pathlib import Path
import pytest
MODULE_PATH = Path(__file__).resolve().parents[2] / "scripts" / "install_all_plugins.py"
SPEC = importlib.util.spec_from_file_location("install_all_plugins", MODULE_PATH)
install_all_plugins = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = install_all_plugins
SPEC.loader.exec_module(install_all_plugins)
PLUGIN_HEADER = '''"""
title: Example Plugin
version: 1.0.0
openwebui_id: 12345678-1234-1234-1234-123456789abc
description: Example description.
"""
'''
def write_plugin(path: Path, header: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(header + "\nclass Action:\n pass\n", encoding="utf-8")
def test_should_skip_plugin_file_filters_localized_and_helper_names():
assert (
install_all_plugins.should_skip_plugin_file(Path("flash_card_cn.py"))
== "localized _cn file"
)
assert (
install_all_plugins.should_skip_plugin_file(Path("verify_generation.py"))
== "test or helper script"
)
assert (
install_all_plugins.should_skip_plugin_file(Path("测试.py"))
== "non-ascii filename"
)
assert install_all_plugins.should_skip_plugin_file(Path("flash_card.py")) is None
def test_build_function_id_prefers_id_then_title_then_filename():
from_id = install_all_plugins.build_function_id(
Path("dummy.py"), {"id": "Async Context Compression"}
)
from_title = install_all_plugins.build_function_id(
Path("dummy.py"), {"title": "GitHub Copilot Official SDK Pipe"}
)
from_file = install_all_plugins.build_function_id(Path("dummy_plugin.py"), {})
assert from_id == "async_context_compression"
assert from_title == "github_copilot_official_sdk_pipe"
assert from_file == "dummy_plugin"
def test_build_payload_uses_native_tool_shape_for_tools():
candidate = install_all_plugins.PluginCandidate(
plugin_type="tool",
file_path=Path("plugins/tools/demo/demo_tool.py"),
metadata={
"title": "Demo Tool",
"description": "Demo tool description",
"openwebui_id": "12345678-1234-1234-1234-123456789abc",
},
content="class Tools:\n pass\n",
function_id="demo_tool",
)
payload = install_all_plugins.build_payload(candidate)
assert payload == {
"id": "demo_tool",
"name": "Demo Tool",
"meta": {
"description": "Demo tool description",
"manifest": {},
},
"content": "class Tools:\n pass\n",
"access_grants": [],
}
def test_build_api_urls_uses_tool_endpoints_for_tools():
candidate = install_all_plugins.PluginCandidate(
plugin_type="tool",
file_path=Path("plugins/tools/demo/demo_tool.py"),
metadata={"title": "Demo Tool"},
content="class Tools:\n pass\n",
function_id="demo_tool",
)
update_url, create_url = install_all_plugins.build_api_urls(
"http://localhost:3000", candidate
)
assert update_url == "http://localhost:3000/api/v1/tools/id/demo_tool/update"
assert create_url == "http://localhost:3000/api/v1/tools/create"
def test_discover_plugins_only_returns_supported_openwebui_plugins(
tmp_path, monkeypatch
):
actions_dir = tmp_path / "plugins" / "actions"
filters_dir = tmp_path / "plugins" / "filters"
pipes_dir = tmp_path / "plugins" / "pipes"
tools_dir = tmp_path / "plugins" / "tools"
write_plugin(actions_dir / "flash-card" / "flash_card.py", PLUGIN_HEADER)
write_plugin(actions_dir / "flash-card" / "flash_card_cn.py", PLUGIN_HEADER)
write_plugin(actions_dir / "infographic" / "verify_generation.py", PLUGIN_HEADER)
write_plugin(
filters_dir / "missing-id" / "missing_id.py", '"""\ntitle: Missing ID\n"""\n'
)
write_plugin(pipes_dir / "sdk" / "github_copilot_sdk.py", PLUGIN_HEADER)
write_plugin(tools_dir / "skills" / "openwebui_skills_manager.py", PLUGIN_HEADER)
monkeypatch.setattr(
install_all_plugins,
"PLUGIN_TYPE_DIRS",
{
"action": actions_dir,
"filter": filters_dir,
"pipe": pipes_dir,
"tool": tools_dir,
},
)
monkeypatch.setattr(install_all_plugins, "REPO_ROOT", tmp_path)
candidates, skipped = install_all_plugins.discover_plugins(
["action", "filter", "pipe", "tool"]
)
candidate_names = [candidate.file_path.name for candidate in candidates]
skipped_reasons = {path.name: reason for path, reason in skipped}
assert candidate_names == [
"flash_card.py",
"github_copilot_sdk.py",
"openwebui_skills_manager.py",
]
assert skipped_reasons["missing_id.py"] == "missing openwebui_id"
assert skipped_reasons["flash_card_cn.py"] == "localized _cn file"
assert skipped_reasons["verify_generation.py"] == "test or helper script"
@pytest.mark.parametrize(
("header", "expected_reason"),
[
('"""\ntitle: Missing ID\n"""\n', "missing openwebui_id"),
("class Action:\n pass\n", "missing plugin header"),
],
)
def test_discover_plugins_reports_missing_metadata(
tmp_path, monkeypatch, header, expected_reason
):
action_dir = tmp_path / "plugins" / "actions"
plugin_file = action_dir / "demo" / "demo.py"
write_plugin(plugin_file, header)
monkeypatch.setattr(
install_all_plugins,
"PLUGIN_TYPE_DIRS",
{
"action": action_dir,
"filter": tmp_path / "plugins" / "filters",
"pipe": tmp_path / "plugins" / "pipes",
"tool": tmp_path / "plugins" / "tools",
},
)
monkeypatch.setattr(install_all_plugins, "REPO_ROOT", tmp_path)
candidates, skipped = install_all_plugins.discover_plugins(["action"])
assert candidates == []
assert skipped == [(plugin_file, expected_reason)]

View File

@@ -1,139 +0,0 @@
#!/bin/bash
# ==============================================================================
# ai-tabs - Ultra Orchestrator
# Version: v1.0.0
# License: MIT
# Author: Fu-Jie
# Description: Batch-launches and orchestrates multiple AI CLI tools as Tabs.
# ==============================================================================
# 1. Single-Instance Lock
LOCK_FILE="/tmp/ai_terminal_launch.lock"
# If lock is less than 10 seconds old, another instance is running. Exit.
if [ -f "$LOCK_FILE" ]; then
LOCK_TIME=$(stat -f %m "$LOCK_FILE")
NOW=$(date +%s)
if (( NOW - LOCK_TIME < 10 )); then
echo "⚠️ Another launch in progress. Skipping to prevent duplicates."
exit 0
fi
fi
touch "$LOCK_FILE"
trap 'rm -f "$LOCK_FILE"' EXIT
# 2. Configuration & Constants
INIT_DELAY=4.5
PASTE_DELAY=0.3
CMD_CREATION_DELAY=0.3
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
# Search for .env
if [ -f "${SCRIPT_DIR}/.env" ]; then
ENV_FILE="${SCRIPT_DIR}/.env"
elif [ -f "${PARENT_DIR}/.env" ]; then
ENV_FILE="${PARENT_DIR}/.env"
fi
# Supported Tools
SUPPORTED_TOOLS=(
"claude:--continue"
"opencode:--continue"
"gemini:--resume latest"
"copilot:--continue"
"iflow:--continue"
"kilo:--continue"
)
FOUND_TOOLS_NAMES=()
FOUND_CMDS=()
# 3. Part A: Load Manual Configuration
if [ -f "$ENV_FILE" ]; then
set -a; source "$ENV_FILE"; set +a
for var in $(compgen -v | grep '^TOOL_[0-9]' | sort -V); do
TPATH="${!var}"
if [ -x "$TPATH" ]; then
NAME=$(basename "$TPATH")
FLAG="--continue"
for item in "${SUPPORTED_TOOLS[@]}"; do
[[ "${item%%:*}" == "$NAME" ]] && FLAG="${item#*:}" && break
done
FOUND_TOOLS_NAMES+=("$NAME")
FOUND_CMDS+=("'$TPATH' $FLAG || '$TPATH' || exec \$SHELL")
fi
done
fi
# 4. Part B: Automatic Tool Discovery
for item in "${SUPPORTED_TOOLS[@]}"; do
NAME="${item%%:*}"
FLAG="${item#*:}"
ALREADY_CONFIGURED=false
for configured in "${FOUND_TOOLS_NAMES[@]}"; do
[[ "$configured" == "$NAME" ]] && ALREADY_CONFIGURED=true && break
done
[[ "$ALREADY_CONFIGURED" == true ]] && continue
TPATH=$(which "$NAME" 2>/dev/null)
if [ -z "$TPATH" ]; then
SEARCH_PATHS=(
"/opt/homebrew/bin/$NAME"
"/usr/local/bin/$NAME"
"$HOME/.local/bin/$NAME"
"$HOME/bin/$NAME"
"$HOME/.$NAME/bin/$NAME"
"$HOME/.nvm/versions/node/*/bin/$NAME"
"$HOME/.npm-global/bin/$NAME"
"$HOME/.cargo/bin/$NAME"
)
for p in "${SEARCH_PATHS[@]}"; do
for found_p in $p; do [[ -x "$found_p" ]] && TPATH="$found_p" && break 2; done
done
fi
if [ -n "$TPATH" ]; then
FOUND_TOOLS_NAMES+=("$NAME")
FOUND_CMDS+=("'$TPATH' $FLAG || '$TPATH' || exec \$SHELL")
fi
done
NUM_FOUND=${#FOUND_CMDS[@]}
[[ "$NUM_FOUND" -eq 0 ]] && exit 1
# 5. Core Orchestration (Reset + Launch)
# Using Command Palette automation to avoid the need for manual shortcut binding.
AS_SCRIPT="tell application \"System Events\"\n"
# Phase A: Creation (Using Command Palette to ensure it opens in Editor Area)
for ((i=1; i<=NUM_FOUND; i++)); do
AS_SCRIPT+=" keystroke \"p\" using {command down, shift down}\n"
AS_SCRIPT+=" delay 0.1\n"
# Ensure we are searching for the command. Using clipboard for speed and universal language support.
AS_SCRIPT+=" set the clipboard to \"workspace: new center terminal\"\n"
AS_SCRIPT+=" keystroke \"v\" using {command down}\n"
AS_SCRIPT+=" delay 0.1\n"
AS_SCRIPT+=" keystroke return\n"
AS_SCRIPT+=" delay $CMD_CREATION_DELAY\n"
done
# Phase B: Warmup
AS_SCRIPT+=" delay $INIT_DELAY\n"
# Phase C: Command Injection (Reverse)
for ((i=NUM_FOUND-1; i>=0; i--)); do
FULL_CMD="${FOUND_CMDS[$i]}"
CLEAN_CMD=$(echo "$FULL_CMD" | sed 's/"/\\"/g')
AS_SCRIPT+=" set the clipboard to \"$CLEAN_CMD\"\n"
AS_SCRIPT+=" delay 0.1\n"
AS_SCRIPT+=" keystroke \"v\" using {command down}\n"
AS_SCRIPT+=" delay $PASTE_DELAY\n"
AS_SCRIPT+=" keystroke return\n"
if [ $i -gt 0 ]; then
AS_SCRIPT+=" delay 0.5\n"
AS_SCRIPT+=" keystroke \"[\" using {command down, shift down}\n"
fi
done
AS_SCRIPT+="end tell"
# Execute
echo -e "$AS_SCRIPT" | osascript
echo "✨ Ai tabs initialized successfully ($NUM_FOUND tools found)."