""" title: Smart Mind Map Tool author: Fu-Jie author_url: https://github.com/Fu-Jie/openwebui-extensions funding_url: https://github.com/open-webui version: 1.1.0 description: Intelligently analyzes text content and generates interactive mind maps inline to help users structure and visualize knowledge. """ import asyncio import logging import re import time import json from datetime import datetime, timezone from typing import Any, Callable, Awaitable, Dict, Optional from fastapi import Request from pydantic import BaseModel, Field from open_webui.utils.chat import generate_chat_completion from open_webui.models.users import Users logger = logging.getLogger(__name__) class Tools: class Valves(BaseModel): MODEL_ID: str = Field(default="", description="The model ID to use for mind map generation. If empty, uses the current conversation model.") MIN_TEXT_LENGTH: int = Field(default=50, description="Minimum text length required for analysis.") SHOW_STATUS: bool = Field(default=True, description="Whether to show status messages.") def __init__(self): self.valves = self.Valves() self.__translations = { "en-US": { "status_analyzing": "Smart Mind Map: Analyzing text structure...", "status_drawing": "Smart Mind Map: Drawing completed!", "notification_success": "Mind map has been generated, {user_name}!", "error_text_too_short": "Text content is too short ({len} characters). Min: {min_len}.", "error_user_facing": "Sorry, Smart Mind Map encountered an error: {error}", "status_failed": "Smart Mind Map: Failed.", "ui_title": "🧠 Smart Mind Map", "ui_download_png": "PNG", "ui_download_svg": "SVG", "ui_download_md": "Markdown", "ui_zoom_out": "Zoom Out", "ui_zoom_reset": "Reset", "ui_zoom_in": "Zoom In", "ui_depth_select": "Expand Level", "ui_depth_all": "All", "ui_depth_2": "L2", "ui_depth_3": "L3", "ui_fullscreen": "Fullscreen", "ui_theme": "Theme", "ui_footer": "Powered by Markmap", "html_error_missing_content": "⚠️ Missing content.", "html_error_load_failed": "⚠️ Resource load failed.", "js_done": "Done", }, "zh-CN": { "status_analyzing": "思维导图:深入分析文本结构...", "status_drawing": "思维导图:绘制完成!", "notification_success": "思维导图已生成,{user_name}!", "error_text_too_short": "文本内容过短({len}字符),请提供至少{min_len}字符。", "error_user_facing": "抱歉,思维导图处理出错:{error}", "status_failed": "思维导图:处理失败。", "ui_title": "🧠 智能思维导图", "ui_download_png": "PNG", "ui_download_svg": "SVG", "ui_download_md": "Markdown", "ui_zoom_out": "缩小", "ui_zoom_reset": "重置", "ui_zoom_in": "放大", "ui_depth_select": "展开层级", "ui_depth_all": "全部", "ui_depth_2": "2级", "ui_depth_3": "3级", "ui_fullscreen": "全屏", "ui_theme": "主题", "ui_footer": "Powered by Markmap", "html_error_missing_content": "⚠️ 缺少有效内容。", "html_error_load_failed": "⚠️ 资源加载失败。", "js_done": "完成", } } self.__system_prompt = """You are a professional mind map assistant. Analyze text and output Markdown list syntax for Markmap.js. Guidelines: - Root node (#) must be ultra-compact (max 10 chars for CJK, 5 words for Latin). - Use '-' with 2-space indentation. - Output ONLY Markdown wrapped in ```markdown. - Match the language of the input text.""" self.__css_template = """ :root { --primary-color: #1e88e5; --secondary-color: #43a047; --background-color: #f4f6f8; --card-bg-color: #ffffff; --text-color: #000000; --link-color: #546e7a; --node-stroke-color: #90a4ae; --muted-text-color: #546e7a; --border-color: #e0e0e0; --shadow: 0 4px 12px rgba(0, 0, 0, 0.05); --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } .theme-dark { --primary-color: #3b82f6; --secondary-color: #22c55e; --background-color: #0d1117; --card-bg-color: #161b22; --text-color: #ffffff; --link-color: #58a6ff; --node-stroke-color: #8b949e; --muted-text-color: #7d8590; --border-color: #30363d; } html, body { margin: 0; padding: 0; width: 100%; height: 600px; background: transparent; overflow: hidden; font-family: var(--font-family); } .mindmap-wrapper { display: flex; flex-direction: column; width: 100%; height: 100%; background: var(--card-bg-color); border: 1px solid var(--border-color); border-radius: 12px; overflow: hidden; box-shadow: var(--shadow); } .header { display: flex; align-items: center; padding: 8px 16px; border-bottom: 1px solid var(--border-color); background: var(--card-bg-color); flex-shrink: 0; gap: 12px; } .header h1 { margin: 0; font-size: 1rem; flex-grow: 1; color: var(--text-color); } .btn-group { display: flex; gap: 2px; background: var(--background-color); padding: 2px; border-radius: 6px; } .control-btn { border: none; background: transparent; color: var(--text-color); padding: 4px 8px; cursor: pointer; border-radius: 4px; font-size: 0.8rem; opacity: 0.7; } .control-btn:hover { background: var(--card-bg-color); opacity: 1; } .content { flex-grow: 1; position: relative; } .markmap-container { position: absolute; top:0; left:0; right:0; bottom:0; } svg text { fill: var(--text-color) !important; } svg .markmap-link { stroke: var(--link-color) !important; } """ self.__content_template = """