Compare commits
8 Commits
v2026.01.0
...
v2026.01.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28d55c1469 | ||
|
|
59933e9361 | ||
|
|
7cbd0e2920 | ||
|
|
88038b35cc | ||
|
|
1fd7d90284 | ||
|
|
aee9c93bfb | ||
|
|
3951f7f91d | ||
|
|
3680fcf39f |
47
.github/copilot-instructions.md
vendored
47
.github/copilot-instructions.md
vendored
@@ -13,13 +13,13 @@ This document defines the standard conventions and best practices for OpenWebUI
|
|||||||
每个插件必须提供两个版本:
|
每个插件必须提供两个版本:
|
||||||
|
|
||||||
1. **英文版本**: `plugin_name.py` - 英文界面、提示词和注释
|
1. **英文版本**: `plugin_name.py` - 英文界面、提示词和注释
|
||||||
2. **中文版本**: `plugin_name_cn.py` 或 `插件中文名.py` - 中文界面、提示词和注释
|
2. **中文版本**: `plugin_name_cn.py` - 中文界面、提示词和注释
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
```
|
```
|
||||||
plugins/actions/export_to_docx/
|
plugins/actions/export_to_docx/
|
||||||
├── export_to_word.py # English version
|
├── export_to_word.py # English version
|
||||||
├── 导出为Word.py # Chinese version
|
├── export_to_word_cn.py # Chinese version
|
||||||
├── README.md # English documentation
|
├── README.md # English documentation
|
||||||
└── README_CN.md # Chinese documentation
|
└── README_CN.md # Chinese documentation
|
||||||
```
|
```
|
||||||
@@ -798,6 +798,49 @@ For iframe plugins to access parent document theme information, users need to co
|
|||||||
- [ ] 使用 logging 而非 print
|
- [ ] 使用 logging 而非 print
|
||||||
- [ ] 测试双语界面
|
- [ ] 测试双语界面
|
||||||
- [ ] **一致性检查 (Consistency Check)**:
|
- [ ] **一致性检查 (Consistency Check)**:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 高级开发模式 (Advanced Development Patterns)
|
||||||
|
|
||||||
|
### 混合服务端-客户端生成 (Hybrid Server-Client Generation)
|
||||||
|
|
||||||
|
对于需要复杂前端渲染(如 Mermaid 图表、ECharts)但最终生成文件(如 DOCX、PDF)的场景,建议采用混合模式:
|
||||||
|
|
||||||
|
1. **服务端 (Python)**:
|
||||||
|
* 处理文本解析、Markdown 转换、文档结构构建。
|
||||||
|
* 为复杂组件生成**占位符**(如带有特定 ID 或元数据的图片/文本块)。
|
||||||
|
* 将半成品文件(如 Base64 编码的 ZIP/DOCX)发送给前端。
|
||||||
|
|
||||||
|
2. **客户端 (JavaScript)**:
|
||||||
|
* 在浏览器中加载半成品文件(使用 JSZip 等库)。
|
||||||
|
* 利用浏览器能力渲染复杂组件(如 `mermaid.render`)。
|
||||||
|
* 将渲染结果(SVG/PNG)回填到占位符位置。
|
||||||
|
* 触发最终文件的下载。
|
||||||
|
|
||||||
|
**优势**:
|
||||||
|
* 无需在服务端安装 Headless Browser(如 Puppeteer),降低部署复杂度。
|
||||||
|
* 利用用户浏览器的计算能力。
|
||||||
|
* 支持动态、交互式内容的静态化导出。
|
||||||
|
|
||||||
|
### 原生 Word 公式支持 (Native Word Math Support)
|
||||||
|
|
||||||
|
对于需要生成高质量数学公式的 Word 文档,推荐使用 `latex2mathml` + `mathml2omml` 组合:
|
||||||
|
|
||||||
|
1. **LaTeX -> MathML**: 使用 `latex2mathml` 将 LaTeX 字符串转换为标准 MathML。
|
||||||
|
2. **MathML -> OMML**: 使用 `mathml2omml` 将 MathML 转换为 Office Math Markup Language (OMML)。
|
||||||
|
3. **插入 Word**: 将 OMML XML 插入到 `python-docx` 的段落中。
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 示例代码
|
||||||
|
from latex2mathml.converter import convert as latex2mathml
|
||||||
|
from mathml2omml import convert as mathml2omml
|
||||||
|
|
||||||
|
def add_math(paragraph, latex_str):
|
||||||
|
mathml = latex2mathml(latex_str)
|
||||||
|
omml = mathml2omml(mathml)
|
||||||
|
# ... 插入 OMML 到 paragraph._element ...
|
||||||
|
```
|
||||||
- [ ] 更新 `README.md` 插件列表
|
- [ ] 更新 `README.md` 插件列表
|
||||||
- [ ] 更新 `README_CN.md` 插件列表
|
- [ ] 更新 `README_CN.md` 插件列表
|
||||||
- [ ] 更新/创建 `docs/` 下的对应文档
|
- [ ] 更新/创建 `docs/` 下的对应文档
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -60,10 +60,16 @@ This project is a collection of resources and does not require a Python environm
|
|||||||
|
|
||||||
### Using Plugins
|
### Using Plugins
|
||||||
|
|
||||||
1. Browse the `/plugins` directory and download the plugin file (`.py`) you need.
|
1. **Install from OpenWebUI Community (Recommended)**:
|
||||||
2. Go to OpenWebUI **Admin Panel** -> **Settings** -> **Plugins**.
|
- Visit my profile: [Fu-Jie's Profile](https://openwebui.com/u/Fu-Jie)
|
||||||
3. Click the upload button and select the `.py` file you just downloaded.
|
- Browse the plugins and select the one you like.
|
||||||
4. Once uploaded, refresh the page to enable the plugin in your chat settings or toolbar.
|
- Click "Get" to import it directly into your OpenWebUI instance.
|
||||||
|
|
||||||
|
2. **Manual Installation**:
|
||||||
|
- Browse the `/plugins` directory and download the plugin file (`.py`) you need.
|
||||||
|
- Go to OpenWebUI **Admin Panel** -> **Settings** -> **Plugins**.
|
||||||
|
- Click the upload button and select the `.py` file you just downloaded.
|
||||||
|
- Once uploaded, refresh the page to enable the plugin in your chat settings or toolbar.
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
|
|||||||
14
README_CN.md
14
README_CN.md
@@ -87,10 +87,16 @@ OpenWebUI 增强功能集合。包含个人开发与收集的### 🧩 插件 (Pl
|
|||||||
|
|
||||||
### 使用插件 (Plugins)
|
### 使用插件 (Plugins)
|
||||||
|
|
||||||
1. 在 `/plugins` 目录中浏览并下载你需要的插件文件 (`.py`)。
|
1. **从 OpenWebUI 社区安装 (推荐)**:
|
||||||
2. 打开 OpenWebUI 的 **管理员面板 (Admin Panel)** -> **设置 (Settings)** -> **插件 (Plugins)**。
|
- 访问我的主页: [Fu-Jie's Profile](https://openwebui.com/u/Fu-Jie)
|
||||||
3. 点击上传按钮,选择刚才下载的 `.py` 文件。
|
- 浏览插件列表,选择你喜欢的插件。
|
||||||
4. 上传成功后,刷新页面,你就可以在聊天设置或工具栏中启用该插件了。
|
- 点击 "Get" 按钮,将其直接导入到你的 OpenWebUI 实例中。
|
||||||
|
|
||||||
|
2. **手动安装**:
|
||||||
|
- 在 `/plugins` 目录中浏览并下载你需要的插件文件 (`.py`)。
|
||||||
|
- 打开 OpenWebUI 的 **管理员面板 (Admin Panel)** -> **设置 (Settings)** -> **插件 (Plugins)**。
|
||||||
|
- 点击上传按钮,选择刚才下载的 `.py` 文件。
|
||||||
|
- 上传成功后,刷新页面,你就可以在聊天设置或工具栏中启用该插件了。
|
||||||
|
|
||||||
### 贡献代码
|
### 贡献代码
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
Open WebUI 通过文件顶部的特定格式注释来识别和展示插件信息。
|
Open WebUI 通过文件顶部的特定格式注释来识别和展示插件信息。
|
||||||
|
|
||||||
**代码示例 (`思维导图.py`):**
|
**代码示例 (`smart_mind_map_cn.py`):**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""
|
"""
|
||||||
@@ -45,7 +45,7 @@ description: 智能分析文本内容,生成交互式思维导图,帮助用户
|
|||||||
|
|
||||||
通过在 `Action` 类内部定义一个 `Valves` Pydantic 模型,可以为插件创建可在 Web UI 中配置的参数。
|
通过在 `Action` 类内部定义一个 `Valves` Pydantic 模型,可以为插件创建可在 Web UI 中配置的参数。
|
||||||
|
|
||||||
**代码示例 (`思维导图.py`):**
|
**代码示例 (`smart_mind_map_cn.py`):**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class Action:
|
class Action:
|
||||||
@@ -83,7 +83,7 @@ class Action:
|
|||||||
|
|
||||||
`action` 方法是插件的执行入口,它是一个异步函数,接收 Open WebUI 传入的上下文信息。
|
`action` 方法是插件的执行入口,它是一个异步函数,接收 Open WebUI 传入的上下文信息。
|
||||||
|
|
||||||
**代码示例 (`思维导图.py`):**
|
**代码示例 (`smart_mind_map_cn.py`):**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
async def action(
|
async def action(
|
||||||
|
|||||||
@@ -104,10 +104,16 @@ hide:
|
|||||||
|
|
||||||
### Using Plugins
|
### Using Plugins
|
||||||
|
|
||||||
1. Browse the [Plugin Center](plugins/index.md) and download the plugin file (`.py`)
|
1. **Install from OpenWebUI Community (Recommended)**:
|
||||||
2. Open OpenWebUI **Admin Panel** → **Settings** → **Plugins**
|
- Visit my profile: [Fu-Jie's Profile](https://openwebui.com/u/Fu-Jie)
|
||||||
3. Click the upload button and select the `.py` file
|
- Browse the plugins and select the one you like.
|
||||||
4. Refresh the page and enable the plugin in your chat settings
|
- Click "Get" to import it directly into your OpenWebUI instance.
|
||||||
|
|
||||||
|
2. **Manual Installation**:
|
||||||
|
- Browse the [Plugin Center](plugins/index.md) and download the plugin file (`.py`)
|
||||||
|
- Open OpenWebUI **Admin Panel** → **Settings** → **Plugins**
|
||||||
|
- Click the upload button and select the `.py` file
|
||||||
|
- Refresh the page and enable the plugin in your chat settings
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -104,10 +104,16 @@ hide:
|
|||||||
|
|
||||||
### 使用插件
|
### 使用插件
|
||||||
|
|
||||||
1. 浏览[插件中心](plugins/index.md)并下载插件文件(`.py`)
|
1. **从 OpenWebUI 社区安装 (推荐)**:
|
||||||
2. 打开 OpenWebUI **管理面板** → **设置** → **插件**
|
- 访问我的主页: [Fu-Jie's Profile](https://openwebui.com/u/Fu-Jie)
|
||||||
3. 点击上传按钮并选择 `.py` 文件
|
- 浏览插件列表,选择你喜欢的插件。
|
||||||
4. 刷新页面并在聊天设置中启用插件
|
- 点击 "Get" 按钮,将其直接导入到你的 OpenWebUI 实例中。
|
||||||
|
|
||||||
|
2. **手动安装**:
|
||||||
|
- 浏览[插件中心](plugins/index.md)并下载插件文件(`.py`)
|
||||||
|
- 打开 OpenWebUI **管理面板** → **设置** → **插件**
|
||||||
|
- 点击上传按钮并选择 `.py` 文件
|
||||||
|
- 刷新页面并在聊天设置中启用插件
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
290
docs/js-visualization-guide.md
Normal file
290
docs/js-visualization-guide.md
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
# 使用 JavaScript 生成可视化内容的技术方案
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本文档描述了在 OpenWebUI Action 插件中使用浏览器端 JavaScript 代码生成可视化内容(如思维导图、信息图等)并将结果保存到消息中的技术方案。
|
||||||
|
|
||||||
|
## 核心架构
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Plugin as Python 插件
|
||||||
|
participant EventCall as __event_call__
|
||||||
|
participant Browser as 浏览器 (JS)
|
||||||
|
participant API as OpenWebUI API
|
||||||
|
participant DB as 数据库
|
||||||
|
|
||||||
|
Plugin->>EventCall: 1. 发送 execute 事件 (含 JS 代码)
|
||||||
|
EventCall->>Browser: 2. 执行 JS 代码
|
||||||
|
Browser->>Browser: 3. 加载可视化库 (D3/Markmap/AntV)
|
||||||
|
Browser->>Browser: 4. 渲染可视化内容
|
||||||
|
Browser->>Browser: 5. 转换为 Base64 Data URI
|
||||||
|
Browser->>API: 6. GET 获取当前消息内容
|
||||||
|
API-->>Browser: 7. 返回消息数据
|
||||||
|
Browser->>API: 8. POST 追加 Markdown 图片到消息
|
||||||
|
API->>DB: 9. 保存更新后的消息
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键步骤
|
||||||
|
|
||||||
|
### 1. Python 端通过 `__event_call__` 执行 JS
|
||||||
|
|
||||||
|
Python 插件**不直接修改 `body["messages"]`**,而是通过 `__event_call__` 发送 JS 代码让浏览器执行:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def action(
|
||||||
|
self,
|
||||||
|
body: dict,
|
||||||
|
__user__: dict = None,
|
||||||
|
__event_emitter__=None,
|
||||||
|
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
|
||||||
|
__metadata__: Optional[dict] = None,
|
||||||
|
__request__: Request = None,
|
||||||
|
) -> dict:
|
||||||
|
# 从 body 获取 chat_id 和 message_id
|
||||||
|
chat_id = body.get("chat_id", "")
|
||||||
|
message_id = body.get("id", "") # 注意:body["id"] 是 message_id
|
||||||
|
|
||||||
|
# 通过 __event_call__ 执行 JS 代码
|
||||||
|
if __event_call__:
|
||||||
|
await __event_call__({
|
||||||
|
"type": "execute",
|
||||||
|
"data": {
|
||||||
|
"code": f"""
|
||||||
|
(async function() {{
|
||||||
|
const chatId = "{chat_id}";
|
||||||
|
const messageId = "{message_id}";
|
||||||
|
// ... JS 渲染和 API 更新逻辑 ...
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
# 不修改 body,直接返回
|
||||||
|
return body
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. JavaScript 加载可视化库
|
||||||
|
|
||||||
|
在浏览器端动态加载所需的 JS 库:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 加载 D3.js
|
||||||
|
if (!window.d3) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = 'https://cdn.jsdelivr.net/npm/d3@7';
|
||||||
|
script.onload = resolve;
|
||||||
|
script.onerror = reject;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载 Markmap (思维导图)
|
||||||
|
if (!window.markmap) {
|
||||||
|
await loadScript('https://cdn.jsdelivr.net/npm/markmap-lib@0.17');
|
||||||
|
await loadScript('https://cdn.jsdelivr.net/npm/markmap-view@0.17');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 渲染并转换为 Data URI
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 创建 SVG 元素
|
||||||
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||||
|
svg.setAttribute('width', '800');
|
||||||
|
svg.setAttribute('height', '600');
|
||||||
|
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
||||||
|
|
||||||
|
// ... 执行渲染逻辑 (添加图形元素) ...
|
||||||
|
|
||||||
|
// 转换为 Base64 Data URI
|
||||||
|
const svgData = new XMLSerializer().serializeToString(svg);
|
||||||
|
const base64 = btoa(unescape(encodeURIComponent(svgData)));
|
||||||
|
const dataUri = 'data:image/svg+xml;base64,' + base64;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 获取当前消息内容
|
||||||
|
|
||||||
|
由于 Python 端不传递原始内容,JS 需要通过 API 获取:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
|
||||||
|
// 获取当前聊天数据
|
||||||
|
const getResponse = await fetch(`/api/v1/chats/${chatId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const chatData = await getResponse.json();
|
||||||
|
|
||||||
|
// 查找目标消息
|
||||||
|
let originalContent = '';
|
||||||
|
if (chatData.chat && chatData.chat.messages) {
|
||||||
|
const targetMsg = chatData.chat.messages.find(m => m.id === messageId);
|
||||||
|
if (targetMsg && targetMsg.content) {
|
||||||
|
originalContent = targetMsg.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 调用 API 更新消息
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 构造新内容:原始内容 + Markdown 图片
|
||||||
|
const markdownImage = ``;
|
||||||
|
const newContent = originalContent + '\n\n' + markdownImage;
|
||||||
|
|
||||||
|
// 调用 API 更新消息
|
||||||
|
const response = await fetch(`/api/v1/chats/${chatId}/messages/${messageId}/event`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: 'chat:message',
|
||||||
|
data: { content: newContent }
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log('消息更新成功!');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
参考 [js_render_poc.py](../plugins/actions/js-render-poc/js_render_poc.py) 获取完整的 PoC 实现。
|
||||||
|
|
||||||
|
## 事件类型
|
||||||
|
|
||||||
|
| 类型 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `chat:message:delta` | 增量更新(追加文本) |
|
||||||
|
| `chat:message` | 完全替换消息内容 |
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 增量更新
|
||||||
|
{ type: "chat:message:delta", data: { content: "追加的内容" } }
|
||||||
|
|
||||||
|
// 完全替换
|
||||||
|
{ type: "chat:message", data: { content: "完整的新内容" } }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键数据来源
|
||||||
|
|
||||||
|
| 数据 | 来源 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `chat_id` | `body["chat_id"]` | 聊天会话 ID |
|
||||||
|
| `message_id` | `body["id"]` | ⚠️ 注意:是 `body["id"]`,不是 `body["message_id"]` |
|
||||||
|
| `token` | `localStorage.getItem('token')` | 用户认证 Token |
|
||||||
|
| `originalContent` | 通过 API `GET /api/v1/chats/{chatId}` 获取 | 当前消息内容 |
|
||||||
|
|
||||||
|
## Python 端 API
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `__event_emitter__` | Callable | 发送状态/通知事件 |
|
||||||
|
| `__event_call__` | Callable | 执行 JS 代码(用于可视化渲染) |
|
||||||
|
| `__metadata__` | dict | 元数据(可能为 None) |
|
||||||
|
| `body` | dict | 请求体,包含 messages、chat_id、id 等 |
|
||||||
|
|
||||||
|
### body 结构示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "gemini-3-flash-preview",
|
||||||
|
"messages": [...],
|
||||||
|
"chat_id": "ac2633a3-5731-4944-98e3-bf9b3f0ef0ab",
|
||||||
|
"id": "2e0bb7d4-dfc0-43d7-b028-fd9e06c6fdc8",
|
||||||
|
"session_id": "bX30sHI8r4_CKxCdAAAL"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 常用事件
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 发送状态更新
|
||||||
|
await __event_emitter__({
|
||||||
|
"type": "status",
|
||||||
|
"data": {"description": "正在渲染...", "done": False}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 执行 JS 代码
|
||||||
|
await __event_call__({
|
||||||
|
"type": "execute",
|
||||||
|
"data": {"code": "console.log('Hello from Python!')"}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 发送通知
|
||||||
|
await __event_emitter__({
|
||||||
|
"type": "notification",
|
||||||
|
"data": {"type": "success", "content": "渲染完成!"}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
- **思维导图** (Markmap)
|
||||||
|
- **信息图** (AntV Infographic)
|
||||||
|
- **流程图** (Mermaid)
|
||||||
|
- **数据图表** (ECharts, Chart.js)
|
||||||
|
- **任何需要 JS 渲染的可视化内容**
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
### 1. 竞态条件问题
|
||||||
|
|
||||||
|
⚠️ **多次快速点击会导致内容覆盖问题**
|
||||||
|
|
||||||
|
由于 API 调用是异步的,如果用户快速多次触发 Action:
|
||||||
|
- 第一次点击:获取原始内容 A → 渲染 → 更新为 A+图片1
|
||||||
|
- 第二次点击:可能获取到旧内容 A(第一次还没保存完)→ 更新为 A+图片2
|
||||||
|
|
||||||
|
结果:图片1 被覆盖丢失!
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
- 添加防抖(debounce)机制
|
||||||
|
- 使用锁/标志位防止重复执行
|
||||||
|
- 或使用 `chat:message:delta` 增量更新
|
||||||
|
|
||||||
|
### 2. 不要直接修改 `body["messages"]`
|
||||||
|
|
||||||
|
消息更新应由 JS 通过 API 完成,确保获取最新内容。
|
||||||
|
|
||||||
|
### 3. f-string 限制
|
||||||
|
|
||||||
|
Python f-string 内不能直接使用反斜杠,需要将转义字符串预先处理:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 转义 JSON 中的特殊字符
|
||||||
|
body_json = json.dumps(data, ensure_ascii=False)
|
||||||
|
escaped = body_json.replace("\\", "\\\\").replace("`", "\\`").replace("${", "\\${")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Data URI 大小限制
|
||||||
|
|
||||||
|
Base64 编码会增加约 33% 的体积,复杂图片可能导致消息过大。
|
||||||
|
|
||||||
|
### 5. 跨域问题
|
||||||
|
|
||||||
|
确保 CDN 资源支持 CORS。
|
||||||
|
|
||||||
|
### 6. API 权限
|
||||||
|
|
||||||
|
确保用户 token 有权限访问和更新目标消息。
|
||||||
|
|
||||||
|
## 与传统方式对比
|
||||||
|
|
||||||
|
| 特性 | 传统方式 (修改 body) | 新方式 (__event_call__) |
|
||||||
|
|------|---------------------|------------------------|
|
||||||
|
| 消息更新 | Python 直接修改 | JS 通过 API 更新 |
|
||||||
|
| 原始内容 | Python 传递给 JS | JS 通过 API 获取 |
|
||||||
|
| 灵活性 | 低 | 高 |
|
||||||
|
| 实时性 | 一次性 | 可多次更新 |
|
||||||
|
| 复杂度 | 简单 | 中等 |
|
||||||
|
| 竞态风险 | 低 | ⚠️ 需要处理 |
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Export to Excel
|
# Export to Excel
|
||||||
|
|
||||||
<span class="category-badge action">Action</span>
|
<span class="category-badge action">Action</span>
|
||||||
<span class="version-badge">v0.3.6</span>
|
<span class="version-badge">v0.3.7</span>
|
||||||
|
|
||||||
Export chat conversations to Excel spreadsheet format for analysis, archiving, and sharing.
|
Export chat conversations to Excel spreadsheet format for analysis, archiving, and sharing.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Export to Excel(导出到 Excel)
|
# Export to Excel(导出到 Excel)
|
||||||
|
|
||||||
<span class="category-badge action">Action</span>
|
<span class="category-badge action">Action</span>
|
||||||
<span class="version-badge">v0.3.6</span>
|
<span class="version-badge">v0.3.7</span>
|
||||||
|
|
||||||
将聊天记录导出为 Excel 表格,便于分析、归档和分享。
|
将聊天记录导出为 Excel 表格,便于分析、归档和分享。
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Export to Word
|
# Export to Word
|
||||||
|
|
||||||
<span class="category-badge action">Action</span>
|
<span class="category-badge action">Action</span>
|
||||||
<span class="version-badge">v0.1.0</span>
|
<span class="version-badge">v0.2.0</span>
|
||||||
|
|
||||||
Export chat conversations to Word (.docx) with Markdown formatting, syntax highlighting, and smarter filenames.
|
Export conversation to Word (.docx) with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -13,11 +13,17 @@ The Export to Word plugin converts chat messages from Markdown to a polished Wor
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- :material-file-word-box: **DOCX Export**: Generate Word files with one click
|
- :material-file-word-box: **One-Click Export**: Adds an "Export to Word" action button to the chat.
|
||||||
- :material-format-bold: **Rich Markdown Support**: Headings, bold/italic, lists, tables
|
- :material-format-bold: **Markdown Conversion**: Converts Markdown syntax to Word formatting (headings, bold, italic, code, tables, lists).
|
||||||
- :material-code-tags: **Syntax Highlighting**: Pygments-powered code blocks
|
- :material-code-tags: **Syntax Highlighting**: Code blocks are highlighted with Pygments (supports 500+ languages).
|
||||||
- :material-format-quote-close: **Styled Blockquotes**: Left-border gray quote styling
|
- :material-sigma: **Native Math Equations**: LaTeX math (`$$...$$`, `\[...\]`, `$...$`, `\(...\)`) converted to editable Word equations.
|
||||||
- :material-file-document-outline: **Smart Filenames**: Configurable title source (Chat Title, AI Generated, or Markdown Title)
|
- :material-graph: **Mermaid Diagrams**: Mermaid flowcharts and sequence diagrams rendered as images in the document.
|
||||||
|
- :material-book-open-page-variant: **Citations & References**: Auto-generates a References section from OpenWebUI sources with clickable citation links.
|
||||||
|
- :material-brain-off: **Reasoning Stripping**: Automatically removes AI thinking blocks (`<think>`, `<analysis>`) from exports.
|
||||||
|
- :material-table: **Enhanced Tables**: Smart column widths, column alignment (`:---`, `---:`, `:---:`), header row repeat across pages.
|
||||||
|
- :material-format-quote-close: **Blockquote Support**: Markdown blockquotes are rendered with left border and gray styling.
|
||||||
|
- :material-translate: **Multi-language Support**: Properly handles both Chinese and English text.
|
||||||
|
- :material-file-document-outline: **Smarter Filenames**: Configurable title source (Chat Title, AI Generated, or Markdown Title).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -25,9 +31,14 @@ The Export to Word plugin converts chat messages from Markdown to a polished Wor
|
|||||||
|
|
||||||
You can configure the following settings via the **Valves** button in the plugin settings:
|
You can configure the following settings via the **Valves** button in the plugin settings:
|
||||||
|
|
||||||
| Valve | Description | Default |
|
| Valve | Description | Default |
|
||||||
| :------------- | :------------------------------------------------------------------------------------------ | :----------- |
|
| :--- | :--- | :--- |
|
||||||
| `TITLE_SOURCE` | Source for document title/filename. Options: `chat_title`, `ai_generated`, `markdown_title` | `chat_title` |
|
| `TITLE_SOURCE` | Source for document title/filename. Options: `chat_title`, `ai_generated`, `markdown_title` | `chat_title` |
|
||||||
|
| `MERMAID_JS_URL` | URL for the Mermaid.js library (for diagram rendering). | `https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js` |
|
||||||
|
| `MERMAID_PNG_SCALE` | Scale factor for Mermaid PNG generation (Resolution). Higher = clearer but larger file size. | `3.0` |
|
||||||
|
| `MERMAID_DISPLAY_SCALE` | Scale factor for Mermaid visual size in Word. >1.0 to enlarge, <1.0 to shrink. | `1.5` |
|
||||||
|
| `MERMAID_OPTIMIZE_LAYOUT` | Automatically convert LR (Left-Right) flowcharts to TD (Top-Down) for better fit. | `True` |
|
||||||
|
| `MERMAID_CAPTIONS_ENABLE` | Enable/disable figure captions for Mermaid diagrams. | `True` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -47,31 +58,37 @@ You can configure the following settings via the **Valves** button in the plugin
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Supported Markdown
|
## Supported Markdown Syntax
|
||||||
|
|
||||||
| Syntax | Word Result |
|
| Syntax | Word Result |
|
||||||
| :---------------------------------- | :----------------------------- |
|
| :--- | :--- |
|
||||||
| `# Heading 1` to `###### Heading 6` | Heading levels 1-6 |
|
| `# Heading 1` to `###### Heading 6` | Heading levels 1-6 |
|
||||||
| `**bold**` / `__bold__` | Bold text |
|
| `**bold**` or `__bold__` | Bold text |
|
||||||
| `*italic*` / `_italic_` | Italic text |
|
| `*italic*` or `_italic_` | Italic text |
|
||||||
| `***bold italic***` | Bold + Italic |
|
| `***bold italic***` | Bold + Italic |
|
||||||
| `` `inline code` `` | Monospace with gray background |
|
| `` `inline code` `` | Monospace with gray background |
|
||||||
| <code>``` code block ```</code> | Syntax-highlighted code block |
|
| ` ``` code block ``` ` | **Syntax highlighted** code block |
|
||||||
| `> blockquote` | Left-bordered gray italic text |
|
| `> blockquote` | Left-bordered gray italic text |
|
||||||
| `[link](url)` | Blue underlined link |
|
| `[link](url)` | Blue underlined link text |
|
||||||
| `~~strikethrough~~` | Strikethrough |
|
| `~~strikethrough~~` | Strikethrough text |
|
||||||
| `- item` / `* item` | Bullet list |
|
| `- item` or `* item` | Bullet list |
|
||||||
| `1. item` | Numbered list |
|
| `1. item` | Numbered list |
|
||||||
| Markdown tables | Grid table |
|
| Markdown tables | **Enhanced table** with smart widths |
|
||||||
| `---` / `***` | Horizontal rule |
|
| `---` or `***` | Horizontal rule |
|
||||||
|
| `$$LaTeX$$` or `\[LaTeX\]` | **Native Word equation** (display) |
|
||||||
|
| `$LaTeX$` or `\(LaTeX\)` | **Native Word equation** (inline) |
|
||||||
|
| ` ```mermaid ... ``` ` | **Mermaid diagram** as image |
|
||||||
|
| `[1]` citation markers | **Clickable links** to References |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
!!! note "Prerequisites"
|
!!! note "Prerequisites"
|
||||||
- `python-docx==1.1.2` (document generation)
|
- `python-docx==1.1.2` - Word document generation
|
||||||
- `Pygments>=2.15.0` (syntax highlighting, optional but recommended)
|
- `Pygments>=2.15.0` - Syntax highlighting
|
||||||
|
- `latex2mathml` - LaTeX to MathML conversion
|
||||||
|
- `mathml2omml` - MathML to Office Math (OMML) conversion
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Export to Word(导出为 Word)
|
# Export to Word(导出为 Word)
|
||||||
|
|
||||||
<span class="category-badge action">Action</span>
|
<span class="category-badge action">Action</span>
|
||||||
<span class="version-badge">v0.1.0</span>
|
<span class="version-badge">v0.2.0</span>
|
||||||
|
|
||||||
将聊天记录按 Markdown 格式导出为 Word (.docx),支持语法高亮、引用样式和更智能的文件命名。
|
将当前对话导出为完美格式的 Word 文档,支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用资料**以及**增强表格**渲染。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -13,11 +13,17 @@ Export to Word 插件会把聊天消息从 Markdown 转成精致的 Word 文档
|
|||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
- :material-file-word-box: **DOCX 导出**:一键生成 Word 文件
|
- :material-file-word-box: **一键导出**:在聊天界面添加"导出为 Word"动作按钮。
|
||||||
- :material-format-bold: **丰富 Markdown 支持**:标题、粗斜体、列表、表格
|
- :material-format-bold: **Markdown 转换**:将 Markdown 语法转换为 Word 格式(标题、粗体、斜体、代码、表格、列表)。
|
||||||
- :material-code-tags: **语法高亮**:Pygments 驱动的代码块上色
|
- :material-code-tags: **代码语法高亮**:使用 Pygments 库为代码块添加语法高亮(支持 500+ 种语言)。
|
||||||
- :material-format-quote-close: **引用样式**:左侧边框的灰色斜体引用
|
- :material-sigma: **原生数学公式**:LaTeX 公式(`$$...$$`、`\[...\]`、`$...$`、`\(...\)`)转换为可编辑的 Word 公式。
|
||||||
- :material-file-document-outline: **智能文件名**:可配置标题来源(对话标题、AI 生成或 Markdown 标题)
|
- :material-graph: **Mermaid 图表**:Mermaid 流程图和时序图渲染为文档中的图片。
|
||||||
|
- :material-book-open-page-variant: **引用与参考**:自动从 OpenWebUI 来源生成参考资料章节,支持可点击的引用链接。
|
||||||
|
- :material-brain-off: **移除思考过程**:自动移除 AI 思考块(`<think>`、`<analysis>`)。
|
||||||
|
- :material-table: **增强表格**:智能列宽、列对齐(`:---`、`---:`、`:---:`)、表头跨页重复。
|
||||||
|
- :material-format-quote-close: **引用块支持**:Markdown 引用块渲染为带左侧边框的灰色斜体样式。
|
||||||
|
- :material-translate: **多语言支持**:正确处理中文和英文文本,无乱码问题。
|
||||||
|
- :material-file-document-outline: **智能文件名**:可配置标题来源(对话标题、AI 生成或 Markdown 标题)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -25,9 +31,14 @@ Export to Word 插件会把聊天消息从 Markdown 转成精致的 Word 文档
|
|||||||
|
|
||||||
您可以通过插件设置中的 **Valves** 按钮配置以下选项:
|
您可以通过插件设置中的 **Valves** 按钮配置以下选项:
|
||||||
|
|
||||||
| Valve | 说明 | 默认值 |
|
| Valve | 说明 | 默认值 |
|
||||||
| :------------- | :--------------------------------------------------------------------------------------------------------------- | :----------- |
|
| :--- | :--- | :--- |
|
||||||
| `TITLE_SOURCE` | 文档标题/文件名的来源。选项:`chat_title` (对话标题), `ai_generated` (AI 生成), `markdown_title` (Markdown 标题) | `chat_title` |
|
| `TITLE_SOURCE` | 文档标题/文件名的来源。选项:`chat_title` (对话标题), `ai_generated` (AI 生成), `markdown_title` (Markdown 标题) | `chat_title` |
|
||||||
|
| `MERMAID_JS_URL` | Mermaid.js 库的 URL(用于图表渲染)。 | `https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js` |
|
||||||
|
| `MERMAID_PNG_SCALE` | Mermaid PNG 生成缩放比例(分辨率)。越高越清晰但文件越大。 | `3.0` |
|
||||||
|
| `MERMAID_DISPLAY_SCALE` | Mermaid 在 Word 中的显示比例(视觉大小)。>1.0 放大, <1.0 缩小。 | `1.5` |
|
||||||
|
| `MERMAID_OPTIMIZE_LAYOUT` | 优化 Mermaid 布局: 自动将 LR (左右) 转换为 TD (上下) 以适应页面。 | `True` |
|
||||||
|
| `MERMAID_CAPTIONS_ENABLE` | 启用/禁用 Mermaid 图表的图注。 | `True` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -47,23 +58,27 @@ Export to Word 插件会把聊天消息从 Markdown 转成精致的 Word 文档
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 支持的 Markdown
|
## 支持的 Markdown 语法
|
||||||
|
|
||||||
| 语法 | Word 效果 |
|
| 语法 | Word 效果 |
|
||||||
| :-------------------------- | :------------------ |
|
| :--- | :--- |
|
||||||
| `# 标题1` 到 `###### 标题6` | 标题级别 1-6 |
|
| `# 标题1` 到 `###### 标题6` | 标题级别 1-6 |
|
||||||
| `**粗体**` / `__粗体__` | 粗体文本 |
|
| `**粗体**` / `__粗体__` | 粗体文本 |
|
||||||
| `*斜体*` / `_斜体_` | 斜体文本 |
|
| `*斜体*` / `_斜体_` | 斜体文本 |
|
||||||
| `***粗斜体***` | 粗体 + 斜体 |
|
| `***粗斜体***` | 粗体 + 斜体 |
|
||||||
| `` `行内代码` `` | 等宽字体 + 灰色背景 |
|
| `` `行内代码` `` | 等宽字体 + 灰色背景 |
|
||||||
| <code>``` 代码块 ```</code> | 语法高亮代码块 |
|
| <code>``` 代码块 ```</code> | 语法高亮代码块 |
|
||||||
| `> 引用文本` | 左侧边框的灰色斜体 |
|
| `> 引用文本` | 左侧边框的灰色斜体 |
|
||||||
| `[链接](url)` | 蓝色下划线链接 |
|
| `[链接](url)` | 蓝色下划线链接 |
|
||||||
| `~~删除线~~` | 删除线 |
|
| `~~删除线~~` | 删除线 |
|
||||||
| `- 项目` / `* 项目` | 无序列表 |
|
| `- 项目` / `* 项目` | 无序列表 |
|
||||||
| `1. 项目` | 有序列表 |
|
| `1. 项目` | 有序列表 |
|
||||||
| Markdown 表格 | 带边框表格 |
|
| Markdown 表格 | **增强表格**(智能列宽) |
|
||||||
| `---` / `***` | 水平分割线 |
|
| `---` / `***` | 水平分割线 |
|
||||||
|
| `$$LaTeX$$` 或 `\[LaTeX\]` | **原生 Word 公式**(块级) |
|
||||||
|
| `$LaTeX$` 或 `\(LaTeX\)` | **原生 Word 公式**(行内) |
|
||||||
|
| ` ```mermaid ... ``` ` | **Mermaid 图表**(图片形式) |
|
||||||
|
| `[1]` 引用标记 | **可点击链接**到参考资料 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -71,7 +86,9 @@ Export to Word 插件会把聊天消息从 Markdown 转成精致的 Word 文档
|
|||||||
|
|
||||||
!!! note "前置条件"
|
!!! note "前置条件"
|
||||||
- `python-docx==1.1.2`(文档生成)
|
- `python-docx==1.1.2`(文档生成)
|
||||||
- `Pygments>=2.15.0`(语法高亮,建议安装)
|
- `Pygments>=2.15.0`(语法高亮)
|
||||||
|
- `latex2mathml`(LaTeX 转 MathML)
|
||||||
|
- `mathml2omml`(MathML 转 Office Math)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -53,17 +53,17 @@ Actions are interactive plugins that:
|
|||||||
|
|
||||||
Export chat conversations to Excel spreadsheet format for analysis and archiving.
|
Export chat conversations to Excel spreadsheet format for analysis and archiving.
|
||||||
|
|
||||||
**Version:** 0.3.6
|
**Version:** 0.3.7
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](export-to-excel.md)
|
[:octicons-arrow-right-24: Documentation](export-to-excel.md)
|
||||||
|
|
||||||
- :material-file-word-box:{ .lg .middle } **Export to Word**
|
- :material-file-word-box:{ .lg .middle } **Export to Word (Enhanced Formatting)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Export chat content as Word (.docx) with Markdown formatting and syntax highlighting.
|
Export the current conversation to a formatted Word doc with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.
|
||||||
|
|
||||||
**Version:** 0.1.0
|
**Version:** 0.2.0
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Documentation](export-to-word.md)
|
[:octicons-arrow-right-24: Documentation](export-to-word.md)
|
||||||
|
|
||||||
|
|||||||
@@ -53,17 +53,17 @@ Actions 是交互式插件,能够:
|
|||||||
|
|
||||||
将聊天记录导出为 Excel 电子表格,方便分析或归档。
|
将聊天记录导出为 Excel 电子表格,方便分析或归档。
|
||||||
|
|
||||||
**版本:** 0.3.6
|
**版本:** 0.3.7
|
||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](export-to-excel.md)
|
[:octicons-arrow-right-24: 查看文档](export-to-excel.md)
|
||||||
|
|
||||||
- :material-file-word-box:{ .lg .middle } **Export to Word**
|
- :material-file-word-box:{ .lg .middle } **Word 导出 (格式增强)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
将聊天内容按 Markdown 格式导出为 Word (.docx),支持语法高亮。
|
将当前对话导出为完美格式的 Word 文档,支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用资料**以及**增强表格**渲染。
|
||||||
|
|
||||||
**版本:** 0.1.0
|
**版本:** 0.2.0
|
||||||
|
|
||||||
[:octicons-arrow-right-24: 查看文档](export-to-word.md)
|
[:octicons-arrow-right-24: 查看文档](export-to-word.md)
|
||||||
|
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ class Action:
|
|||||||
if role == "user"
|
if role == "user"
|
||||||
else "Assistant" if role == "assistant" else role
|
else "Assistant" if role == "assistant" else role
|
||||||
)
|
)
|
||||||
aggregated_parts.append(f"[{role_label} Message {i}]\n{text_content}")
|
aggregated_parts.append(f"{text_content}")
|
||||||
|
|
||||||
if not aggregated_parts:
|
if not aggregated_parts:
|
||||||
return body # Or handle error
|
return body # Or handle error
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ class Action:
|
|||||||
if role == "user"
|
if role == "user"
|
||||||
else "助手" if role == "assistant" else role
|
else "助手" if role == "assistant" else role
|
||||||
)
|
)
|
||||||
aggregated_parts.append(f"[{role_label} 消息 {i}]\n{text_content}")
|
aggregated_parts.append(f"{text_content}")
|
||||||
|
|
||||||
if not aggregated_parts:
|
if not aggregated_parts:
|
||||||
return body # 或者处理错误
|
return body # 或者处理错误
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
# Export to Word
|
# Export to Word
|
||||||
|
|
||||||
Export current conversation from Markdown to Word (.docx) with **syntax highlighting**, **blockquote support**, and smarter filenames.
|
Export conversation to Word (.docx) with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **One-Click Export**: Adds an "Export to Word" action button to the chat.
|
- **One-Click Export**: Adds an "Export to Word" action button to the chat.
|
||||||
- **Markdown Conversion**: Converts Markdown syntax to Word formatting (headings, bold, italic, code, tables, lists).
|
- **Markdown Conversion**: Converts Markdown syntax to Word formatting (headings, bold, italic, code, tables, lists).
|
||||||
- **Syntax Highlighting**: Code blocks are highlighted with Pygments (supports 500+ languages).
|
- **Syntax Highlighting**: Code blocks are highlighted with Pygments (supports 500+ languages).
|
||||||
|
- **Native Math Equations**: LaTeX math (`$$...$$`, `\[...\]`, `$...$`, `\(...\)`) converted to editable Word equations.
|
||||||
|
- **Mermaid Diagrams**: Mermaid flowcharts and sequence diagrams rendered as images in the document.
|
||||||
|
- **Citations & References**: Auto-generates a References section from OpenWebUI sources with clickable citation links.
|
||||||
|
- **Reasoning Stripping**: Automatically removes AI thinking blocks (`<think>`, `<analysis>`) from exports.
|
||||||
|
- **Enhanced Tables**: Smart column widths, column alignment (`:---`, `---:`, `:---:`), header row repeat across pages.
|
||||||
- **Blockquote Support**: Markdown blockquotes are rendered with left border and gray styling.
|
- **Blockquote Support**: Markdown blockquotes are rendered with left border and gray styling.
|
||||||
- **Multi-language Support**: Properly handles both Chinese and English text without garbled characters.
|
- **Multi-language Support**: Properly handles both Chinese and English text.
|
||||||
- **Smarter Filenames**: Configurable title source (Chat Title, AI Generated, or Markdown Title).
|
- **Smarter Filenames**: Configurable title source (Chat Title, AI Generated, or Markdown Title).
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -19,24 +24,33 @@ You can configure the following settings via the **Valves** button in the plugin
|
|||||||
- `chat_title`: Use the conversation title (default).
|
- `chat_title`: Use the conversation title (default).
|
||||||
- `ai_generated`: Use AI to generate a short title based on the content.
|
- `ai_generated`: Use AI to generate a short title based on the content.
|
||||||
- `markdown_title`: Extract the first h1/h2 heading from the Markdown content.
|
- `markdown_title`: Extract the first h1/h2 heading from the Markdown content.
|
||||||
|
- **MERMAID_JS_URL**: URL for the Mermaid.js library (for diagram rendering).
|
||||||
|
- **MERMAID_PNG_SCALE**: Scale factor for Mermaid PNG generation (Resolution). Default: `3.0`.
|
||||||
|
- **MERMAID_DISPLAY_SCALE**: Scale factor for Mermaid visual size in Word. Default: `1.5`.
|
||||||
|
- **MERMAID_OPTIMIZE_LAYOUT**: Automatically convert LR (Left-Right) flowcharts to TD (Top-Down). Default: `True`.
|
||||||
|
- **MERMAID_CAPTIONS_ENABLE**: Enable/disable figure captions for Mermaid diagrams.
|
||||||
|
|
||||||
## Supported Markdown Syntax
|
## Supported Markdown Syntax
|
||||||
|
|
||||||
| Syntax | Word Result |
|
| Syntax | Word Result |
|
||||||
| :---------------------------------- | :-------------------------------- |
|
| :---------------------------------- | :------------------------------------ |
|
||||||
| `# Heading 1` to `###### Heading 6` | Heading levels 1-6 |
|
| `# Heading 1` to `###### Heading 6` | Heading levels 1-6 |
|
||||||
| `**bold**` or `__bold__` | Bold text |
|
| `**bold**` or `__bold__` | Bold text |
|
||||||
| `*italic*` or `_italic_` | Italic text |
|
| `*italic*` or `_italic_` | Italic text |
|
||||||
| `***bold italic***` | Bold + Italic |
|
| `***bold italic***` | Bold + Italic |
|
||||||
| `` `inline code` `` | Monospace with gray background |
|
| `` `inline code` `` | Monospace with gray background |
|
||||||
| ` ``` code block ``` ` | **Syntax highlighted** code block |
|
| ` ``` code block ``` ` | **Syntax highlighted** code block |
|
||||||
| `> blockquote` | Left-bordered gray italic text |
|
| `> blockquote` | Left-bordered gray italic text |
|
||||||
| `[link](url)` | Blue underlined link text |
|
| `[link](url)` | Blue underlined link text |
|
||||||
| `~~strikethrough~~` | Strikethrough text |
|
| `~~strikethrough~~` | Strikethrough text |
|
||||||
| `- item` or `* item` | Bullet list |
|
| `- item` or `* item` | Bullet list |
|
||||||
| `1. item` | Numbered list |
|
| `1. item` | Numbered list |
|
||||||
| Markdown tables | Table with grid |
|
| Markdown tables | **Enhanced table** with smart widths |
|
||||||
| `---` or `***` | Horizontal rule |
|
| `---` or `***` | Horizontal rule |
|
||||||
|
| `$$LaTeX$$` or `\[LaTeX\]` | **Native Word equation** (display) |
|
||||||
|
| `$LaTeX$` or `\(LaTeX\)` | **Native Word equation** (inline) |
|
||||||
|
| ` ```mermaid ... ``` ` | **Mermaid diagram** as image |
|
||||||
|
| `[1]` citation markers | **Clickable links** to References |
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -44,19 +58,14 @@ You can configure the following settings via the **Valves** button in the plugin
|
|||||||
2. In any chat, click the "Export to Word" button.
|
2. In any chat, click the "Export to Word" button.
|
||||||
3. The .docx file will be automatically downloaded to your device.
|
3. The .docx file will be automatically downloaded to your device.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
### Notes
|
|
||||||
|
|
||||||
- Title detection only considers h1/h2 headings.
|
|
||||||
- If the request carries `chat_id` (body or metadata), the plugin will fetch the chat title from the database when the body lacks one.
|
|
||||||
- Default fonts: Times New Roman (en), SimSun/SimHei (zh), Consolas (code).
|
|
||||||
|
|
||||||
### Requirements
|
|
||||||
|
|
||||||
- `python-docx==1.1.2` - Word document generation
|
- `python-docx==1.1.2` - Word document generation
|
||||||
- `Pygments>=2.15.0` - Syntax highlighting (optional but recommended)
|
- `Pygments>=2.15.0` - Syntax highlighting
|
||||||
|
- `latex2mathml` - LaTeX to MathML conversion
|
||||||
|
- `mathml2omml` - MathML to Office Math (OMML) conversion
|
||||||
|
|
||||||
Both are declared in the plugin docstring; ensure they are installed in your environment.
|
All dependencies are declared in the plugin docstring.
|
||||||
|
|
||||||
## Font Configuration
|
## Font Configuration
|
||||||
|
|
||||||
@@ -64,6 +73,26 @@ Both are declared in the plugin docstring; ensure they are installed in your env
|
|||||||
- **Chinese Text**: SimSun (宋体) for body, SimHei (黑体) for headings
|
- **Chinese Text**: SimSun (宋体) for body, SimHei (黑体) for headings
|
||||||
- **Code**: Consolas
|
- **Code**: Consolas
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v0.3.0
|
||||||
|
|
||||||
|
- **Mermaid Diagrams**: Native support for rendering Mermaid diagrams as images in Word.
|
||||||
|
- **Native Math**: Converts LaTeX equations to native Office MathML for editable equations.
|
||||||
|
- **Citations**: Automatic bibliography generation and citation linking.
|
||||||
|
- **Reasoning Removal**: Option to strip `<think>` blocks from the output.
|
||||||
|
- **Table Enhancements**: Improved table formatting with smart column widths.
|
||||||
|
|
||||||
|
### v0.2.0
|
||||||
|
- Added native math equation support (LaTeX → OMML)
|
||||||
|
- Added Mermaid diagram rendering
|
||||||
|
- Added citations and references section generation
|
||||||
|
- Added automatic reasoning block stripping
|
||||||
|
- Enhanced table formatting with smart column widths and alignment
|
||||||
|
|
||||||
|
### v0.1.1
|
||||||
|
- Initial release with basic Markdown to Word conversion
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
||||||
Fu-Jie
|
Fu-Jie
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
# 导出为 Word
|
# 导出为 Word
|
||||||
|
|
||||||
将当前对话内容从 Markdown 转换并导出为 Word (.docx) 文件,支持**代码语法高亮**、**引用块样式**和更智能的文件命名。
|
将对话导出为 Word (.docx),支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用参考**和**增强表格格式**。
|
||||||
|
|
||||||
## 功能特点
|
## 功能特点
|
||||||
|
|
||||||
- **一键导出**:在聊天界面添加“导出为 Word”动作按钮。
|
- **一键导出**:在聊天界面添加"导出为 Word"动作按钮。
|
||||||
- **Markdown 转换**:将 Markdown 语法转换为 Word 格式(标题、粗体、斜体、代码、表格、列表)。
|
- **Markdown 转换**:将 Markdown 语法转换为 Word 格式(标题、粗体、斜体、代码、表格、列表)。
|
||||||
- **代码语法高亮**:使用 Pygments 库为代码块添加语法高亮(支持 500+ 种语言)。
|
- **代码语法高亮**:使用 Pygments 库为代码块添加语法高亮(支持 500+ 种语言)。
|
||||||
- **引用块支持**:Markdown 引用块会渲染为带左侧边框的灰色斜体样式。
|
- **原生数学公式**:LaTeX 公式(`$$...$$`、`\[...\]`、`$...$`、`\(...\)`)转换为可编辑的 Word 公式。
|
||||||
|
- **Mermaid 图表**:Mermaid 流程图和时序图渲染为文档中的图片。
|
||||||
|
- **引用与参考**:自动从 OpenWebUI 来源生成参考资料章节,支持可点击的引用链接。
|
||||||
|
- **移除思考过程**:自动移除 AI 思考块(`<think>`、`<analysis>`)。
|
||||||
|
- **增强表格**:智能列宽、列对齐(`:---`、`---:`、`:---:`)、表头跨页重复。
|
||||||
|
- **引用块支持**:Markdown 引用块渲染为带左侧边框的灰色斜体样式。
|
||||||
- **多语言支持**:正确处理中文和英文文本,无乱码问题。
|
- **多语言支持**:正确处理中文和英文文本,无乱码问题。
|
||||||
- **更智能的文件名**:可配置标题来源(对话标题、AI 生成或 Markdown 标题)。
|
- **智能文件名**:可配置标题来源(对话标题、AI 生成或 Markdown 标题)。
|
||||||
|
|
||||||
## 配置 (Configuration)
|
## 配置
|
||||||
|
|
||||||
您可以通过插件设置中的 **Valves** 按钮配置以下选项:
|
您可以通过插件设置中的 **Valves** 按钮配置以下选项:
|
||||||
|
|
||||||
@@ -19,24 +24,33 @@
|
|||||||
- `chat_title`:使用对话标题(默认)。
|
- `chat_title`:使用对话标题(默认)。
|
||||||
- `ai_generated`:使用 AI 根据内容生成简短标题。
|
- `ai_generated`:使用 AI 根据内容生成简短标题。
|
||||||
- `markdown_title`:从 Markdown 内容中提取第一个一级或二级标题。
|
- `markdown_title`:从 Markdown 内容中提取第一个一级或二级标题。
|
||||||
|
- **MERMAID_JS_URL**:Mermaid.js 库的 URL(用于图表渲染)。
|
||||||
|
- **MERMAID_PNG_SCALE**:Mermaid PNG 生成缩放比例(分辨率)。默认:`3.0`。
|
||||||
|
- **MERMAID_DISPLAY_SCALE**:Mermaid 在 Word 中的显示比例(视觉大小)。默认:`1.5`。
|
||||||
|
- **MERMAID_OPTIMIZE_LAYOUT**:自动将 LR(左右)流程图转换为 TD(上下)。默认:`True`。
|
||||||
|
- **MERMAID_CAPTIONS_ENABLE**:启用/禁用 Mermaid 图表的图注。
|
||||||
|
|
||||||
## 支持的 Markdown 语法
|
## 支持的 Markdown 语法
|
||||||
|
|
||||||
| 语法 | Word 效果 |
|
| 语法 | Word 效果 |
|
||||||
| :-------------------------- | :----------------------- |
|
| :---------------------------- | :-------------------------------- |
|
||||||
| `# 标题1` 到 `###### 标题6` | 标题级别 1-6 |
|
| `# 标题1` 到 `###### 标题6` | 标题级别 1-6 |
|
||||||
| `**粗体**` 或 `__粗体__` | 粗体文本 |
|
| `**粗体**` 或 `__粗体__` | 粗体文本 |
|
||||||
| `*斜体*` 或 `_斜体_` | 斜体文本 |
|
| `*斜体*` 或 `_斜体_` | 斜体文本 |
|
||||||
| `***粗斜体***` | 粗体 + 斜体 |
|
| `***粗斜体***` | 粗体 + 斜体 |
|
||||||
| `` `行内代码` `` | 等宽字体 + 灰色背景 |
|
| `` `行内代码` `` | 等宽字体 + 灰色背景 |
|
||||||
| ` ``` 代码块 ``` ` | **语法高亮**的代码块 |
|
| ` ``` 代码块 ``` ` | **语法高亮**的代码块 |
|
||||||
| `> 引用文本` | 带左侧边框的灰色斜体文本 |
|
| `> 引用文本` | 带左侧边框的灰色斜体文本 |
|
||||||
| `[链接](url)` | 蓝色下划线链接文本 |
|
| `[链接](url)` | 蓝色下划线链接文本 |
|
||||||
| `~~删除线~~` | 删除线文本 |
|
| `~~删除线~~` | 删除线文本 |
|
||||||
| `- 项目` 或 `* 项目` | 无序列表 |
|
| `- 项目` 或 `* 项目` | 无序列表 |
|
||||||
| `1. 项目` | 有序列表 |
|
| `1. 项目` | 有序列表 |
|
||||||
| Markdown 表格 | 带边框表格 |
|
| Markdown 表格 | **增强表格**(智能列宽) |
|
||||||
| `---` 或 `***` | 水平分割线 |
|
| `---` 或 `***` | 水平分割线 |
|
||||||
|
| `$$LaTeX$$` 或 `\[LaTeX\]` | **原生 Word 公式**(块级) |
|
||||||
|
| `$LaTeX$` 或 `\(LaTeX\)` | **原生 Word 公式**(行内) |
|
||||||
|
| ` ```mermaid ... ``` ` | **Mermaid 图表**(图片形式) |
|
||||||
|
| `[1]` 引用标记 | **可点击链接**到参考资料 |
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
@@ -44,18 +58,14 @@
|
|||||||
2. 在任意对话中,点击"导出为 Word"按钮。
|
2. 在任意对话中,点击"导出为 Word"按钮。
|
||||||
3. .docx 文件将自动下载到你的设备。
|
3. .docx 文件将自动下载到你的设备。
|
||||||
|
|
||||||
### 说明
|
## 依赖
|
||||||
|
|
||||||
- 标题检测仅考虑一级/二级标题(h1/h2)。
|
|
||||||
- 若请求体或 metadata 提供 `chat_id`,当正文缺少标题时会从数据库查询对话标题。
|
|
||||||
- 默认字体:英文 Times New Roman,中文宋体/黑体,代码 Consolas。
|
|
||||||
|
|
||||||
### 依赖
|
|
||||||
|
|
||||||
- `python-docx==1.1.2` - Word 文档生成
|
- `python-docx==1.1.2` - Word 文档生成
|
||||||
- `Pygments>=2.15.0` - 语法高亮(可选但建议安装)
|
- `Pygments>=2.15.0` - 语法高亮
|
||||||
|
- `latex2mathml` - LaTeX 转 MathML
|
||||||
|
- `mathml2omml` - MathML 转 Office Math (OMML)
|
||||||
|
|
||||||
两者已在插件文档字符串中声明,请确保环境已安装。
|
所有依赖已在插件文档字符串中声明。
|
||||||
|
|
||||||
## 字体配置
|
## 字体配置
|
||||||
|
|
||||||
@@ -63,6 +73,26 @@
|
|||||||
- **中文文本**:宋体(正文)、黑体(标题)
|
- **中文文本**:宋体(正文)、黑体(标题)
|
||||||
- **代码**:Consolas
|
- **代码**:Consolas
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### v0.3.0
|
||||||
|
|
||||||
|
- **Mermaid 图表**: 原生支持将 Mermaid 图表渲染为 Word 中的图片。
|
||||||
|
- **原生公式**: 将 LaTeX 公式转换为原生 Office MathML,支持在 Word 中编辑。
|
||||||
|
- **引用参考**: 自动生成参考文献列表并链接引用。
|
||||||
|
- **移除推理**: 选项支持从输出中移除 `<think>` 推理块。
|
||||||
|
- **表格增强**: 改进表格格式,支持智能列宽。
|
||||||
|
|
||||||
|
### v0.2.0
|
||||||
|
- 新增原生数学公式支持(LaTeX → OMML)
|
||||||
|
- 新增 Mermaid 图表渲染
|
||||||
|
- 新增引用与参考资料章节生成
|
||||||
|
- 新增自动移除 AI 思考块
|
||||||
|
- 增强表格格式(智能列宽、对齐)
|
||||||
|
|
||||||
|
### v0.1.1
|
||||||
|
- 初始版本,支持基本 Markdown 转 Word
|
||||||
|
|
||||||
## 作者
|
## 作者
|
||||||
|
|
||||||
Fu-Jie
|
Fu-Jie
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
1844
plugins/actions/export_to_docx/export_to_word_cn.py
Normal file
1844
plugins/actions/export_to_docx/export_to_word_cn.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,882 +0,0 @@
|
|||||||
"""
|
|
||||||
title: 导出为 Word
|
|
||||||
author: Fu-Jie
|
|
||||||
author_url: https://github.com/Fu-Jie
|
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
|
||||||
version: 0.1.0
|
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
|
|
||||||
requirements: python-docx==1.1.2, Pygments>=2.15.0
|
|
||||||
description: 将当前对话内容从 Markdown 转换并导出为 Word (.docx) 文件,支持代码语法高亮和引用块。
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import base64
|
|
||||||
import datetime
|
|
||||||
import io
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
from typing import Optional, Callable, Awaitable, Any, List, Tuple
|
|
||||||
from docx import Document
|
|
||||||
from docx.shared import Pt, Inches, RGBColor, Cm
|
|
||||||
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
|
|
||||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
|
||||||
from docx.enum.style import WD_STYLE_TYPE
|
|
||||||
from docx.oxml.ns import qn
|
|
||||||
from docx.oxml import OxmlElement
|
|
||||||
from open_webui.models.chats import Chats
|
|
||||||
from open_webui.models.users import Users
|
|
||||||
from open_webui.utils.chat import generate_chat_completion
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
# Pygments for syntax highlighting
|
|
||||||
try:
|
|
||||||
from pygments import lex
|
|
||||||
from pygments.lexers import get_lexer_by_name, TextLexer
|
|
||||||
from pygments.token import Token
|
|
||||||
|
|
||||||
PYGMENTS_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
PYGMENTS_AVAILABLE = False
|
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
||||||
)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Action:
|
|
||||||
class Valves(BaseModel):
|
|
||||||
TITLE_SOURCE: str = Field(
|
|
||||||
default="chat_title",
|
|
||||||
description="标题来源: 'chat_title' (对话标题), 'ai_generated' (AI 生成), 'markdown_title' (Markdown 标题)",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.valves = self.Valves()
|
|
||||||
|
|
||||||
async def _send_notification(self, emitter: Callable, type: str, content: str):
|
|
||||||
await emitter(
|
|
||||||
{"type": "notification", "data": {"type": type, "content": content}}
|
|
||||||
)
|
|
||||||
|
|
||||||
async def action(
|
|
||||||
self,
|
|
||||||
body: dict,
|
|
||||||
__user__=None,
|
|
||||||
__event_emitter__=None,
|
|
||||||
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
|
|
||||||
__metadata__: Optional[dict] = None,
|
|
||||||
__request__: Optional[Any] = None,
|
|
||||||
):
|
|
||||||
logger.info(f"action:{__name__}")
|
|
||||||
|
|
||||||
# 解析用户信息
|
|
||||||
if isinstance(__user__, (list, tuple)):
|
|
||||||
user_language = (
|
|
||||||
__user__[0].get("language", "zh-CN") if __user__ else "zh-CN"
|
|
||||||
)
|
|
||||||
user_name = __user__[0].get("name", "用户") if __user__[0] else "用户"
|
|
||||||
user_id = (
|
|
||||||
__user__[0]["id"]
|
|
||||||
if __user__ and "id" in __user__[0]
|
|
||||||
else "unknown_user"
|
|
||||||
)
|
|
||||||
elif isinstance(__user__, dict):
|
|
||||||
user_language = __user__.get("language", "zh-CN")
|
|
||||||
user_name = __user__.get("name", "用户")
|
|
||||||
user_id = __user__.get("id", "unknown_user")
|
|
||||||
|
|
||||||
if __event_emitter__:
|
|
||||||
last_assistant_message = body["messages"][-1]
|
|
||||||
|
|
||||||
await __event_emitter__(
|
|
||||||
{
|
|
||||||
"type": "status",
|
|
||||||
"data": {"description": "正在转换为 Word 文档...", "done": False},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
message_content = last_assistant_message["content"]
|
|
||||||
|
|
||||||
if not message_content or not message_content.strip():
|
|
||||||
await self._send_notification(
|
|
||||||
__event_emitter__, "error", "没有找到可导出的内容!"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# 生成文件名
|
|
||||||
title = ""
|
|
||||||
chat_id = self.extract_chat_id(body, __metadata__)
|
|
||||||
|
|
||||||
# 直接通过 chat_id 获取标题,因为 body 中通常不包含标题
|
|
||||||
chat_title = ""
|
|
||||||
if chat_id:
|
|
||||||
chat_title = await self.fetch_chat_title(chat_id, user_id)
|
|
||||||
|
|
||||||
# 根据配置决定文件名使用的标题
|
|
||||||
if (
|
|
||||||
self.valves.TITLE_SOURCE == "chat_title"
|
|
||||||
or not self.valves.TITLE_SOURCE
|
|
||||||
):
|
|
||||||
title = chat_title
|
|
||||||
elif self.valves.TITLE_SOURCE == "markdown_title":
|
|
||||||
title = self.extract_title(message_content)
|
|
||||||
elif self.valves.TITLE_SOURCE == "ai_generated":
|
|
||||||
title = await self.generate_title_using_ai(
|
|
||||||
body, message_content, user_id, __request__
|
|
||||||
)
|
|
||||||
|
|
||||||
current_datetime = datetime.datetime.now()
|
|
||||||
formatted_date = current_datetime.strftime("%Y%m%d")
|
|
||||||
|
|
||||||
if title:
|
|
||||||
filename = f"{self.clean_filename(title)}.docx"
|
|
||||||
else:
|
|
||||||
filename = f"{user_name}_{formatted_date}.docx"
|
|
||||||
|
|
||||||
# 创建 Word 文档;若正文无一级标题,使用对话标题作为一级标题
|
|
||||||
# 如果选择了 chat_title 且获取到了,则作为 top_heading
|
|
||||||
# 如果选择了其他方式,title 就是文件名,也可以作为 top_heading
|
|
||||||
|
|
||||||
# 保持原有逻辑:top_heading 主要是为了在文档开头补充标题
|
|
||||||
# 这里我们尽量使用 chat_title 作为 top_heading,如果它存在的话,因为它通常是对话的主题
|
|
||||||
# 即使文件名是 AI 生成的,文档内的标题用 chat_title 也是合理的
|
|
||||||
# 但如果用户选择了 markdown_title,可能不希望插入 chat_title
|
|
||||||
|
|
||||||
top_heading = ""
|
|
||||||
if chat_title:
|
|
||||||
top_heading = chat_title
|
|
||||||
elif title:
|
|
||||||
top_heading = title
|
|
||||||
|
|
||||||
has_h1 = bool(re.search(r"^#\s+.+$", message_content, re.MULTILINE))
|
|
||||||
doc = self.markdown_to_docx(
|
|
||||||
message_content, top_heading=top_heading, has_h1=has_h1
|
|
||||||
)
|
|
||||||
|
|
||||||
# 保存到内存
|
|
||||||
doc_buffer = io.BytesIO()
|
|
||||||
doc.save(doc_buffer)
|
|
||||||
doc_buffer.seek(0)
|
|
||||||
file_content = doc_buffer.read()
|
|
||||||
base64_blob = base64.b64encode(file_content).decode("utf-8")
|
|
||||||
|
|
||||||
# 触发文件下载
|
|
||||||
if __event_call__:
|
|
||||||
await __event_call__(
|
|
||||||
{
|
|
||||||
"type": "execute",
|
|
||||||
"data": {
|
|
||||||
"code": f"""
|
|
||||||
try {{
|
|
||||||
const base64Data = "{base64_blob}";
|
|
||||||
const binaryData = atob(base64Data);
|
|
||||||
const arrayBuffer = new Uint8Array(binaryData.length);
|
|
||||||
for (let i = 0; i < binaryData.length; i++) {{
|
|
||||||
arrayBuffer[i] = binaryData.charCodeAt(i);
|
|
||||||
}}
|
|
||||||
const blob = new Blob([arrayBuffer], {{ type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }});
|
|
||||||
const filename = "{filename}";
|
|
||||||
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.style.display = "none";
|
|
||||||
a.href = url;
|
|
||||||
a.download = filename;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
document.body.removeChild(a);
|
|
||||||
}} catch (error) {{
|
|
||||||
console.error('触发下载时出错:', error);
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
await __event_emitter__(
|
|
||||||
{
|
|
||||||
"type": "status",
|
|
||||||
"data": {"description": "Word 文档已导出", "done": True},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
await self._send_notification(
|
|
||||||
__event_emitter__, "success", f"已成功导出为 {filename}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"message": "下载事件已触发"}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error exporting to Word: {str(e)}")
|
|
||||||
await __event_emitter__(
|
|
||||||
{
|
|
||||||
"type": "status",
|
|
||||||
"data": {
|
|
||||||
"description": f"导出失败: {str(e)}",
|
|
||||||
"done": True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
await self._send_notification(
|
|
||||||
__event_emitter__, "error", f"导出 Word 文档时出错: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def generate_title_using_ai(
|
|
||||||
self, body: dict, content: str, user_id: str, request: Any
|
|
||||||
) -> str:
|
|
||||||
if not request:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
user_obj = Users.get_user_by_id(user_id)
|
|
||||||
model = body.get("model")
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"model": model,
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"role": "system",
|
|
||||||
"content": "You are a helpful assistant. Generate a short, concise title (max 10 words) for the following text. Do not use quotes. Only output the title.",
|
|
||||||
},
|
|
||||||
{"role": "user", "content": content[:2000]}, # Limit content length
|
|
||||||
],
|
|
||||||
"stream": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
response = await generate_chat_completion(request, payload, user_obj)
|
|
||||||
if response and "choices" in response:
|
|
||||||
return response["choices"][0]["message"]["content"].strip()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error generating title: {e}")
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def extract_title(self, content: str) -> str:
|
|
||||||
"""从 Markdown 内容提取一级/二级标题"""
|
|
||||||
lines = content.split("\n")
|
|
||||||
for line in lines:
|
|
||||||
# 仅匹配 h1-h2 标题
|
|
||||||
match = re.match(r"^#{1,2}\s+(.+)$", line.strip())
|
|
||||||
if match:
|
|
||||||
return match.group(1).strip()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def extract_chat_title(self, body: dict) -> str:
|
|
||||||
"""从请求体中提取会话标题"""
|
|
||||||
if not isinstance(body, dict):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
candidates = []
|
|
||||||
|
|
||||||
for key in ("chat", "conversation"):
|
|
||||||
if isinstance(body.get(key), dict):
|
|
||||||
candidates.append(body.get(key, {}).get("title", ""))
|
|
||||||
|
|
||||||
for key in ("title", "chat_title"):
|
|
||||||
value = body.get(key)
|
|
||||||
if isinstance(value, str):
|
|
||||||
candidates.append(value)
|
|
||||||
|
|
||||||
for candidate in candidates:
|
|
||||||
if candidate and isinstance(candidate, str):
|
|
||||||
return candidate.strip()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
|
||||||
"""从 body 或 metadata 中提取 chat_id"""
|
|
||||||
if isinstance(body, dict):
|
|
||||||
chat_id = body.get("chat_id") or body.get("id")
|
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
|
||||||
return chat_id.strip()
|
|
||||||
|
|
||||||
for key in ("chat", "conversation"):
|
|
||||||
nested = body.get(key)
|
|
||||||
if isinstance(nested, dict):
|
|
||||||
nested_id = nested.get("id") or nested.get("chat_id")
|
|
||||||
if isinstance(nested_id, str) and nested_id.strip():
|
|
||||||
return nested_id.strip()
|
|
||||||
if isinstance(metadata, dict):
|
|
||||||
chat_id = metadata.get("chat_id")
|
|
||||||
if isinstance(chat_id, str) and chat_id.strip():
|
|
||||||
return chat_id.strip()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
async def fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
|
|
||||||
"""根据 chat_id 从数据库获取标题"""
|
|
||||||
if not chat_id:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _load_chat():
|
|
||||||
if user_id:
|
|
||||||
return Chats.get_chat_by_id_and_user_id(id=chat_id, user_id=user_id)
|
|
||||||
return Chats.get_chat_by_id(chat_id)
|
|
||||||
|
|
||||||
try:
|
|
||||||
chat = await asyncio.to_thread(_load_chat)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(f"加载聊天 {chat_id} 失败: {exc}")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
if not chat:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
data = getattr(chat, "chat", {}) or {}
|
|
||||||
title = data.get("title") or getattr(chat, "title", "")
|
|
||||||
return title.strip() if isinstance(title, str) else ""
|
|
||||||
|
|
||||||
def clean_filename(self, name: str) -> str:
|
|
||||||
"""清理文件名中的非法字符"""
|
|
||||||
return re.sub(r'[\\/*?:"<>|]', "", name).strip()[:50]
|
|
||||||
|
|
||||||
def markdown_to_docx(
|
|
||||||
self, markdown_text: str, top_heading: str = "", has_h1: bool = False
|
|
||||||
) -> Document:
|
|
||||||
"""
|
|
||||||
将 Markdown 文本转换为 Word 文档
|
|
||||||
支持:标题、段落、粗体、斜体、代码块、列表、表格、链接
|
|
||||||
"""
|
|
||||||
doc = Document()
|
|
||||||
|
|
||||||
# 设置默认中文字体
|
|
||||||
self.set_document_default_font(doc)
|
|
||||||
|
|
||||||
# 若正文无一级标题且有对话标题,则作为一级标题写入
|
|
||||||
if top_heading and not has_h1:
|
|
||||||
self.add_heading(doc, top_heading, 1)
|
|
||||||
|
|
||||||
lines = markdown_text.split("\n")
|
|
||||||
i = 0
|
|
||||||
in_code_block = False
|
|
||||||
code_block_content = []
|
|
||||||
code_block_lang = ""
|
|
||||||
in_list = False
|
|
||||||
list_items = []
|
|
||||||
list_type = None # 'ordered' or 'unordered'
|
|
||||||
|
|
||||||
while i < len(lines):
|
|
||||||
line = lines[i]
|
|
||||||
|
|
||||||
# 处理代码块
|
|
||||||
if line.strip().startswith("```"):
|
|
||||||
if not in_code_block:
|
|
||||||
# 先处理之前积累的列表
|
|
||||||
if in_list and list_items:
|
|
||||||
self.add_list_to_doc(doc, list_items, list_type)
|
|
||||||
list_items = []
|
|
||||||
in_list = False
|
|
||||||
|
|
||||||
in_code_block = True
|
|
||||||
code_block_lang = line.strip()[3:].strip()
|
|
||||||
code_block_content = []
|
|
||||||
else:
|
|
||||||
# 代码块结束
|
|
||||||
in_code_block = False
|
|
||||||
self.add_code_block(
|
|
||||||
doc, "\n".join(code_block_content), code_block_lang
|
|
||||||
)
|
|
||||||
code_block_content = []
|
|
||||||
code_block_lang = ""
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_code_block:
|
|
||||||
code_block_content.append(line)
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 处理表格
|
|
||||||
if line.strip().startswith("|") and line.strip().endswith("|"):
|
|
||||||
# 先处理之前积累的列表
|
|
||||||
if in_list and list_items:
|
|
||||||
self.add_list_to_doc(doc, list_items, list_type)
|
|
||||||
list_items = []
|
|
||||||
in_list = False
|
|
||||||
|
|
||||||
table_lines = []
|
|
||||||
while i < len(lines) and lines[i].strip().startswith("|"):
|
|
||||||
table_lines.append(lines[i])
|
|
||||||
i += 1
|
|
||||||
self.add_table(doc, table_lines)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 处理标题
|
|
||||||
header_match = re.match(r"^(#{1,6})\s+(.+)$", line.strip())
|
|
||||||
if header_match:
|
|
||||||
# 先处理之前积累的列表
|
|
||||||
if in_list and list_items:
|
|
||||||
self.add_list_to_doc(doc, list_items, list_type)
|
|
||||||
list_items = []
|
|
||||||
in_list = False
|
|
||||||
|
|
||||||
level = len(header_match.group(1))
|
|
||||||
text = header_match.group(2)
|
|
||||||
self.add_heading(doc, text, level)
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 处理无序列表
|
|
||||||
unordered_match = re.match(r"^(\s*)[-*+]\s+(.+)$", line)
|
|
||||||
if unordered_match:
|
|
||||||
if not in_list or list_type != "unordered":
|
|
||||||
if in_list and list_items:
|
|
||||||
self.add_list_to_doc(doc, list_items, list_type)
|
|
||||||
list_items = []
|
|
||||||
in_list = True
|
|
||||||
list_type = "unordered"
|
|
||||||
indent = len(unordered_match.group(1)) // 2
|
|
||||||
list_items.append((indent, unordered_match.group(2)))
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 处理有序列表
|
|
||||||
ordered_match = re.match(r"^(\s*)\d+[.)]\s+(.+)$", line)
|
|
||||||
if ordered_match:
|
|
||||||
if not in_list or list_type != "ordered":
|
|
||||||
if in_list and list_items:
|
|
||||||
self.add_list_to_doc(doc, list_items, list_type)
|
|
||||||
list_items = []
|
|
||||||
in_list = True
|
|
||||||
list_type = "ordered"
|
|
||||||
indent = len(ordered_match.group(1)) // 2
|
|
||||||
list_items.append((indent, ordered_match.group(2)))
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 处理引用块
|
|
||||||
if line.strip().startswith(">"):
|
|
||||||
# 先处理之前积累的列表
|
|
||||||
if in_list and list_items:
|
|
||||||
self.add_list_to_doc(doc, list_items, list_type)
|
|
||||||
list_items = []
|
|
||||||
in_list = False
|
|
||||||
|
|
||||||
# 收集连续的引用行
|
|
||||||
blockquote_lines = []
|
|
||||||
while i < len(lines) and lines[i].strip().startswith(">"):
|
|
||||||
# 移除开头的 > 和可能的空格
|
|
||||||
quote_line = re.sub(r"^>\s?", "", lines[i])
|
|
||||||
blockquote_lines.append(quote_line)
|
|
||||||
i += 1
|
|
||||||
self.add_blockquote(doc, "\n".join(blockquote_lines))
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 处理水平分割线
|
|
||||||
if re.match(r"^[-*_]{3,}$", line.strip()):
|
|
||||||
# 先处理之前积累的列表
|
|
||||||
if in_list and list_items:
|
|
||||||
self.add_list_to_doc(doc, list_items, list_type)
|
|
||||||
list_items = []
|
|
||||||
in_list = False
|
|
||||||
|
|
||||||
self.add_horizontal_rule(doc)
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 处理空行
|
|
||||||
if not line.strip():
|
|
||||||
# 列表结束
|
|
||||||
if in_list and list_items:
|
|
||||||
self.add_list_to_doc(doc, list_items, list_type)
|
|
||||||
list_items = []
|
|
||||||
in_list = False
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 处理普通段落
|
|
||||||
if in_list and list_items:
|
|
||||||
self.add_list_to_doc(doc, list_items, list_type)
|
|
||||||
list_items = []
|
|
||||||
in_list = False
|
|
||||||
|
|
||||||
self.add_paragraph(doc, line)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# 处理剩余的列表
|
|
||||||
if in_list and list_items:
|
|
||||||
self.add_list_to_doc(doc, list_items, list_type)
|
|
||||||
|
|
||||||
return doc
|
|
||||||
|
|
||||||
def set_document_default_font(self, doc: Document):
|
|
||||||
"""设置文档默认字体,确保中英文都正常显示"""
|
|
||||||
# 设置正文样式
|
|
||||||
style = doc.styles["Normal"]
|
|
||||||
font = style.font
|
|
||||||
font.name = "Times New Roman" # 英文字体
|
|
||||||
font.size = Pt(11)
|
|
||||||
|
|
||||||
# 设置中文字体
|
|
||||||
style._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
|
|
||||||
|
|
||||||
# 设置段落格式
|
|
||||||
paragraph_format = style.paragraph_format
|
|
||||||
paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
|
|
||||||
paragraph_format.space_after = Pt(6)
|
|
||||||
|
|
||||||
def add_heading(self, doc: Document, text: str, level: int):
|
|
||||||
"""添加标题"""
|
|
||||||
# Word 标题级别从 0 开始,Markdown 从 1 开始
|
|
||||||
heading_level = min(level, 9) # Word 最多支持 Heading 9
|
|
||||||
heading = doc.add_heading(level=heading_level)
|
|
||||||
|
|
||||||
# 解析并添加格式化文本
|
|
||||||
self.add_formatted_text(heading, text)
|
|
||||||
|
|
||||||
# 设置中文字体
|
|
||||||
for run in heading.runs:
|
|
||||||
run.font.name = "Times New Roman"
|
|
||||||
run._element.rPr.rFonts.set(qn("w:eastAsia"), "黑体")
|
|
||||||
run.font.color.rgb = RGBColor(0, 0, 0)
|
|
||||||
|
|
||||||
def add_paragraph(self, doc: Document, text: str):
|
|
||||||
"""添加段落,支持内联格式"""
|
|
||||||
paragraph = doc.add_paragraph()
|
|
||||||
self.add_formatted_text(paragraph, text)
|
|
||||||
|
|
||||||
# 设置中文字体
|
|
||||||
for run in paragraph.runs:
|
|
||||||
run.font.name = "Times New Roman"
|
|
||||||
run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
|
|
||||||
|
|
||||||
def add_formatted_text(self, paragraph, text: str):
|
|
||||||
"""
|
|
||||||
解析 Markdown 内联格式并添加到段落
|
|
||||||
支持:粗体、斜体、行内代码、链接、删除线
|
|
||||||
"""
|
|
||||||
# 定义格式化模式
|
|
||||||
patterns = [
|
|
||||||
# 粗斜体 ***text*** 或 ___text___
|
|
||||||
(r"\*\*\*(.+?)\*\*\*|___(.+?)___", {"bold": True, "italic": True}),
|
|
||||||
# 粗体 **text** 或 __text__
|
|
||||||
(r"\*\*(.+?)\*\*|__(.+?)__", {"bold": True}),
|
|
||||||
# 斜体 *text* 或 _text_
|
|
||||||
(
|
|
||||||
r"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|(?<!_)_(?!_)(.+?)(?<!_)_(?!_)",
|
|
||||||
{"italic": True},
|
|
||||||
),
|
|
||||||
# 行内代码 `code`
|
|
||||||
(r"`([^`]+)`", {"code": True}),
|
|
||||||
# 链接 [text](url)
|
|
||||||
(r"\[([^\]]+)\]\(([^)]+)\)", {"link": True}),
|
|
||||||
# 删除线 ~~text~~
|
|
||||||
(r"~~(.+?)~~", {"strike": True}),
|
|
||||||
]
|
|
||||||
|
|
||||||
# 简化处理:逐段解析
|
|
||||||
remaining = text
|
|
||||||
last_end = 0
|
|
||||||
|
|
||||||
# 合并所有匹配项
|
|
||||||
all_matches = []
|
|
||||||
|
|
||||||
for pattern, style in patterns:
|
|
||||||
for match in re.finditer(pattern, text):
|
|
||||||
# 获取匹配的文本内容
|
|
||||||
groups = match.groups()
|
|
||||||
matched_text = next((g for g in groups if g is not None), "")
|
|
||||||
all_matches.append(
|
|
||||||
{
|
|
||||||
"start": match.start(),
|
|
||||||
"end": match.end(),
|
|
||||||
"text": matched_text,
|
|
||||||
"style": style,
|
|
||||||
"full_match": match.group(0),
|
|
||||||
"url": (
|
|
||||||
groups[1] if style.get("link") and len(groups) > 1 else None
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 按位置排序
|
|
||||||
all_matches.sort(key=lambda x: x["start"])
|
|
||||||
|
|
||||||
# 移除重叠的匹配
|
|
||||||
filtered_matches = []
|
|
||||||
last_end = 0
|
|
||||||
for m in all_matches:
|
|
||||||
if m["start"] >= last_end:
|
|
||||||
filtered_matches.append(m)
|
|
||||||
last_end = m["end"]
|
|
||||||
|
|
||||||
# 构建最终文本
|
|
||||||
pos = 0
|
|
||||||
for match in filtered_matches:
|
|
||||||
# 添加匹配前的普通文本
|
|
||||||
if match["start"] > pos:
|
|
||||||
plain_text = text[pos : match["start"]]
|
|
||||||
if plain_text:
|
|
||||||
paragraph.add_run(plain_text)
|
|
||||||
|
|
||||||
# 添加格式化文本
|
|
||||||
style = match["style"]
|
|
||||||
run_text = match["text"]
|
|
||||||
|
|
||||||
if style.get("link"):
|
|
||||||
# 链接处理
|
|
||||||
run = paragraph.add_run(run_text)
|
|
||||||
run.font.color.rgb = RGBColor(0, 0, 255)
|
|
||||||
run.font.underline = True
|
|
||||||
elif style.get("code"):
|
|
||||||
# 行内代码
|
|
||||||
run = paragraph.add_run(run_text)
|
|
||||||
run.font.name = "Consolas"
|
|
||||||
run._element.rPr.rFonts.set(qn("w:eastAsia"), "SimHei")
|
|
||||||
run.font.size = Pt(10)
|
|
||||||
# 添加背景色
|
|
||||||
shading = OxmlElement("w:shd")
|
|
||||||
shading.set(qn("w:fill"), "E8E8E8")
|
|
||||||
run._element.rPr.append(shading)
|
|
||||||
else:
|
|
||||||
run = paragraph.add_run(run_text)
|
|
||||||
if style.get("bold"):
|
|
||||||
run.bold = True
|
|
||||||
if style.get("italic"):
|
|
||||||
run.italic = True
|
|
||||||
if style.get("strike"):
|
|
||||||
run.font.strike = True
|
|
||||||
|
|
||||||
pos = match["end"]
|
|
||||||
|
|
||||||
# 添加剩余的普通文本
|
|
||||||
if pos < len(text):
|
|
||||||
paragraph.add_run(text[pos:])
|
|
||||||
|
|
||||||
def add_code_block(self, doc: Document, code: str, language: str = ""):
|
|
||||||
"""添加代码块,支持语法高亮"""
|
|
||||||
# 语法高亮颜色映射 (基于常见的 IDE 配色)
|
|
||||||
TOKEN_COLORS = {
|
|
||||||
Token.Keyword: RGBColor(0, 92, 197), # macOS 风格蓝 - 关键字
|
|
||||||
Token.Keyword.Constant: RGBColor(0, 92, 197),
|
|
||||||
Token.Keyword.Declaration: RGBColor(0, 92, 197),
|
|
||||||
Token.Keyword.Namespace: RGBColor(0, 92, 197),
|
|
||||||
Token.Keyword.Type: RGBColor(0, 92, 197),
|
|
||||||
Token.Name.Function: RGBColor(0, 0, 0), # 函数名保持黑色
|
|
||||||
Token.Name.Class: RGBColor(38, 82, 120), # 深青蓝 - 类名
|
|
||||||
Token.Name.Decorator: RGBColor(170, 51, 0), # 暖橙 - 装饰器
|
|
||||||
Token.Name.Builtin: RGBColor(0, 110, 71), # 墨绿 - 内置
|
|
||||||
Token.String: RGBColor(196, 26, 22), # 红色 - 字符串
|
|
||||||
Token.String.Doc: RGBColor(109, 120, 133), # 灰 - 文档字符串
|
|
||||||
Token.Comment: RGBColor(109, 120, 133), # 灰 - 注释
|
|
||||||
Token.Comment.Single: RGBColor(109, 120, 133),
|
|
||||||
Token.Comment.Multiline: RGBColor(109, 120, 133),
|
|
||||||
Token.Number: RGBColor(28, 0, 207), # 靛蓝 - 数字
|
|
||||||
Token.Number.Integer: RGBColor(28, 0, 207),
|
|
||||||
Token.Number.Float: RGBColor(28, 0, 207),
|
|
||||||
Token.Operator: RGBColor(90, 99, 120), # 灰蓝 - 运算符
|
|
||||||
Token.Punctuation: RGBColor(0, 0, 0), # 黑色 - 标点
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_token_color(token_type):
|
|
||||||
"""递归查找 token 颜色"""
|
|
||||||
while token_type:
|
|
||||||
if token_type in TOKEN_COLORS:
|
|
||||||
return TOKEN_COLORS[token_type]
|
|
||||||
token_type = token_type.parent
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 添加语言标签(如果有)
|
|
||||||
if language:
|
|
||||||
lang_para = doc.add_paragraph()
|
|
||||||
lang_para.paragraph_format.space_before = Pt(6)
|
|
||||||
lang_para.paragraph_format.space_after = Pt(0)
|
|
||||||
lang_para.paragraph_format.left_indent = Cm(0.5)
|
|
||||||
lang_run = lang_para.add_run(language.upper())
|
|
||||||
lang_run.font.name = "Consolas"
|
|
||||||
lang_run.font.size = Pt(8)
|
|
||||||
lang_run.font.color.rgb = RGBColor(100, 100, 100)
|
|
||||||
lang_run.font.bold = True
|
|
||||||
|
|
||||||
# 添加代码块段落
|
|
||||||
paragraph = doc.add_paragraph()
|
|
||||||
paragraph.paragraph_format.left_indent = Cm(0.5)
|
|
||||||
paragraph.paragraph_format.space_before = Pt(3) if language else Pt(6)
|
|
||||||
paragraph.paragraph_format.space_after = Pt(6)
|
|
||||||
|
|
||||||
# 添加浅灰色背景
|
|
||||||
shading = OxmlElement("w:shd")
|
|
||||||
shading.set(qn("w:fill"), "F7F7F7")
|
|
||||||
paragraph._element.pPr.append(shading)
|
|
||||||
|
|
||||||
# 尝试使用 Pygments 进行语法高亮
|
|
||||||
if PYGMENTS_AVAILABLE and language:
|
|
||||||
try:
|
|
||||||
lexer = get_lexer_by_name(language, stripall=False)
|
|
||||||
except Exception:
|
|
||||||
lexer = TextLexer()
|
|
||||||
|
|
||||||
tokens = list(lex(code, lexer))
|
|
||||||
|
|
||||||
for token_type, token_value in tokens:
|
|
||||||
if not token_value:
|
|
||||||
continue
|
|
||||||
run = paragraph.add_run(token_value)
|
|
||||||
run.font.name = "Consolas"
|
|
||||||
run._element.rPr.rFonts.set(qn("w:eastAsia"), "SimHei")
|
|
||||||
run.font.size = Pt(10)
|
|
||||||
|
|
||||||
# 应用颜色
|
|
||||||
color = get_token_color(token_type)
|
|
||||||
if color:
|
|
||||||
run.font.color.rgb = color
|
|
||||||
|
|
||||||
# 关键字加粗
|
|
||||||
if token_type in Token.Keyword:
|
|
||||||
run.font.bold = True
|
|
||||||
else:
|
|
||||||
# 无语法高亮,纯文本显示
|
|
||||||
run = paragraph.add_run(code)
|
|
||||||
run.font.name = "Consolas"
|
|
||||||
run._element.rPr.rFonts.set(qn("w:eastAsia"), "SimHei")
|
|
||||||
run.font.size = Pt(10)
|
|
||||||
|
|
||||||
def add_table(self, doc: Document, table_lines: List[str]):
|
|
||||||
"""添加表格,支持表头底色与隔行底色"""
|
|
||||||
if len(table_lines) < 2:
|
|
||||||
return
|
|
||||||
|
|
||||||
def _set_cell_shading(cell, fill: str):
|
|
||||||
tc_pr = cell._element.get_or_add_tcPr()
|
|
||||||
shd = OxmlElement("w:shd")
|
|
||||||
shd.set(qn("w:fill"), fill)
|
|
||||||
tc_pr.append(shd)
|
|
||||||
|
|
||||||
header_fill = "F2F2F2"
|
|
||||||
zebra_fill = "FBFBFB"
|
|
||||||
|
|
||||||
# 解析表格数据
|
|
||||||
rows = []
|
|
||||||
for line in table_lines:
|
|
||||||
cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
|
|
||||||
# 跳过分隔行
|
|
||||||
if all(re.fullmatch(r"[-:]+", cell) for cell in cells):
|
|
||||||
continue
|
|
||||||
rows.append(cells)
|
|
||||||
|
|
||||||
if not rows:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 确定列数
|
|
||||||
num_cols = max(len(row) for row in rows)
|
|
||||||
|
|
||||||
# 创建表格
|
|
||||||
table = doc.add_table(rows=len(rows), cols=num_cols)
|
|
||||||
table.style = "Table Grid"
|
|
||||||
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
||||||
|
|
||||||
# 填充表格
|
|
||||||
for row_idx, row_data in enumerate(rows):
|
|
||||||
row = table.rows[row_idx]
|
|
||||||
for col_idx, cell_text in enumerate(row_data):
|
|
||||||
if col_idx < num_cols:
|
|
||||||
cell = row.cells[col_idx]
|
|
||||||
# 清除默认段落
|
|
||||||
cell.paragraphs[0].clear()
|
|
||||||
para = cell.paragraphs[0]
|
|
||||||
para.paragraph_format.space_after = Pt(3)
|
|
||||||
para.paragraph_format.space_before = Pt(1)
|
|
||||||
para.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
||||||
|
|
||||||
self.add_formatted_text(para, cell_text)
|
|
||||||
|
|
||||||
# 设置单元格字体
|
|
||||||
for run in para.runs:
|
|
||||||
run.font.name = "Times New Roman"
|
|
||||||
run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
|
|
||||||
run.font.size = Pt(10)
|
|
||||||
|
|
||||||
# 表头加粗并填充底色
|
|
||||||
if row_idx == 0:
|
|
||||||
for run in para.runs:
|
|
||||||
run.bold = True
|
|
||||||
_set_cell_shading(cell, header_fill)
|
|
||||||
# 隔行底色
|
|
||||||
elif row_idx % 2 == 1:
|
|
||||||
_set_cell_shading(cell, zebra_fill)
|
|
||||||
|
|
||||||
# 统一列对齐为左对齐,避免居中导致阅读困难
|
|
||||||
for row in table.rows:
|
|
||||||
for cell in row.cells:
|
|
||||||
for para in cell.paragraphs:
|
|
||||||
para.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
||||||
|
|
||||||
def add_list_to_doc(
|
|
||||||
self, doc: Document, items: List[Tuple[int, str]], list_type: str
|
|
||||||
):
|
|
||||||
"""添加列表"""
|
|
||||||
for indent, text in items:
|
|
||||||
paragraph = doc.add_paragraph()
|
|
||||||
|
|
||||||
if list_type == "unordered":
|
|
||||||
# 无序列表使用项目符号
|
|
||||||
paragraph.style = "List Bullet"
|
|
||||||
else:
|
|
||||||
# 有序列表使用编号
|
|
||||||
paragraph.style = "List Number"
|
|
||||||
|
|
||||||
# 设置缩进
|
|
||||||
paragraph.paragraph_format.left_indent = Cm(0.5 * (indent + 1))
|
|
||||||
|
|
||||||
# 添加格式化文本
|
|
||||||
self.add_formatted_text(paragraph, text)
|
|
||||||
|
|
||||||
# 设置字体
|
|
||||||
for run in paragraph.runs:
|
|
||||||
run.font.name = "Times New Roman"
|
|
||||||
run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
|
|
||||||
|
|
||||||
def add_horizontal_rule(self, doc: Document):
|
|
||||||
"""添加水平分割线"""
|
|
||||||
paragraph = doc.add_paragraph()
|
|
||||||
paragraph.paragraph_format.space_before = Pt(12)
|
|
||||||
paragraph.paragraph_format.space_after = Pt(12)
|
|
||||||
|
|
||||||
# 添加底部边框作为分割线
|
|
||||||
pPr = paragraph._element.get_or_add_pPr()
|
|
||||||
pBdr = OxmlElement("w:pBdr")
|
|
||||||
bottom = OxmlElement("w:bottom")
|
|
||||||
bottom.set(qn("w:val"), "single")
|
|
||||||
bottom.set(qn("w:sz"), "6")
|
|
||||||
bottom.set(qn("w:space"), "1")
|
|
||||||
bottom.set(qn("w:color"), "auto")
|
|
||||||
pBdr.append(bottom)
|
|
||||||
pPr.append(pBdr)
|
|
||||||
|
|
||||||
def add_blockquote(self, doc: Document, text: str):
|
|
||||||
"""添加引用块,带有左侧边框和灰色背景"""
|
|
||||||
for line in text.split("\n"):
|
|
||||||
paragraph = doc.add_paragraph()
|
|
||||||
paragraph.paragraph_format.left_indent = Cm(1.0)
|
|
||||||
paragraph.paragraph_format.space_before = Pt(3)
|
|
||||||
paragraph.paragraph_format.space_after = Pt(3)
|
|
||||||
|
|
||||||
# 添加左侧边框
|
|
||||||
pPr = paragraph._element.get_or_add_pPr()
|
|
||||||
pBdr = OxmlElement("w:pBdr")
|
|
||||||
left = OxmlElement("w:left")
|
|
||||||
left.set(qn("w:val"), "single")
|
|
||||||
left.set(qn("w:sz"), "24") # 边框粗细
|
|
||||||
left.set(qn("w:space"), "4") # 边框与文字间距
|
|
||||||
left.set(qn("w:color"), "CCCCCC") # 灰色边框
|
|
||||||
pBdr.append(left)
|
|
||||||
pPr.append(pBdr)
|
|
||||||
|
|
||||||
# 添加浅灰色背景
|
|
||||||
shading = OxmlElement("w:shd")
|
|
||||||
shading.set(qn("w:fill"), "F9F9F9")
|
|
||||||
pPr.append(shading)
|
|
||||||
|
|
||||||
# 添加格式化文本
|
|
||||||
self.add_formatted_text(paragraph, line)
|
|
||||||
|
|
||||||
# 设置字体为斜体灰色
|
|
||||||
for run in paragraph.runs:
|
|
||||||
run.font.name = "Times New Roman"
|
|
||||||
run._element.rPr.rFonts.set(qn("w:eastAsia"), "楷体")
|
|
||||||
run.font.color.rgb = RGBColor(85, 85, 85) # 深灰色文字
|
|
||||||
run.italic = True
|
|
||||||
@@ -3,7 +3,7 @@ title: Export to Excel
|
|||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
version: 0.3.6
|
version: 0.3.7
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: 导出为 Excel
|
|||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
version: 0.3.6
|
version: 0.3.7
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
|
||||||
description: 从聊天消息中提取表格并导出为 Excel (.xlsx) 文件,支持智能格式化。
|
description: 从聊天消息中提取表格并导出为 Excel (.xlsx) 文件,支持智能格式化。
|
||||||
"""
|
"""
|
||||||
@@ -48,3 +48,9 @@ GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v0.2.4
|
||||||
|
|
||||||
|
- Removed debug messages from output
|
||||||
|
|||||||
@@ -48,3 +48,9 @@ GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
|||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### v0.2.4
|
||||||
|
|
||||||
|
- 移除输出中的调试信息
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: Flash Card
|
|||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
version: 0.2.2
|
version: 0.2.4
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
|
||||||
description: Quickly generates beautiful flashcards from text, extracting key points and categories.
|
description: Quickly generates beautiful flashcards from text, extracting key points and categories.
|
||||||
"""
|
"""
|
||||||
@@ -147,7 +147,7 @@ class Action:
|
|||||||
if role == "user"
|
if role == "user"
|
||||||
else "Assistant" if role == "assistant" else role
|
else "Assistant" if role == "assistant" else role
|
||||||
)
|
)
|
||||||
aggregated_parts.append(f"[{role_label} Message {i}]\n{text_content}")
|
aggregated_parts.append(f"{text_content}")
|
||||||
|
|
||||||
if not aggregated_parts:
|
if not aggregated_parts:
|
||||||
return body
|
return body
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: 闪记卡 (Flash Card)
|
|||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
version: 0.2.2
|
version: 0.2.4
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
|
||||||
description: 快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。
|
description: 快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。
|
||||||
"""
|
"""
|
||||||
@@ -144,7 +144,7 @@ class Action:
|
|||||||
if role == "user"
|
if role == "user"
|
||||||
else "助手" if role == "assistant" else role
|
else "助手" if role == "assistant" else role
|
||||||
)
|
)
|
||||||
aggregated_parts.append(f"[{role_label} 消息 {i}]\n{text_content}")
|
aggregated_parts.append(f"{text_content}")
|
||||||
|
|
||||||
if not aggregated_parts:
|
if not aggregated_parts:
|
||||||
return body
|
return body
|
||||||
@@ -63,3 +63,9 @@ data
|
|||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v1.3.2
|
||||||
|
|
||||||
|
- Removed debug messages from output
|
||||||
|
|||||||
@@ -63,3 +63,9 @@ data
|
|||||||
## 📄 许可证
|
## 📄 许可证
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### v1.3.2
|
||||||
|
|
||||||
|
- 移除输出中的调试信息
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: 📊 Smart Infographic (AntV)
|
|||||||
author: jeff
|
author: jeff
|
||||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
|
||||||
version: 1.3.0
|
version: 1.3.2
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -961,9 +961,7 @@ class Action:
|
|||||||
if role == "user"
|
if role == "user"
|
||||||
else "Assistant" if role == "assistant" else role
|
else "Assistant" if role == "assistant" else role
|
||||||
)
|
)
|
||||||
aggregated_parts.append(
|
aggregated_parts.append(f"{text_content}")
|
||||||
f"[{role_label} Message {i}]\n{text_content}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not aggregated_parts:
|
if not aggregated_parts:
|
||||||
raise ValueError("Unable to get valid user message content.")
|
raise ValueError("Unable to get valid user message content.")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: 📊 智能信息图 (AntV Infographic)
|
|||||||
author: jeff
|
author: jeff
|
||||||
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
author_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
|
||||||
version: 1.3.0
|
version: 1.3.2
|
||||||
description: 基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。
|
description: 基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -1026,7 +1026,7 @@ class Action:
|
|||||||
if role == "user"
|
if role == "user"
|
||||||
else "助手" if role == "assistant" else role
|
else "助手" if role == "assistant" else role
|
||||||
)
|
)
|
||||||
aggregated_parts.append(f"[{role_label} 消息 {i}]\n{text_content}")
|
aggregated_parts.append(f"{text_content}")
|
||||||
|
|
||||||
if not aggregated_parts:
|
if not aggregated_parts:
|
||||||
raise ValueError("无法获取有效的用户消息内容。")
|
raise ValueError("无法获取有效的用户消息内容。")
|
||||||
@@ -24,7 +24,7 @@ if not API_KEY or not BASE_URL:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# =================================================================
|
# =================================================================
|
||||||
# Prompts (Extracted from 信息图.py)
|
# Prompts (Extracted from infographic_cn.py)
|
||||||
# =================================================================
|
# =================================================================
|
||||||
|
|
||||||
SYSTEM_PROMPT_INFOGRAPHIC_ASSISTANT = """
|
SYSTEM_PROMPT_INFOGRAPHIC_ASSISTANT = """
|
||||||
|
|||||||
257
plugins/actions/js-render-poc/js_render_poc.py
Normal file
257
plugins/actions/js-render-poc/js_render_poc.py
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
"""
|
||||||
|
title: JS Render PoC
|
||||||
|
author: Fu-Jie
|
||||||
|
version: 0.6.0
|
||||||
|
description: Proof of concept for JS rendering + API write-back pattern. JS renders SVG and updates message via API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Optional, Callable, Awaitable, Any
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from fastapi import Request
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Action:
|
||||||
|
class Valves(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.valves = self.Valves()
|
||||||
|
|
||||||
|
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||||
|
"""Extract chat_id from body or metadata"""
|
||||||
|
if isinstance(body, dict):
|
||||||
|
# body["chat_id"] 是 chat_id
|
||||||
|
chat_id = body.get("chat_id")
|
||||||
|
if isinstance(chat_id, str) and chat_id.strip():
|
||||||
|
return chat_id.strip()
|
||||||
|
|
||||||
|
body_metadata = body.get("metadata", {})
|
||||||
|
if isinstance(body_metadata, dict):
|
||||||
|
chat_id = body_metadata.get("chat_id")
|
||||||
|
if isinstance(chat_id, str) and chat_id.strip():
|
||||||
|
return chat_id.strip()
|
||||||
|
|
||||||
|
if isinstance(metadata, dict):
|
||||||
|
chat_id = metadata.get("chat_id")
|
||||||
|
if isinstance(chat_id, str) and chat_id.strip():
|
||||||
|
return chat_id.strip()
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str:
|
||||||
|
"""Extract message_id from body or metadata"""
|
||||||
|
if isinstance(body, dict):
|
||||||
|
# body["id"] 是 message_id
|
||||||
|
message_id = body.get("id")
|
||||||
|
if isinstance(message_id, str) and message_id.strip():
|
||||||
|
return message_id.strip()
|
||||||
|
|
||||||
|
body_metadata = body.get("metadata", {})
|
||||||
|
if isinstance(body_metadata, dict):
|
||||||
|
message_id = body_metadata.get("message_id")
|
||||||
|
if isinstance(message_id, str) and message_id.strip():
|
||||||
|
return message_id.strip()
|
||||||
|
|
||||||
|
if isinstance(metadata, dict):
|
||||||
|
message_id = metadata.get("message_id")
|
||||||
|
if isinstance(message_id, str) and message_id.strip():
|
||||||
|
return message_id.strip()
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
async def action(
|
||||||
|
self,
|
||||||
|
body: dict,
|
||||||
|
__user__: dict = None,
|
||||||
|
__event_emitter__=None,
|
||||||
|
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
|
||||||
|
__metadata__: Optional[dict] = None,
|
||||||
|
__request__: Request = None,
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
PoC: Use __event_call__ to execute JS that renders SVG and updates message via API.
|
||||||
|
"""
|
||||||
|
# 准备调试数据
|
||||||
|
body_for_log = {}
|
||||||
|
for k, v in body.items():
|
||||||
|
if k == "messages":
|
||||||
|
body_for_log[k] = f"[{len(v)} messages]"
|
||||||
|
else:
|
||||||
|
body_for_log[k] = v
|
||||||
|
|
||||||
|
body_json = json.dumps(body_for_log, ensure_ascii=False, default=str)
|
||||||
|
metadata_json = (
|
||||||
|
json.dumps(__metadata__, ensure_ascii=False, default=str)
|
||||||
|
if __metadata__
|
||||||
|
else "null"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 转义 JSON 中的特殊字符以便嵌入 JS
|
||||||
|
body_json_escaped = (
|
||||||
|
body_json.replace("\\", "\\\\").replace("`", "\\`").replace("${", "\\${")
|
||||||
|
)
|
||||||
|
metadata_json_escaped = (
|
||||||
|
metadata_json.replace("\\", "\\\\")
|
||||||
|
.replace("`", "\\`")
|
||||||
|
.replace("${", "\\${")
|
||||||
|
)
|
||||||
|
|
||||||
|
chat_id = self._extract_chat_id(body, __metadata__)
|
||||||
|
message_id = self._extract_message_id(body, __metadata__)
|
||||||
|
|
||||||
|
unique_id = f"poc_{int(time.time() * 1000)}"
|
||||||
|
|
||||||
|
if __event_emitter__:
|
||||||
|
await __event_emitter__(
|
||||||
|
{
|
||||||
|
"type": "status",
|
||||||
|
"data": {"description": "🔄 正在渲染...", "done": False},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if __event_call__:
|
||||||
|
await __event_call__(
|
||||||
|
{
|
||||||
|
"type": "execute",
|
||||||
|
"data": {
|
||||||
|
"code": f"""
|
||||||
|
(async function() {{
|
||||||
|
const uniqueId = "{unique_id}";
|
||||||
|
const chatId = "{chat_id}";
|
||||||
|
const messageId = "{message_id}";
|
||||||
|
|
||||||
|
// ===== DEBUG: 输出 Python 端的数据 =====
|
||||||
|
console.log("[JS Render PoC] ===== DEBUG INFO (from Python) =====");
|
||||||
|
console.log("[JS Render PoC] body:", `{body_json_escaped}`);
|
||||||
|
console.log("[JS Render PoC] __metadata__:", `{metadata_json_escaped}`);
|
||||||
|
console.log("[JS Render PoC] Extracted: chatId=", chatId, "messageId=", messageId);
|
||||||
|
console.log("[JS Render PoC] =========================================");
|
||||||
|
|
||||||
|
try {{
|
||||||
|
console.log("[JS Render PoC] Starting SVG render...");
|
||||||
|
|
||||||
|
// Create SVG
|
||||||
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||||
|
svg.setAttribute("width", "200");
|
||||||
|
svg.setAttribute("height", "200");
|
||||||
|
svg.setAttribute("viewBox", "0 0 200 200");
|
||||||
|
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
||||||
|
|
||||||
|
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
||||||
|
const gradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
|
||||||
|
gradient.setAttribute("id", "grad-" + uniqueId);
|
||||||
|
gradient.innerHTML = `
|
||||||
|
<stop offset="0%" style="stop-color:#1e88e5;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#43a047;stop-opacity:1" />
|
||||||
|
`;
|
||||||
|
defs.appendChild(gradient);
|
||||||
|
svg.appendChild(defs);
|
||||||
|
|
||||||
|
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
||||||
|
circle.setAttribute("cx", "100");
|
||||||
|
circle.setAttribute("cy", "100");
|
||||||
|
circle.setAttribute("r", "80");
|
||||||
|
circle.setAttribute("fill", `url(#grad-${{uniqueId}})`);
|
||||||
|
svg.appendChild(circle);
|
||||||
|
|
||||||
|
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
||||||
|
text.setAttribute("x", "100");
|
||||||
|
text.setAttribute("y", "105");
|
||||||
|
text.setAttribute("text-anchor", "middle");
|
||||||
|
text.setAttribute("fill", "white");
|
||||||
|
text.setAttribute("font-size", "16");
|
||||||
|
text.setAttribute("font-weight", "bold");
|
||||||
|
text.textContent = "PoC Success!";
|
||||||
|
svg.appendChild(text);
|
||||||
|
|
||||||
|
// Convert to Base64 Data URI
|
||||||
|
const svgData = new XMLSerializer().serializeToString(svg);
|
||||||
|
const base64 = btoa(unescape(encodeURIComponent(svgData)));
|
||||||
|
const dataUri = "data:image/svg+xml;base64," + base64;
|
||||||
|
|
||||||
|
console.log("[JS Render PoC] SVG rendered, data URI length:", dataUri.length);
|
||||||
|
|
||||||
|
// Call API - 完全替换方案(更稳定)
|
||||||
|
if (chatId && messageId) {{
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
|
// 1. 获取当前消息内容
|
||||||
|
const getResponse = await fetch(`/api/v1/chats/${{chatId}}`, {{
|
||||||
|
method: "GET",
|
||||||
|
headers: {{ "Authorization": `Bearer ${{token}}` }}
|
||||||
|
}});
|
||||||
|
|
||||||
|
if (!getResponse.ok) {{
|
||||||
|
throw new Error("Failed to get chat data: " + getResponse.status);
|
||||||
|
}}
|
||||||
|
|
||||||
|
const chatData = await getResponse.json();
|
||||||
|
console.log("[JS Render PoC] Got chat data");
|
||||||
|
|
||||||
|
let originalContent = "";
|
||||||
|
if (chatData.chat && chatData.chat.messages) {{
|
||||||
|
const targetMsg = chatData.chat.messages.find(m => m.id === messageId);
|
||||||
|
if (targetMsg && targetMsg.content) {{
|
||||||
|
originalContent = targetMsg.content;
|
||||||
|
console.log("[JS Render PoC] Found original content, length:", originalContent.length);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
// 2. 移除已存在的 PoC 图片(如果有的话)
|
||||||
|
// 匹配  格式
|
||||||
|
const pocImagePattern = /\\n*!\\[JS Render PoC[^\\]]*\\]\\(data:image\\/svg\\+xml;base64,[^)]+\\)/g;
|
||||||
|
let cleanedContent = originalContent.replace(pocImagePattern, "");
|
||||||
|
// 移除可能残留的多余空行
|
||||||
|
cleanedContent = cleanedContent.replace(/\\n{{3,}}/g, "\\n\\n").trim();
|
||||||
|
|
||||||
|
if (cleanedContent !== originalContent) {{
|
||||||
|
console.log("[JS Render PoC] Removed existing PoC image(s)");
|
||||||
|
}}
|
||||||
|
|
||||||
|
// 3. 添加新的 Markdown 图片
|
||||||
|
const markdownImage = ``;
|
||||||
|
const newContent = cleanedContent + "\\n\\n" + markdownImage;
|
||||||
|
|
||||||
|
// 3. 使用 chat:message 完全替换
|
||||||
|
const updateResponse = await fetch(`/api/v1/chats/${{chatId}}/messages/${{messageId}}/event`, {{
|
||||||
|
method: "POST",
|
||||||
|
headers: {{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${{token}}`
|
||||||
|
}},
|
||||||
|
body: JSON.stringify({{
|
||||||
|
type: "chat:message",
|
||||||
|
data: {{ content: newContent }}
|
||||||
|
}})
|
||||||
|
}});
|
||||||
|
|
||||||
|
if (updateResponse.ok) {{
|
||||||
|
console.log("[JS Render PoC] ✅ Message updated successfully!");
|
||||||
|
}} else {{
|
||||||
|
console.error("[JS Render PoC] API error:", updateResponse.status, await updateResponse.text());
|
||||||
|
}}
|
||||||
|
}} else {{
|
||||||
|
console.warn("[JS Render PoC] ⚠️ Missing chatId or messageId, cannot persist.");
|
||||||
|
}}
|
||||||
|
|
||||||
|
}} catch (error) {{
|
||||||
|
console.error("[JS Render PoC] Error:", error);
|
||||||
|
}}
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if __event_emitter__:
|
||||||
|
await __event_emitter__(
|
||||||
|
{"type": "status", "data": {"description": "✅ 渲染完成", "done": True}}
|
||||||
|
)
|
||||||
|
|
||||||
|
return body
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Smart Mind Map - Mind Mapping Generation Plugin
|
# Smart Mind Map - Mind Mapping Generation Plugin
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.8.0 | **License:** MIT
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.8.2 | **License:** MIT
|
||||||
|
|
||||||
> **Important**: To ensure the maintainability and usability of all plugins, each plugin should be accompanied by clear and comprehensive documentation to ensure its functionality, configuration, and usage are well explained.
|
> **Important**: To ensure the maintainability and usability of all plugins, each plugin should be accompanied by clear and comprehensive documentation to ensure its functionality, configuration, and usage are well explained.
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ Smart Mind Map is a powerful OpenWebUI action plugin that intelligently analyzes
|
|||||||
|
|
||||||
### 1. Plugin Installation
|
### 1. Plugin Installation
|
||||||
|
|
||||||
1. Download the `思维导图.py` file to your local computer
|
1. Download the `smart_mind_map_cn.py` file to your local computer
|
||||||
2. In OpenWebUI Admin Settings, find the "Plugins" section
|
2. In OpenWebUI Admin Settings, find the "Plugins" section
|
||||||
3. Select "Actions" type
|
3. Select "Actions" type
|
||||||
4. Upload the downloaded file
|
4. Upload the downloaded file
|
||||||
@@ -277,7 +277,11 @@ This plugin uses only OpenWebUI's built-in dependencies. **No additional package
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
### v0.8.0 (Current Version)
|
### v0.8.2
|
||||||
|
|
||||||
|
- Removed debug messages from output
|
||||||
|
|
||||||
|
### v0.8.0 (Previous Version)
|
||||||
|
|
||||||
**Major Features:**
|
**Major Features:**
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 思维导图 - 思维导图生成插件
|
# 思维导图 - 思维导图生成插件
|
||||||
|
|
||||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 0.8.0 | **许可证:** MIT
|
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 0.8.2 | **许可证:** MIT
|
||||||
|
|
||||||
> **重要提示**:为了确保所有插件的可维护性和易用性,每个插件都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
|
> **重要提示**:为了确保所有插件的可维护性和易用性,每个插件都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
### 1. 插件安装
|
### 1. 插件安装
|
||||||
|
|
||||||
1. 下载 `思维导图.py` 文件到本地
|
1. 下载 `smart_mind_map_cn.py` 文件到本地
|
||||||
2. 在 OpenWebUI 管理员设置中找到"插件"(Plugins)部分
|
2. 在 OpenWebUI 管理员设置中找到"插件"(Plugins)部分
|
||||||
3. 选择"动作"(Actions)类型
|
3. 选择"动作"(Actions)类型
|
||||||
4. 上传下载的文件
|
4. 上传下载的文件
|
||||||
@@ -277,7 +277,11 @@
|
|||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
### v0.8.0(当前版本)
|
### v0.8.2
|
||||||
|
|
||||||
|
- 移除输出中的调试信息
|
||||||
|
|
||||||
|
### v0.8.0 (Previous Version)
|
||||||
|
|
||||||
**主要功能:**
|
**主要功能:**
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: Smart Mind Map
|
|||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
version: 0.8.0
|
version: 0.8.2
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
|
||||||
description: Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.
|
description: Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.
|
||||||
"""
|
"""
|
||||||
@@ -960,7 +960,7 @@ class Action:
|
|||||||
if role == "user"
|
if role == "user"
|
||||||
else "Assistant" if role == "assistant" else role
|
else "Assistant" if role == "assistant" else role
|
||||||
)
|
)
|
||||||
aggregated_parts.append(f"[{role_label} Message {i}]\n{text_content}")
|
aggregated_parts.append(f"{text_content}")
|
||||||
|
|
||||||
if not aggregated_parts:
|
if not aggregated_parts:
|
||||||
error_message = "Unable to retrieve valid user message content."
|
error_message = "Unable to retrieve valid user message content."
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: 思维导图
|
|||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
version: 0.8.0
|
version: 0.8.2
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
|
||||||
description: 智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。
|
description: 智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。
|
||||||
"""
|
"""
|
||||||
@@ -957,7 +957,7 @@ class Action:
|
|||||||
if role == "user"
|
if role == "user"
|
||||||
else "助手" if role == "assistant" else role
|
else "助手" if role == "assistant" else role
|
||||||
)
|
)
|
||||||
aggregated_parts.append(f"[{role_label} 消息 {i}]\n{text_content}")
|
aggregated_parts.append(f"{text_content}")
|
||||||
|
|
||||||
if not aggregated_parts:
|
if not aggregated_parts:
|
||||||
error_message = "无法获取有效的用户消息内容。"
|
error_message = "无法获取有效的用户消息内容。"
|
||||||
@@ -22,3 +22,9 @@ GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v0.1.2
|
||||||
|
|
||||||
|
- Removed debug messages from output
|
||||||
|
|||||||
@@ -22,3 +22,9 @@ GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
|||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### v0.1.2
|
||||||
|
|
||||||
|
- 移除输出中的调试信息
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: Deep Reading & Summary
|
|||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie
|
author_url: https://github.com/Fu-Jie
|
||||||
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
funding_url: https://github.com/Fu-Jie/awesome-openwebui
|
||||||
version: 0.1.0
|
version: 0.1.2
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAxMmgtNSIvPjxwYXRoIGQ9Ik0xNSA4aC01Ii8+PHBhdGggZD0iTTE5IDE3VjVhMiAyIDAgMCAwLTItMkg0Ii8+PHBhdGggZD0iTTggMjFoMTJhMiAyIDAgMCAwIDItMnYtMWExIDEgMCAwIDAtMS0xSDExYTEgMSAwIDAgMC0xIDF2MWEyIDIgMCAxIDEtNCAwVjVhMiAyIDAgMSAwLTQgMHYyYTEgMSAwIDAgMCAxIDFoMyIvPjwvc3ZnPg==
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAxMmgtNSIvPjxwYXRoIGQ9Ik0xNSA4aC01Ii8+PHBhdGggZD0iTTE5IDE3VjVhMiAyIDAgMCAwLTItMkg0Ii8+PHBhdGggZD0iTTggMjFoMTJhMiAyIDAgMCAwIDItMnYtMWExIDEgMCAwIDAtMS0xSDExYTEgMSAwIDAgMC0xIDF2MWEyIDIgMCAxIDEtNCAwVjVhMiAyIDAgMSAwLTQgMHYyYTEgMSAwIDAgMCAxIDFoMyIvPjwvc3ZnPg==
|
||||||
description: Provides deep reading analysis and summarization for long texts.
|
description: Provides deep reading analysis and summarization for long texts.
|
||||||
requirements: jinja2, markdown
|
requirements: jinja2, markdown
|
||||||
@@ -529,9 +529,7 @@ class Action:
|
|||||||
if role == "user"
|
if role == "user"
|
||||||
else "Assistant" if role == "assistant" else role
|
else "Assistant" if role == "assistant" else role
|
||||||
)
|
)
|
||||||
aggregated_parts.append(
|
aggregated_parts.append(f"{text_content}")
|
||||||
f"[{role_label} Message {i}]\n{text_content}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not aggregated_parts:
|
if not aggregated_parts:
|
||||||
raise ValueError("Unable to get valid user message content.")
|
raise ValueError("Unable to get valid user message content.")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
title: 精读 (Deep Reading)
|
title: 精读 (Deep Reading)
|
||||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIxIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjNDI4NWY0Ii8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjMWU4OGU1Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTYgMmg4bDYgNnYxMmEyIDIgMCAwIDEtMiAySDZhMiAyIDAgMCAxLTItMlY0YTIgMiAwIDAgMSAyLTJ6IiBmaWxsPSJ1cmwoI2cpIi8+PHBhdGggZD0iTTE0IDJsNiA2aC02eiIgZmlsbD0iIzFlODhlNSIgb3BhY2l0eT0iMC42Ii8+PGxpbmUgeDE9IjgiIHkxPSIxMyIgeDI9IjE2IiB5Mj0iMTMiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48bGluZSB4MT0iOCIgeTE9IjE3IiB4Mj0iMTQiIHkyPSIxNyIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMTgiIHI9IjMiIGZpbGw9IiNmZmQ3MDAiLz48cGF0aCBkPSJNMTYgMTZsMS41IDEuNSIgc3Ryb2tlPSIjNDI4NWY0IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg==
|
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIxIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjNDI4NWY0Ii8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjMWU4OGU1Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTYgMmg4bDYgNnYxMmEyIDIgMCAwIDEtMiAySDZhMiAyIDAgMCAxLTItMlY0YTIgMiAwIDAgMSAyLTJ6IiBmaWxsPSJ1cmwoI2cpIi8+PHBhdGggZD0iTTE0IDJsNiA2aC02eiIgZmlsbD0iIzFlODhlNSIgb3BhY2l0eT0iMC42Ii8+PGxpbmUgeDE9IjgiIHkxPSIxMyIgeDI9IjE2IiB5Mj0iMTMiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48bGluZSB4MT0iOCIgeTE9IjE3IiB4Mj0iMTQiIHkyPSIxNyIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMTgiIHI9IjMiIGZpbGw9IiNmZmQ3MDAiLz48cGF0aCBkPSJNMTYgMTZsMS41IDEuNSIgc3Ryb2tlPSIjNDI4NWY0IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg==
|
||||||
version: 0.1.0
|
version: 0.1.2
|
||||||
description: 深度分析长篇文本,提炼详细摘要、关键信息点和可执行的行动建议,适合工作和学习场景。
|
description: 深度分析长篇文本,提炼详细摘要、关键信息点和可执行的行动建议,适合工作和学习场景。
|
||||||
requirements: jinja2, markdown
|
requirements: jinja2, markdown
|
||||||
"""
|
"""
|
||||||
@@ -528,7 +528,7 @@ class Action:
|
|||||||
if role == "user"
|
if role == "user"
|
||||||
else "助手" if role == "assistant" else role
|
else "助手" if role == "assistant" else role
|
||||||
)
|
)
|
||||||
aggregated_parts.append(f"[{role_label} 消息 {i}]\n{text_content}")
|
aggregated_parts.append(f"{text_content}")
|
||||||
|
|
||||||
if not aggregated_parts:
|
if not aggregated_parts:
|
||||||
raise ValueError("无法获取有效的用户消息内容。")
|
raise ValueError("无法获取有效的用户消息内容。")
|
||||||
@@ -41,19 +41,19 @@
|
|||||||
|
|
||||||
## Actions (动作插件)
|
## Actions (动作插件)
|
||||||
|
|
||||||
1. **📊 智能信息图 (infographic/信息图.py)** - 基于 AntV Infographic 的智能信息图生成插件,支持多种专业模板与 SVG/PNG 下载
|
1. **📊 智能信息图 (infographic/infographic_cn.py)** - 基于 AntV Infographic 的智能信息图生成插件,支持多种专业模板与 SVG/PNG 下载
|
||||||
|
|
||||||
2. **🧠 思维导图 (smart-mind-map/思维导图.py)** - 智能分析文本内容生成交互式思维导图,帮助用户结构化和可视化知识
|
2. **🧠 思维导图 (smart-mind-map/smart_mind_map_cn.py)** - 智能分析文本内容生成交互式思维导图,帮助用户结构化和可视化知识
|
||||||
|
|
||||||
3. **📊 导出为 Excel (export_to_excel/导出为Excel.py)** - 将对话历史中的 Markdown 表格导出为符合中国规范的 Excel 文件
|
3. **📊 导出为 Excel (export_to_excel/export_to_excel_cn.py)** - 将对话历史中的 Markdown 表格导出为符合中国规范的 Excel 文件
|
||||||
|
|
||||||
4. **⚡ 闪记卡 (knowledge-card/闪记卡.py)** - 快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类
|
4. **⚡ 闪记卡 (flash-card/flash_card_cn.py)** - 快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类
|
||||||
|
|
||||||
5. **📖 精读 (summary/精读.py)** - 深度分析长篇文本,提炼详细摘要、关键信息点和可执行的行动建议
|
5. **📖 精读 (summary/summary_cn.py)** - 深度分析长篇文本,提炼详细摘要、关键信息点和可执行的行动建议
|
||||||
|
|
||||||
## Filters (过滤器插件)
|
## Filters (过滤器插件)
|
||||||
|
|
||||||
1. **🔄 异步上下文压缩 (async-context-compression/异步上下文压缩.py)** - 异步生成摘要并压缩对话历史,支持数据库持久化存储
|
1. **🔄 异步上下文压缩 (async-context-compression/async_context_compression_cn.py)** - 异步生成摘要并压缩对话历史,支持数据库持久化存储
|
||||||
|
|
||||||
2. **✨ 上下文增强过滤器 (context_enhancement_filter/context_enhancement_filter.py)** - 增强请求上下文和优化模型功能,包含环境变量管理、模型功能适配和内容清洗
|
2. **✨ 上下文增强过滤器 (context_enhancement_filter/context_enhancement_filter.py)** - 增强请求上下文和优化模型功能,包含环境变量管理、模型功能适配和内容清洗
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user