diff --git a/plugins/actions/deep-dive/README.md b/plugins/actions/deep-dive/README.md index 5867fce..70b3d8f 100644 --- a/plugins/actions/deep-dive/README.md +++ b/plugins/actions/deep-dive/README.md @@ -1,6 +1,6 @@ # 🌊 Deep Dive -**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.0.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) +**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.0.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths. diff --git a/plugins/actions/deep-dive/README_CN.md b/plugins/actions/deep-dive/README_CN.md index ca6ac28..292472a 100644 --- a/plugins/actions/deep-dive/README_CN.md +++ b/plugins/actions/deep-dive/README_CN.md @@ -1,6 +1,6 @@ # 📖 精读 -**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.0.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) +**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.0.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。 diff --git a/plugins/actions/deep-dive/deep_dive.py b/plugins/actions/deep-dive/deep_dive.py index 72372a4..9f1f73f 100644 --- a/plugins/actions/deep-dive/deep_dive.py +++ b/plugins/actions/deep-dive/deep_dive.py @@ -1,8 +1,8 @@ """ title: Deep Dive author: Fu-Jie -author_url: https://github.com/Fu-Jie -funding_url: https://github.com/Fu-Jie/awesome-openwebui +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui version: 1.0.0 icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg== requirements: markdown @@ -466,6 +466,10 @@ class Action: default=True, description="Whether to show operation status updates.", ) + SHOW_DEBUG_LOG: bool = Field( + default=False, + description="Whether to print debug logs in the browser console.", + ) MODEL_ID: str = Field( default="", description="LLM Model ID for analysis. Empty = use current model.", @@ -501,6 +505,42 @@ class Action: "user_language": user_data.get("language", "en-US"), } + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + Unified extraction of chat context information (chat_id, message_id). + Prioritizes extraction from body, then metadata. + """ + chat_id = "" + message_id = "" + + # 1. Try to get from body + if isinstance(body, dict): + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id is usually 'id' in body + + # Check body.metadata as fallback + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") + + # 2. Try to get from __metadata__ (as supplement) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") + + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } + def _process_llm_output(self, llm_output: str) -> Dict[str, str]: """Parse LLM output and convert to styled HTML.""" # Extract sections using flexible regex @@ -700,6 +740,26 @@ class Action: {"type": "notification", "data": {"type": ntype, "content": content}} ) + async def _emit_debug_log(self, emitter, title: str, data: dict): + """Print structured debug logs in the browser console""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + import json + + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") + def _remove_existing_html(self, content: str) -> str: """Removes existing plugin-generated HTML.""" pattern = r"```html\s*[\s\S]*?```" diff --git a/plugins/actions/deep-dive/deep_dive_cn.py b/plugins/actions/deep-dive/deep_dive_cn.py index a557fd3..5a61d24 100644 --- a/plugins/actions/deep-dive/deep_dive_cn.py +++ b/plugins/actions/deep-dive/deep_dive_cn.py @@ -1,8 +1,8 @@ """ title: 精读 author: Fu-Jie -author_url: https://github.com/Fu-Jie -funding_url: https://github.com/Fu-Jie/awesome-openwebui +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui version: 1.0.0 icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg== requirements: markdown @@ -466,6 +466,10 @@ class Action: default=True, description="是否显示操作状态更新。", ) + SHOW_DEBUG_LOG: bool = Field( + default=False, + description="是否在浏览器控制台打印调试日志。", + ) MODEL_ID: str = Field( default="", description="用于分析的 LLM 模型 ID。留空则使用当前模型。", @@ -501,6 +505,42 @@ class Action: "user_language": user_data.get("language", "zh-CN"), } + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + 统一提取聊天上下文信息 (chat_id, message_id)。 + 优先从 body 中提取,其次从 metadata 中提取。 + """ + chat_id = "" + message_id = "" + + # 1. 尝试从 body 获取 + if isinstance(body, dict): + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id 在 body 中通常是 id + + # 再次检查 body.metadata + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") + + # 2. 尝试从 __metadata__ 获取 (作为补充) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") + + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } + def _process_llm_output(self, llm_output: str) -> Dict[str, str]: """解析 LLM 输出并转换为样式化 HTML。""" # 使用灵活的正则提取各部分 @@ -694,6 +734,26 @@ class Action: {"type": "notification", "data": {"type": ntype, "content": content}} ) + async def _emit_debug_log(self, emitter, title: str, data: dict): + """在浏览器控制台打印结构化调试日志""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + import json + + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") + def _remove_existing_html(self, content: str) -> str: """移除已有的插件生成的 HTML。""" pattern = r"```html\s*[\s\S]*?```" diff --git a/plugins/actions/export_to_docx/README.md b/plugins/actions/export_to_docx/README.md index be2c79a..66a39e5 100644 --- a/plugins/actions/export_to_docx/README.md +++ b/plugins/actions/export_to_docx/README.md @@ -1,6 +1,6 @@ # 📝 Export to Word (Enhanced) -**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) +**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) Export conversation to Word (.docx) with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**. diff --git a/plugins/actions/export_to_docx/README_CN.md b/plugins/actions/export_to_docx/README_CN.md index 2ecb377..b78e0d5 100644 --- a/plugins/actions/export_to_docx/README_CN.md +++ b/plugins/actions/export_to_docx/README_CN.md @@ -1,6 +1,6 @@ # 📝 导出为 Word (增强版) -**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) +**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 将对话导出为 Word (.docx),支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用参考**和**增强表格格式**。 diff --git a/plugins/actions/export_to_docx/export_to_word.py b/plugins/actions/export_to_docx/export_to_word.py index 00f0804..56fb1ed 100644 --- a/plugins/actions/export_to_docx/export_to_word.py +++ b/plugins/actions/export_to_docx/export_to_word.py @@ -1,8 +1,8 @@ """ title: Export to Word (Enhanced) author: Fu-Jie -author_url: https://github.com/Fu-Jie -funding_url: https://github.com/Fu-Jie/awesome-openwebui +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui version: 0.4.3 openwebui_id: fca6a315-2a45-42cc-8c96-55cbc85f87f2 icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K @@ -150,6 +150,14 @@ class Action: default="chat_title", description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)", ) + SHOW_STATUS: bool = Field( + default=True, + description="Whether to show operation status updates.", + ) + SHOW_DEBUG_LOG: bool = Field( + default=False, + description="Whether to print debug logs in the browser console.", + ) MAX_EMBED_IMAGE_MB: int = Field( default=20, @@ -320,10 +328,100 @@ class Action: return msg return msg - async def _send_notification(self, emitter: Callable, type: str, content: str): - await emitter( - {"type": "notification", "data": {"type": type, "content": content}} - ) + def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]: + """Safely extracts user context information.""" + if isinstance(__user__, (list, tuple)): + user_data = __user__[0] if __user__ else {} + elif isinstance(__user__, dict): + user_data = __user__ + else: + user_data = {} + + return { + "user_id": user_data.get("id", "unknown_user"), + "user_name": user_data.get("name", "User"), + "user_language": user_data.get("language", "en-US"), + } + + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + Unified extraction of chat context information (chat_id, message_id). + Prioritizes extraction from body, then metadata. + """ + chat_id = "" + message_id = "" + + # 1. Try to get from body + if isinstance(body, dict): + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id is usually 'id' in body + + # Check body.metadata as fallback + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") + + # 2. Try to get from __metadata__ (as supplement) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") + + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } + + async def _emit_status( + self, + emitter: Optional[Callable[[Any], Awaitable[None]]], + description: str, + done: bool = False, + ): + """Emits a status update event.""" + if self.valves.SHOW_STATUS and emitter: + await emitter( + {"type": "status", "data": {"description": description, "done": done}} + ) + + async def _emit_notification( + self, + emitter: Optional[Callable[[Any], Awaitable[None]]], + content: str, + ntype: str = "info", + ): + """Emits a notification event (info, success, warning, error).""" + if emitter: + await emitter( + {"type": "notification", "data": {"type": ntype, "content": content}} + ) + + async def _emit_debug_log(self, emitter, title: str, data: dict): + """Print structured debug logs in the browser console""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + import json + + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") async def action( self, @@ -397,14 +495,15 @@ class Action: message_content = self._strip_reasoning_blocks(message_content) if not message_content or not message_content.strip(): - await self._send_notification( - __event_emitter__, "error", self._get_msg("error_no_content") + await self._emit_notification( + __event_emitter__, self._get_msg("error_no_content"), "error" ) return # Generate filename title = "" - chat_id = self.extract_chat_id(body, __metadata__) + chat_ctx = self._get_chat_context(body, __metadata__) + chat_id = chat_ctx["chat_id"] # Fetch chat_title directly via chat_id as it's usually missing in body chat_title = "" @@ -873,10 +972,10 @@ class Action: } ) - await self._send_notification( + await self._emit_notification( __event_emitter__, - "success", self._get_msg("success", filename=filename), + "success", ) return {"message": "Download triggered"} @@ -892,10 +991,10 @@ class Action: }, } ) - await self._send_notification( + await self._emit_notification( __event_emitter__, - "error", self._get_msg("error_export", error=str(e)), + "error", ) async def generate_title_using_ai( diff --git a/plugins/actions/export_to_docx/export_to_word_cn.py b/plugins/actions/export_to_docx/export_to_word_cn.py index cd058df..ec89efa 100644 --- a/plugins/actions/export_to_docx/export_to_word_cn.py +++ b/plugins/actions/export_to_docx/export_to_word_cn.py @@ -1,8 +1,8 @@ """ title: 导出为 Word (增强版) author: Fu-Jie -author_url: https://github.com/Fu-Jie -funding_url: https://github.com/Fu-Jie/awesome-openwebui +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui version: 0.4.3 openwebui_id: 8a6306c0-d005-4e46-aaae-8db3532c9ed5 icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K @@ -150,6 +150,14 @@ class Action: default="chat_title", description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)", ) + SHOW_STATUS: bool = Field( + default=True, + description="是否显示操作状态更新。", + ) + SHOW_DEBUG_LOG: bool = Field( + default=False, + description="是否在浏览器控制台打印调试日志。", + ) 最大嵌入图片大小MB: int = Field( default=20, @@ -320,10 +328,100 @@ class Action: return msg return msg - async def _send_notification(self, emitter: Callable, type: str, content: str): - await emitter( - {"type": "notification", "data": {"type": type, "content": content}} - ) + def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]: + """安全提取用户上下文信息。""" + if isinstance(__user__, (list, tuple)): + user_data = __user__[0] if __user__ else {} + elif isinstance(__user__, dict): + user_data = __user__ + else: + user_data = {} + + return { + "user_id": user_data.get("id", "unknown_user"), + "user_name": user_data.get("name", "用户"), + "user_language": user_data.get("language", "zh-CN"), + } + + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + 统一提取聊天上下文信息 (chat_id, message_id)。 + 优先从 body 中提取,其次从 metadata 中提取。 + """ + chat_id = "" + message_id = "" + + # 1. 尝试从 body 获取 + if isinstance(body, dict): + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id 在 body 中通常是 id + + # 再次检查 body.metadata + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") + + # 2. 尝试从 __metadata__ 获取 (作为补充) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") + + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } + + async def _emit_status( + self, + emitter: Optional[Callable[[Any], Awaitable[None]]], + description: str, + done: bool = False, + ): + """Emits a status update event.""" + if self.valves.SHOW_STATUS and emitter: + await emitter( + {"type": "status", "data": {"description": description, "done": done}} + ) + + async def _emit_notification( + self, + emitter: Optional[Callable[[Any], Awaitable[None]]], + content: str, + ntype: str = "info", + ): + """Emits a notification event (info, success, warning, error).""" + if emitter: + await emitter( + {"type": "notification", "data": {"type": ntype, "content": content}} + ) + + async def _emit_debug_log(self, emitter, title: str, data: dict): + """在浏览器控制台打印结构化调试日志""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + import json + + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") async def action( self, @@ -395,14 +493,15 @@ class Action: message_content = self._strip_reasoning_blocks(message_content) if not message_content or not message_content.strip(): - await self._send_notification( - __event_emitter__, "error", self._get_msg("error_no_content") + await self._emit_notification( + __event_emitter__, self._get_msg("error_no_content"), "error" ) return # Generate filename title = "" - chat_id = self.extract_chat_id(body, __metadata__) + chat_ctx = self._get_chat_context(body, __metadata__) + chat_id = chat_ctx["chat_id"] # Fetch chat_title directly via chat_id as it's usually missing in body chat_title = "" @@ -871,10 +970,10 @@ class Action: } ) - await self._send_notification( + await self._emit_notification( __event_emitter__, - "success", self._get_msg("success", filename=filename), + "success", ) return {"message": "Download triggered"} @@ -890,10 +989,10 @@ class Action: }, } ) - await self._send_notification( + await self._emit_notification( __event_emitter__, - "error", self._get_msg("error_export", error=str(e)), + "error", ) async def generate_title_using_ai( diff --git a/plugins/actions/flash-card/flash_card.py b/plugins/actions/flash-card/flash_card.py index ad91e47..e537f37 100644 --- a/plugins/actions/flash-card/flash_card.py +++ b/plugins/actions/flash-card/flash_card.py @@ -1,8 +1,8 @@ """ title: Flash Card author: Fu-Jie -author_url: https://github.com/Fu-Jie -funding_url: https://github.com/Fu-Jie/awesome-openwebui +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui version: 0.2.4 openwebui_id: 65a2ea8f-2a13-4587-9d76-55eea0035cc8 icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4= @@ -89,6 +89,10 @@ class Action: default=True, description="Whether to show status updates in the chat interface.", ) + SHOW_DEBUG_LOG: bool = Field( + default=False, + description="Whether to print debug logs in the browser console.", + ) CLEAR_PREVIOUS_HTML: bool = Field( default=False, description="Whether to force clear previous plugin results (if True, overwrites instead of merging).", @@ -116,6 +120,42 @@ class Action: "user_language": user_data.get("language", "en-US"), } + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + Unified extraction of chat context information (chat_id, message_id). + Prioritizes extraction from body, then metadata. + """ + chat_id = "" + message_id = "" + + # 1. Try to get from body + if isinstance(body, dict): + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id is usually 'id' in body + + # Check body.metadata as fallback + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") + + # 2. Try to get from __metadata__ (as supplement) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") + + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } + async def action( self, body: dict, @@ -331,6 +371,26 @@ Important Principles: {"type": "notification", "data": {"type": ntype, "content": content}} ) + async def _emit_debug_log(self, emitter, title: str, data: dict): + """Print structured debug logs in the browser console""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + import json + + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") + def _remove_existing_html(self, content: str) -> str: """Removes existing plugin-generated HTML code blocks from the content.""" pattern = r"```html\s*[\s\S]*?```" diff --git a/plugins/actions/flash-card/flash_card_cn.py b/plugins/actions/flash-card/flash_card_cn.py index 711449a..112a425 100644 --- a/plugins/actions/flash-card/flash_card_cn.py +++ b/plugins/actions/flash-card/flash_card_cn.py @@ -1,8 +1,8 @@ """ title: 闪记卡 (Flash Card) author: Fu-Jie -author_url: https://github.com/Fu-Jie -funding_url: https://github.com/Fu-Jie/awesome-openwebui +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui version: 0.2.4 openwebui_id: 4a31eac3-a3c4-4c30-9ca5-dab36b5fac65 icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4= @@ -86,6 +86,10 @@ class Action: SHOW_STATUS: bool = Field( default=True, description="是否在聊天界面显示状态更新。" ) + SHOW_DEBUG_LOG: bool = Field( + default=False, + description="是否在浏览器控制台打印调试日志。", + ) CLEAR_PREVIOUS_HTML: bool = Field( default=False, description="是否强制清除旧的插件结果(如果为 True,则不合并,直接覆盖)。", @@ -113,6 +117,42 @@ class Action: "user_language": user_data.get("language", "zh-CN"), } + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + 统一提取聊天上下文信息 (chat_id, message_id)。 + 优先从 body 中提取,其次从 metadata 中提取。 + """ + chat_id = "" + message_id = "" + + # 1. 尝试从 body 获取 + if isinstance(body, dict): + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id 在 body 中通常是 id + + # 再次检查 body.metadata + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") + + # 2. 尝试从 __metadata__ 获取 (作为补充) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") + + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } + async def action( self, body: dict, @@ -314,6 +354,26 @@ class Action: {"type": "notification", "data": {"type": ntype, "content": content}} ) + async def _emit_debug_log(self, emitter, title: str, data: dict): + """在浏览器控制台打印结构化调试日志""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + import json + + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") + def _remove_existing_html(self, content: str) -> str: """移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。""" pattern = r"```html\s*[\s\S]*?```" diff --git a/plugins/actions/infographic/README.md b/plugins/actions/infographic/README.md index 51e068d..bc95d3a 100644 --- a/plugins/actions/infographic/README.md +++ b/plugins/actions/infographic/README.md @@ -1,6 +1,6 @@ # 📊 Smart Infographic (AntV) -**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.4.9 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) +**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.4.9 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) An Open WebUI plugin powered by the AntV Infographic engine. It transforms long text into professional, beautiful infographics with a single click. diff --git a/plugins/actions/infographic/README_CN.md b/plugins/actions/infographic/README_CN.md index d0639f2..b255092 100644 --- a/plugins/actions/infographic/README_CN.md +++ b/plugins/actions/infographic/README_CN.md @@ -1,6 +1,6 @@ # 📊 智能信息图 (AntV Infographic) -**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.4.9 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) +**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.4.9 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 基于 AntV Infographic 引擎的 Open WebUI 插件,能够将长文本内容一键转换为专业、美观的信息图表。 diff --git a/plugins/actions/infographic/infographic.py b/plugins/actions/infographic/infographic.py index 9589ada..e1461e1 100644 --- a/plugins/actions/infographic/infographic.py +++ b/plugins/actions/infographic/infographic.py @@ -2,6 +2,7 @@ title: 📊 Smart Infographic (AntV) author: Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4= version: 1.4.9 openwebui_id: ad6f0c7f-c571-4dea-821d-8e71697274cf @@ -263,6 +264,8 @@ data 4. **Indentation**: Use 2 spaces. """ +import json + USER_PROMPT_GENERATE_INFOGRAPHIC = """ Please analyze the following text content and convert its core information into AntV Infographic syntax format. @@ -947,49 +950,64 @@ class Action: default="image", description="Output mode: 'html' for interactive HTML, or 'image' to embed as Markdown image (default).", ) + SHOW_DEBUG_LOG: bool = Field( + default=False, + description="Whether to print debug logs in the browser console.", + ) 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""" + def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]: + """Safely extracts user context information.""" + if isinstance(__user__, (list, tuple)): + user_data = __user__[0] if __user__ else {} + elif isinstance(__user__, dict): + user_data = __user__ + else: + user_data = {} + + return { + "user_id": user_data.get("id", "unknown_user"), + "user_name": user_data.get("name", "User"), + "user_language": user_data.get("language", "en-US"), + } + + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + Unified extraction of chat context information (chat_id, message_id). + Prioritizes extraction from body, then metadata. + """ + chat_id = "" + message_id = "" + + # 1. Try to get from body if isinstance(body, dict): - chat_id = body.get("chat_id") - if isinstance(chat_id, str) and chat_id.strip(): - return chat_id.strip() + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id is usually 'id' in body - 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() + # Check body.metadata as fallback + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") - if isinstance(metadata, dict): - chat_id = metadata.get("chat_id") - if isinstance(chat_id, str) and chat_id.strip(): - return chat_id.strip() + # 2. Try to get from __metadata__ (as supplement) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") - return "" - - def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str: - """Extract message_id from body or metadata""" - if isinstance(body, dict): - 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 "" + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } def _extract_infographic_syntax(self, llm_output: str) -> str: """Extract infographic syntax from LLM output""" @@ -1018,6 +1036,24 @@ class Action: {"type": "notification", "data": {"type": ntype, "content": content}} ) + async def _emit_debug_log(self, emitter, title: str, data: dict): + """Print structured debug logs in the browser console""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") + def _remove_existing_html(self, content: str) -> str: """Remove existing plugin-generated HTML code blocks from content""" pattern = r"```html\s*[\s\S]*?```" @@ -1628,8 +1664,9 @@ class Action: # Check output mode if self.valves.OUTPUT_MODE == "image": # Image mode: use JavaScript to render and embed as Markdown image - chat_id = self._extract_chat_id(body, body.get("metadata")) - message_id = self._extract_message_id(body, body.get("metadata")) + chat_ctx = self._get_chat_context(body, __metadata__) + chat_id = chat_ctx["chat_id"] + message_id = chat_ctx["message_id"] await self._emit_status( __event_emitter__, diff --git a/plugins/actions/infographic/infographic_cn.py b/plugins/actions/infographic/infographic_cn.py index 9ea5a64..7ee754f 100644 --- a/plugins/actions/infographic/infographic_cn.py +++ b/plugins/actions/infographic/infographic_cn.py @@ -2,6 +2,7 @@ title: 📊 智能信息图 (AntV Infographic) author: Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4= version: 1.4.9 openwebui_id: e04a48ff-23ee-4a41-8ea7-66c19524e7c8 @@ -244,6 +245,8 @@ data 3. **Language**: Use the user's requested language for content. """ +import json + USER_PROMPT_GENERATE_INFOGRAPHIC = """ 请分析以下文本内容,将其核心信息转换为 AntV Infographic 语法格式。 @@ -954,6 +957,10 @@ class Action: default="image", description="输出模式:'html' 为交互式HTML,'image' 将嵌入为Markdown图片(默认)。", ) + SHOW_DEBUG_LOG: bool = Field( + default=False, + description="是否在浏览器控制台打印调试日志。", + ) def __init__(self): self.valves = self.Valves() @@ -967,45 +974,56 @@ class Action: "Sunday": "星期日", } - def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str: - """从 body 或 metadata 中提取 chat_id""" + def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]: + """安全提取用户上下文信息。""" + if isinstance(__user__, (list, tuple)): + user_data = __user__[0] if __user__ else {} + elif isinstance(__user__, dict): + user_data = __user__ + else: + user_data = {} + + return { + "user_id": user_data.get("id", "unknown_user"), + "user_name": user_data.get("name", "用户"), + "user_language": user_data.get("language", "zh-CN"), + } + + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + 统一提取聊天上下文信息 (chat_id, message_id)。 + 优先从 body 中提取,其次从 metadata 中提取。 + """ + chat_id = "" + message_id = "" + + # 1. 尝试从 body 获取 if isinstance(body, dict): - chat_id = body.get("chat_id") - if isinstance(chat_id, str) and chat_id.strip(): - return chat_id.strip() + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id 在 body 中通常是 id - 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() + # 再次检查 body.metadata + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") - if isinstance(metadata, dict): - chat_id = metadata.get("chat_id") - if isinstance(chat_id, str) and chat_id.strip(): - return chat_id.strip() + # 2. 尝试从 __metadata__ 获取 (作为补充) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") - return "" - - def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str: - """从 body 或 metadata 中提取 message_id""" - if isinstance(body, dict): - 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 "" + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } def _extract_infographic_syntax(self, llm_output: str) -> str: """提取LLM输出中的infographic语法""" @@ -1058,6 +1076,24 @@ class Action: {"type": "notification", "data": {"type": ntype, "content": content}} ) + async def _emit_debug_log(self, emitter, title: str, data: dict): + """在浏览器控制台打印结构化调试日志""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") + def _remove_existing_html(self, content: str) -> str: """移除内容中已有的插件生成 HTML 代码块""" pattern = r"```html\s*[\s\S]*?```" @@ -1662,8 +1698,9 @@ class Action: # 检查输出模式 if self.valves.OUTPUT_MODE == "image": # 图片模式:使用 JavaScript 渲染并嵌入为 Markdown 图片 - chat_id = self._extract_chat_id(body, body.get("metadata")) - message_id = self._extract_message_id(body, body.get("metadata")) + chat_ctx = self._get_chat_context(body, __metadata__) + chat_id = chat_ctx["chat_id"] + message_id = chat_ctx["message_id"] await self._emit_status( __event_emitter__, diff --git a/plugins/actions/smart-mind-map/README.md b/plugins/actions/smart-mind-map/README.md index e981626..33514bb 100644 --- a/plugins/actions/smart-mind-map/README.md +++ b/plugins/actions/smart-mind-map/README.md @@ -1,6 +1,6 @@ # Smart Mind Map - Mind Mapping Generation Plugin -**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.9.1 | **License:** MIT +**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.9.1 | **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. diff --git a/plugins/actions/smart-mind-map/README_CN.md b/plugins/actions/smart-mind-map/README_CN.md index a7bac3a..336b0ea 100644 --- a/plugins/actions/smart-mind-map/README_CN.md +++ b/plugins/actions/smart-mind-map/README_CN.md @@ -1,6 +1,6 @@ # 思维导图 - 思维导图生成插件 -**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 0.9.1 | **许可证:** MIT +**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.9.1 | **许可证:** MIT > **重要提示**:为了确保所有插件的可维护性和易用性,每个插件都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。 diff --git a/plugins/actions/smart-mind-map/smart_mind_map.py b/plugins/actions/smart-mind-map/smart_mind_map.py index 907a645..7448a21 100644 --- a/plugins/actions/smart-mind-map/smart_mind_map.py +++ b/plugins/actions/smart-mind-map/smart_mind_map.py @@ -1,7 +1,8 @@ """ title: Smart Mind Map author: Fu-Jie -author_url: https://github.com/Fu-Jie +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui funding_url: https://github.com/Fu-Jie/awesome-openwebui version: 0.9.1 openwebui_id: 3094c59a-b4dd-4e0c-9449-15e2dd547dc4 @@ -49,6 +50,8 @@ Please strictly follow these guidelines: ``` """ +import json + 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. @@ -791,6 +794,10 @@ class Action: default="html", description="Output mode: 'html' for interactive HTML (default), or 'image' to embed as Markdown image.", ) + SHOW_DEBUG_LOG: bool = Field( + default=False, + description="Whether to print debug logs in the browser console.", + ) def __init__(self): self.valves = self.Valves() @@ -819,45 +826,41 @@ class Action: "user_language": user_data.get("language", "en-US"), } - def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str: - """Extract chat_id from body or metadata""" + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + Unified extraction of chat context information (chat_id, message_id). + Prioritizes extraction from body, then metadata. + """ + chat_id = "" + message_id = "" + + # 1. Try to get from body if isinstance(body, dict): - chat_id = body.get("chat_id") - if isinstance(chat_id, str) and chat_id.strip(): - return chat_id.strip() + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id is usually 'id' in body - 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() + # Check body.metadata as fallback + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") - if isinstance(metadata, dict): - chat_id = metadata.get("chat_id") - if isinstance(chat_id, str) and chat_id.strip(): - return chat_id.strip() + # 2. Try to get from __metadata__ (as supplement) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") - return "" - - def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str: - """Extract message_id from body or metadata""" - if isinstance(body, dict): - 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 "" + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } def _extract_markdown_syntax(self, llm_output: str) -> str: match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL) @@ -884,6 +887,42 @@ class Action: {"type": "notification", "data": {"type": ntype, "content": content}} ) + async def _emit_debug_log(self, emitter, title: str, data: dict): + """Print structured debug logs in the browser console""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") + + async def _emit_debug_log(self, emitter, title: str, data: dict): + """Print structured debug logs in the browser console""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") + def _remove_existing_html(self, content: str) -> str: """Removes existing plugin-generated HTML code blocks from the content.""" pattern = r"```html\s*[\s\S]*?```" @@ -1515,8 +1554,9 @@ class Action: # Check output mode if self.valves.OUTPUT_MODE == "image": # Image mode: use JavaScript to render and embed as Markdown image - chat_id = self._extract_chat_id(body, __metadata__) - message_id = self._extract_message_id(body, __metadata__) + chat_ctx = self._get_chat_context(body, __metadata__) + chat_id = chat_ctx["chat_id"] + message_id = chat_ctx["message_id"] await self._emit_status( __event_emitter__, diff --git a/plugins/actions/smart-mind-map/smart_mind_map_cn.py b/plugins/actions/smart-mind-map/smart_mind_map_cn.py index 9a30522..345a4fe 100644 --- a/plugins/actions/smart-mind-map/smart_mind_map_cn.py +++ b/plugins/actions/smart-mind-map/smart_mind_map_cn.py @@ -1,8 +1,8 @@ """ title: 思维导图 author: Fu-Jie -author_url: https://github.com/Fu-Jie -funding_url: https://github.com/Fu-Jie/awesome-openwebui +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui version: 0.9.1 openwebui_id: 8d4b097b-219b-4dd2-b509-05fbe6388335 icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4= @@ -49,6 +49,8 @@ SYSTEM_PROMPT_MINDMAP_ASSISTANT = """ ``` """ +import json + USER_PROMPT_GENERATE_MINDMAP = """ 请分析以下长篇文本,并将其核心主题、关键概念、分支和子分支结构化为标准的Markdown列表语法,以供Markmap.js渲染。 @@ -790,6 +792,10 @@ class Action: default="html", description="输出模式: 'html' 为交互式HTML(默认),'image' 为嵌入Markdown图片。", ) + SHOW_DEBUG_LOG: bool = Field( + default=False, + description="是否在浏览器控制台打印调试日志。", + ) def __init__(self): self.valves = self.Valves() @@ -818,45 +824,41 @@ class Action: "user_language": user_data.get("language", "zh-CN"), } - def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str: - """从 body 或 metadata 中提取 chat_id""" + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + 统一提取聊天上下文信息 (chat_id, message_id)。 + 优先从 body 中提取,其次从 metadata 中提取。 + """ + chat_id = "" + message_id = "" + + # 1. 尝试从 body 获取 if isinstance(body, dict): - chat_id = body.get("chat_id") - if isinstance(chat_id, str) and chat_id.strip(): - return chat_id.strip() + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id 在 body 中通常是 id - 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() + # 再次检查 body.metadata + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") - if isinstance(metadata, dict): - chat_id = metadata.get("chat_id") - if isinstance(chat_id, str) and chat_id.strip(): - return chat_id.strip() + # 2. 尝试从 __metadata__ 获取 (作为补充) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") - return "" - - def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str: - """从 body 或 metadata 中提取 message_id""" - if isinstance(body, dict): - 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 "" + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } def _extract_markdown_syntax(self, llm_output: str) -> str: match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL) @@ -881,6 +883,24 @@ class Action: {"type": "notification", "data": {"type": ntype, "content": content}} ) + async def _emit_debug_log(self, emitter, title: str, data: dict): + """在浏览器控制台打印结构化调试日志""" + if not self.valves.SHOW_DEBUG_LOG or not emitter: + return + + try: + js_code = f""" + (async function() {{ + console.group("🛠️ {title}"); + console.log({json.dumps(data, ensure_ascii=False)}); + console.groupEnd(); + }})(); + """ + + await emitter({"type": "execute", "data": {"code": js_code}}) + except Exception as e: + print(f"Error emitting debug log: {e}") + def _remove_existing_html(self, content: str) -> str: """移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。""" pattern = r"```html\s*[\s\S]*?```" @@ -1508,8 +1528,9 @@ class Action: # 检查输出模式 if self.valves.OUTPUT_MODE == "image": # 图片模式: 使用 JavaScript 渲染并嵌入为 Markdown 图片 - chat_id = self._extract_chat_id(body, __metadata__) - message_id = self._extract_message_id(body, __metadata__) + chat_ctx = self._get_chat_context(body, __metadata__) + chat_id = chat_ctx["chat_id"] + message_id = chat_ctx["message_id"] await self._emit_status( __event_emitter__, diff --git a/plugins/filters/async-context-compression/README.md b/plugins/filters/async-context-compression/README.md index 18db77b..298d392 100644 --- a/plugins/filters/async-context-compression/README.md +++ b/plugins/filters/async-context-compression/README.md @@ -1,6 +1,6 @@ # Async Context Compression Filter -**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.1.3 | **License:** MIT +**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 1.1.3 | **License:** MIT This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent. diff --git a/plugins/filters/async-context-compression/README_CN.md b/plugins/filters/async-context-compression/README_CN.md index aadb7e2..2d7dd40 100644 --- a/plugins/filters/async-context-compression/README_CN.md +++ b/plugins/filters/async-context-compression/README_CN.md @@ -1,6 +1,6 @@ # 异步上下文压缩过滤器 -**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.1.3 | **许可证:** MIT +**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 1.1.3 | **许可证:** MIT > **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。 diff --git a/plugins/filters/async-context-compression/async_context_compression.py b/plugins/filters/async-context-compression/async_context_compression.py index 2145073..a3e83a1 100644 --- a/plugins/filters/async-context-compression/async_context_compression.py +++ b/plugins/filters/async-context-compression/async_context_compression.py @@ -2,8 +2,8 @@ title: Async Context Compression id: async_context_compression author: Fu-Jie -author_url: https://github.com/Fu-Jie -funding_url: https://github.com/Fu-Jie/awesome-openwebui +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui description: Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression. version: 1.1.3 openwebui_id: b1655bc8-6de9-4cad-8cb5-a6f7829a02ce @@ -621,25 +621,41 @@ class Filter: "max_context_tokens": self.valves.max_context_tokens, } - def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str: - """Extract chat_id from body or metadata.""" + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + Unified extraction of chat context information (chat_id, message_id). + Prioritizes extraction from body, then metadata. + """ + chat_id = "" + message_id = "" + + # 1. Try to get from body if isinstance(body, dict): - chat_id = body.get("chat_id") - if isinstance(chat_id, str) and chat_id.strip(): - return chat_id.strip() + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id is usually 'id' in body - 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() + # Check body.metadata as fallback + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") - if isinstance(metadata, dict): - chat_id = metadata.get("chat_id") - if isinstance(chat_id, str) and chat_id.strip(): - return chat_id.strip() + # 2. Try to get from __metadata__ (as supplement) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") - return "" + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } async def _emit_debug_log( self, @@ -750,7 +766,8 @@ class Filter: Compression Strategy: Only responsible for injecting existing summaries, no Token calculation. """ messages = body.get("messages", []) - chat_id = self._extract_chat_id(body, __metadata__) + chat_ctx = self._get_chat_context(body, __metadata__) + chat_id = chat_ctx["chat_id"] if not chat_id: await self._log( @@ -867,7 +884,8 @@ class Filter: Executed after the LLM response is complete. Calculates Token count in the background and triggers summary generation (does not block current response, does not affect content output). """ - chat_id = self._extract_chat_id(body, __metadata__) + chat_ctx = self._get_chat_context(body, __metadata__) + chat_id = chat_ctx["chat_id"] if not chat_id: await self._log( "[Outlet] ❌ Missing chat_id in metadata, skipping compression", diff --git a/plugins/filters/async-context-compression/async_context_compression_cn.py b/plugins/filters/async-context-compression/async_context_compression_cn.py index ce2111c..5cfea97 100644 --- a/plugins/filters/async-context-compression/async_context_compression_cn.py +++ b/plugins/filters/async-context-compression/async_context_compression_cn.py @@ -2,8 +2,8 @@ title: 异步上下文压缩 id: async_context_compression author: Fu-Jie -author_url: https://github.com/Fu-Jie -funding_url: https://github.com/Fu-Jie/awesome-openwebui +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/open-webui description: 通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。 version: 1.1.3 openwebui_id: 5c0617cb-a9e4-4bd6-a440-d276534ebd18 @@ -472,6 +472,42 @@ class Filter: "max_context_tokens": self.valves.max_context_tokens, } + def _get_chat_context( + self, body: dict, __metadata__: Optional[dict] = None + ) -> Dict[str, str]: + """ + 统一提取聊天上下文信息 (chat_id, message_id)。 + 优先从 body 中提取,其次从 metadata 中提取。 + """ + chat_id = "" + message_id = "" + + # 1. 尝试从 body 获取 + if isinstance(body, dict): + chat_id = body.get("chat_id", "") + message_id = body.get("id", "") # message_id 在 body 中通常是 id + + # 再次检查 body.metadata + if not chat_id or not message_id: + body_metadata = body.get("metadata", {}) + if isinstance(body_metadata, dict): + if not chat_id: + chat_id = body_metadata.get("chat_id", "") + if not message_id: + message_id = body_metadata.get("message_id", "") + + # 2. 尝试从 __metadata__ 获取 (作为补充) + if __metadata__ and isinstance(__metadata__, dict): + if not chat_id: + chat_id = __metadata__.get("chat_id", "") + if not message_id: + message_id = __metadata__.get("message_id", "") + + return { + "chat_id": str(chat_id).strip(), + "message_id": str(message_id).strip(), + } + async def _emit_debug_log( self, __event_call__, @@ -581,7 +617,8 @@ class Filter: 压缩策略:只负责注入已有的摘要,不进行 Token 计算 """ messages = body.get("messages", []) - chat_id = __metadata__["chat_id"] + chat_ctx = self._get_chat_context(body, __metadata__) + chat_id = chat_ctx["chat_id"] if self.valves.debug_mode or self.valves.show_debug_log: await self._log( @@ -690,7 +727,8 @@ class Filter: 在 LLM 响应完成后执行 在后台计算 Token 数并触发摘要生成(不阻塞当前响应,不影响内容输出) """ - chat_id = __metadata__["chat_id"] + chat_ctx = self._get_chat_context(body, __metadata__) + chat_id = chat_ctx["chat_id"] model = body.get("model") or "" # 直接计算目标压缩进度 diff --git a/plugins/filters/multi_model_context_merger.py b/plugins/filters/multi_model_context_merger.py index 7055350..203d838 100644 --- a/plugins/filters/multi_model_context_merger.py +++ b/plugins/filters/multi_model_context_merger.py @@ -1,3 +1,12 @@ +""" +title: Multi-Model Context Merger +author: Fu-Jie +author_url: https://github.com/Fu-Jie/awesome-openwebui +funding_url: https://github.com/Fu-Jie/awesome-openwebui +version: 0.1.0 +description: Automatically merges context from multiple model responses in the previous turn. +""" + import asyncio from typing import List, Optional, Dict from pydantic import BaseModel, Field