feat: 新增插件系统、多种插件类型、开发指南及多语言文档。
This commit is contained in:
210
plugins/actions/smart-mind-map/README.md
Normal file
210
plugins/actions/smart-mind-map/README.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Smart Mind Map - Mind Mapping Generation Plugin
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.7.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.
|
||||
|
||||
Smart Mind Map is a powerful OpenWebUI action plugin that intelligently analyzes long-form text content and automatically generates interactive mind maps, helping users structure and visualize knowledge.
|
||||
|
||||
---
|
||||
|
||||
## Core Features
|
||||
|
||||
- ✅ **Intelligent Text Analysis**: Automatically identifies core themes, key concepts, and hierarchical structures
|
||||
- ✅ **Interactive Visualization**: Generates beautiful interactive mind maps based on Markmap.js
|
||||
- ✅ **Multi-language Support**: Automatically adjusts output based on user language
|
||||
- ✅ **Real-time Rendering**: Renders mind maps directly in the chat interface without navigation
|
||||
- ✅ **Export Capabilities**: Supports copying SVG code and Markdown source
|
||||
- ✅ **Customizable Configuration**: Configurable LLM model, minimum text length, and other parameters
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Text Extraction**: Extracts text content from user messages (automatically filters HTML code blocks)
|
||||
2. **Intelligent Analysis**: Analyzes text structure using the configured LLM model
|
||||
3. **Markdown Generation**: Converts analysis results to Markmap-compatible Markdown format
|
||||
4. **Visual Rendering**: Renders the mind map using Markmap.js in an HTML template
|
||||
5. **Interactive Display**: Presents the mind map to users in an interactive format within the chat interface
|
||||
|
||||
---
|
||||
|
||||
## Installation and Configuration
|
||||
|
||||
### 1. Plugin Installation
|
||||
|
||||
1. Download the `思维导图.py` file to your local computer
|
||||
2. In OpenWebUI Admin Settings, find the "Plugins" section
|
||||
3. Select "Actions" type
|
||||
4. Upload the downloaded file
|
||||
5. Refresh the page, and the plugin will be available
|
||||
|
||||
### 2. Model Configuration
|
||||
|
||||
The plugin requires access to an LLM model for text analysis. Please ensure:
|
||||
|
||||
- Your OpenWebUI instance has at least one available LLM model configured
|
||||
- Recommended to use fast, economical models (e.g., `gemini-2.5-flash`) for the best experience
|
||||
- Configure the `LLM_MODEL_ID` parameter in the plugin settings
|
||||
|
||||
### 3. Plugin Activation
|
||||
|
||||
Select the "Smart Mind Map" action plugin in chat settings to enable it.
|
||||
|
||||
---
|
||||
|
||||
## Configuration Parameters
|
||||
|
||||
You can adjust the following parameters in the plugin's settings (Valves):
|
||||
|
||||
| Parameter | Default | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `show_status` | `true` | Whether to display operation status updates in the chat interface (e.g., "Analyzing..."). |
|
||||
| `LLM_MODEL_ID` | `gemini-2.5-flash` | LLM model ID for text analysis. Recommended to use fast and economical models. |
|
||||
| `MIN_TEXT_LENGTH` | `100` | Minimum text length (in characters) required for mind map analysis. Text that's too short cannot generate valid mind maps. |
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
1. Enable the "Smart Mind Map" action in chat settings
|
||||
2. Input or paste long-form text content (at least 100 characters) in the conversation
|
||||
3. After sending the message, the plugin will automatically analyze and generate a mind map
|
||||
4. The mind map will be rendered directly in the chat interface
|
||||
|
||||
### Usage Example
|
||||
|
||||
**Input Text:**
|
||||
```
|
||||
Artificial Intelligence (AI) is a branch of computer science dedicated to creating systems capable of performing tasks that typically require human intelligence.
|
||||
Main application areas include:
|
||||
1. Machine Learning - Enables computers to learn from data
|
||||
2. Natural Language Processing - Understanding and generating human language
|
||||
3. Computer Vision - Recognizing and processing images
|
||||
4. Robotics - Creating intelligent systems that can interact with the physical world
|
||||
```
|
||||
|
||||
**Generated Result:**
|
||||
The plugin will generate an interactive mind map centered on "Artificial Intelligence", including major application areas and their sub-concepts.
|
||||
|
||||
### Export Features
|
||||
|
||||
Generated mind maps support two export methods:
|
||||
|
||||
1. **Copy SVG Code**: Click the "Copy SVG Code" button to copy the mind map in SVG format to the clipboard
|
||||
2. **Copy Markdown**: Click the "Copy Markdown" button to copy the raw Markdown format to the clipboard
|
||||
|
||||
---
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
### Frontend Rendering
|
||||
|
||||
- **Markmap.js**: Open-source mind mapping rendering engine
|
||||
- **D3.js**: Data visualization foundation library
|
||||
- **Responsive Design**: Adapts to different screen sizes
|
||||
|
||||
### Backend Processing
|
||||
|
||||
- **LLM Integration**: Calls configured models via `generate_chat_completion`
|
||||
- **Text Preprocessing**: Automatically filters HTML code blocks, extracts plain text content
|
||||
- **Format Conversion**: Converts LLM output to Markmap-compatible Markdown format
|
||||
|
||||
### Security
|
||||
|
||||
- **XSS Protection**: Automatically escapes `</script>` tags to prevent script injection
|
||||
- **Input Validation**: Checks text length to avoid invalid requests
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Plugin Won't Start
|
||||
|
||||
**Solution:**
|
||||
- Check OpenWebUI logs for error messages
|
||||
- Confirm the plugin is correctly uploaded and enabled
|
||||
- Verify OpenWebUI version supports action plugins
|
||||
|
||||
### Issue: Text Content Too Short
|
||||
|
||||
**Symptom:** Prompt shows "Text content is too short for effective analysis"
|
||||
|
||||
**Solution:**
|
||||
- Ensure input text contains at least 100 characters (default configuration)
|
||||
- Lower the `MIN_TEXT_LENGTH` parameter value in plugin settings
|
||||
- Provide more detailed, structured text content
|
||||
|
||||
### Issue: Mind Map Not Generated
|
||||
|
||||
**Solution:**
|
||||
- Check if `LLM_MODEL_ID` is configured correctly
|
||||
- Confirm the configured model is available in OpenWebUI
|
||||
- Review backend logs for LLM call failures
|
||||
- Verify user has sufficient permissions to access the configured model
|
||||
|
||||
### Issue: Mind Map Display Error
|
||||
|
||||
**Symptom:** Shows "⚠️ Mind map rendering failed"
|
||||
|
||||
**Solution:**
|
||||
- Check browser console for error messages
|
||||
- Confirm Markmap.js and D3.js libraries are loading correctly
|
||||
- Verify generated Markdown format conforms to Markmap specifications
|
||||
- Try refreshing the page to re-render
|
||||
|
||||
### Issue: Export Function Not Working
|
||||
|
||||
**Solution:**
|
||||
- Confirm browser supports Clipboard API
|
||||
- Check if browser is blocking clipboard access permissions
|
||||
- Use modern browsers (Chrome, Firefox, Edge, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Text Preparation**
|
||||
- Provide text content with clear structure and distinct hierarchies
|
||||
- Use paragraphs, lists, and other formatting to help LLM understand text structure
|
||||
- Avoid excessively lengthy or unstructured text
|
||||
|
||||
2. **Model Selection**
|
||||
- For daily use, recommend fast models like `gemini-2.5-flash`
|
||||
- For complex text analysis, use more powerful models (e.g., GPT-4)
|
||||
- Balance speed and analysis quality based on needs
|
||||
|
||||
3. **Performance Optimization**
|
||||
- Set `MIN_TEXT_LENGTH` appropriately to avoid processing text that's too short
|
||||
- For particularly long texts, consider summarizing before generating mind maps
|
||||
- Disable `show_status` in production environments to reduce interface updates
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### v0.7.2 (Current Version)
|
||||
- Optimized text extraction logic, automatically filters HTML code blocks
|
||||
- Improved error handling and user feedback
|
||||
- Enhanced export functionality compatibility
|
||||
- Optimized UI styling and interactive experience
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This plugin is released under the MIT License.
|
||||
|
||||
## Contributing
|
||||
|
||||
Welcome to submit issue reports and improvement suggestions! Please visit the project repository: [awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
---
|
||||
|
||||
## Related Resources
|
||||
|
||||
- [Markmap Official Website](https://markmap.js.org/)
|
||||
- [OpenWebUI Documentation](https://docs.openwebui.com/)
|
||||
- [D3.js Official Website](https://d3js.org/)
|
||||
210
plugins/actions/smart-mind-map/README_CN.md
Normal file
210
plugins/actions/smart-mind-map/README_CN.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# 智绘心图 - 思维导图生成插件
|
||||
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 0.7.2 | **许可证:** MIT
|
||||
|
||||
> **重要提示**:为了确保所有插件的可维护性和易用性,每个插件都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。
|
||||
|
||||
智绘心图是一个强大的 OpenWebUI 动作插件,能够智能分析长篇文本内容,自动生成交互式思维导图,帮助用户结构化和可视化知识。
|
||||
|
||||
---
|
||||
|
||||
## 核心特性
|
||||
|
||||
- ✅ **智能文本分析**: 自动识别文本的核心主题、关键概念和层次结构
|
||||
- ✅ **交互式可视化**: 基于 Markmap.js 生成美观的交互式思维导图
|
||||
- ✅ **多语言支持**: 根据用户语言自动调整输出
|
||||
- ✅ **实时渲染**: 在聊天界面中直接渲染思维导图,无需跳转
|
||||
- ✅ **导出功能**: 支持复制 SVG 代码和 Markdown 源码
|
||||
- ✅ **自定义配置**: 可配置 LLM 模型、最小文本长度等参数
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
|
||||
1. **文本提取**: 从用户消息中提取文本内容(自动过滤 HTML 代码块)
|
||||
2. **智能分析**: 使用配置的 LLM 模型分析文本结构
|
||||
3. **Markdown 生成**: 将分析结果转换为 Markmap 兼容的 Markdown 格式
|
||||
4. **可视化渲染**: 在 HTML 模板中使用 Markmap.js 渲染思维导图
|
||||
5. **交互展示**: 在聊天界面中以可交互的形式展示给用户
|
||||
|
||||
---
|
||||
|
||||
## 安装与配置
|
||||
|
||||
### 1. 插件安装
|
||||
|
||||
1. 下载 `思维导图.py` 文件到本地
|
||||
2. 在 OpenWebUI 管理员设置中找到"插件"(Plugins)部分
|
||||
3. 选择"动作"(Actions)类型
|
||||
4. 上传下载的文件
|
||||
5. 刷新页面,插件即可使用
|
||||
|
||||
### 2. 模型配置
|
||||
|
||||
插件需要访问 LLM 模型来分析文本。请确保:
|
||||
|
||||
- 您的 OpenWebUI 实例中配置了至少一个可用的 LLM 模型
|
||||
- 推荐使用快速、经济的模型(如 `gemini-2.5-flash`)来获得最佳体验
|
||||
- 在插件设置中配置 `LLM_MODEL_ID` 参数
|
||||
|
||||
### 3. 插件启用
|
||||
|
||||
在聊天设置中选择"智绘心图"动作插件即可启用。
|
||||
|
||||
---
|
||||
|
||||
## 配置参数
|
||||
|
||||
您可以在插件的设置(Valves)中调整以下参数:
|
||||
|
||||
| 参数 | 默认值 | 描述 |
|
||||
| :--- | :--- | :--- |
|
||||
| `show_status` | `true` | 是否在聊天界面显示操作状态更新(如"正在分析...")。 |
|
||||
| `LLM_MODEL_ID` | `gemini-2.5-flash` | 用于文本分析的 LLM 模型 ID。推荐使用快速且经济的模型。 |
|
||||
| `MIN_TEXT_LENGTH` | `100` | 进行思维导图分析所需的最小文本长度(字符数)。文本过短将无法生成有效的导图。 |
|
||||
|
||||
---
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本使用
|
||||
|
||||
1. 在聊天设置中启用"智绘心图"动作
|
||||
2. 在对话中输入或粘贴长篇文本内容(至少 100 字符)
|
||||
3. 发送消息后,插件会自动分析并生成思维导图
|
||||
4. 思维导图将在聊天界面中直接渲染显示
|
||||
|
||||
### 使用示例
|
||||
|
||||
**输入文本:**
|
||||
```
|
||||
人工智能(AI)是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。
|
||||
主要应用领域包括:
|
||||
1. 机器学习 - 使计算机能够从数据中学习
|
||||
2. 自然语言处理 - 理解和生成人类语言
|
||||
3. 计算机视觉 - 识别和处理图像
|
||||
4. 机器人技术 - 创建能够与物理世界交互的智能系统
|
||||
```
|
||||
|
||||
**生成结果:**
|
||||
插件会生成一个以"人工智能"为中心主题的交互式思维导图,包含主要应用领域及其子概念。
|
||||
|
||||
### 导出功能
|
||||
|
||||
生成的思维导图支持两种导出方式:
|
||||
|
||||
1. **复制 SVG 代码**: 点击"复制 SVG 代码"按钮,可将思维导图的 SVG 格式复制到剪贴板
|
||||
2. **复制 Markdown**: 点击"复制 Markdown"按钮,可将原始 Markdown 格式复制到剪贴板
|
||||
|
||||
---
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 前端渲染
|
||||
|
||||
- **Markmap.js**: 开源的思维导图渲染引擎
|
||||
- **D3.js**: 数据可视化基础库
|
||||
- **响应式设计**: 适配不同屏幕尺寸
|
||||
|
||||
### 后端处理
|
||||
|
||||
- **LLM 集成**: 通过 `generate_chat_completion` 调用配置的模型
|
||||
- **文本预处理**: 自动过滤 HTML 代码块,提取纯文本内容
|
||||
- **格式转换**: 将 LLM 输出转换为 Markmap 兼容的 Markdown 格式
|
||||
|
||||
### 安全性
|
||||
|
||||
- **XSS 防护**: 自动转义 `</script>` 标签,防止脚本注入
|
||||
- **输入验证**: 检查文本长度,避免无效请求
|
||||
|
||||
---
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 问题:插件无法启动
|
||||
|
||||
**解决方案:**
|
||||
- 检查 OpenWebUI 日志,查看是否有错误信息
|
||||
- 确认插件已正确上传并启用
|
||||
- 验证 OpenWebUI 版本是否支持动作插件
|
||||
|
||||
### 问题:文本内容过短
|
||||
|
||||
**现象:** 提示"文本内容过短,无法进行有效分析"
|
||||
|
||||
**解决方案:**
|
||||
- 确保输入的文本至少包含 100 个字符(默认配置)
|
||||
- 可以在插件设置中降低 `MIN_TEXT_LENGTH` 参数值
|
||||
- 提供更详细、结构化的文本内容
|
||||
|
||||
### 问题:思维导图未生成
|
||||
|
||||
**解决方案:**
|
||||
- 检查 `LLM_MODEL_ID` 是否配置正确
|
||||
- 确认配置的模型在 OpenWebUI 中可用
|
||||
- 查看后端日志,检查是否有 LLM 调用失败的错误
|
||||
- 验证用户是否有足够的权限访问配置的模型
|
||||
|
||||
### 问题:思维导图显示错误
|
||||
|
||||
**现象:** 显示"⚠️ 思维导图渲染失败"
|
||||
|
||||
**解决方案:**
|
||||
- 检查浏览器控制台的错误信息
|
||||
- 确认 Markmap.js 和 D3.js 库是否正确加载
|
||||
- 验证生成的 Markdown 格式是否符合 Markmap 规范
|
||||
- 尝试刷新页面重新渲染
|
||||
|
||||
### 问题:导出功能不工作
|
||||
|
||||
**解决方案:**
|
||||
- 确认浏览器支持剪贴板 API
|
||||
- 检查浏览器是否阻止了剪贴板访问权限
|
||||
- 使用现代浏览器(Chrome、Firefox、Edge 等)
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **文本准备**
|
||||
- 提供结构清晰、层次分明的文本内容
|
||||
- 使用段落、列表等格式帮助 LLM 理解文本结构
|
||||
- 避免过于冗长或无结构的文本
|
||||
|
||||
2. **模型选择**
|
||||
- 对于日常使用,推荐 `gemini-2.5-flash` 等快速模型
|
||||
- 对于复杂文本分析,可以使用更强大的模型(如 GPT-4)
|
||||
- 根据需求平衡速度和分析质量
|
||||
|
||||
3. **性能优化**
|
||||
- 合理设置 `MIN_TEXT_LENGTH`,避免处理过短的文本
|
||||
- 对于特别长的文本,考虑先进行摘要再生成思维导图
|
||||
- 在生产环境中关闭 `show_status` 以减少界面更新
|
||||
|
||||
---
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v0.7.2 (当前版本)
|
||||
- 优化文本提取逻辑,自动过滤 HTML 代码块
|
||||
- 改进错误处理和用户反馈
|
||||
- 增强导出功能的兼容性
|
||||
- 优化 UI 样式和交互体验
|
||||
|
||||
---
|
||||
|
||||
## 许可证
|
||||
|
||||
本插件采用 MIT 许可证发布。
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交问题报告和改进建议!请访问项目仓库:[awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)
|
||||
|
||||
---
|
||||
|
||||
## 相关资源
|
||||
|
||||
- [Markmap 官方网站](https://markmap.js.org/)
|
||||
- [OpenWebUI 文档](https://docs.openwebui.com/)
|
||||
- [D3.js 官方网站](https://d3js.org/)
|
||||
611
plugins/actions/smart-mind-map/smart_mind_map.py
Normal file
611
plugins/actions/smart-mind-map/smart_mind_map.py
Normal file
@@ -0,0 +1,611 @@
|
||||
"""
|
||||
title: Smart Mind Map
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+CiAgPGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMyIgZmlsbD0iY3VycmVudENvbG9yIi8+CiAgPGxpbmUgeDE9IjEyIiB5MT0iOSIgeDI9IjEyIiB5Mj0iNCIvPgogIDxjaXJjbGUgY3g9IjEyIiBjeT0iMyIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjEyIiB5MT0iMTUiIHgyPSIxMiIgeTI9IjIwIi8+CiAgPGNpcmNsZSBjeD0iMTIiIGN5PSIyMSIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjkiIHkxPSIxMiIgeDI9IjQiIHkyPSIxMiIvPgogIDxjaXJjbGUgY3g9IjMiIGN5PSIxMiIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjE1IiB5MT0iMTIiIHgyPSIyMCIgeTI9IjEyIi8+CiAgPGNpcmNsZSBjeD0iMjEiIGN5PSIxMiIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjEwLjUiIHkxPSIxMC41IiB4Mj0iNiIgeTI9IjYiLz4KICA8Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjEzLjUiIHkxPSIxMC41IiB4Mj0iMTgiIHkyPSI2Ii8+CiAgPGNpcmNsZSBjeD0iMTkiIGN5PSI1IiByPSIxLjUiLz4KICA8bGluZSB4MT0iMTAuNSIgeTE9IjEzLjUiIHgyPSI2IiB5Mj0iMTgiLz4KICA8Y2lyY2xlIGN4PSI1IiBjeT0iMTkiIHI9IjEuNSIvPgogIDxsaW5lIHgxPSIxMy41IiB5MT0iMTMuNSIgeDI9IjE4IiB5Mj0iMTgiLz4KICA8Y2lyY2xlIGN4PSIxOSIgY3k9IjE5IiByPSIxLjUiLz4KPC9zdmc+
|
||||
version: 0.7.3
|
||||
description: 智能分析长文本并生成交互式思维导图,支持 SVG/Markdown 导出。
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Dict, Any
|
||||
import logging
|
||||
import time
|
||||
import re
|
||||
from fastapi import Request
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
from open_webui.models.users import Users
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SYSTEM_PROMPT_MINDMAP_ASSISTANT = """
|
||||
You are a professional mind map generation assistant, capable of efficiently analyzing long-form text provided by users and structuring its core themes, key concepts, branches, and sub-branches into standard Markdown list syntax for rendering by Markmap.js.
|
||||
|
||||
Please strictly follow these guidelines:
|
||||
- **Language**: All output must be in the language specified by the user.
|
||||
- **Format**: Your output must strictly be in Markdown list format, wrapped with ```markdown and ```.
|
||||
- Use `#` to define the central theme (root node).
|
||||
- Use `-` with two-space indentation to represent branches and sub-branches.
|
||||
- **Content**:
|
||||
- Identify the central theme of the text as the `#` heading.
|
||||
- Identify main concepts as first-level list items.
|
||||
- Identify supporting details or sub-concepts as nested list items.
|
||||
- Node content should be concise and clear, avoiding verbosity.
|
||||
- **Output Markdown syntax only**: Do not include any additional greetings, explanations, or guiding text.
|
||||
- **If text is too short or cannot generate a valid mind map**: Output a simple Markdown list indicating inability to generate, for example:
|
||||
```markdown
|
||||
# Unable to Generate Mind Map
|
||||
- Reason: Insufficient or unclear text content
|
||||
```
|
||||
"""
|
||||
|
||||
USER_PROMPT_GENERATE_MINDMAP = """
|
||||
Please analyze the following long-form text and structure its core themes, key concepts, branches, and sub-branches into standard Markdown list syntax for Markmap.js rendering.
|
||||
|
||||
---
|
||||
**User Context Information:**
|
||||
User Name: {user_name}
|
||||
Current Date & Time: {current_date_time_str}
|
||||
Current Weekday: {current_weekday}
|
||||
Current Timezone: {current_timezone_str}
|
||||
User Language: {user_language}
|
||||
---
|
||||
|
||||
**Long-form Text Content:**
|
||||
{long_text_content}
|
||||
"""
|
||||
|
||||
HTML_TEMPLATE_MINDMAP = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="{user_language}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Smart Mind Map: Mind Map Visualization</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/markmap-lib@0.17"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/markmap-view@0.17"></script>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #1e88e5;
|
||||
--secondary-color: #43a047;
|
||||
--background-color: #f4f6f8;
|
||||
--card-bg-color: #ffffff;
|
||||
--text-color: #263238;
|
||||
--muted-text-color: #546e7a;
|
||||
--border-color: #e0e0e0;
|
||||
--header-gradient: linear-gradient(135deg, var(--secondary-color), var(--primary-color));
|
||||
--shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
|
||||
--border-radius: 12px;
|
||||
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
line-height: 1.7;
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
background-color: var(--background-color);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
margin: 20px auto;
|
||||
background: var(--card-bg-color);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.header {
|
||||
background: var(--header-gradient);
|
||||
color: white;
|
||||
padding: 32px 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 2em;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||
}
|
||||
.user-context {
|
||||
font-size: 0.85em;
|
||||
color: var(--muted-text-color);
|
||||
background-color: #eceff1;
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.user-context span { margin: 4px 10px; }
|
||||
.content-area {
|
||||
padding: 30px 40px;
|
||||
}
|
||||
.markmap-container {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
background-image: radial-gradient(var(--border-color) 0.5px, transparent 0.5px);
|
||||
background-size: 20px 20px;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 24px;
|
||||
min-height: 700px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
.download-area {
|
||||
text-align: center;
|
||||
padding-top: 30px;
|
||||
margin-top: 30px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
.download-btn {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
margin: 0 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.download-btn.secondary {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
.download-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
.download-btn.copied {
|
||||
background-color: #2e7d32; /* A darker green for success */
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
font-size: 0.85em;
|
||||
color: #90a4ae;
|
||||
background-color: #eceff1;
|
||||
}
|
||||
.footer a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.error-message {
|
||||
color: #c62828;
|
||||
background-color: #ffcdd2;
|
||||
border: 1px solid #ef9a9a;
|
||||
padding: 20px;
|
||||
border-radius: var(--border-radius);
|
||||
font-weight: 500;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🧠 Smart Mind Map</h1>
|
||||
</div>
|
||||
<div class="user-context">
|
||||
<span><strong>User:</strong> {user_name}</span>
|
||||
<span><strong>Analysis Time:</strong> {current_date_time_str}</span>
|
||||
<span><strong>Weekday:</strong> {current_weekday_zh}</span>
|
||||
</div>
|
||||
<div class="content-area">
|
||||
<div class="markmap-container" id="markmap-container-{unique_id}"></div>
|
||||
<div class="download-area">
|
||||
<button id="download-svg-btn-{unique_id}" class="download-btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
<span class="btn-text">Copy SVG Code</span>
|
||||
</button>
|
||||
<button id="download-md-btn-{unique_id}" class="download-btn secondary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
|
||||
<span class="btn-text">Copy Markdown</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>© {current_year} Smart Mind Map • Rendering engine powered by <a href="https://markmap.js.org/" target="_blank">Markmap</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="markdown-source-{unique_id}">{markdown_syntax}</script>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const renderMindmap = () => {
|
||||
const uniqueId = "{unique_id}";
|
||||
const containerEl = document.getElementById('markmap-container-' + uniqueId);
|
||||
if (!containerEl || containerEl.dataset.markmapRendered) return;
|
||||
|
||||
const sourceEl = document.getElementById('markdown-source-' + uniqueId);
|
||||
if (!sourceEl) return;
|
||||
|
||||
const markdownContent = sourceEl.textContent.trim();
|
||||
if (!markdownContent) {
|
||||
containerEl.innerHTML = '<div class="error-message">⚠️ Unable to load mind map: Missing valid content.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svgEl.style.width = '100%';
|
||||
svgEl.style.height = '700px';
|
||||
containerEl.innerHTML = '';
|
||||
containerEl.appendChild(svgEl);
|
||||
|
||||
const { Transformer, Markmap } = window.markmap;
|
||||
const transformer = new Transformer();
|
||||
const { root } = transformer.transform(markdownContent);
|
||||
|
||||
const style = (id) => `${id} text { font-size: 16px !important; }`;
|
||||
|
||||
const options = {
|
||||
autoFit: true,
|
||||
style: style
|
||||
};
|
||||
Markmap.create(svgEl, options, root);
|
||||
|
||||
containerEl.dataset.markmapRendered = 'true';
|
||||
|
||||
attachDownloadHandlers(uniqueId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Markmap rendering error:', error);
|
||||
containerEl.innerHTML = '<div class="error-message">⚠️ Mind map rendering failed!<br>Reason: ' + error.message + '</div>';
|
||||
}
|
||||
};
|
||||
|
||||
const attachDownloadHandlers = (uniqueId) => {
|
||||
const downloadSvgBtn = document.getElementById('download-svg-btn-' + uniqueId);
|
||||
const downloadMdBtn = document.getElementById('download-md-btn-' + uniqueId);
|
||||
const containerEl = document.getElementById('markmap-container-' + uniqueId);
|
||||
|
||||
const showFeedback = (button, isSuccess) => {
|
||||
const buttonText = button.querySelector('.btn-text');
|
||||
const originalText = buttonText.textContent;
|
||||
|
||||
button.disabled = true;
|
||||
if (isSuccess) {
|
||||
buttonText.textContent = '✅ Copied!';
|
||||
button.classList.add('copied');
|
||||
} else {
|
||||
buttonText.textContent = '❌ Copy Failed';
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
buttonText.textContent = originalText;
|
||||
button.disabled = false;
|
||||
button.classList.remove('copied');
|
||||
}, 2500);
|
||||
};
|
||||
|
||||
const copyToClipboard = (content, button) => {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
showFeedback(button, true);
|
||||
}, () => {
|
||||
showFeedback(button, false);
|
||||
});
|
||||
} else {
|
||||
// Fallback for older/insecure contexts
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = content;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.opacity = '0';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showFeedback(button, true);
|
||||
} catch (err) {
|
||||
showFeedback(button, false);
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
};
|
||||
|
||||
if (downloadSvgBtn) {
|
||||
downloadSvgBtn.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
const svgEl = containerEl.querySelector('svg');
|
||||
if (svgEl) {
|
||||
const svgData = new XMLSerializer().serializeToString(svgEl);
|
||||
copyToClipboard(svgData, downloadSvgBtn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (downloadMdBtn) {
|
||||
downloadMdBtn.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
const markdownContent = document.getElementById('markdown-source-' + uniqueId).textContent;
|
||||
copyToClipboard(markdownContent, downloadMdBtn);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', renderMindmap);
|
||||
} else {
|
||||
renderMindmap();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
class Action:
|
||||
class Valves(BaseModel):
|
||||
show_status: bool = Field(
|
||||
default=True,
|
||||
description="Whether to show action status updates in the chat interface.",
|
||||
)
|
||||
LLM_MODEL_ID: str = Field(
|
||||
default="gemini-2.5-flash",
|
||||
description="Built-in LLM model ID for text analysis.",
|
||||
)
|
||||
MIN_TEXT_LENGTH: int = Field(
|
||||
default=100,
|
||||
description="Minimum text length (character count) required for mind map analysis.",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
self.weekday_map = {
|
||||
"Monday": "Monday",
|
||||
"Tuesday": "Tuesday",
|
||||
"Wednesday": "Wednesday",
|
||||
"Thursday": "Thursday",
|
||||
"Friday": "Friday",
|
||||
"Saturday": "Saturday",
|
||||
"Sunday": "Sunday",
|
||||
}
|
||||
|
||||
def _extract_markdown_syntax(self, llm_output: str) -> str:
|
||||
match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL)
|
||||
if match:
|
||||
extracted_content = match.group(1).strip()
|
||||
else:
|
||||
logger.warning(
|
||||
"LLM output did not strictly follow the expected Markdown format, treating the entire output as summary."
|
||||
)
|
||||
extracted_content = llm_output.strip()
|
||||
return extracted_content.replace("</script>", "<\\/script>")
|
||||
|
||||
async def action(
|
||||
self,
|
||||
body: dict,
|
||||
__user__: Optional[Dict[str, Any]] = None,
|
||||
__event_emitter__: Optional[Any] = None,
|
||||
__request__: Optional[Request] = None,
|
||||
) -> Optional[dict]:
|
||||
logger.info("Action: Smart Mind Map (v0.7.2) started")
|
||||
|
||||
if isinstance(__user__, (list, tuple)):
|
||||
user_language = (
|
||||
__user__[0].get("language", "en-US") if __user__ else "en-US"
|
||||
)
|
||||
user_name = __user__[0].get("name", "User") if __user__[0] else "User"
|
||||
user_id = (
|
||||
__user__[0]["id"]
|
||||
if __user__ and "id" in __user__[0]
|
||||
else "unknown_user"
|
||||
)
|
||||
elif isinstance(__user__, dict):
|
||||
user_language = __user__.get("language", "en-US")
|
||||
user_name = __user__.get("name", "User")
|
||||
user_id = __user__.get("id", "unknown_user")
|
||||
|
||||
try:
|
||||
shanghai_tz = pytz.timezone("Asia/Shanghai")
|
||||
current_datetime_shanghai = datetime.now(shanghai_tz)
|
||||
current_date_time_str = current_datetime_shanghai.strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
current_weekday_en = current_datetime_shanghai.strftime("%A")
|
||||
current_weekday_zh = self.weekday_map.get(current_weekday_en, "Unknown")
|
||||
current_year = current_datetime_shanghai.strftime("%Y")
|
||||
current_timezone_str = "Asia/Shanghai"
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get timezone info: {e}, using default values.")
|
||||
now = datetime.now()
|
||||
current_date_time_str = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
current_weekday_zh = "Unknown"
|
||||
current_year = now.strftime("%Y")
|
||||
current_timezone_str = "Unknown"
|
||||
|
||||
if __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {
|
||||
"type": "info",
|
||||
"content": "Smart Mind Map is starting, generating mind map for you...",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
messages = body.get("messages")
|
||||
if (
|
||||
not messages
|
||||
or not isinstance(messages, list)
|
||||
or not messages[-1].get("content")
|
||||
):
|
||||
error_message = "Unable to retrieve valid user message content."
|
||||
if __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {"type": "error", "content": error_message},
|
||||
}
|
||||
)
|
||||
return {
|
||||
"messages": [{"role": "assistant", "content": f"❌ {error_message}"}]
|
||||
}
|
||||
|
||||
parts = re.split(r"```html.*?```", messages[-1]["content"], flags=re.DOTALL)
|
||||
long_text_content = ""
|
||||
if parts:
|
||||
for part in reversed(parts):
|
||||
if part.strip():
|
||||
long_text_content = part.strip()
|
||||
break
|
||||
|
||||
if not long_text_content:
|
||||
long_text_content = messages[-1]["content"].strip()
|
||||
|
||||
if len(long_text_content) < self.valves.MIN_TEXT_LENGTH:
|
||||
short_text_message = f"Text content is too short ({len(long_text_content)} characters), unable to perform effective analysis. Please provide at least {self.valves.MIN_TEXT_LENGTH} characters of text."
|
||||
if __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {"type": "warning", "content": short_text_message},
|
||||
}
|
||||
)
|
||||
return {
|
||||
"messages": [
|
||||
{"role": "assistant", "content": f"⚠️ {short_text_message}"}
|
||||
]
|
||||
}
|
||||
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": "Smart Mind Map: Analyzing text structure in depth...",
|
||||
"done": False,
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
unique_id = f"id_{int(time.time() * 1000)}"
|
||||
|
||||
formatted_user_prompt = USER_PROMPT_GENERATE_MINDMAP.format(
|
||||
user_name=user_name,
|
||||
current_date_time_str=current_date_time_str,
|
||||
current_weekday=current_weekday_zh,
|
||||
current_timezone_str=current_timezone_str,
|
||||
user_language=user_language,
|
||||
long_text_content=long_text_content,
|
||||
)
|
||||
|
||||
llm_payload = {
|
||||
"model": self.valves.LLM_MODEL_ID,
|
||||
"messages": [
|
||||
{"role": "system", "content": SYSTEM_PROMPT_MINDMAP_ASSISTANT},
|
||||
{"role": "user", "content": formatted_user_prompt},
|
||||
],
|
||||
"temperature": 0.5,
|
||||
"stream": False,
|
||||
}
|
||||
user_obj = Users.get_user_by_id(user_id)
|
||||
if not user_obj:
|
||||
raise ValueError(f"Unable to get user object, user ID: {user_id}")
|
||||
|
||||
llm_response = await generate_chat_completion(
|
||||
__request__, llm_payload, user_obj
|
||||
)
|
||||
|
||||
if (
|
||||
not llm_response
|
||||
or "choices" not in llm_response
|
||||
or not llm_response["choices"]
|
||||
):
|
||||
raise ValueError("LLM response format is incorrect or empty.")
|
||||
|
||||
assistant_response_content = llm_response["choices"][0]["message"][
|
||||
"content"
|
||||
]
|
||||
markdown_syntax = self._extract_markdown_syntax(assistant_response_content)
|
||||
|
||||
final_html_content = (
|
||||
HTML_TEMPLATE_MINDMAP.replace("{unique_id}", unique_id)
|
||||
.replace("{user_language}", user_language)
|
||||
.replace("{user_name}", user_name)
|
||||
.replace("{current_date_time_str}", current_date_time_str)
|
||||
.replace("{current_weekday_zh}", current_weekday_zh)
|
||||
.replace("{current_year}", current_year)
|
||||
.replace("{markdown_syntax}", markdown_syntax)
|
||||
)
|
||||
|
||||
html_embed_tag = f"```html\n{final_html_content}\n```"
|
||||
body["messages"][-1]["content"] = f"{long_text_content}\n\n{html_embed_tag}"
|
||||
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": "Smart Mind Map: Drawing completed!",
|
||||
"done": True,
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {
|
||||
"type": "success",
|
||||
"content": f"Mind map has been generated, {user_name}!",
|
||||
},
|
||||
}
|
||||
)
|
||||
logger.info("Action: Smart Mind Map (v0.7.2) completed successfully")
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"Smart Mind Map processing failed: {str(e)}"
|
||||
logger.error(f"Smart Mind Map error: {error_message}", exc_info=True)
|
||||
user_facing_error = f"Sorry, Smart Mind Map encountered an error during processing: {str(e)}.\nPlease check the Open WebUI backend logs for more details."
|
||||
body["messages"][-1][
|
||||
"content"
|
||||
] = f"{long_text_content}\n\n❌ **Error:** {user_facing_error}"
|
||||
|
||||
if __event_emitter__:
|
||||
if self.valves.show_status:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": "Smart Mind Map: Processing failed.",
|
||||
"done": True,
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {
|
||||
"type": "error",
|
||||
"content": f"Smart Mind Map generation failed, {user_name}!",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return body
|
||||
611
plugins/actions/smart-mind-map/思维导图.py
Normal file
611
plugins/actions/smart-mind-map/思维导图.py
Normal file
@@ -0,0 +1,611 @@
|
||||
"""
|
||||
title: 智绘心图
|
||||
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+CiAgPGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMyIgZmlsbD0iY3VycmVudENvbG9yIi8+CiAgPGxpbmUgeDE9IjEyIiB5MT0iOSIgeDI9IjEyIiB5Mj0iNCIvPgogIDxjaXJjbGUgY3g9IjEyIiBjeT0iMyIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjEyIiB5MT0iMTUiIHgyPSIxMiIgeTI9IjIwIi8+CiAgPGNpcmNsZSBjeD0iMTIiIGN5PSIyMSIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjkiIHkxPSIxMiIgeDI9IjQiIHkyPSIxMiIvPgogIDxjaXJjbGUgY3g9IjMiIGN5PSIxMiIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjE1IiB5MT0iMTIiIHgyPSIyMCIgeTI9IjEyIi8+CiAgPGNpcmNsZSBjeD0iMjEiIGN5PSIxMiIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjEwLjUiIHkxPSIxMC41IiB4Mj0iNiIgeTI9IjYiLz4KICA8Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iMS41Ii8+CiAgPGxpbmUgeDE9IjEzLjUiIHkxPSIxMC41IiB4Mj0iMTgiIHkyPSI2Ii8+CiAgPGNpcmNsZSBjeD0iMTkiIGN5PSI1IiByPSIxLjUiLz4KICA8bGluZSB4MT0iMTAuNSIgeTE9IjEzLjUiIHgyPSI2IiB5Mj0iMTgiLz4KICA8Y2lyY2xlIGN4PSI1IiBjeT0iMTkiIHI9IjEuNSIvPgogIDxsaW5lIHgxPSIxMy41IiB5MT0iMTMuNSIgeDI9IjE4IiB5Mj0iMTgiLz4KICA8Y2lyY2xlIGN4PSIxOSIgY3k9IjE5IiByPSIxLjUiLz4KPC9zdmc+
|
||||
version: 0.7.2
|
||||
description: 智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Dict, Any
|
||||
import logging
|
||||
import time
|
||||
import re
|
||||
from fastapi import Request
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
from open_webui.models.users import Users
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SYSTEM_PROMPT_MINDMAP_ASSISTANT = """
|
||||
你是一个专业的思维导图生成助手,能够高效地分析用户提供的长篇文本,并将其核心主题、关键概念、分支和子分支结构化为标准的Markdown列表语法,以便Markmap.js进行渲染。
|
||||
|
||||
请严格遵循以下指导原则:
|
||||
- **语言**: 所有输出必须使用用户指定的语言。
|
||||
- **格式**: 你的输出必须严格为Markdown列表格式,并用```markdown 和 ``` 包裹。
|
||||
- 使用 `#` 定义中心主题(根节点)。
|
||||
- 使用 `-` 和两个空格的缩进表示分支和子分支。
|
||||
- **内容**:
|
||||
- 识别文本的中心主题作为 `#` 标题。
|
||||
- 识别主要概念作为一级列表项。
|
||||
- 识别支持性细节或子概念作为嵌套的列表项。
|
||||
- 节点内容应简洁明了,避免冗长。
|
||||
- **只输出Markdown语法**: 不要包含任何额外的寒暄、解释或引导性文字。
|
||||
- **如果文本过短或无法生成有效导图**: 请输出一个简单的Markdown列表,表示无法生成,例如:
|
||||
```markdown
|
||||
# 无法生成思维导图
|
||||
- 原因: 文本内容不足或不明确
|
||||
```
|
||||
"""
|
||||
|
||||
USER_PROMPT_GENERATE_MINDMAP = """
|
||||
请分析以下长篇文本,并将其核心主题、关键概念、分支和子分支结构化为标准的Markdown列表语法,以供Markmap.js渲染。
|
||||
|
||||
---
|
||||
**用户上下文信息:**
|
||||
用户姓名: {user_name}
|
||||
当前日期时间: {current_date_time_str}
|
||||
当前星期: {current_weekday}
|
||||
当前时区: {current_timezone_str}
|
||||
用户语言: {user_language}
|
||||
---
|
||||
|
||||
**长篇文本内容:**
|
||||
Use code with caution.
|
||||
Python
|
||||
{long_text_content}
|
||||
"""
|
||||
|
||||
HTML_TEMPLATE_MINDMAP = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="{user_language}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>智绘心图: 思维导图</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/markmap-lib@0.17"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/markmap-view@0.17"></script>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #1e88e5;
|
||||
--secondary-color: #43a047;
|
||||
--background-color: #f4f6f8;
|
||||
--card-bg-color: #ffffff;
|
||||
--text-color: #263238;
|
||||
--muted-text-color: #546e7a;
|
||||
--border-color: #e0e0e0;
|
||||
--header-gradient: linear-gradient(135deg, var(--secondary-color), var(--primary-color));
|
||||
--shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
|
||||
--border-radius: 12px;
|
||||
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
line-height: 1.7;
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
background-color: var(--background-color);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
margin: 20px auto;
|
||||
background: var(--card-bg-color);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.header {
|
||||
background: var(--header-gradient);
|
||||
color: white;
|
||||
padding: 32px 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 2em;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||
}
|
||||
.user-context {
|
||||
font-size: 0.85em;
|
||||
color: var(--muted-text-color);
|
||||
background-color: #eceff1;
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.user-context span { margin: 4px 10px; }
|
||||
.content-area {
|
||||
padding: 30px 40px;
|
||||
}
|
||||
.markmap-container {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
background-image: radial-gradient(var(--border-color) 0.5px, transparent 0.5px);
|
||||
background-size: 20px 20px;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 24px;
|
||||
min-height: 700px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
.download-area {
|
||||
text-align: center;
|
||||
padding-top: 30px;
|
||||
margin-top: 30px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
.download-btn {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
margin: 0 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.download-btn.secondary {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
.download-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
.download-btn.copied {
|
||||
background-color: #2e7d32; /* A darker green for success */
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
font-size: 0.85em;
|
||||
color: #90a4ae;
|
||||
background-color: #eceff1;
|
||||
}
|
||||
.footer a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.error-message {
|
||||
color: #c62828;
|
||||
background-color: #ffcdd2;
|
||||
border: 1px solid #ef9a9a;
|
||||
padding: 20px;
|
||||
border-radius: var(--border-radius);
|
||||
font-weight: 500;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🧠 智绘心图</h1>
|
||||
</div>
|
||||
<div class="user-context">
|
||||
<span><strong>用户:</strong> {user_name}</span>
|
||||
<span><strong>分析时间:</strong> {current_date_time_str}</span>
|
||||
<span><strong>星期:</strong> {current_weekday_zh}</span>
|
||||
</div>
|
||||
<div class="content-area">
|
||||
<div class="markmap-container" id="markmap-container-{unique_id}"></div>
|
||||
<div class="download-area">
|
||||
<button id="download-svg-btn-{unique_id}" class="download-btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
<span class="btn-text">复制 SVG 代码</span>
|
||||
</button>
|
||||
<button id="download-md-btn-{unique_id}" class="download-btn secondary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
|
||||
<span class="btn-text">复制 Markdown</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>© {current_year} 智绘心图 • 渲染引擎由 <a href="https://markmap.js.org/" target="_blank">Markmap</a> 提供</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="markdown-source-{unique_id}">{markdown_syntax}</script>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const renderMindmap = () => {
|
||||
const uniqueId = "{unique_id}";
|
||||
const containerEl = document.getElementById('markmap-container-' + uniqueId);
|
||||
if (!containerEl || containerEl.dataset.markmapRendered) return;
|
||||
|
||||
const sourceEl = document.getElementById('markdown-source-' + uniqueId);
|
||||
if (!sourceEl) return;
|
||||
|
||||
const markdownContent = sourceEl.textContent.trim();
|
||||
if (!markdownContent) {
|
||||
containerEl.innerHTML = '<div class="error-message">⚠️ 无法加载思维导图: 缺少有效内容。</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svgEl.style.width = '100%';
|
||||
svgEl.style.height = '700px';
|
||||
containerEl.innerHTML = '';
|
||||
containerEl.appendChild(svgEl);
|
||||
|
||||
const { Transformer, Markmap } = window.markmap;
|
||||
const transformer = new Transformer();
|
||||
const { root } = transformer.transform(markdownContent);
|
||||
|
||||
const style = (id) => `${id} text { font-size: 16px !important; }`;
|
||||
|
||||
const options = {
|
||||
autoFit: true,
|
||||
style: style
|
||||
};
|
||||
Markmap.create(svgEl, options, root);
|
||||
|
||||
containerEl.dataset.markmapRendered = 'true';
|
||||
|
||||
attachDownloadHandlers(uniqueId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Markmap rendering error:', error);
|
||||
containerEl.innerHTML = '<div class="error-message">⚠️ 思维导图渲染失败!<br>原因: ' + error.message + '</div>';
|
||||
}
|
||||
};
|
||||
|
||||
const attachDownloadHandlers = (uniqueId) => {
|
||||
const downloadSvgBtn = document.getElementById('download-svg-btn-' + uniqueId);
|
||||
const downloadMdBtn = document.getElementById('download-md-btn-' + uniqueId);
|
||||
const containerEl = document.getElementById('markmap-container-' + uniqueId);
|
||||
|
||||
const showFeedback = (button, isSuccess) => {
|
||||
const buttonText = button.querySelector('.btn-text');
|
||||
const originalText = buttonText.textContent;
|
||||
|
||||
button.disabled = true;
|
||||
if (isSuccess) {
|
||||
buttonText.textContent = '✅ 已复制!';
|
||||
button.classList.add('copied');
|
||||
} else {
|
||||
buttonText.textContent = '❌ 复制失败';
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
buttonText.textContent = originalText;
|
||||
button.disabled = false;
|
||||
button.classList.remove('copied');
|
||||
}, 2500);
|
||||
};
|
||||
|
||||
const copyToClipboard = (content, button) => {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
showFeedback(button, true);
|
||||
}, () => {
|
||||
showFeedback(button, false);
|
||||
});
|
||||
} else {
|
||||
// Fallback for older/insecure contexts
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = content;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.opacity = '0';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showFeedback(button, true);
|
||||
} catch (err) {
|
||||
showFeedback(button, false);
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
};
|
||||
|
||||
if (downloadSvgBtn) {
|
||||
downloadSvgBtn.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
const svgEl = containerEl.querySelector('svg');
|
||||
if (svgEl) {
|
||||
const svgData = new XMLSerializer().serializeToString(svgEl);
|
||||
copyToClipboard(svgData, downloadSvgBtn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (downloadMdBtn) {
|
||||
downloadMdBtn.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
const markdownContent = document.getElementById('markdown-source-' + uniqueId).textContent;
|
||||
copyToClipboard(markdownContent, downloadMdBtn);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', renderMindmap);
|
||||
} else {
|
||||
renderMindmap();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
class Action:
|
||||
class Valves(BaseModel):
|
||||
show_status: bool = Field(
|
||||
default=True, description="是否在聊天界面显示操作状态更新。"
|
||||
)
|
||||
LLM_MODEL_ID: str = Field(
|
||||
default="gemini-2.5-flash",
|
||||
description="用于文本分析的内置LLM模型ID。",
|
||||
)
|
||||
MIN_TEXT_LENGTH: int = Field(
|
||||
default=100, description="进行思维导图分析所需的最小文本长度(字符数)。"
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
self.weekday_map = {
|
||||
"Monday": "星期一",
|
||||
"Tuesday": "星期二",
|
||||
"Wednesday": "星期三",
|
||||
"Thursday": "星期四",
|
||||
"Friday": "星期五",
|
||||
"Saturday": "星期六",
|
||||
"Sunday": "星期日",
|
||||
}
|
||||
|
||||
def _extract_markdown_syntax(self, llm_output: str) -> str:
|
||||
match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL)
|
||||
if match:
|
||||
extracted_content = match.group(1).strip()
|
||||
else:
|
||||
logger.warning(
|
||||
"LLM输出未严格遵循预期Markdown格式,将整个输出作为摘要处理。"
|
||||
)
|
||||
extracted_content = llm_output.strip()
|
||||
return extracted_content.replace("</script>", "<\\/script>")
|
||||
|
||||
async def action(
|
||||
self,
|
||||
body: dict,
|
||||
__user__: Optional[Dict[str, Any]] = None,
|
||||
__event_emitter__: Optional[Any] = None,
|
||||
__request__: Optional[Request] = None,
|
||||
) -> Optional[dict]:
|
||||
logger.info("Action: 智绘心图 (v12 - Final Feedback Fix) started")
|
||||
|
||||
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")
|
||||
|
||||
try:
|
||||
shanghai_tz = pytz.timezone("Asia/Shanghai")
|
||||
current_datetime_shanghai = datetime.now(shanghai_tz)
|
||||
current_date_time_str = current_datetime_shanghai.strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
current_weekday_en = current_datetime_shanghai.strftime("%A")
|
||||
current_weekday_zh = self.weekday_map.get(current_weekday_en, "未知星期")
|
||||
current_year = current_datetime_shanghai.strftime("%Y")
|
||||
current_timezone_str = "Asia/Shanghai"
|
||||
except Exception as e:
|
||||
logger.warning(f"获取时区信息失败: {e},使用默认值。")
|
||||
now = datetime.now()
|
||||
current_date_time_str = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
current_weekday_zh = "未知星期"
|
||||
current_year = now.strftime("%Y")
|
||||
current_timezone_str = "未知时区"
|
||||
|
||||
if __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {
|
||||
"type": "info",
|
||||
"content": "智绘心图已启动,正在为您生成思维导图...",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
messages = body.get("messages")
|
||||
if (
|
||||
not messages
|
||||
or not isinstance(messages, list)
|
||||
or not messages[-1].get("content")
|
||||
):
|
||||
error_message = "无法获取有效的用户消息内容。"
|
||||
if __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {"type": "error", "content": error_message},
|
||||
}
|
||||
)
|
||||
return {
|
||||
"messages": [{"role": "assistant", "content": f"❌ {error_message}"}]
|
||||
}
|
||||
|
||||
parts = re.split(r"```html.*?```", messages[-1]["content"], flags=re.DOTALL)
|
||||
long_text_content = ""
|
||||
if parts:
|
||||
for part in reversed(parts):
|
||||
if part.strip():
|
||||
long_text_content = part.strip()
|
||||
break
|
||||
|
||||
if not long_text_content:
|
||||
long_text_content = messages[-1]["content"].strip()
|
||||
|
||||
if len(long_text_content) < self.valves.MIN_TEXT_LENGTH:
|
||||
short_text_message = f"文本内容过短({len(long_text_content)}字符),无法进行有效分析。请提供至少{self.valves.MIN_TEXT_LENGTH}字符的文本。"
|
||||
if __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {"type": "warning", "content": short_text_message},
|
||||
}
|
||||
)
|
||||
return {
|
||||
"messages": [
|
||||
{"role": "assistant", "content": f"⚠️ {short_text_message}"}
|
||||
]
|
||||
}
|
||||
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": "智绘心图: 深入分析文本结构...",
|
||||
"done": False,
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
unique_id = f"id_{int(time.time() * 1000)}"
|
||||
|
||||
formatted_user_prompt = USER_PROMPT_GENERATE_MINDMAP.format(
|
||||
user_name=user_name,
|
||||
current_date_time_str=current_date_time_str,
|
||||
current_weekday=current_weekday_zh,
|
||||
current_timezone_str=current_timezone_str,
|
||||
user_language=user_language,
|
||||
long_text_content=long_text_content,
|
||||
)
|
||||
|
||||
llm_payload = {
|
||||
"model": self.valves.LLM_MODEL_ID,
|
||||
"messages": [
|
||||
{"role": "system", "content": SYSTEM_PROMPT_MINDMAP_ASSISTANT},
|
||||
{"role": "user", "content": formatted_user_prompt},
|
||||
],
|
||||
"temperature": 0.5,
|
||||
"stream": False,
|
||||
}
|
||||
user_obj = Users.get_user_by_id(user_id)
|
||||
if not user_obj:
|
||||
raise ValueError(f"无法获取用户对象,用户ID: {user_id}")
|
||||
|
||||
llm_response = await generate_chat_completion(
|
||||
__request__, llm_payload, user_obj
|
||||
)
|
||||
|
||||
if (
|
||||
not llm_response
|
||||
or "choices" not in llm_response
|
||||
or not llm_response["choices"]
|
||||
):
|
||||
raise ValueError("LLM响应格式不正确或为空。")
|
||||
|
||||
assistant_response_content = llm_response["choices"][0]["message"][
|
||||
"content"
|
||||
]
|
||||
markdown_syntax = self._extract_markdown_syntax(assistant_response_content)
|
||||
|
||||
final_html_content = (
|
||||
HTML_TEMPLATE_MINDMAP.replace("{unique_id}", unique_id)
|
||||
.replace("{user_language}", user_language)
|
||||
.replace("{user_name}", user_name)
|
||||
.replace("{current_date_time_str}", current_date_time_str)
|
||||
.replace("{current_weekday_zh}", current_weekday_zh)
|
||||
.replace("{current_year}", current_year)
|
||||
.replace("{markdown_syntax}", markdown_syntax)
|
||||
)
|
||||
|
||||
html_embed_tag = f"```html\n{final_html_content}\n```"
|
||||
body["messages"][-1]["content"] = f"{long_text_content}\n\n{html_embed_tag}"
|
||||
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": "智绘心图: 绘制完成!",
|
||||
"done": True,
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {
|
||||
"type": "success",
|
||||
"content": f"思维导图已生成,{user_name}!",
|
||||
},
|
||||
}
|
||||
)
|
||||
logger.info("Action: 智绘心图 (v12) completed successfully")
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"智绘心图处理失败: {str(e)}"
|
||||
logger.error(f"智绘心图错误: {error_message}", exc_info=True)
|
||||
user_facing_error = f"抱歉,智绘心图在处理时遇到错误: {str(e)}。\n请检查Open WebUI后端日志获取更多详情。"
|
||||
body["messages"][-1][
|
||||
"content"
|
||||
] = f"{long_text_content}\n\n❌ **错误:** {user_facing_error}"
|
||||
|
||||
if __event_emitter__:
|
||||
if self.valves.show_status:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": "智绘心图: 处理失败。",
|
||||
"done": True,
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "notification",
|
||||
"data": {
|
||||
"type": "error",
|
||||
"content": f"智绘心图生成失败, {user_name}!",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return body
|
||||
Reference in New Issue
Block a user