fix:修复禁言插件和豆包画图插件
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
"""测试插件动作模块"""
|
||||
|
||||
# 导入所有动作模块以确保装饰器被执行
|
||||
from . import pic_action # noqa
|
||||
@@ -1,122 +0,0 @@
|
||||
import os
|
||||
import toml
|
||||
from src.common.logger_manager import get_logger
|
||||
|
||||
logger = get_logger("pic_config")
|
||||
|
||||
CONFIG_CONTENT = """\
|
||||
# 火山方舟 API 的基础 URL
|
||||
base_url = "https://ark.cn-beijing.volces.com/api/v3"
|
||||
# 用于图片生成的API密钥
|
||||
volcano_generate_api_key = "YOUR_VOLCANO_GENERATE_API_KEY_HERE"
|
||||
# 默认图片生成模型
|
||||
default_model = "doubao-seedream-3-0-t2i-250415"
|
||||
# 默认图片尺寸
|
||||
default_size = "1024x1024"
|
||||
|
||||
|
||||
# 是否默认开启水印
|
||||
default_watermark = true
|
||||
# 默认引导强度
|
||||
default_guidance_scale = 2.5
|
||||
# 默认随机种子
|
||||
default_seed = 42
|
||||
|
||||
# 缓存设置
|
||||
cache_enabled = true
|
||||
cache_max_size = 10
|
||||
|
||||
# 更多插件特定配置可以在此添加...
|
||||
# custom_parameter = "some_value"
|
||||
"""
|
||||
|
||||
# 默认配置字典,用于验证和修复
|
||||
DEFAULT_CONFIG = {
|
||||
"base_url": "https://ark.cn-beijing.volces.com/api/v3",
|
||||
"volcano_generate_api_key": "YOUR_VOLCANO_GENERATE_API_KEY_HERE",
|
||||
"default_model": "doubao-seedream-3-0-t2i-250415",
|
||||
"default_size": "1024x1024",
|
||||
"default_watermark": True,
|
||||
"default_guidance_scale": 2.5,
|
||||
"default_seed": 42,
|
||||
"cache_enabled": True,
|
||||
"cache_max_size": 10,
|
||||
}
|
||||
|
||||
|
||||
def validate_and_fix_config(config_path: str) -> bool:
|
||||
"""验证并修复配置文件"""
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = toml.load(f)
|
||||
|
||||
# 检查缺失的配置项
|
||||
missing_keys = []
|
||||
fixed = False
|
||||
|
||||
for key, default_value in DEFAULT_CONFIG.items():
|
||||
if key not in config:
|
||||
missing_keys.append(key)
|
||||
config[key] = default_value
|
||||
fixed = True
|
||||
logger.info(f"添加缺失的配置项: {key} = {default_value}")
|
||||
|
||||
# 验证配置值的类型和范围
|
||||
if isinstance(config.get("default_guidance_scale"), (int, float)):
|
||||
if not 0.1 <= config["default_guidance_scale"] <= 20.0:
|
||||
config["default_guidance_scale"] = 2.5
|
||||
fixed = True
|
||||
logger.info("修复无效的 default_guidance_scale 值")
|
||||
|
||||
if isinstance(config.get("default_seed"), (int, float)):
|
||||
config["default_seed"] = int(config["default_seed"])
|
||||
else:
|
||||
config["default_seed"] = 42
|
||||
fixed = True
|
||||
logger.info("修复无效的 default_seed 值")
|
||||
|
||||
if config.get("cache_max_size") and not isinstance(config["cache_max_size"], int):
|
||||
config["cache_max_size"] = 10
|
||||
fixed = True
|
||||
logger.info("修复无效的 cache_max_size 值")
|
||||
|
||||
# 如果有修复,写回文件
|
||||
if fixed:
|
||||
# 创建备份
|
||||
backup_path = config_path + ".backup"
|
||||
if os.path.exists(config_path):
|
||||
os.rename(config_path, backup_path)
|
||||
logger.info(f"已创建配置备份: {backup_path}")
|
||||
|
||||
# 写入修复后的配置
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
toml.dump(config, f)
|
||||
logger.info(f"配置文件已修复: {config_path}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"验证配置文件时出错: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def generate_config():
|
||||
# 获取当前脚本所在的目录
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
config_file_path = os.path.join(current_dir, "pic_action_config.toml")
|
||||
|
||||
if not os.path.exists(config_file_path):
|
||||
try:
|
||||
with open(config_file_path, "w", encoding="utf-8") as f:
|
||||
f.write(CONFIG_CONTENT)
|
||||
logger.info(f"配置文件已生成: {config_file_path}")
|
||||
logger.info("请记得编辑该文件,填入您的火山引擎API 密钥。")
|
||||
except IOError as e:
|
||||
logger.error(f"错误:无法写入配置文件 {config_file_path}。原因: {e}")
|
||||
else:
|
||||
# 验证并修复现有配置
|
||||
validate_and_fix_config(config_file_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_config()
|
||||
@@ -1,19 +0,0 @@
|
||||
# 火山方舟 API 的基础 URL
|
||||
base_url = "https://ark.cn-beijing.volces.com/api/v3"
|
||||
# 用于图片生成的API密钥
|
||||
volcano_generate_api_key = "YOUR_VOLCANO_GENERATE_API_KEY_HERE"
|
||||
# 默认图片生成模型
|
||||
default_model = "doubao-seedream-3-0-t2i-250415"
|
||||
# 默认图片尺寸
|
||||
default_size = "1024x1024"
|
||||
|
||||
|
||||
# 是否默认开启水印
|
||||
default_watermark = true
|
||||
# 默认引导强度
|
||||
default_guidance_scale = 2.5
|
||||
# 默认随机种子
|
||||
default_seed = 42
|
||||
|
||||
# 更多插件特定配置可以在此添加...
|
||||
# custom_parameter = "some_value"
|
||||
@@ -4,7 +4,7 @@
|
||||
这是一个测试插件,用于测试图片发送功能
|
||||
"""
|
||||
|
||||
"""豆包图片生成插件
|
||||
"""豆包图片生成插件包
|
||||
|
||||
这是一个基于火山引擎豆包模型的AI图片生成插件。
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
- 将文字描述转换为视觉图像
|
||||
- 创意图片和艺术作品生成
|
||||
|
||||
配置文件:src/plugins/doubao_pic/actions/pic_action_config.toml
|
||||
配置文件:config.toml
|
||||
|
||||
配置要求:
|
||||
1. 设置火山引擎API密钥 (volcano_generate_api_key)
|
||||
@@ -30,3 +30,7 @@
|
||||
|
||||
注意:需要有效的火山引擎API访问权限才能正常使用。
|
||||
"""
|
||||
|
||||
from .plugin import DoubaoImagePlugin
|
||||
|
||||
__all__ = ["DoubaoImagePlugin"]
|
||||
@@ -1,9 +1,20 @@
|
||||
# 豆包图片生成插件配置文件
|
||||
|
||||
# API配置
|
||||
base_url = "https://ark.cn-beijing.volces.com/api/v3"
|
||||
volcano_generate_api_key = "YOUR_VOLCANO_GENERATE_API_KEY_HERE"
|
||||
volcano_generate_api_key = "9481fe36-8db7-4353-b53d-eae8c74b6b96"
|
||||
|
||||
# 生成参数配置
|
||||
default_model = "doubao-seedream-3-0-t2i-250415"
|
||||
default_size = "1024x1024"
|
||||
default_watermark = true
|
||||
default_guidance_scale = 2.5
|
||||
default_seed = 42
|
||||
|
||||
# 缓存配置
|
||||
cache_enabled = true
|
||||
cache_max_size = 10
|
||||
|
||||
# 组件启用配置
|
||||
[components]
|
||||
enable_image_generation = true
|
||||
@@ -1,51 +1,56 @@
|
||||
"""
|
||||
豆包图片生成插件
|
||||
|
||||
基于火山引擎豆包模型的AI图片生成插件。
|
||||
|
||||
功能特性:
|
||||
- 智能LLM判定:根据聊天内容智能判断是否需要生成图片
|
||||
- 高质量图片生成:使用豆包Seed Dream模型生成图片
|
||||
- 结果缓存:避免重复生成相同内容的图片
|
||||
- 配置验证:自动验证和修复配置文件
|
||||
- 参数验证:完整的输入参数验证和错误处理
|
||||
- 多尺寸支持:支持多种图片尺寸生成
|
||||
|
||||
包含组件:
|
||||
- 图片生成Action - 根据描述使用火山引擎API生成图片
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import base64 # 新增:用于Base64编码
|
||||
import traceback # 新增:用于打印堆栈跟踪
|
||||
from typing import Tuple
|
||||
from src.chat.actions.plugin_action import PluginAction, register_action
|
||||
from src.chat.actions.base_action import ActionActivationType, ChatMode
|
||||
import base64
|
||||
import traceback
|
||||
import random
|
||||
from typing import List, Tuple, Type, Optional
|
||||
|
||||
# 导入新插件系统
|
||||
from src.plugin_system.base.base_plugin import BasePlugin
|
||||
from src.plugin_system.base.base_plugin import register_plugin
|
||||
from src.plugin_system.base.base_action import BaseAction
|
||||
from src.plugin_system.base.component_types import ComponentInfo, ActionActivationType, ChatMode
|
||||
from src.common.logger_manager import get_logger
|
||||
from .generate_pic_config import generate_config
|
||||
|
||||
logger = get_logger("pic_action")
|
||||
|
||||
# 当此模块被加载时,尝试生成配置文件(如果它不存在)
|
||||
# 注意:在某些插件加载机制下,这可能会在每次机器人启动或插件重载时执行
|
||||
# 考虑是否需要更复杂的逻辑来决定何时运行 (例如,仅在首次安装时)
|
||||
generate_config()
|
||||
logger = get_logger("doubao_pic_plugin")
|
||||
|
||||
|
||||
@register_action
|
||||
class PicAction(PluginAction):
|
||||
"""根据描述使用火山引擎HTTP API生成图片的动作处理类"""
|
||||
# ===== Action组件 =====
|
||||
|
||||
action_name = "pic_action"
|
||||
action_description = (
|
||||
"可以根据特定的描述,生成并发送一张图片,如果没提供描述,就根据聊天内容生成,你可以立刻画好,不用等待"
|
||||
)
|
||||
action_parameters = {
|
||||
"description": "图片描述,输入你想要生成并发送的图片的描述,必填",
|
||||
"size": "图片尺寸,例如 '1024x1024' (可选, 默认从配置或 '1024x1024')",
|
||||
}
|
||||
action_require = [
|
||||
"当有人让你画东西时使用,你可以立刻画好,不用等待",
|
||||
"当有人要求你生成并发送一张图片时使用",
|
||||
"当有人让你画一张图时使用",
|
||||
]
|
||||
enable_plugin = False
|
||||
action_config_file_name = "pic_action_config.toml"
|
||||
|
||||
# 激活类型设置
|
||||
class DoubaoImageGenerationAction(BaseAction):
|
||||
"""豆包图片生成Action - 根据描述使用火山引擎API生成图片"""
|
||||
|
||||
# Action基本信息
|
||||
action_name = "doubao_image_generation"
|
||||
action_description = "可以根据特定的描述,生成并发送一张图片,如果没提供描述,就根据聊天内容生成,你可以立刻画好,不用等待"
|
||||
|
||||
# 激活设置
|
||||
focus_activation_type = ActionActivationType.LLM_JUDGE # Focus模式使用LLM判定,精确理解需求
|
||||
normal_activation_type = ActionActivationType.KEYWORD # Normal模式使用关键词激活,快速响应
|
||||
|
||||
normal_activation_type = ActionActivationType.KEYWORD # Normal模式使用关键词激活,快速响应
|
||||
|
||||
# 关键词设置(用于Normal模式)
|
||||
activation_keywords = ["画", "绘制", "生成图片", "画图", "draw", "paint", "图片生成"]
|
||||
keyword_case_sensitive = False
|
||||
|
||||
|
||||
# LLM判定提示词(用于Focus模式)
|
||||
llm_judge_prompt = """
|
||||
判定是否需要使用图片生成动作的条件:
|
||||
@@ -67,77 +72,45 @@ class PicAction(PluginAction):
|
||||
4. 技术讨论中提到绘图概念但无生成需求
|
||||
5. 用户明确表示不需要图片时
|
||||
"""
|
||||
|
||||
# Random激活概率(备用)
|
||||
random_activation_probability = 0.15 # 适中概率,图片生成比较有趣
|
||||
|
||||
|
||||
mode_enable = ChatMode.ALL
|
||||
parallel_action = True
|
||||
|
||||
# Action参数定义
|
||||
action_parameters = {
|
||||
"description": "图片描述,输入你想要生成并发送的图片的描述,必填",
|
||||
"size": "图片尺寸,例如 '1024x1024' (可选, 默认从配置或 '1024x1024')",
|
||||
}
|
||||
|
||||
# Action使用场景
|
||||
action_require = [
|
||||
"当有人让你画东西时使用,你可以立刻画好,不用等待",
|
||||
"当有人要求你生成并发送一张图片时使用",
|
||||
"当有人让你画一张图时使用",
|
||||
]
|
||||
|
||||
# 简单的请求缓存,避免短时间内重复请求
|
||||
_request_cache = {}
|
||||
_cache_max_size = 10
|
||||
|
||||
# 模式启用设置 - 图片生成在所有模式下可用
|
||||
mode_enable = ChatMode.ALL
|
||||
|
||||
# 并行执行设置 - 图片生成可以与回复并行执行,不覆盖回复内容
|
||||
parallel_action = False
|
||||
|
||||
@classmethod
|
||||
def _get_cache_key(cls, description: str, model: str, size: str) -> str:
|
||||
"""生成缓存键"""
|
||||
return f"{description[:100]}|{model}|{size}" # 限制描述长度避免键过长
|
||||
|
||||
@classmethod
|
||||
def _cleanup_cache(cls):
|
||||
"""清理缓存,保持大小在限制内"""
|
||||
if len(cls._request_cache) > cls._cache_max_size:
|
||||
# 简单的FIFO策略,移除最旧的条目
|
||||
keys_to_remove = list(cls._request_cache.keys())[: -cls._cache_max_size // 2]
|
||||
for key in keys_to_remove:
|
||||
del cls._request_cache[key]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action_data: dict,
|
||||
reasoning: str,
|
||||
cycle_timers: dict,
|
||||
thinking_id: str,
|
||||
global_config: dict = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(action_data, reasoning, cycle_timers, thinking_id, global_config, **kwargs)
|
||||
|
||||
logger.info(f"{self.log_prefix} 开始绘图!原因是:{self.reasoning}")
|
||||
|
||||
http_base_url = self.config.get("base_url")
|
||||
http_api_key = self.config.get("volcano_generate_api_key")
|
||||
|
||||
if not (http_base_url and http_api_key):
|
||||
logger.error(
|
||||
f"{self.log_prefix} PicAction初始化, 但HTTP配置 (base_url 或 volcano_generate_api_key) 缺失. HTTP图片生成将失败."
|
||||
)
|
||||
else:
|
||||
logger.info(f"{self.log_prefix} HTTP方式初始化完成. Base URL: {http_base_url}, API Key已配置.")
|
||||
|
||||
# _restore_env_vars 方法不再需要,已移除
|
||||
|
||||
async def process(self) -> Tuple[bool, str]:
|
||||
"""处理图片生成动作(通过HTTP API)"""
|
||||
logger.info(f"{self.log_prefix} 执行 pic_action (HTTP): {self.reasoning}")
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
"""执行图片生成动作"""
|
||||
logger.info(f"{self.log_prefix} 执行豆包图片生成动作")
|
||||
|
||||
# 配置验证
|
||||
http_base_url = self.config.get("base_url")
|
||||
http_api_key = self.config.get("volcano_generate_api_key")
|
||||
http_base_url = self.api.get_config("base_url")
|
||||
http_api_key = self.api.get_config("volcano_generate_api_key")
|
||||
|
||||
if not (http_base_url and http_api_key):
|
||||
error_msg = "抱歉,图片生成功能所需的HTTP配置(如API地址或密钥)不完整,无法提供服务。"
|
||||
await self.send_message_by_expressor(error_msg)
|
||||
await self.send_reply(error_msg)
|
||||
logger.error(f"{self.log_prefix} HTTP调用配置缺失: base_url 或 volcano_generate_api_key.")
|
||||
return False, "HTTP配置不完整"
|
||||
|
||||
# API密钥验证
|
||||
if http_api_key == "YOUR_VOLCANO_GENERATE_API_KEY_HERE":
|
||||
if http_api_key == "YOUR_DOUBAO_API_KEY_HERE":
|
||||
error_msg = "图片生成功能尚未配置,请设置正确的API密钥。"
|
||||
await self.send_message_by_expressor(error_msg)
|
||||
await self.send_reply(error_msg)
|
||||
logger.error(f"{self.log_prefix} API密钥未配置")
|
||||
return False, "API密钥未配置"
|
||||
|
||||
@@ -145,7 +118,7 @@ class PicAction(PluginAction):
|
||||
description = self.action_data.get("description")
|
||||
if not description or not description.strip():
|
||||
logger.warning(f"{self.log_prefix} 图片描述为空,无法生成图片。")
|
||||
await self.send_message_by_expressor("你需要告诉我想要画什么样的图片哦~ 比如说'画一只可爱的小猫'")
|
||||
await self.send_reply("你需要告诉我想要画什么样的图片哦~ 比如说'画一只可爱的小猫'")
|
||||
return False, "图片描述为空"
|
||||
|
||||
# 清理和验证描述
|
||||
@@ -155,8 +128,8 @@ class PicAction(PluginAction):
|
||||
logger.info(f"{self.log_prefix} 图片描述过长,已截断")
|
||||
|
||||
# 获取配置
|
||||
default_model = self.config.get("default_model", "doubao-seedream-3-0-t2i-250415")
|
||||
image_size = self.action_data.get("size", self.config.get("default_size", "1024x1024"))
|
||||
default_model = self.api.get_config("default_model", "doubao-seedream-3-0-t2i-250415")
|
||||
image_size = self.action_data.get("size", self.api.get_config("default_size", "1024x1024"))
|
||||
|
||||
# 验证图片尺寸格式
|
||||
if not self._validate_image_size(image_size):
|
||||
@@ -168,58 +141,23 @@ class PicAction(PluginAction):
|
||||
if cache_key in self._request_cache:
|
||||
cached_result = self._request_cache[cache_key]
|
||||
logger.info(f"{self.log_prefix} 使用缓存的图片结果")
|
||||
await self.send_message_by_expressor("我之前画过类似的图片,用之前的结果~")
|
||||
|
||||
await self.send_reply("我之前画过类似的图片,用之前的结果~")
|
||||
|
||||
# 直接发送缓存的结果
|
||||
send_success = await self.send_message(type="image", data=cached_result)
|
||||
send_success = await self._send_image(cached_result)
|
||||
if send_success:
|
||||
await self.send_message_by_expressor("图片表情已发送!")
|
||||
return True, "图片表情已发送(缓存)"
|
||||
await self.send_reply("图片已发送!")
|
||||
return True, "图片已发送(缓存)"
|
||||
else:
|
||||
# 缓存失败,清除这个缓存项并继续正常流程
|
||||
del self._request_cache[cache_key]
|
||||
|
||||
# guidance_scale 现在完全由配置文件控制
|
||||
guidance_scale_input = self.config.get("default_guidance_scale", 2.5) # 默认2.5
|
||||
guidance_scale_val = 2.5 # Fallback default
|
||||
try:
|
||||
guidance_scale_val = float(guidance_scale_input)
|
||||
except (ValueError, TypeError):
|
||||
logger.warning(
|
||||
f"{self.log_prefix} 配置文件中的 default_guidance_scale 值 '{guidance_scale_input}' 无效 (应为浮点数),使用默认值 2.5。"
|
||||
)
|
||||
guidance_scale_val = 2.5
|
||||
# 获取其他配置参数
|
||||
guidance_scale_val = self._get_guidance_scale()
|
||||
seed_val = self._get_seed()
|
||||
watermark_val = self._get_watermark()
|
||||
|
||||
# Seed parameter - ensure it's always an integer
|
||||
seed_config_value = self.config.get("default_seed")
|
||||
seed_val = 42 # Default seed if not configured or invalid
|
||||
if seed_config_value is not None:
|
||||
try:
|
||||
seed_val = int(seed_config_value)
|
||||
except (ValueError, TypeError):
|
||||
logger.warning(
|
||||
f"{self.log_prefix} 配置文件中的 default_seed ('{seed_config_value}') 无效,将使用默认种子 42。"
|
||||
)
|
||||
# seed_val is already 42
|
||||
else:
|
||||
logger.info(
|
||||
f"{self.log_prefix} 未在配置中找到 default_seed,将使用默认种子 42。建议在配置文件中添加 default_seed。"
|
||||
)
|
||||
# seed_val is already 42
|
||||
|
||||
# Watermark 现在完全由配置文件控制
|
||||
effective_watermark_source = self.config.get("default_watermark", True) # 默认True
|
||||
if isinstance(effective_watermark_source, bool):
|
||||
watermark_val = effective_watermark_source
|
||||
elif isinstance(effective_watermark_source, str):
|
||||
watermark_val = effective_watermark_source.lower() == "true"
|
||||
else:
|
||||
logger.warning(
|
||||
f"{self.log_prefix} 配置文件中的 default_watermark 值 '{effective_watermark_source}' 无效 (应为布尔值或 'true'/'false'),使用默认值 True。"
|
||||
)
|
||||
watermark_val = True
|
||||
|
||||
await self.send_message_by_expressor(
|
||||
await self.send_reply(
|
||||
f"收到!正在为您生成关于 '{description}' 的图片,请稍候...(模型: {default_model}, 尺寸: {image_size})"
|
||||
)
|
||||
|
||||
@@ -253,17 +191,17 @@ class PicAction(PluginAction):
|
||||
|
||||
if encode_success:
|
||||
base64_image_string = encode_result
|
||||
send_success = await self.send_message(type="image", data=base64_image_string)
|
||||
send_success = await self._send_image(base64_image_string)
|
||||
if send_success:
|
||||
# 缓存成功的结果
|
||||
self._request_cache[cache_key] = base64_image_string
|
||||
self._cleanup_cache()
|
||||
|
||||
await self.send_message_by_expressor("图片表情已发送!")
|
||||
return True, "图片表情已发送"
|
||||
|
||||
await self.send_message_by_expressor("图片已发送!")
|
||||
return True, "图片已发送"
|
||||
else:
|
||||
await self.send_message_by_expressor("图片已处理为Base64,但作为表情发送失败了。")
|
||||
return False, "图片表情发送失败 (Base64)"
|
||||
await self.send_message_by_expressor("图片已处理为Base64,但发送失败了。")
|
||||
return False, "图片发送失败 (Base64)"
|
||||
else:
|
||||
await self.send_message_by_expressor(f"获取到图片URL,但在处理图片时失败了:{encode_result}")
|
||||
return False, f"图片处理失败(Base64): {encode_result}"
|
||||
@@ -272,6 +210,90 @@ class PicAction(PluginAction):
|
||||
await self.send_message_by_expressor(f"哎呀,生成图片时遇到问题:{error_message}")
|
||||
return False, f"图片生成失败: {error_message}"
|
||||
|
||||
def _get_guidance_scale(self) -> float:
|
||||
"""获取guidance_scale配置值"""
|
||||
guidance_scale_input = self.api.get_config("default_guidance_scale", 2.5)
|
||||
try:
|
||||
return float(guidance_scale_input)
|
||||
except (ValueError, TypeError):
|
||||
logger.warning(f"{self.log_prefix} default_guidance_scale 值无效,使用默认值 2.5")
|
||||
return 2.5
|
||||
|
||||
def _get_seed(self) -> int:
|
||||
"""获取seed配置值"""
|
||||
seed_config_value = self.api.get_config("default_seed")
|
||||
if seed_config_value is not None:
|
||||
try:
|
||||
return int(seed_config_value)
|
||||
except (ValueError, TypeError):
|
||||
logger.warning(f"{self.log_prefix} default_seed 值无效,使用默认值 42")
|
||||
return 42
|
||||
|
||||
def _get_watermark(self) -> bool:
|
||||
"""获取watermark配置值"""
|
||||
watermark_source = self.api.get_config("default_watermark", True)
|
||||
if isinstance(watermark_source, bool):
|
||||
return watermark_source
|
||||
elif isinstance(watermark_source, str):
|
||||
return watermark_source.lower() == "true"
|
||||
else:
|
||||
logger.warning(f"{self.log_prefix} default_watermark 值无效,使用默认值 True")
|
||||
return True
|
||||
|
||||
async def _send_image(self, base64_image: str) -> bool:
|
||||
"""发送图片"""
|
||||
try:
|
||||
# 使用聊天流信息确定发送目标
|
||||
chat_stream = self.api.get_service('chat_stream')
|
||||
if not chat_stream:
|
||||
logger.error(f"{self.log_prefix} 没有可用的聊天流发送图片")
|
||||
return False
|
||||
|
||||
if chat_stream.group_info:
|
||||
# 群聊
|
||||
return await self.api.send_message_to_target(
|
||||
message_type="image",
|
||||
content=base64_image,
|
||||
platform=chat_stream.platform,
|
||||
target_id=str(chat_stream.group_info.group_id),
|
||||
is_group=True,
|
||||
display_message="发送生成的图片"
|
||||
)
|
||||
else:
|
||||
# 私聊
|
||||
return await self.api.send_message_to_target(
|
||||
message_type="image",
|
||||
content=base64_image,
|
||||
platform=chat_stream.platform,
|
||||
target_id=str(chat_stream.user_info.user_id),
|
||||
is_group=False,
|
||||
display_message="发送生成的图片"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 发送图片时出错: {e}")
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _get_cache_key(cls, description: str, model: str, size: str) -> str:
|
||||
"""生成缓存键"""
|
||||
return f"{description[:100]}|{model}|{size}"
|
||||
|
||||
@classmethod
|
||||
def _cleanup_cache(cls):
|
||||
"""清理缓存,保持大小在限制内"""
|
||||
if len(cls._request_cache) > cls._cache_max_size:
|
||||
keys_to_remove = list(cls._request_cache.keys())[:-cls._cache_max_size//2]
|
||||
for key in keys_to_remove:
|
||||
del cls._request_cache[key]
|
||||
|
||||
def _validate_image_size(self, image_size: str) -> bool:
|
||||
"""验证图片尺寸格式"""
|
||||
try:
|
||||
width, height = map(int, image_size.split('x'))
|
||||
return 100 <= width <= 10000 and 100 <= height <= 10000
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
def _download_and_encode_base64(self, image_url: str) -> Tuple[bool, str]:
|
||||
"""下载图片并将其编码为Base64字符串"""
|
||||
logger.info(f"{self.log_prefix} (B64) 下载并编码图片: {image_url[:70]}...")
|
||||
@@ -286,16 +308,17 @@ class PicAction(PluginAction):
|
||||
error_msg = f"下载图片失败 (状态: {response.status})"
|
||||
logger.error(f"{self.log_prefix} (B64) {error_msg} URL: {image_url}")
|
||||
return False, error_msg
|
||||
except Exception as e: # Catches all exceptions from urlopen, b64encode, etc.
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} (B64) 下载或编码时错误: {e!r}", exc_info=True)
|
||||
traceback.print_exc()
|
||||
return False, f"下载或编码图片时发生错误: {str(e)[:100]}"
|
||||
|
||||
def _make_http_image_request(
|
||||
self, prompt: str, model: str, size: str, seed: int | None, guidance_scale: float, watermark: bool
|
||||
self, prompt: str, model: str, size: str, seed: int, guidance_scale: float, watermark: bool
|
||||
) -> Tuple[bool, str]:
|
||||
base_url = self.config.get("base_url")
|
||||
generate_api_key = self.config.get("volcano_generate_api_key")
|
||||
"""发送HTTP请求生成图片"""
|
||||
base_url = self.api.get_config("base_url")
|
||||
generate_api_key = self.api.get_config("volcano_generate_api_key")
|
||||
|
||||
endpoint = f"{base_url.rstrip('/')}/images/generations"
|
||||
|
||||
@@ -306,11 +329,9 @@ class PicAction(PluginAction):
|
||||
"size": size,
|
||||
"guidance_scale": guidance_scale,
|
||||
"watermark": watermark,
|
||||
"seed": seed, # seed is now always an int from process()
|
||||
"seed": seed,
|
||||
"api-key": generate_api_key,
|
||||
}
|
||||
# if seed is not None: # No longer needed, seed is always an int
|
||||
# payload_dict["seed"] = seed
|
||||
|
||||
data = json.dumps(payload_dict).encode("utf-8")
|
||||
headers = {
|
||||
@@ -320,12 +341,6 @@ class PicAction(PluginAction):
|
||||
}
|
||||
|
||||
logger.info(f"{self.log_prefix} (HTTP) 发起图片请求: {model}, Prompt: {prompt[:30]}... To: {endpoint}")
|
||||
logger.debug(
|
||||
f"{self.log_prefix} (HTTP) Request Headers: {{...Authorization: Bearer {generate_api_key[:10]}...}}"
|
||||
)
|
||||
logger.debug(
|
||||
f"{self.log_prefix} (HTTP) Request Body (api-key omitted): {json.dumps({k: v for k, v in payload_dict.items() if k != 'api-key'})}"
|
||||
)
|
||||
|
||||
req = urllib.request.Request(endpoint, data=data, headers=headers, method="POST")
|
||||
|
||||
@@ -353,24 +368,48 @@ class PicAction(PluginAction):
|
||||
logger.info(f"{self.log_prefix} (HTTP) 图片生成成功,URL: {image_url[:70]}...")
|
||||
return True, image_url
|
||||
else:
|
||||
logger.error(
|
||||
f"{self.log_prefix} (HTTP) API成功但无图片URL. 响应预览: {response_body_str[:300]}..."
|
||||
)
|
||||
logger.error(f"{self.log_prefix} (HTTP) API成功但无图片URL")
|
||||
return False, "图片生成API响应成功但未找到图片URL"
|
||||
else:
|
||||
logger.error(
|
||||
f"{self.log_prefix} (HTTP) API请求失败. 状态: {response.status}. 正文: {response_body_str[:300]}..."
|
||||
)
|
||||
logger.error(f"{self.log_prefix} (HTTP) API请求失败. 状态: {response.status}")
|
||||
return False, f"图片API请求失败(状态码 {response.status})"
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} (HTTP) 图片生成时意外错误: {e!r}", exc_info=True)
|
||||
traceback.print_exc()
|
||||
return False, f"图片生成HTTP请求时发生意外错误: {str(e)[:100]}"
|
||||
|
||||
def _validate_image_size(self, image_size: str) -> bool:
|
||||
"""验证图片尺寸格式"""
|
||||
try:
|
||||
width, height = map(int, image_size.split("x"))
|
||||
return 100 <= width <= 10000 and 100 <= height <= 10000
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
# ===== 插件主类 =====
|
||||
|
||||
@register_plugin
|
||||
class DoubaoImagePlugin(BasePlugin):
|
||||
"""豆包图片生成插件
|
||||
|
||||
基于火山引擎豆包模型的AI图片生成插件:
|
||||
- 图片生成Action:根据描述使用火山引擎API生成图片
|
||||
"""
|
||||
|
||||
# 插件基本信息
|
||||
plugin_name = "doubao_pic_plugin"
|
||||
plugin_description = "基于火山引擎豆包模型的AI图片生成插件"
|
||||
plugin_version = "2.0.0"
|
||||
plugin_author = "MaiBot开发团队"
|
||||
enable_plugin = True
|
||||
config_file_name = "config.toml"
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
"""返回插件包含的组件列表"""
|
||||
|
||||
# 从配置获取组件启用状态
|
||||
enable_image_generation = self.get_config("components.enable_image_generation", True)
|
||||
|
||||
components = []
|
||||
|
||||
# 添加图片生成Action
|
||||
if enable_image_generation:
|
||||
components.append((
|
||||
DoubaoImageGenerationAction.get_action_info(),
|
||||
DoubaoImageGenerationAction
|
||||
))
|
||||
|
||||
return components
|
||||
@@ -9,7 +9,7 @@ description = "群聊禁言管理插件,提供智能禁言功能"
|
||||
# 组件启用控制
|
||||
[components]
|
||||
enable_smart_mute = true # 启用智能禁言Action
|
||||
enable_mute_command = true # 启用禁言命令Command
|
||||
enable_mute_command = false # 启用禁言命令Command
|
||||
|
||||
# 禁言配置
|
||||
[mute]
|
||||
@@ -29,9 +29,9 @@ templates = [
|
||||
"好的,禁言 {target} {duration},理由:{reason}",
|
||||
"收到,对 {target} 执行禁言 {duration},因为{reason}",
|
||||
"明白了,禁言 {target} {duration},原因是{reason}",
|
||||
"✅ 已禁言 {target} {duration},理由:{reason}",
|
||||
"🔇 对 {target} 执行禁言 {duration},因为{reason}",
|
||||
"⛔ 禁言 {target} {duration},原因:{reason}"
|
||||
"哇哈哈哈哈哈,已禁言 {target} {duration},理由:{reason}",
|
||||
"哎呦我去,对 {target} 执行禁言 {duration},因为{reason}",
|
||||
"{target},你完蛋了,我要禁言你 {duration} 秒,原因:{reason}"
|
||||
]
|
||||
|
||||
# 错误消息模板
|
||||
@@ -57,9 +57,6 @@ allow_parallel = false
|
||||
|
||||
# 禁言命令配置
|
||||
[mute_command]
|
||||
# 是否需要管理员权限
|
||||
require_admin = true
|
||||
|
||||
# 最大批量禁言数量
|
||||
max_batch_size = 5
|
||||
|
||||
|
||||
@@ -153,7 +153,8 @@ class MuteAction(BaseAction):
|
||||
|
||||
# 获取模板化消息
|
||||
message = self._get_template_message(target, time_str, reason)
|
||||
await self.send_reply(message)
|
||||
# await self.send_reply(message)
|
||||
await self.send_message_by_expressor(message)
|
||||
|
||||
# 发送群聊禁言命令
|
||||
success = await self.send_command(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 综合示例插件配置文件
|
||||
|
||||
[plugin]
|
||||
name = "example_comprehensive"
|
||||
name = "example_plugin"
|
||||
version = "2.0.0"
|
||||
enabled = true
|
||||
description = "展示新插件系统完整功能的综合示例插件"
|
||||
description = "展示新插件系统完整功能的示例插件"
|
||||
|
||||
# 组件启用控制
|
||||
[components]
|
||||
|
||||
@@ -408,7 +408,7 @@ class ExampleComprehensivePlugin(BasePlugin):
|
||||
"""
|
||||
|
||||
# 插件基本信息
|
||||
plugin_name = "example_comprehensive"
|
||||
plugin_name = "example_plugin"
|
||||
plugin_description = "综合示例插件,展示新插件系统的完整功能"
|
||||
plugin_version = "2.0.0"
|
||||
plugin_author = "MaiBot开发团队"
|
||||
|
||||
Reference in New Issue
Block a user