Fix: Remove duplicate parameters and correct documentation

This commit is contained in:
fujie
2026-01-15 00:24:52 +08:00
parent 8868b28a84
commit e4cbf231a6
23 changed files with 856 additions and 218 deletions

View File

@@ -1,6 +1,6 @@
# 🌊 Deep Dive # 🌊 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. A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.

View File

@@ -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)
全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。 全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。

View File

@@ -1,8 +1,8 @@
""" """
title: Deep Dive title: Deep Dive
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/open-webui
version: 1.0.0 version: 1.0.0
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg== icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg==
requirements: markdown requirements: markdown
@@ -466,6 +466,10 @@ class Action:
default=True, default=True,
description="Whether to show operation status updates.", 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( MODEL_ID: str = Field(
default="", default="",
description="LLM Model ID for analysis. Empty = use current model.", description="LLM Model ID for analysis. Empty = use current model.",
@@ -501,6 +505,42 @@ class Action:
"user_language": user_data.get("language", "en-US"), "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]: def _process_llm_output(self, llm_output: str) -> Dict[str, str]:
"""Parse LLM output and convert to styled HTML.""" """Parse LLM output and convert to styled HTML."""
# Extract sections using flexible regex # Extract sections using flexible regex
@@ -700,6 +740,26 @@ class Action:
{"type": "notification", "data": {"type": ntype, "content": content}} {"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: def _remove_existing_html(self, content: str) -> str:
"""Removes existing plugin-generated HTML.""" """Removes existing plugin-generated HTML."""
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```" pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"

View File

@@ -1,8 +1,8 @@
""" """
title: 精读 title: 精读
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/open-webui
version: 1.0.0 version: 1.0.0
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg== icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg==
requirements: markdown requirements: markdown
@@ -466,6 +466,10 @@ class Action:
default=True, default=True,
description="是否显示操作状态更新。", description="是否显示操作状态更新。",
) )
SHOW_DEBUG_LOG: bool = Field(
default=False,
description="是否在浏览器控制台打印调试日志。",
)
MODEL_ID: str = Field( MODEL_ID: str = Field(
default="", default="",
description="用于分析的 LLM 模型 ID。留空则使用当前模型。", description="用于分析的 LLM 模型 ID。留空则使用当前模型。",
@@ -501,6 +505,42 @@ class Action:
"user_language": user_data.get("language", "zh-CN"), "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]: def _process_llm_output(self, llm_output: str) -> Dict[str, str]:
"""解析 LLM 输出并转换为样式化 HTML。""" """解析 LLM 输出并转换为样式化 HTML。"""
# 使用灵活的正则提取各部分 # 使用灵活的正则提取各部分
@@ -694,6 +734,26 @@ class Action:
{"type": "notification", "data": {"type": ntype, "content": content}} {"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: def _remove_existing_html(self, content: str) -> str:
"""移除已有的插件生成的 HTML。""" """移除已有的插件生成的 HTML。"""
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```" pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"

View File

@@ -1,6 +1,6 @@
# 📝 Export to Word (Enhanced) # 📝 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**. Export conversation to Word (.docx) with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.

View File

@@ -1,6 +1,6 @@
# 📝 导出为 Word (增强版) # 📝 导出为 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 图表**、**引用参考**和**增强表格格式**。 将对话导出为 Word (.docx),支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用参考**和**增强表格格式**。

View File

@@ -1,8 +1,8 @@
""" """
title: Export to Word (Enhanced) title: Export to Word (Enhanced)
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/open-webui
version: 0.4.3 version: 0.4.3
openwebui_id: fca6a315-2a45-42cc-8c96-55cbc85f87f2 openwebui_id: fca6a315-2a45-42cc-8c96-55cbc85f87f2
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
@@ -150,6 +150,14 @@ class Action:
default="chat_title", default="chat_title",
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown 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( MAX_EMBED_IMAGE_MB: int = Field(
default=20, default=20,
@@ -320,10 +328,100 @@ class Action:
return msg return msg
return msg return msg
async def _send_notification(self, emitter: Callable, type: str, content: str): def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
await emitter( """Safely extracts user context information."""
{"type": "notification", "data": {"type": type, "content": content}} 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( async def action(
self, self,
@@ -397,14 +495,15 @@ class Action:
message_content = self._strip_reasoning_blocks(message_content) message_content = self._strip_reasoning_blocks(message_content)
if not message_content or not message_content.strip(): if not message_content or not message_content.strip():
await self._send_notification( await self._emit_notification(
__event_emitter__, "error", self._get_msg("error_no_content") __event_emitter__, self._get_msg("error_no_content"), "error"
) )
return return
# Generate filename # Generate filename
title = "" 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 # Fetch chat_title directly via chat_id as it's usually missing in body
chat_title = "" chat_title = ""
@@ -873,10 +972,10 @@ class Action:
} }
) )
await self._send_notification( await self._emit_notification(
__event_emitter__, __event_emitter__,
"success",
self._get_msg("success", filename=filename), self._get_msg("success", filename=filename),
"success",
) )
return {"message": "Download triggered"} return {"message": "Download triggered"}
@@ -892,10 +991,10 @@ class Action:
}, },
} }
) )
await self._send_notification( await self._emit_notification(
__event_emitter__, __event_emitter__,
"error",
self._get_msg("error_export", error=str(e)), self._get_msg("error_export", error=str(e)),
"error",
) )
async def generate_title_using_ai( async def generate_title_using_ai(

View File

@@ -1,8 +1,8 @@
""" """
title: 导出为 Word (增强版) title: 导出为 Word (增强版)
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/open-webui
version: 0.4.3 version: 0.4.3
openwebui_id: 8a6306c0-d005-4e46-aaae-8db3532c9ed5 openwebui_id: 8a6306c0-d005-4e46-aaae-8db3532c9ed5
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
@@ -150,6 +150,14 @@ class Action:
default="chat_title", default="chat_title",
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown 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( 最大嵌入图片大小MB: int = Field(
default=20, default=20,
@@ -320,10 +328,100 @@ class Action:
return msg return msg
return msg return msg
async def _send_notification(self, emitter: Callable, type: str, content: str): def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
await emitter( """安全提取用户上下文信息。"""
{"type": "notification", "data": {"type": type, "content": content}} 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( async def action(
self, self,
@@ -395,14 +493,15 @@ class Action:
message_content = self._strip_reasoning_blocks(message_content) message_content = self._strip_reasoning_blocks(message_content)
if not message_content or not message_content.strip(): if not message_content or not message_content.strip():
await self._send_notification( await self._emit_notification(
__event_emitter__, "error", self._get_msg("error_no_content") __event_emitter__, self._get_msg("error_no_content"), "error"
) )
return return
# Generate filename # Generate filename
title = "" 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 # Fetch chat_title directly via chat_id as it's usually missing in body
chat_title = "" chat_title = ""
@@ -871,10 +970,10 @@ class Action:
} }
) )
await self._send_notification( await self._emit_notification(
__event_emitter__, __event_emitter__,
"success",
self._get_msg("success", filename=filename), self._get_msg("success", filename=filename),
"success",
) )
return {"message": "Download triggered"} return {"message": "Download triggered"}
@@ -890,10 +989,10 @@ class Action:
}, },
} }
) )
await self._send_notification( await self._emit_notification(
__event_emitter__, __event_emitter__,
"error",
self._get_msg("error_export", error=str(e)), self._get_msg("error_export", error=str(e)),
"error",
) )
async def generate_title_using_ai( async def generate_title_using_ai(

View File

@@ -1,8 +1,8 @@
""" """
title: Flash Card title: Flash Card
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/open-webui
version: 0.2.4 version: 0.2.4
openwebui_id: 65a2ea8f-2a13-4587-9d76-55eea0035cc8 openwebui_id: 65a2ea8f-2a13-4587-9d76-55eea0035cc8
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4= icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
@@ -89,6 +89,10 @@ class Action:
default=True, default=True,
description="Whether to show status updates in the chat interface.", 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( CLEAR_PREVIOUS_HTML: bool = Field(
default=False, default=False,
description="Whether to force clear previous plugin results (if True, overwrites instead of merging).", 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"), "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( async def action(
self, self,
body: dict, body: dict,
@@ -331,6 +371,26 @@ Important Principles:
{"type": "notification", "data": {"type": ntype, "content": content}} {"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: def _remove_existing_html(self, content: str) -> str:
"""Removes existing plugin-generated HTML code blocks from the content.""" """Removes existing plugin-generated HTML code blocks from the content."""
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```" pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"

View File

@@ -1,8 +1,8 @@
""" """
title: 闪记卡 (Flash Card) title: 闪记卡 (Flash Card)
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/open-webui
version: 0.2.4 version: 0.2.4
openwebui_id: 4a31eac3-a3c4-4c30-9ca5-dab36b5fac65 openwebui_id: 4a31eac3-a3c4-4c30-9ca5-dab36b5fac65
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4= icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwb2x5Z29uIHBvaW50cz0iMTIgMiAyIDcgMTIgMTIgMjIgNyAxMiAyIi8+PHBvbHlsaW5lIHBvaW50cz0iMiAxNyAxMiAyMiAyMiAxNyIvPjxwb2x5bGluZSBwb2ludHM9IjIgMTIgMTIgMTcgMjIgMTIiLz48L3N2Zz4=
@@ -86,6 +86,10 @@ class Action:
SHOW_STATUS: bool = Field( SHOW_STATUS: bool = Field(
default=True, description="是否在聊天界面显示状态更新。" default=True, description="是否在聊天界面显示状态更新。"
) )
SHOW_DEBUG_LOG: bool = Field(
default=False,
description="是否在浏览器控制台打印调试日志。",
)
CLEAR_PREVIOUS_HTML: bool = Field( CLEAR_PREVIOUS_HTML: bool = Field(
default=False, default=False,
description="是否强制清除旧的插件结果(如果为 True则不合并直接覆盖", description="是否强制清除旧的插件结果(如果为 True则不合并直接覆盖",
@@ -113,6 +117,42 @@ class Action:
"user_language": user_data.get("language", "zh-CN"), "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( async def action(
self, self,
body: dict, body: dict,
@@ -314,6 +354,26 @@ class Action:
{"type": "notification", "data": {"type": ntype, "content": content}} {"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: def _remove_existing_html(self, content: str) -> str:
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。""" """移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```" pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"

View File

@@ -1,6 +1,6 @@
# 📊 Smart Infographic (AntV) # 📊 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. An Open WebUI plugin powered by the AntV Infographic engine. It transforms long text into professional, beautiful infographics with a single click.

View File

@@ -1,6 +1,6 @@
# 📊 智能信息图 (AntV Infographic) # 📊 智能信息图 (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 插件,能够将长文本内容一键转换为专业、美观的信息图表。 基于 AntV Infographic 引擎的 Open WebUI 插件,能够将长文本内容一键转换为专业、美观的信息图表。

View File

@@ -2,6 +2,7 @@
title: 📊 Smart Infographic (AntV) title: 📊 Smart Infographic (AntV)
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4= icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
version: 1.4.9 version: 1.4.9
openwebui_id: ad6f0c7f-c571-4dea-821d-8e71697274cf openwebui_id: ad6f0c7f-c571-4dea-821d-8e71697274cf
@@ -263,6 +264,8 @@ data
4. **Indentation**: Use 2 spaces. 4. **Indentation**: Use 2 spaces.
""" """
import json
USER_PROMPT_GENERATE_INFOGRAPHIC = """ USER_PROMPT_GENERATE_INFOGRAPHIC = """
Please analyze the following text content and convert its core information into AntV Infographic syntax format. Please analyze the following text content and convert its core information into AntV Infographic syntax format.
@@ -947,49 +950,64 @@ class Action:
default="image", default="image",
description="Output mode: 'html' for interactive HTML, or 'image' to embed as Markdown image (default).", 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): def __init__(self):
self.valves = self.Valves() self.valves = self.Valves()
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str: def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
"""Extract chat_id from body or metadata""" """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): if isinstance(body, dict):
chat_id = body.get("chat_id") chat_id = body.get("chat_id", "")
if isinstance(chat_id, str) and chat_id.strip(): message_id = body.get("id", "") # message_id is usually 'id' in body
return chat_id.strip()
body_metadata = body.get("metadata", {}) # Check body.metadata as fallback
if isinstance(body_metadata, dict): if not chat_id or not message_id:
chat_id = body_metadata.get("chat_id") body_metadata = body.get("metadata", {})
if isinstance(chat_id, str) and chat_id.strip(): if isinstance(body_metadata, dict):
return chat_id.strip() 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): # 2. Try to get from __metadata__ (as supplement)
chat_id = metadata.get("chat_id") if __metadata__ and isinstance(__metadata__, dict):
if isinstance(chat_id, str) and chat_id.strip(): if not chat_id:
return chat_id.strip() chat_id = __metadata__.get("chat_id", "")
if not message_id:
message_id = __metadata__.get("message_id", "")
return "" return {
"chat_id": str(chat_id).strip(),
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str: "message_id": str(message_id).strip(),
"""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 ""
def _extract_infographic_syntax(self, llm_output: str) -> str: def _extract_infographic_syntax(self, llm_output: str) -> str:
"""Extract infographic syntax from LLM output""" """Extract infographic syntax from LLM output"""
@@ -1018,6 +1036,24 @@ class Action:
{"type": "notification", "data": {"type": ntype, "content": content}} {"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: def _remove_existing_html(self, content: str) -> str:
"""Remove existing plugin-generated HTML code blocks from content""" """Remove existing plugin-generated HTML code blocks from content"""
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```" pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
@@ -1628,8 +1664,9 @@ class Action:
# Check output mode # Check output mode
if self.valves.OUTPUT_MODE == "image": if self.valves.OUTPUT_MODE == "image":
# Image mode: use JavaScript to render and embed as Markdown image # Image mode: use JavaScript to render and embed as Markdown image
chat_id = self._extract_chat_id(body, body.get("metadata")) chat_ctx = self._get_chat_context(body, __metadata__)
message_id = self._extract_message_id(body, body.get("metadata")) chat_id = chat_ctx["chat_id"]
message_id = chat_ctx["message_id"]
await self._emit_status( await self._emit_status(
__event_emitter__, __event_emitter__,

View File

@@ -2,6 +2,7 @@
title: 📊 智能信息图 (AntV Infographic) title: 📊 智能信息图 (AntV Infographic)
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4= icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4=
version: 1.4.9 version: 1.4.9
openwebui_id: e04a48ff-23ee-4a41-8ea7-66c19524e7c8 openwebui_id: e04a48ff-23ee-4a41-8ea7-66c19524e7c8
@@ -244,6 +245,8 @@ data
3. **Language**: Use the user's requested language for content. 3. **Language**: Use the user's requested language for content.
""" """
import json
USER_PROMPT_GENERATE_INFOGRAPHIC = """ USER_PROMPT_GENERATE_INFOGRAPHIC = """
请分析以下文本内容,将其核心信息转换为 AntV Infographic 语法格式。 请分析以下文本内容,将其核心信息转换为 AntV Infographic 语法格式。
@@ -954,6 +957,10 @@ class Action:
default="image", default="image",
description="输出模式:'html' 为交互式HTML'image' 将嵌入为Markdown图片默认", description="输出模式:'html' 为交互式HTML'image' 将嵌入为Markdown图片默认",
) )
SHOW_DEBUG_LOG: bool = Field(
default=False,
description="是否在浏览器控制台打印调试日志。",
)
def __init__(self): def __init__(self):
self.valves = self.Valves() self.valves = self.Valves()
@@ -967,45 +974,56 @@ class Action:
"Sunday": "星期日", "Sunday": "星期日",
} }
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str: def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
"""从 body 或 metadata 中提取 chat_id""" """安全提取用户上下文信息。"""
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): if isinstance(body, dict):
chat_id = body.get("chat_id") chat_id = body.get("chat_id", "")
if isinstance(chat_id, str) and chat_id.strip(): message_id = body.get("id", "") # message_id 在 body 中通常是 id
return chat_id.strip()
body_metadata = body.get("metadata", {}) # 再次检查 body.metadata
if isinstance(body_metadata, dict): if not chat_id or not message_id:
chat_id = body_metadata.get("chat_id") body_metadata = body.get("metadata", {})
if isinstance(chat_id, str) and chat_id.strip(): if isinstance(body_metadata, dict):
return chat_id.strip() 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): # 2. 尝试从 __metadata__ 获取 (作为补充)
chat_id = metadata.get("chat_id") if __metadata__ and isinstance(__metadata__, dict):
if isinstance(chat_id, str) and chat_id.strip(): if not chat_id:
return chat_id.strip() chat_id = __metadata__.get("chat_id", "")
if not message_id:
message_id = __metadata__.get("message_id", "")
return "" return {
"chat_id": str(chat_id).strip(),
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str: "message_id": str(message_id).strip(),
"""从 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 ""
def _extract_infographic_syntax(self, llm_output: str) -> str: def _extract_infographic_syntax(self, llm_output: str) -> str:
"""提取LLM输出中的infographic语法""" """提取LLM输出中的infographic语法"""
@@ -1058,6 +1076,24 @@ class Action:
{"type": "notification", "data": {"type": ntype, "content": content}} {"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: def _remove_existing_html(self, content: str) -> str:
"""移除内容中已有的插件生成 HTML 代码块""" """移除内容中已有的插件生成 HTML 代码块"""
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```" pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
@@ -1662,8 +1698,9 @@ class Action:
# 检查输出模式 # 检查输出模式
if self.valves.OUTPUT_MODE == "image": if self.valves.OUTPUT_MODE == "image":
# 图片模式:使用 JavaScript 渲染并嵌入为 Markdown 图片 # 图片模式:使用 JavaScript 渲染并嵌入为 Markdown 图片
chat_id = self._extract_chat_id(body, body.get("metadata")) chat_ctx = self._get_chat_context(body, __metadata__)
message_id = self._extract_message_id(body, body.get("metadata")) chat_id = chat_ctx["chat_id"]
message_id = chat_ctx["message_id"]
await self._emit_status( await self._emit_status(
__event_emitter__, __event_emitter__,

View File

@@ -1,6 +1,6 @@
# Smart Mind Map - Mind Mapping Generation Plugin # Smart Mind Map - Mind Mapping Generation Plugin
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.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. > **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.

View File

@@ -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
> **重要提示**:为了确保所有插件的可维护性和易用性,每个插件都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。 > **重要提示**:为了确保所有插件的可维护性和易用性,每个插件都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。

View File

@@ -1,7 +1,8 @@
""" """
title: Smart Mind Map title: Smart Mind Map
author: Fu-Jie 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 funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 0.9.1 version: 0.9.1
openwebui_id: 3094c59a-b4dd-4e0c-9449-15e2dd547dc4 openwebui_id: 3094c59a-b4dd-4e0c-9449-15e2dd547dc4
@@ -49,6 +50,8 @@ Please strictly follow these guidelines:
``` ```
""" """
import json
USER_PROMPT_GENERATE_MINDMAP = """ 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. 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", default="html",
description="Output mode: 'html' for interactive HTML (default), or 'image' to embed as Markdown image.", 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): def __init__(self):
self.valves = self.Valves() self.valves = self.Valves()
@@ -819,45 +826,41 @@ class Action:
"user_language": user_data.get("language", "en-US"), "user_language": user_data.get("language", "en-US"),
} }
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str: def _get_chat_context(
"""Extract chat_id from body or metadata""" 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): if isinstance(body, dict):
chat_id = body.get("chat_id") chat_id = body.get("chat_id", "")
if isinstance(chat_id, str) and chat_id.strip(): message_id = body.get("id", "") # message_id is usually 'id' in body
return chat_id.strip()
body_metadata = body.get("metadata", {}) # Check body.metadata as fallback
if isinstance(body_metadata, dict): if not chat_id or not message_id:
chat_id = body_metadata.get("chat_id") body_metadata = body.get("metadata", {})
if isinstance(chat_id, str) and chat_id.strip(): if isinstance(body_metadata, dict):
return chat_id.strip() 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): # 2. Try to get from __metadata__ (as supplement)
chat_id = metadata.get("chat_id") if __metadata__ and isinstance(__metadata__, dict):
if isinstance(chat_id, str) and chat_id.strip(): if not chat_id:
return chat_id.strip() chat_id = __metadata__.get("chat_id", "")
if not message_id:
message_id = __metadata__.get("message_id", "")
return "" return {
"chat_id": str(chat_id).strip(),
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str: "message_id": str(message_id).strip(),
"""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 ""
def _extract_markdown_syntax(self, llm_output: str) -> str: def _extract_markdown_syntax(self, llm_output: str) -> str:
match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL) match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL)
@@ -884,6 +887,42 @@ class Action:
{"type": "notification", "data": {"type": ntype, "content": content}} {"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: def _remove_existing_html(self, content: str) -> str:
"""Removes existing plugin-generated HTML code blocks from the content.""" """Removes existing plugin-generated HTML code blocks from the content."""
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```" pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
@@ -1515,8 +1554,9 @@ class Action:
# Check output mode # Check output mode
if self.valves.OUTPUT_MODE == "image": if self.valves.OUTPUT_MODE == "image":
# Image mode: use JavaScript to render and embed as Markdown image # Image mode: use JavaScript to render and embed as Markdown image
chat_id = self._extract_chat_id(body, __metadata__) chat_ctx = self._get_chat_context(body, __metadata__)
message_id = self._extract_message_id(body, __metadata__) chat_id = chat_ctx["chat_id"]
message_id = chat_ctx["message_id"]
await self._emit_status( await self._emit_status(
__event_emitter__, __event_emitter__,

View File

@@ -1,8 +1,8 @@
""" """
title: 思维导图 title: 思维导图
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/open-webui
version: 0.9.1 version: 0.9.1
openwebui_id: 8d4b097b-219b-4dd2-b509-05fbe6388335 openwebui_id: 8d4b097b-219b-4dd2-b509-05fbe6388335
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4= icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSIyIiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iNiIgcng9IjEiLz48cmVjdCB4PSI5IiB5PSIyIiB3aWR0aD0iNiIgaGVpZ2h0PSI2IiByeD0iMSIvPjxwYXRoIGQ9Ik01IDE2di0zYTEgMSAwIDAgMSAxLTFoMTJhMSAxIDAgMCAxIDEgMXYzIi8+PHBhdGggZD0iTTEyIDEyVjgiLz48L3N2Zz4=
@@ -49,6 +49,8 @@ SYSTEM_PROMPT_MINDMAP_ASSISTANT = """
``` ```
""" """
import json
USER_PROMPT_GENERATE_MINDMAP = """ USER_PROMPT_GENERATE_MINDMAP = """
请分析以下长篇文本,并将其核心主题、关键概念、分支和子分支结构化为标准的Markdown列表语法,以供Markmap.js渲染。 请分析以下长篇文本,并将其核心主题、关键概念、分支和子分支结构化为标准的Markdown列表语法,以供Markmap.js渲染。
@@ -790,6 +792,10 @@ class Action:
default="html", default="html",
description="输出模式: 'html' 为交互式HTML(默认),'image' 为嵌入Markdown图片。", description="输出模式: 'html' 为交互式HTML(默认),'image' 为嵌入Markdown图片。",
) )
SHOW_DEBUG_LOG: bool = Field(
default=False,
description="是否在浏览器控制台打印调试日志。",
)
def __init__(self): def __init__(self):
self.valves = self.Valves() self.valves = self.Valves()
@@ -818,45 +824,41 @@ class Action:
"user_language": user_data.get("language", "zh-CN"), "user_language": user_data.get("language", "zh-CN"),
} }
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str: def _get_chat_context(
"""从 body 或 metadata 中提取 chat_id""" 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): if isinstance(body, dict):
chat_id = body.get("chat_id") chat_id = body.get("chat_id", "")
if isinstance(chat_id, str) and chat_id.strip(): message_id = body.get("id", "") # message_id 在 body 中通常是 id
return chat_id.strip()
body_metadata = body.get("metadata", {}) # 再次检查 body.metadata
if isinstance(body_metadata, dict): if not chat_id or not message_id:
chat_id = body_metadata.get("chat_id") body_metadata = body.get("metadata", {})
if isinstance(chat_id, str) and chat_id.strip(): if isinstance(body_metadata, dict):
return chat_id.strip() 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): # 2. 尝试从 __metadata__ 获取 (作为补充)
chat_id = metadata.get("chat_id") if __metadata__ and isinstance(__metadata__, dict):
if isinstance(chat_id, str) and chat_id.strip(): if not chat_id:
return chat_id.strip() chat_id = __metadata__.get("chat_id", "")
if not message_id:
message_id = __metadata__.get("message_id", "")
return "" return {
"chat_id": str(chat_id).strip(),
def _extract_message_id(self, body: dict, metadata: Optional[dict]) -> str: "message_id": str(message_id).strip(),
"""从 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 ""
def _extract_markdown_syntax(self, llm_output: str) -> str: def _extract_markdown_syntax(self, llm_output: str) -> str:
match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL) match = re.search(r"```markdown\s*(.*?)\s*```", llm_output, re.DOTALL)
@@ -881,6 +883,24 @@ class Action:
{"type": "notification", "data": {"type": ntype, "content": content}} {"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: def _remove_existing_html(self, content: str) -> str:
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。""" """移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```" pattern = r"```html\s*<!-- OPENWEBUI_PLUGIN_OUTPUT -->[\s\S]*?```"
@@ -1508,8 +1528,9 @@ class Action:
# 检查输出模式 # 检查输出模式
if self.valves.OUTPUT_MODE == "image": if self.valves.OUTPUT_MODE == "image":
# 图片模式: 使用 JavaScript 渲染并嵌入为 Markdown 图片 # 图片模式: 使用 JavaScript 渲染并嵌入为 Markdown 图片
chat_id = self._extract_chat_id(body, __metadata__) chat_ctx = self._get_chat_context(body, __metadata__)
message_id = self._extract_message_id(body, __metadata__) chat_id = chat_ctx["chat_id"]
message_id = chat_ctx["message_id"]
await self._emit_status( await self._emit_status(
__event_emitter__, __event_emitter__,

View File

@@ -1,6 +1,6 @@
# Async Context Compression Filter # 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. This filter reduces token consumption in long conversations through intelligent summarization and message compression while keeping conversations coherent.

View File

@@ -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
> **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。 > **重要提示**:为了确保所有过滤器的可维护性和易用性,每个过滤器都应附带清晰、完整的文档,以确保其功能、配置和使用方法得到充分说明。

View File

@@ -2,8 +2,8 @@
title: Async Context Compression title: Async Context Compression
id: async_context_compression id: async_context_compression
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_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. description: Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.
version: 1.1.3 version: 1.1.3
openwebui_id: b1655bc8-6de9-4cad-8cb5-a6f7829a02ce openwebui_id: b1655bc8-6de9-4cad-8cb5-a6f7829a02ce
@@ -621,25 +621,41 @@ class Filter:
"max_context_tokens": self.valves.max_context_tokens, "max_context_tokens": self.valves.max_context_tokens,
} }
def _extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str: def _get_chat_context(
"""Extract chat_id from body or metadata.""" 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): if isinstance(body, dict):
chat_id = body.get("chat_id") chat_id = body.get("chat_id", "")
if isinstance(chat_id, str) and chat_id.strip(): message_id = body.get("id", "") # message_id is usually 'id' in body
return chat_id.strip()
body_metadata = body.get("metadata", {}) # Check body.metadata as fallback
if isinstance(body_metadata, dict): if not chat_id or not message_id:
chat_id = body_metadata.get("chat_id") body_metadata = body.get("metadata", {})
if isinstance(chat_id, str) and chat_id.strip(): if isinstance(body_metadata, dict):
return chat_id.strip() 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): # 2. Try to get from __metadata__ (as supplement)
chat_id = metadata.get("chat_id") if __metadata__ and isinstance(__metadata__, dict):
if isinstance(chat_id, str) and chat_id.strip(): if not chat_id:
return chat_id.strip() 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( async def _emit_debug_log(
self, self,
@@ -750,7 +766,8 @@ class Filter:
Compression Strategy: Only responsible for injecting existing summaries, no Token calculation. Compression Strategy: Only responsible for injecting existing summaries, no Token calculation.
""" """
messages = body.get("messages", []) 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: if not chat_id:
await self._log( await self._log(
@@ -867,7 +884,8 @@ class Filter:
Executed after the LLM response is complete. 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). 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: if not chat_id:
await self._log( await self._log(
"[Outlet] ❌ Missing chat_id in metadata, skipping compression", "[Outlet] ❌ Missing chat_id in metadata, skipping compression",

View File

@@ -2,8 +2,8 @@
title: 异步上下文压缩 title: 异步上下文压缩
id: async_context_compression id: async_context_compression
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/open-webui
description: 通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。 description: 通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。
version: 1.1.3 version: 1.1.3
openwebui_id: 5c0617cb-a9e4-4bd6-a440-d276534ebd18 openwebui_id: 5c0617cb-a9e4-4bd6-a440-d276534ebd18
@@ -472,6 +472,42 @@ class Filter:
"max_context_tokens": self.valves.max_context_tokens, "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( async def _emit_debug_log(
self, self,
__event_call__, __event_call__,
@@ -581,7 +617,8 @@ class Filter:
压缩策略:只负责注入已有的摘要,不进行 Token 计算 压缩策略:只负责注入已有的摘要,不进行 Token 计算
""" """
messages = body.get("messages", []) 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: if self.valves.debug_mode or self.valves.show_debug_log:
await self._log( await self._log(
@@ -690,7 +727,8 @@ class Filter:
在 LLM 响应完成后执行 在 LLM 响应完成后执行
在后台计算 Token 数并触发摘要生成(不阻塞当前响应,不影响内容输出) 在后台计算 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 "" model = body.get("model") or ""
# 直接计算目标压缩进度 # 直接计算目标压缩进度

View File

@@ -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 import asyncio
from typing import List, Optional, Dict from typing import List, Optional, Dict
from pydantic import BaseModel, Field from pydantic import BaseModel, Field