feat(openwebui-skills-manager): enhance auto-discovery and structural refactoring
- Enable default overwrite installation policy for overlapping skills - Support deep recursive GitHub trees discovery mechanism to resolve #58 - Refactor internal architecture to fully decouple stateless helper logic - READMEs and docs synced (v0.3.0)
This commit is contained in:
65
plugins/filters/chat-session-mapping-filter/README.md
Normal file
65
plugins/filters/chat-session-mapping-filter/README.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 🔗 Chat Session Mapping Filter
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.1.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
|
||||
|
||||
Automatically tracks and persists the mapping between user IDs and chat IDs for seamless session management.
|
||||
|
||||
## Key Features
|
||||
|
||||
🔄 **Automatic Tracking** - Captures user_id and chat_id on every message without manual intervention
|
||||
💾 **Persistent Storage** - Saves mappings to JSON file for session recovery and analytics
|
||||
🛡️ **Atomic Operations** - Uses temporary file writes to prevent data corruption
|
||||
⚙️ **Configurable** - Enable/disable tracking via Valves setting
|
||||
🔍 **Smart Context Extraction** - Safely extracts IDs from multiple source locations (body, metadata, __metadata__)
|
||||
|
||||
## How to Use
|
||||
|
||||
1. **Install the filter** - Add it to your OpenWebUI plugins
|
||||
2. **Enable globally** - No configuration needed; tracking is enabled by default
|
||||
3. **Monitor mappings** - Check `copilot_workspace/api_key_chat_id_mapping.json` for stored mappings
|
||||
|
||||
## Configuration
|
||||
|
||||
| Parameter | Default | Description |
|
||||
|-----------|---------|-------------|
|
||||
| `ENABLE_TRACKING` | `true` | Master switch for chat session mapping tracking |
|
||||
|
||||
## How It Works
|
||||
|
||||
This filter intercepts messages at the **inlet** stage (before processing) and:
|
||||
|
||||
1. **Extracts IDs**: Safely gets user_id from `__user__` and chat_id from `body`/`metadata`
|
||||
2. **Validates**: Confirms both IDs are non-empty before proceeding
|
||||
3. **Persists**: Writes or updates the mapping in a JSON file with atomic file operations
|
||||
4. **Handles Errors**: Gracefully logs warnings if any step fails, without blocking the chat flow
|
||||
|
||||
### Storage Location
|
||||
|
||||
- **Container Environment** (`/app/backend/data` exists):
|
||||
`/app/backend/data/copilot_workspace/api_key_chat_id_mapping.json`
|
||||
|
||||
- **Local Development** (no `/app/backend/data`):
|
||||
`./copilot_workspace/api_key_chat_id_mapping.json`
|
||||
|
||||
### File Format
|
||||
|
||||
Stored as a JSON object with user IDs as keys and chat IDs as values:
|
||||
|
||||
```json
|
||||
{
|
||||
"user-1": "chat-abc-123",
|
||||
"user-2": "chat-def-456",
|
||||
"user-3": "chat-ghi-789"
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- **No Response Modification**: The outlet hook returns the response unchanged
|
||||
- **Atomic Writes**: Prevents partial writes using `.tmp` intermediate files
|
||||
- **Context-Aware ID Extraction**: Handles `__user__` as dict/list/None and metadata from multiple sources
|
||||
- **Logging**: All operations are logged for debugging; enable verbose logging with `SHOW_DEBUG_LOG` in dependent plugins
|
||||
65
plugins/filters/chat-session-mapping-filter/README_CN.md
Normal file
65
plugins/filters/chat-session-mapping-filter/README_CN.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 🔗 聊天会话映射过滤器
|
||||
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 0.1.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
|
||||
|
||||
自动追踪并持久化用户 ID 与聊天 ID 的映射关系,实现无缝的会话管理。
|
||||
|
||||
## 核心功能
|
||||
|
||||
🔄 **自动追踪** - 无需手动干预,在每条消息上自动捕获 user_id 和 chat_id
|
||||
💾 **持久化存储** - 将映射关系保存到 JSON 文件,便于会话恢复和数据分析
|
||||
🛡️ **原子性操作** - 使用临时文件写入防止数据损坏
|
||||
⚙️ **灵活配置** - 通过 Valves 参数启用/禁用追踪功能
|
||||
🔍 **智能上下文提取** - 从多个数据源(body、metadata、__metadata__)安全提取 ID
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. **安装过滤器** - 将其添加到 OpenWebUI 插件
|
||||
2. **全局启用** - 无需配置,追踪功能默认启用
|
||||
3. **查看映射** - 检查 `copilot_workspace/api_key_chat_id_mapping.json` 中的存储映射
|
||||
|
||||
## 配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `ENABLE_TRACKING` | `true` | 聊天会话映射追踪的主开关 |
|
||||
|
||||
## 工作原理
|
||||
|
||||
该过滤器在 **inlet** 阶段(消息处理前)拦截消息并执行以下步骤:
|
||||
|
||||
1. **提取 ID**: 安全地从 `__user__` 获取 user_id,从 `body`/`metadata` 获取 chat_id
|
||||
2. **验证**: 确认两个 ID 都非空后再继续
|
||||
3. **持久化**: 使用原子文件操作将映射写入或更新 JSON 文件
|
||||
4. **错误处理**: 任何步骤失败时都会优雅地记录警告,不阻断聊天流程
|
||||
|
||||
### 存储位置
|
||||
|
||||
- **容器环境**(存在 `/app/backend/data`):
|
||||
`/app/backend/data/copilot_workspace/api_key_chat_id_mapping.json`
|
||||
|
||||
- **本地开发**(无 `/app/backend/data`):
|
||||
`./copilot_workspace/api_key_chat_id_mapping.json`
|
||||
|
||||
### 文件格式
|
||||
|
||||
存储为 JSON 对象,键是用户 ID,值是聊天 ID:
|
||||
|
||||
```json
|
||||
{
|
||||
"user-1": "chat-abc-123",
|
||||
"user-2": "chat-def-456",
|
||||
"user-3": "chat-ghi-789"
|
||||
}
|
||||
```
|
||||
|
||||
## 支持我们
|
||||
|
||||
如果这个插件对你有帮助,欢迎到 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 点个 Star,这将是我持续改进的动力,感谢支持。
|
||||
|
||||
## 技术细节
|
||||
|
||||
- **不修改响应**: outlet 钩子直接返回响应不做修改
|
||||
- **原子写入**: 使用 `.tmp` 临时文件防止不完整的写入
|
||||
- **上下文敏感的 ID 提取**: 处理 `__user__` 为 dict/list/None 的情况,以及来自多个源的 metadata
|
||||
- **日志记录**: 所有操作都会被记录,便于调试;可通过启用依赖插件的 `SHOW_DEBUG_LOG` 查看详细日志
|
||||
@@ -0,0 +1,146 @@
|
||||
"""
|
||||
title: Chat Session Mapping Filter
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.1.0
|
||||
description: Automatically tracks and persists the mapping between user IDs and chat IDs for session management.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Determine the chat mapping file location
|
||||
if os.path.exists("/app/backend/data"):
|
||||
CHAT_MAPPING_FILE = Path(
|
||||
"/app/backend/data/copilot_workspace/api_key_chat_id_mapping.json"
|
||||
)
|
||||
else:
|
||||
CHAT_MAPPING_FILE = Path(os.getcwd()) / "copilot_workspace" / "api_key_chat_id_mapping.json"
|
||||
|
||||
|
||||
class Filter:
|
||||
class Valves(BaseModel):
|
||||
ENABLE_TRACKING: bool = Field(
|
||||
default=True,
|
||||
description="Enable chat session mapping tracking."
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
def inlet(
|
||||
self,
|
||||
body: dict,
|
||||
__user__: Optional[dict] = None,
|
||||
__metadata__: Optional[dict] = None,
|
||||
**kwargs,
|
||||
) -> dict:
|
||||
"""
|
||||
Inlet hook: Called before message processing.
|
||||
Persists the mapping of user_id to chat_id.
|
||||
"""
|
||||
if not self.valves.ENABLE_TRACKING:
|
||||
return body
|
||||
|
||||
user_id = self._get_user_id(__user__)
|
||||
chat_id = self._get_chat_id(body, __metadata__)
|
||||
|
||||
if user_id and chat_id:
|
||||
self._persist_mapping(user_id, chat_id)
|
||||
|
||||
return body
|
||||
|
||||
def outlet(
|
||||
self,
|
||||
body: dict,
|
||||
response: str,
|
||||
__user__: Optional[dict] = None,
|
||||
__metadata__: Optional[dict] = None,
|
||||
**kwargs,
|
||||
) -> str:
|
||||
"""
|
||||
Outlet hook: No modification to response needed.
|
||||
This filter only tracks mapping on inlet.
|
||||
"""
|
||||
return response
|
||||
|
||||
def _get_user_id(self, __user__: Optional[dict]) -> Optional[str]:
|
||||
"""Safely extract user ID from __user__ parameter."""
|
||||
if isinstance(__user__, (list, tuple)):
|
||||
user_data = __user__[0] if __user__ else {}
|
||||
elif isinstance(__user__, dict):
|
||||
user_data = __user__
|
||||
else:
|
||||
user_data = {}
|
||||
|
||||
return str(user_data.get("id", "")).strip() or None
|
||||
|
||||
def _get_chat_id(
|
||||
self, body: dict, __metadata__: Optional[dict] = None
|
||||
) -> Optional[str]:
|
||||
"""Safely extract chat ID from body or metadata."""
|
||||
chat_id = ""
|
||||
|
||||
# Try to extract from body
|
||||
if isinstance(body, dict):
|
||||
chat_id = body.get("chat_id", "")
|
||||
|
||||
# Fallback: Check body.metadata
|
||||
if not chat_id:
|
||||
body_metadata = body.get("metadata", {})
|
||||
if isinstance(body_metadata, dict):
|
||||
chat_id = body_metadata.get("chat_id", "")
|
||||
|
||||
# Fallback: Check __metadata__
|
||||
if not chat_id and __metadata__ and isinstance(__metadata__, dict):
|
||||
chat_id = __metadata__.get("chat_id", "")
|
||||
|
||||
return str(chat_id).strip() or None
|
||||
|
||||
def _persist_mapping(self, user_id: str, chat_id: str) -> None:
|
||||
"""Persist the user_id to chat_id mapping to file."""
|
||||
try:
|
||||
# Create parent directory if needed
|
||||
CHAT_MAPPING_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Load existing mapping
|
||||
mapping = {}
|
||||
if CHAT_MAPPING_FILE.exists():
|
||||
try:
|
||||
loaded = json.loads(
|
||||
CHAT_MAPPING_FILE.read_text(encoding="utf-8")
|
||||
)
|
||||
if isinstance(loaded, dict):
|
||||
mapping = {str(k): str(v) for k, v in loaded.items()}
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Failed to read mapping file {CHAT_MAPPING_FILE}: {e}"
|
||||
)
|
||||
|
||||
# Update mapping with current user_id and chat_id
|
||||
mapping[user_id] = chat_id
|
||||
|
||||
# Write to temporary file and atomically replace
|
||||
temp_file = CHAT_MAPPING_FILE.with_suffix(
|
||||
CHAT_MAPPING_FILE.suffix + ".tmp"
|
||||
)
|
||||
temp_file.write_text(
|
||||
json.dumps(mapping, ensure_ascii=False, indent=2, sort_keys=True)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
temp_file.replace(CHAT_MAPPING_FILE)
|
||||
|
||||
logger.info(
|
||||
f"Persisted mapping: user_id={user_id} -> chat_id={chat_id}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to persist chat session mapping: {e}")
|
||||
Reference in New Issue
Block a user