diff --git a/plugins/actions/ACTION_PLUGIN_TEMPLATE.py b/plugins/actions/ACTION_PLUGIN_TEMPLATE.py
index 7b99038..addf720 100644
--- a/plugins/actions/ACTION_PLUGIN_TEMPLATE.py
+++ b/plugins/actions/ACTION_PLUGIN_TEMPLATE.py
@@ -101,11 +101,11 @@ HTML_WRAPPER_TEMPLATE = """
class Action:
class Valves(BaseModel):
- show_status: bool = Field(
+ SHOW_STATUS: bool = Field(
default=True,
description="Whether to show operation status updates in the chat interface.",
)
- LLM_MODEL_ID: str = Field(
+ MODEL_ID: str = Field(
default="",
description="Built-in LLM Model ID used for processing. If empty, uses the current conversation's model.",
)
@@ -231,7 +231,7 @@ class Action:
done: bool = False,
):
"""Emits a status update event."""
- if self.valves.show_status and emitter:
+ if self.valves.SHOW_STATUS and emitter:
await emitter(
{"type": "status", "data": {"description": description, "done": done}}
)
@@ -301,7 +301,7 @@ class Action:
)
# 5. Determine Model
- target_model = self.valves.LLM_MODEL_ID
+ target_model = self.valves.MODEL_ID
if not target_model:
target_model = body.get("model")
# Note: No hardcoded fallback here, relies on system/user context
@@ -362,4 +362,9 @@ class Action:
# Append error to chat (optional)
body["messages"][-1]["content"] += f"\n\n❌ **Error**: {error_msg}"
+ await self._emit_status(__event_emitter__, "Processing failed.", done=True)
+ await self._emit_notification(
+ __event_emitter__, "Action failed, please check logs.", "error"
+ )
+
return body
diff --git a/plugins/actions/ACTION_PLUGIN_TEMPLATE_CN.py b/plugins/actions/ACTION_PLUGIN_TEMPLATE_CN.py
index e83c2cf..50d0ddb 100644
--- a/plugins/actions/ACTION_PLUGIN_TEMPLATE_CN.py
+++ b/plugins/actions/ACTION_PLUGIN_TEMPLATE_CN.py
@@ -101,11 +101,11 @@ HTML_WRAPPER_TEMPLATE = """
class Action:
class Valves(BaseModel):
- show_status: bool = Field(
+ SHOW_STATUS: bool = Field(
default=True,
description="是否在聊天界面显示操作状态更新。",
)
- LLM_MODEL_ID: str = Field(
+ MODEL_ID: str = Field(
default="",
description="用于处理的内置 LLM 模型 ID。如果为空,则使用当前对话的模型。",
)
@@ -242,7 +242,7 @@ class Action:
done: bool = False,
):
"""发送状态更新事件。"""
- if self.valves.show_status and emitter:
+ if self.valves.SHOW_STATUS and emitter:
await emitter(
{"type": "status", "data": {"description": description, "done": done}}
)
@@ -312,7 +312,7 @@ class Action:
)
# 5. 确定模型
- target_model = self.valves.LLM_MODEL_ID
+ target_model = self.valves.MODEL_ID
if not target_model:
target_model = body.get("model")
# 注意: 这里没有硬编码的回退,依赖于系统/用户上下文
diff --git a/plugins/actions/PLUGIN_DEVELOPMENT_GUIDE.md b/plugins/actions/PLUGIN_DEVELOPMENT_GUIDE.md
new file mode 100644
index 0000000..d689ead
--- /dev/null
+++ b/plugins/actions/PLUGIN_DEVELOPMENT_GUIDE.md
@@ -0,0 +1,292 @@
+# OpenWebUI HTML Action 插件开发指南
+
+> 本文档定义了开发 OpenWebUI Action 插件的标准规范和最佳实践。
+
+## 📐 核心技术规范
+
+### 1. Valves 配置规范 (Pydantic BaseModel)
+
+**命名规则**: 所有字段必须使用 **大写+下划线** (UPPER_CASE)。
+
+```python
+class Valves(BaseModel):
+ SHOW_STATUS: bool = Field(default=True, description="是否显示状态更新")
+ MODEL_ID: str = Field(default="", description="指定模型 ID,留空则使用当前对话模型")
+ MIN_TEXT_LENGTH: int = Field(default=50, description="最小字符限制")
+ CLEAR_PREVIOUS_HTML: bool = Field(default=False, description="是否清除旧结果")
+ # 根据需要添加更多配置...
+```
+
+**常用字段参考**:
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| `SHOW_STATUS` | `bool` | 控制是否显示状态更新 |
+| `MODEL_ID` | `str` | 允许用户指定 LLM 模型 |
+| `MIN_TEXT_LENGTH` | `int` | 设置触发分析的最小字符数 |
+| `MAX_TEXT_LENGTH` | `int` | 设置推荐的最大字符数 |
+| `CLEAR_PREVIOUS_HTML` | `bool` | 控制是覆盖还是合并旧的插件输出 |
+| `LANGUAGE` | `str` | 目标语言 (如 'zh', 'en') |
+
+---
+
+### 2. 事件发送规范 (Event Emission)
+
+**禁止直接调用** `await __event_emitter__`。必须实现并使用以下辅助方法:
+
+```python
+async def _emit_status(self, emitter, description: str, done: bool = False):
+ """发送状态更新事件。"""
+ if self.valves.SHOW_STATUS and emitter:
+ await emitter(
+ {"type": "status", "data": {"description": description, "done": done}}
+ )
+
+async def _emit_notification(self, emitter, content: str, ntype: str = "info"):
+ """发送通知事件 (info/success/warning/error)。"""
+ if emitter:
+ await emitter(
+ {"type": "notification", "data": {"type": ntype, "content": content}}
+ )
+```
+
+**使用示例**:
+```python
+# 开始处理
+await self._emit_status(__event_emitter__, "正在分析文本...", done=False)
+
+# 处理完成
+await self._emit_status(__event_emitter__, "分析完成!", done=True)
+await self._emit_notification(__event_emitter__, "报告已生成", "success")
+
+# 发生错误
+await self._emit_status(__event_emitter__, "处理失败。", done=True)
+await self._emit_notification(__event_emitter__, f"错误: {str(e)}", "error")
+```
+
+---
+
+### 3. 日志与调试
+
+- **严禁使用** `print()`
+- 必须使用 Python 标准库 `logging`
+
+```python
+import logging
+
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.INFO)
+
+# 记录关键路径
+logger.info(f"Action: {__name__} started")
+
+# 记录异常 (包含堆栈信息)
+logger.error(f"处理失败: {str(e)}", exc_info=True)
+```
+
+---
+
+### 4. HTML 注入逻辑
+
+#### 4.1 HTML 包装器
+使用统一的注释标记插件内容:
+```html
+
+
+
+
+
+
+```
+
+#### 4.2 合并机制
+必须实现以下方法,支持在同一条消息中多次运行插件:
+
+```python
+def _remove_existing_html(self, content: str) -> str:
+ """移除已存在的插件 HTML 块。"""
+ pattern = r"```html\s*[\s\S]*?```"
+ return re.sub(pattern, "", content).strip()
+
+def _merge_html(
+ self,
+ existing_html: str,
+ new_content: str,
+ new_styles: str = "",
+ new_scripts: str = "",
+ user_language: str = "en-US",
+) -> str:
+ """合并新内容到已有 HTML 容器,或创建新容器。"""
+ # 实现逻辑...
+```
+
+#### 4.3 注入流程
+```python
+# 1. 提取已有 HTML
+existing_html_block = ""
+match = re.search(
+ r"```html\s*([\s\S]*?)```",
+ original_content,
+)
+if match:
+ existing_html_block = match.group(1)
+
+# 2. 根据配置决定是否清除旧内容
+if self.valves.CLEAR_PREVIOUS_HTML:
+ original_content = self._remove_existing_html(original_content)
+ final_html = self._merge_html("", new_content, new_styles, "", user_language)
+else:
+ # 合并到已有 HTML
+ final_html = self._merge_html(existing_html_block, new_content, new_styles, "", user_language)
+
+# 3. 注入到消息
+html_embed_tag = f"```html\n{final_html}\n```"
+body["messages"][-1]["content"] = f"{original_content}\n\n{html_embed_tag}"
+```
+
+---
+
+## 📝 完整代码结构模板
+
+```python
+"""
+title: 插件名称
+author: 作者名
+version: 0.1.0
+description: 插件描述
+"""
+
+import logging
+import re
+import json
+import time
+from typing import Optional, Dict, Any, Callable, Awaitable
+from pydantic import BaseModel, Field
+from starlette.requests import Request
+
+from open_webui.apps.webui.models.users import Users
+from open_webui.main import generate_chat_completion
+
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.INFO)
+
+# ============ 提示词模板 ============
+SYSTEM_PROMPT = """你是一个专业的..."""
+USER_PROMPT_TEMPLATE = """请分析以下内容: {content}"""
+
+# ============ HTML 模板 ============
+HTML_WRAPPER_TEMPLATE = """
+
+
+
+
+"""
+
+class Action:
+ class Valves(BaseModel):
+ SHOW_STATUS: bool = Field(default=True, description="是否显示状态更新")
+ MODEL_ID: str = Field(default="", description="指定模型 ID")
+ MIN_TEXT_LENGTH: int = Field(default=50, description="最小字符限制")
+ CLEAR_PREVIOUS_HTML: bool = Field(default=False, description="是否清除旧结果")
+
+ def __init__(self):
+ self.valves = self.Valves()
+
+ # ========== 事件发送辅助方法 ==========
+ async def _emit_status(self, emitter, description: str, done: bool = False):
+ if self.valves.SHOW_STATUS and emitter:
+ await emitter({"type": "status", "data": {"description": description, "done": done}})
+
+ async def _emit_notification(self, emitter, content: str, ntype: str = "info"):
+ if emitter:
+ await emitter({"type": "notification", "data": {"type": ntype, "content": content}})
+
+ # ========== HTML 处理辅助方法 ==========
+ def _remove_existing_html(self, content: str) -> str:
+ pattern = r"```html\s*[\s\S]*?```"
+ return re.sub(pattern, "", content).strip()
+
+ def _merge_html(self, existing_html: str, new_content: str, new_styles: str = "", new_scripts: str = "", user_language: str = "en-US") -> str:
+ # 实现合并逻辑...
+ pass
+
+ # ========== 主入口 ==========
+ async def action(
+ self,
+ body: dict,
+ __user__: Optional[Dict[str, Any]] = None,
+ __event_emitter__: Optional[Callable[[Any], Awaitable[None]]] = None,
+ __request__: Optional[Request] = None,
+ ) -> Optional[dict]:
+ logger.info(f"Action: {__name__} started")
+
+ # 1. 输入校验
+ messages = body.get("messages", [])
+ if not messages or not messages[-1].get("content"):
+ return body
+
+ original_content = messages[-1]["content"]
+
+ if len(original_content) < self.valves.MIN_TEXT_LENGTH:
+ await self._emit_notification(__event_emitter__, "文本过短", "warning")
+ return body
+
+ # 2. 发送开始状态
+ await self._emit_status(__event_emitter__, "正在处理...", done=False)
+
+ try:
+ # 3. 调用 LLM
+ user_id = __user__.get("id") if __user__ else "default"
+ user_obj = Users.get_user_by_id(user_id)
+
+ target_model = self.valves.MODEL_ID or body.get("model")
+
+ payload = {
+ "model": target_model,
+ "messages": [
+ {"role": "system", "content": SYSTEM_PROMPT},
+ {"role": "user", "content": USER_PROMPT_TEMPLATE.format(content=original_content)},
+ ],
+ "stream": False,
+ }
+
+ llm_response = await generate_chat_completion(__request__, payload, user_obj)
+ result = llm_response["choices"][0]["message"]["content"]
+
+ # 4. 生成 HTML
+ # ...
+
+ # 5. 注入到消息
+ html_embed_tag = f"```html\n{final_html}\n```"
+ body["messages"][-1]["content"] = f"{original_content}\n\n{html_embed_tag}"
+
+ # 6. 发送成功通知
+ await self._emit_status(__event_emitter__, "处理完成!", done=True)
+ await self._emit_notification(__event_emitter__, "操作成功", "success")
+
+ except Exception as e:
+ logger.error(f"处理失败: {e}", exc_info=True)
+ await self._emit_status(__event_emitter__, "处理失败", done=True)
+ await self._emit_notification(__event_emitter__, f"错误: {str(e)}", "error")
+
+ return body
+```
+
+---
+
+## 🎨 UI 设计原则
+
+1. **响应式**: 确保 HTML 在移动端和桌面端都能完美显示
+2. **交互性**: 适当添加 JavaScript 交互(如点击展开、切换视图、复制内容)
+3. **本地化**: 根据 `__user__.get("language")` 自动适配中英文界面
+4. **美学设计**: 优先使用现代 UI 设计
+ - 毛玻璃效果 (backdrop-filter)
+ - 渐变色 (linear-gradient)
+ - 圆角卡片 (border-radius)
+ - Google Fonts 字体
+
+---
+
+## 📚 参考模板
+
+- [英文模板](./ACTION_PLUGIN_TEMPLATE.py)
+- [中文模板](./ACTION_PLUGIN_TEMPLATE_CN.py)
diff --git a/plugins/actions/knowledge-card/knowledge_card.py b/plugins/actions/knowledge-card/knowledge_card.py
index c32e8fb..7cc9e8e 100644
--- a/plugins/actions/knowledge-card/knowledge_card.py
+++ b/plugins/actions/knowledge-card/knowledge_card.py
@@ -71,27 +71,27 @@ HTML_WRAPPER_TEMPLATE = """
class Action:
class Valves(BaseModel):
- model_id: str = Field(
+ MODEL_ID: str = Field(
default="",
description="Model ID used for generating card content. If empty, uses the current model.",
)
- min_text_length: int = Field(
+ MIN_TEXT_LENGTH: int = Field(
default=50,
description="Minimum text length required to generate a flashcard (characters).",
)
- max_text_length: int = Field(
+ MAX_TEXT_LENGTH: int = Field(
default=2000,
description="Recommended maximum text length. For longer texts, deep analysis tools are recommended.",
)
- language: str = Field(
+ LANGUAGE: str = Field(
default="en",
description="Target language for card content (e.g., 'en', 'zh').",
)
- show_status: bool = Field(
+ SHOW_STATUS: bool = Field(
default=True,
description="Whether to show status updates in the chat interface.",
)
- clear_previous_html: bool = Field(
+ CLEAR_PREVIOUS_HTML: bool = Field(
default=False,
description="Whether to force clear previous plugin results (if True, overwrites instead of merging).",
)
@@ -106,7 +106,7 @@ class Action:
__event_emitter__: Optional[Any] = None,
__request__: Optional[Any] = None,
) -> Optional[dict]:
- print(f"action:{__name__} triggered")
+ logger.info(f"Action: {__name__} triggered")
if not __event_emitter__:
return body
@@ -121,49 +121,34 @@ class Action:
# Check text length
text_length = len(target_message)
- if text_length < self.valves.min_text_length:
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "warning",
- "content": f"Text too short ({text_length} chars), recommended at least {self.valves.min_text_length} chars.",
- },
- }
- )
+ if text_length < self.valves.MIN_TEXT_LENGTH:
+ await self._emit_notification(
+ __event_emitter__,
+ f"Text too short ({text_length} chars), recommended at least {self.valves.MIN_TEXT_LENGTH} chars.",
+ "warning",
+ )
return body
- if text_length > self.valves.max_text_length:
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "info",
- "content": f"Text quite long ({text_length} chars), consider using 'Deep Reading' for deep analysis.",
- },
- }
- )
+ if text_length > self.valves.MAX_TEXT_LENGTH:
+ await self._emit_notification(
+ __event_emitter__,
+ f"Text quite long ({text_length} chars), consider using 'Deep Reading' for deep analysis.",
+ "info",
+ )
# Notify user that we are generating the card
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "info",
- "content": "⚡ Generating Flash Card...",
- },
- }
- )
+ await self._emit_notification(
+ __event_emitter__, "⚡ Generating Flash Card...", "info"
+ )
try:
# 1. Extract information using LLM
user_id = __user__.get("id") if __user__ else "default"
user_obj = Users.get_user_by_id(user_id)
- model = self.valves.model_id if self.valves.model_id else body.get("model")
+ target_model = (
+ self.valves.MODEL_ID if self.valves.MODEL_ID else body.get("model")
+ )
system_prompt = f"""
You are a Flash Card Generation Expert, specializing in creating knowledge cards suitable for learning and memorization. Your task is to distill text into concise, easy-to-remember flashcards.
@@ -178,7 +163,7 @@ Please extract the following fields and return them in JSON format:
4. "tags": List 2-4 classification tags (1-3 words each).
5. "category": Choose a main category (e.g., Concept, Skill, Fact, Method, etc.).
-Target Language: {self.valves.language}
+Target Language: {self.valves.LANGUAGE}
Important Principles:
- **Minimalism**: Refine each point to the extreme.
@@ -191,7 +176,7 @@ Important Principles:
prompt = f"Please refine the following text into a learning flashcard:\n\n{target_message}"
payload = {
- "model": model,
+ "model": target_model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
@@ -213,16 +198,11 @@ Important Principles:
card_data = json.loads(content)
except Exception as e:
logger.error(f"Failed to parse JSON: {e}, content: {content}")
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "error",
- "content": "Failed to generate card data, please try again.",
- },
- }
- )
+ await self._emit_notification(
+ __event_emitter__,
+ "Failed to generate card data, please try again.",
+ "error",
+ )
return body
# 2. Generate HTML components
@@ -238,12 +218,12 @@ Important Principles:
if match:
existing_html_block = match.group(1)
- if self.valves.clear_previous_html:
+ if self.valves.CLEAR_PREVIOUS_HTML:
body["messages"][-1]["content"] = self._remove_existing_html(
body["messages"][-1]["content"]
)
final_html = self._merge_html(
- "", card_content, card_style, "", self.valves.language
+ "", card_content, card_style, "", self.valves.LANGUAGE
)
else:
if existing_html_block:
@@ -255,43 +235,43 @@ Important Principles:
card_content,
card_style,
"",
- self.valves.language,
+ self.valves.LANGUAGE,
)
else:
final_html = self._merge_html(
- "", card_content, card_style, "", self.valves.language
+ "", card_content, card_style, "", self.valves.LANGUAGE
)
html_embed_tag = f"```html\n{final_html}\n```"
body["messages"][-1]["content"] += f"\n\n{html_embed_tag}"
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "success",
- "content": "⚡ Flash Card generated successfully!",
- },
- }
- )
+ await self._emit_notification(
+ __event_emitter__, "⚡ Flash Card generated successfully!", "success"
+ )
return body
except Exception as e:
logger.error(f"Error generating knowledge card: {e}")
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "error",
- "content": f"Error generating knowledge card: {str(e)}",
- },
- }
- )
+ await self._emit_notification(
+ __event_emitter__, f"Error generating knowledge card: {str(e)}", "error"
+ )
return body
+ async def _emit_status(self, emitter, 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, content: str, ntype: str = "info"):
+ """Emits a notification event (info/success/warning/error)."""
+ if emitter:
+ await emitter(
+ {"type": "notification", "data": {"type": ntype, "content": content}}
+ )
+
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/knowledge-card/闪记卡.py b/plugins/actions/knowledge-card/闪记卡.py
index f2ff2c4..48119fd 100644
--- a/plugins/actions/knowledge-card/闪记卡.py
+++ b/plugins/actions/knowledge-card/闪记卡.py
@@ -71,24 +71,24 @@ HTML_WRAPPER_TEMPLATE = """
class Action:
class Valves(BaseModel):
- model_id: str = Field(
+ MODEL_ID: str = Field(
default="",
description="用于生成卡片内容的模型 ID。如果为空,则使用当前模型。",
)
- min_text_length: int = Field(
+ MIN_TEXT_LENGTH: int = Field(
default=50, description="生成闪记卡所需的最小文本长度(字符数)。"
)
- max_text_length: int = Field(
+ MAX_TEXT_LENGTH: int = Field(
default=2000,
description="建议的最大文本长度。超过此长度建议使用深度分析工具。",
)
- language: str = Field(
+ LANGUAGE: str = Field(
default="zh", description="卡片内容的目标语言 (例如 'zh', 'en')。"
)
- show_status: bool = Field(
+ SHOW_STATUS: bool = Field(
default=True, description="是否在聊天界面显示状态更新。"
)
- clear_previous_html: bool = Field(
+ CLEAR_PREVIOUS_HTML: bool = Field(
default=False,
description="是否强制清除旧的插件结果(如果为 True,则不合并,直接覆盖)。",
)
@@ -103,7 +103,7 @@ class Action:
__event_emitter__: Optional[Any] = None,
__request__: Optional[Any] = None,
) -> Optional[dict]:
- print(f"action:{__name__} triggered")
+ logger.info(f"Action: {__name__} 触发")
if not __event_emitter__:
return body
@@ -118,49 +118,32 @@ class Action:
# Check text length
text_length = len(target_message)
- if text_length < self.valves.min_text_length:
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "warning",
- "content": f"文本过短({text_length}字符),建议至少{self.valves.min_text_length}字符。",
- },
- }
- )
+ if text_length < self.valves.MIN_TEXT_LENGTH:
+ await self._emit_notification(
+ __event_emitter__,
+ f"文本内容过短 ({text_length} 字符),建议至少 {self.valves.MIN_TEXT_LENGTH} 字符。",
+ "warning",
+ )
return body
- if text_length > self.valves.max_text_length:
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "info",
- "content": f"文本较长({text_length}字符),建议使用'墨海拾贝'进行深度分析。",
- },
- }
- )
+ if text_length > self.valves.MAX_TEXT_LENGTH:
+ await self._emit_notification(
+ __event_emitter__,
+ f"文本较长({text_length}字符),建议使用'墨海拾贝'进行深度分析。",
+ "info",
+ )
# Notify user that we are generating the card
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "info",
- "content": "⚡ 正在生成闪记卡...",
- },
- }
- )
+ await self._emit_notification(__event_emitter__, "⚡ 正在生成闪记卡...", "info")
try:
# 1. Extract information using LLM
user_id = __user__.get("id") if __user__ else "default"
user_obj = Users.get_user_by_id(user_id)
- model = self.valves.model_id if self.valves.model_id else body.get("model")
+ target_model = (
+ self.valves.MODEL_ID if self.valves.MODEL_ID else body.get("model")
+ )
system_prompt = f"""
你是一个闪记卡生成专家,专注于创建适合学习和记忆的知识卡片。你的任务是将文本提炼成简洁、易记的学习卡片。
@@ -175,7 +158,7 @@ class Action:
4. "tags": 列出 2-4 个分类标签(每个 2-5 字)
5. "category": 选择一个主分类(如:概念、技能、事实、方法等)
-目标语言: {self.valves.language}
+目标语言: {self.valves.LANGUAGE}
重要原则:
- **极简主义**: 每个要点都要精炼到极致
@@ -188,7 +171,7 @@ class Action:
prompt = f"请将以下文本提炼成一张学习记忆卡片:\n\n{target_message}"
payload = {
- "model": model,
+ "model": target_model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
@@ -210,16 +193,9 @@ class Action:
card_data = json.loads(content)
except Exception as e:
logger.error(f"Failed to parse JSON: {e}, content: {content}")
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "error",
- "content": "生成卡片数据失败,请重试。",
- },
- }
- )
+ await self._emit_notification(
+ __event_emitter__, "生成卡片数据失败,请重试。", "error"
+ )
return body
# 2. Generate HTML components
@@ -235,12 +211,12 @@ class Action:
if match:
existing_html_block = match.group(1)
- if self.valves.clear_previous_html:
+ if self.valves.CLEAR_PREVIOUS_HTML:
body["messages"][-1]["content"] = self._remove_existing_html(
body["messages"][-1]["content"]
)
final_html = self._merge_html(
- "", card_content, card_style, "", self.valves.language
+ "", card_content, card_style, "", self.valves.LANGUAGE
)
else:
if existing_html_block:
@@ -252,43 +228,43 @@ class Action:
card_content,
card_style,
"",
- self.valves.language,
+ self.valves.LANGUAGE,
)
else:
final_html = self._merge_html(
- "", card_content, card_style, "", self.valves.language
+ "", card_content, card_style, "", self.valves.LANGUAGE
)
html_embed_tag = f"```html\n{final_html}\n```"
body["messages"][-1]["content"] += f"\n\n{html_embed_tag}"
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "success",
- "content": "⚡ 闪记卡生成成功!",
- },
- }
- )
+ await self._emit_notification(
+ __event_emitter__, "⚡ 闪记卡生成成功!", "success"
+ )
return body
except Exception as e:
logger.error(f"Error generating knowledge card: {e}")
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "error",
- "content": f"生成知识卡片时出错: {str(e)}",
- },
- }
- )
+ await self._emit_notification(
+ __event_emitter__, f"生成知识卡片时出错: {str(e)}", "error"
+ )
return body
+ async def _emit_status(self, emitter, description: str, done: bool = False):
+ """发送状态更新事件。"""
+ if self.valves.SHOW_STATUS and emitter:
+ await emitter(
+ {"type": "status", "data": {"description": description, "done": done}}
+ )
+
+ async def _emit_notification(self, emitter, content: str, ntype: str = "info"):
+ """发送通知事件 (info/success/warning/error)。"""
+ if emitter:
+ await emitter(
+ {"type": "notification", "data": {"type": ntype, "content": content}}
+ )
+
def _remove_existing_html(self, content: str) -> str:
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
pattern = r"```html\s*[\s\S]*?```"
diff --git a/plugins/actions/smart-mind-map/smart_mind_map.py b/plugins/actions/smart-mind-map/smart_mind_map.py
index 4ad5d25..35810e1 100644
--- a/plugins/actions/smart-mind-map/smart_mind_map.py
+++ b/plugins/actions/smart-mind-map/smart_mind_map.py
@@ -394,11 +394,11 @@ SCRIPT_TEMPLATE_MINDMAP = """
class Action:
class Valves(BaseModel):
- show_status: bool = Field(
+ SHOW_STATUS: bool = Field(
default=True,
description="Whether to show action status updates in the chat interface.",
)
- LLM_MODEL_ID: str = Field(
+ MODEL_ID: str = Field(
default="",
description="Built-in LLM model ID for text analysis. If empty, uses the current conversation's model.",
)
@@ -434,6 +434,20 @@ class Action:
extracted_content = llm_output.strip()
return extracted_content.replace("", "<\\/script>")
+ async def _emit_status(self, emitter, 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, content: str, ntype: str = "info"):
+ """Emits a notification event (info/success/warning/error)."""
+ if emitter:
+ await emitter(
+ {"type": "notification", "data": {"type": ntype, "content": content}}
+ )
+
def _remove_existing_html(self, content: str) -> str:
"""Removes existing plugin-generated HTML code blocks from the content."""
pattern = r"```html\s*[\s\S]*?```"
@@ -523,16 +537,11 @@ class Action:
current_year = now.strftime("%Y")
current_timezone_str = "Unknown"
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "info",
- "content": "Smart Mind Map is starting, generating mind map for you...",
- },
- }
- )
+ await self._emit_notification(
+ __event_emitter__,
+ "Smart Mind Map is starting, generating mind map for you...",
+ "info",
+ )
messages = body.get("messages")
if (
@@ -541,13 +550,7 @@ class Action:
or not messages[-1].get("content")
):
error_message = "Unable to retrieve valid user message content."
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {"type": "error", "content": error_message},
- }
- )
+ await self._emit_notification(__event_emitter__, error_message, "error")
return {
"messages": [{"role": "assistant", "content": f"❌ {error_message}"}]
}
@@ -565,30 +568,20 @@ class Action:
if len(long_text_content) < self.valves.MIN_TEXT_LENGTH:
short_text_message = f"Text content is too short ({len(long_text_content)} characters), unable to perform effective analysis. Please provide at least {self.valves.MIN_TEXT_LENGTH} characters of text."
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {"type": "warning", "content": short_text_message},
- }
- )
+ await self._emit_notification(
+ __event_emitter__, short_text_message, "warning"
+ )
return {
"messages": [
{"role": "assistant", "content": f"⚠️ {short_text_message}"}
]
}
- if self.valves.show_status and __event_emitter__:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "Smart Mind Map: Analyzing text structure in depth...",
- "done": False,
- "hidden": False,
- },
- }
- )
+ await self._emit_status(
+ __event_emitter__,
+ "Smart Mind Map: Analyzing text structure in depth...",
+ False,
+ )
try:
unique_id = f"id_{int(time.time() * 1000)}"
@@ -603,7 +596,7 @@ class Action:
)
# Determine model to use
- target_model = self.valves.LLM_MODEL_ID
+ target_model = self.valves.MODEL_ID
if not target_model:
target_model = body.get("model")
@@ -684,26 +677,14 @@ class Action:
html_embed_tag = f"```html\n{final_html}\n```"
body["messages"][-1]["content"] = f"{long_text_content}\n\n{html_embed_tag}"
- if self.valves.show_status and __event_emitter__:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "Smart Mind Map: Drawing completed!",
- "done": True,
- "hidden": False,
- },
- }
- )
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "success",
- "content": f"Mind map has been generated, {user_name}!",
- },
- }
- )
+ await self._emit_status(
+ __event_emitter__, "Smart Mind Map: Drawing completed!", True
+ )
+ await self._emit_notification(
+ __event_emitter__,
+ f"Mind map has been generated, {user_name}!",
+ "success",
+ )
logger.info("Action: Smart Mind Map (v0.7.2) completed successfully")
except Exception as e:
@@ -714,26 +695,13 @@ class Action:
"content"
] = f"{long_text_content}\n\n❌ **Error:** {user_facing_error}"
- if __event_emitter__:
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "Smart Mind Map: Processing failed.",
- "done": True,
- "hidden": False,
- },
- }
- )
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "error",
- "content": f"Smart Mind Map generation failed, {user_name}!",
- },
- }
- )
+ await self._emit_status(
+ __event_emitter__, "Smart Mind Map: Processing failed.", True
+ )
+ await self._emit_notification(
+ __event_emitter__,
+ f"Smart Mind Map generation failed, {user_name}!",
+ "error",
+ )
return body
diff --git a/plugins/actions/smart-mind-map/思维导图.py b/plugins/actions/smart-mind-map/思维导图.py
index b518e1c..f7ba0ff 100644
--- a/plugins/actions/smart-mind-map/思维导图.py
+++ b/plugins/actions/smart-mind-map/思维导图.py
@@ -394,10 +394,10 @@ SCRIPT_TEMPLATE_MINDMAP = """
class Action:
class Valves(BaseModel):
- show_status: bool = Field(
+ SHOW_STATUS: bool = Field(
default=True, description="是否在聊天界面显示操作状态更新。"
)
- LLM_MODEL_ID: str = Field(
+ MODEL_ID: str = Field(
default="",
description="用于文本分析的内置LLM模型ID。如果为空,则使用当前对话的模型。",
)
@@ -433,6 +433,20 @@ class Action:
extracted_content = llm_output.strip()
return extracted_content.replace("", "<\\/script>")
+ async def _emit_status(self, emitter, description: str, done: bool = False):
+ """发送状态更新事件。"""
+ if self.valves.SHOW_STATUS and emitter:
+ await emitter(
+ {"type": "status", "data": {"description": description, "done": done}}
+ )
+
+ async def _emit_notification(self, emitter, content: str, ntype: str = "info"):
+ """发送通知事件 (info/success/warning/error)。"""
+ if emitter:
+ await emitter(
+ {"type": "notification", "data": {"type": ntype, "content": content}}
+ )
+
def _remove_existing_html(self, content: str) -> str:
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
pattern = r"```html\s*[\s\S]*?```"
@@ -522,16 +536,9 @@ class Action:
current_year = now.strftime("%Y")
current_timezone_str = "未知时区"
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "info",
- "content": "智绘心图已启动,正在为您生成思维导图...",
- },
- }
- )
+ await self._emit_notification(
+ __event_emitter__, "智绘心图已启动,正在为您生成思维导图...", "info"
+ )
messages = body.get("messages")
if (
@@ -540,13 +547,7 @@ class Action:
or not messages[-1].get("content")
):
error_message = "无法获取有效的用户消息内容。"
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {"type": "error", "content": error_message},
- }
- )
+ await self._emit_notification(__event_emitter__, error_message, "error")
return {
"messages": [{"role": "assistant", "content": f"❌ {error_message}"}]
}
@@ -564,30 +565,18 @@ class Action:
if len(long_text_content) < self.valves.MIN_TEXT_LENGTH:
short_text_message = f"文本内容过短({len(long_text_content)}字符),无法进行有效分析。请提供至少{self.valves.MIN_TEXT_LENGTH}字符的文本。"
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {"type": "warning", "content": short_text_message},
- }
- )
+ await self._emit_notification(
+ __event_emitter__, short_text_message, "warning"
+ )
return {
"messages": [
{"role": "assistant", "content": f"⚠️ {short_text_message}"}
]
}
- if self.valves.show_status and __event_emitter__:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "智绘心图: 深入分析文本结构...",
- "done": False,
- "hidden": False,
- },
- }
- )
+ await self._emit_status(
+ __event_emitter__, "智绘心图: 深入分析文本结构...", False
+ )
try:
unique_id = f"id_{int(time.time() * 1000)}"
@@ -602,7 +591,7 @@ class Action:
)
# 确定使用的模型
- target_model = self.valves.LLM_MODEL_ID
+ target_model = self.valves.MODEL_ID
if not target_model:
target_model = body.get("model")
@@ -682,26 +671,10 @@ class Action:
html_embed_tag = f"```html\n{final_html}\n```"
body["messages"][-1]["content"] = f"{long_text_content}\n\n{html_embed_tag}"
- if self.valves.show_status and __event_emitter__:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "智绘心图: 绘制完成!",
- "done": True,
- "hidden": False,
- },
- }
- )
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "success",
- "content": f"思维导图已生成,{user_name}!",
- },
- }
- )
+ await self._emit_status(__event_emitter__, "智绘心图: 绘制完成!", True)
+ await self._emit_notification(
+ __event_emitter__, f"思维导图已生成,{user_name}!", "success"
+ )
logger.info("Action: 智绘心图 (v12) completed successfully")
except Exception as e:
@@ -712,26 +685,9 @@ class Action:
"content"
] = f"{long_text_content}\n\n❌ **错误:** {user_facing_error}"
- if __event_emitter__:
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "智绘心图: 处理失败。",
- "done": True,
- "hidden": False,
- },
- }
- )
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "error",
- "content": f"智绘心图生成失败, {user_name}!",
- },
- }
- )
+ await self._emit_status(__event_emitter__, "智绘心图: 处理失败。", True)
+ await self._emit_notification(
+ __event_emitter__, f"智绘心图生成失败, {user_name}!", "error"
+ )
return body
diff --git a/plugins/actions/summary/summary.py b/plugins/actions/summary/summary.py
index 2828c84..9b798ea 100644
--- a/plugins/actions/summary/summary.py
+++ b/plugins/actions/summary/summary.py
@@ -305,11 +305,11 @@ CONTENT_TEMPLATE_SUMMARY = """
class Action:
class Valves(BaseModel):
- show_status: bool = Field(
+ SHOW_STATUS: bool = Field(
default=True,
description="Whether to show operation status updates in the chat interface.",
)
- LLM_MODEL_ID: str = Field(
+ MODEL_ID: str = Field(
default="",
description="Built-in LLM Model ID used for text analysis. If empty, uses the current conversation's model.",
)
@@ -381,6 +381,20 @@ class Action:
"actions_html": actions_html,
}
+ async def _emit_status(self, emitter, 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, content: str, ntype: str = "info"):
+ """Emits a notification event (info/success/warning/error)."""
+ if emitter:
+ await emitter(
+ {"type": "notification", "data": {"type": ntype, "content": content}}
+ )
+
def _remove_existing_html(self, content: str) -> str:
"""Removes existing plugin-generated HTML code blocks from the content."""
pattern = r"```html\s*[\s\S]*?```"
@@ -485,13 +499,9 @@ class Action:
if len(original_content) < self.valves.MIN_TEXT_LENGTH:
short_text_message = f"Text content too short ({len(original_content)} chars), recommended at least {self.valves.MIN_TEXT_LENGTH} chars for effective deep analysis.\n\n💡 Tip: For short texts, consider using '⚡ Flash Card' for quick refinement."
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {"type": "warning", "content": short_text_message},
- }
- )
+ await self._emit_notification(
+ __event_emitter__, short_text_message, "warning"
+ )
return {
"messages": [
{"role": "assistant", "content": f"⚠️ {short_text_message}"}
@@ -500,37 +510,22 @@ class Action:
# Recommend for longer texts
if len(original_content) < self.valves.RECOMMENDED_MIN_LENGTH:
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "info",
- "content": f"Text length is {len(original_content)} chars. Recommended {self.valves.RECOMMENDED_MIN_LENGTH}+ chars for best analysis results.",
- },
- }
- )
-
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "info",
- "content": "📖 Deep Reading started, analyzing deeply...",
- },
- }
+ await self._emit_notification(
+ __event_emitter__,
+ f"Text length is {len(original_content)} chars. Recommended {self.valves.RECOMMENDED_MIN_LENGTH}+ chars for best analysis results.",
+ "info",
)
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "📖 Deep Reading: Analyzing text, extracting essence...",
- "done": False,
- },
- }
- )
+
+ await self._emit_notification(
+ __event_emitter__,
+ "📖 Deep Reading started, analyzing deeply...",
+ "info",
+ )
+ await self._emit_status(
+ __event_emitter__,
+ "📖 Deep Reading: Analyzing text, extracting essence...",
+ False,
+ )
formatted_user_prompt = USER_PROMPT_GENERATE_SUMMARY.format(
user_name=user_name,
@@ -542,7 +537,7 @@ class Action:
)
# Determine model to use
- target_model = self.valves.LLM_MODEL_ID
+ target_model = self.valves.MODEL_ID
if not target_model:
target_model = body.get("model")
@@ -611,25 +606,14 @@ class Action:
html_embed_tag = f"```html\n{final_html}\n```"
body["messages"][-1]["content"] = f"{original_content}\n\n{html_embed_tag}"
- if self.valves.show_status and __event_emitter__:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "📖 Deep Reading: Analysis complete!",
- "done": True,
- },
- }
- )
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "success",
- "content": f"📖 Deep Reading complete, {user_name}! Deep analysis report generated.",
- },
- }
- )
+ await self._emit_status(
+ __event_emitter__, "📖 Deep Reading: Analysis complete!", True
+ )
+ await self._emit_notification(
+ __event_emitter__,
+ f"📖 Deep Reading complete, {user_name}! Deep analysis report generated.",
+ "success",
+ )
except Exception as e:
error_message = f"Deep Reading processing failed: {str(e)}"
@@ -639,25 +623,13 @@ class Action:
"content"
] = f"{original_content}\n\n❌ **Error:** {user_facing_error}"
- if __event_emitter__:
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "Deep Reading: Processing failed.",
- "done": True,
- },
- }
- )
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "error",
- "content": f"Deep Reading processing failed, {user_name}!",
- },
- }
- )
+ await self._emit_status(
+ __event_emitter__, "Deep Reading: Processing failed.", True
+ )
+ await self._emit_notification(
+ __event_emitter__,
+ f"Deep Reading processing failed, {user_name}!",
+ "error",
+ )
return body
diff --git a/plugins/actions/summary/精读.py b/plugins/actions/summary/精读.py
index d23835e..63d3975 100644
--- a/plugins/actions/summary/精读.py
+++ b/plugins/actions/summary/精读.py
@@ -302,10 +302,10 @@ CONTENT_TEMPLATE_SUMMARY = """
class Action:
class Valves(BaseModel):
- show_status: bool = Field(
+ SHOW_STATUS: bool = Field(
default=True, description="是否在聊天界面显示操作状态更新。"
)
- LLM_MODEL_ID: str = Field(
+ MODEL_ID: str = Field(
default="",
description="用于文本分析的内置LLM模型ID。如果为空,则使用当前对话的模型。",
)
@@ -379,6 +379,20 @@ class Action:
"actions_html": actions_html,
}
+ async def _emit_status(self, emitter, description: str, done: bool = False):
+ """发送状态更新事件。"""
+ if self.valves.SHOW_STATUS and emitter:
+ await emitter(
+ {"type": "status", "data": {"description": description, "done": done}}
+ )
+
+ async def _emit_notification(self, emitter, content: str, ntype: str = "info"):
+ """发送通知事件 (info/success/warning/error)。"""
+ if emitter:
+ await emitter(
+ {"type": "notification", "data": {"type": ntype, "content": content}}
+ )
+
def _remove_existing_html(self, content: str) -> str:
"""移除内容中已有的插件生成 HTML 代码块 (通过标记识别)。"""
pattern = r"```html\s*[\s\S]*?```"
@@ -484,13 +498,9 @@ class Action:
if len(original_content) < self.valves.MIN_TEXT_LENGTH:
short_text_message = f"文本内容过短({len(original_content)}字符),建议至少{self.valves.MIN_TEXT_LENGTH}字符以获得有效的深度分析。\n\n💡 提示:对于短文本,建议使用'⚡ 闪记卡'进行快速提炼。"
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {"type": "warning", "content": short_text_message},
- }
- )
+ await self._emit_notification(
+ __event_emitter__, short_text_message, "warning"
+ )
return {
"messages": [
{"role": "assistant", "content": f"⚠️ {short_text_message}"}
@@ -499,37 +509,18 @@ class Action:
# Recommend for longer texts
if len(original_content) < self.valves.RECOMMENDED_MIN_LENGTH:
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "info",
- "content": f"文本长度为{len(original_content)}字符。建议{self.valves.RECOMMENDED_MIN_LENGTH}字符以上可获得更好的分析效果。",
- },
- }
- )
-
- if __event_emitter__:
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "info",
- "content": "📖 精读已启动,正在进行深度分析...",
- },
- }
+ await self._emit_notification(
+ __event_emitter__,
+ f"文本长度为{len(original_content)}字符。建议{self.valves.RECOMMENDED_MIN_LENGTH}字符以上可获得更好的分析效果。",
+ "info",
)
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "📖 精读: 深入分析文本,提炼精华...",
- "done": False,
- },
- }
- )
+
+ await self._emit_notification(
+ __event_emitter__, "📖 精读已启动,正在进行深度分析...", "info"
+ )
+ await self._emit_status(
+ __event_emitter__, "📖 精读: 深入分析文本,提炼精华...", False
+ )
formatted_user_prompt = USER_PROMPT_GENERATE_SUMMARY.format(
user_name=user_name,
@@ -541,7 +532,7 @@ class Action:
)
# 确定使用的模型
- target_model = self.valves.LLM_MODEL_ID
+ target_model = self.valves.MODEL_ID
if not target_model:
target_model = body.get("model")
@@ -610,22 +601,12 @@ class Action:
html_embed_tag = f"```html\n{final_html}\n```"
body["messages"][-1]["content"] = f"{original_content}\n\n{html_embed_tag}"
- if self.valves.show_status and __event_emitter__:
- await __event_emitter__(
- {
- "type": "status",
- "data": {"description": "📖 精读: 分析完成!", "done": True},
- }
- )
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "success",
- "content": f"📖 精读完成,{user_name}!深度分析报告已生成。",
- },
- }
- )
+ await self._emit_status(__event_emitter__, "📖 精读: 分析完成!", True)
+ await self._emit_notification(
+ __event_emitter__,
+ f"📖 精读完成,{user_name}!深度分析报告已生成。",
+ "success",
+ )
except Exception as e:
error_message = f"精读处理失败: {str(e)}"
@@ -635,25 +616,9 @@ class Action:
"content"
] = f"{original_content}\n\n❌ **错误:** {user_facing_error}"
- if __event_emitter__:
- if self.valves.show_status:
- await __event_emitter__(
- {
- "type": "status",
- "data": {
- "description": "精读: 处理失败。",
- "done": True,
- },
- }
- )
- await __event_emitter__(
- {
- "type": "notification",
- "data": {
- "type": "error",
- "content": f"精读处理失败, {user_name}!",
- },
- }
- )
+ await self._emit_status(__event_emitter__, "精读: 处理失败。", True)
+ await self._emit_notification(
+ __event_emitter__, f"精读处理失败, {user_name}!", "error"
+ )
return body