├── __init__.py ├── requirements.txt ├── config.json ├── README.md └── siliconflow2cow.py /__init__.py: -------------------------------------------------------------------------------- 1 | from .siliconflow2cow import * 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | pathvalidate 3 | Pillow -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth_token": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 3 | "enhancer_auth_token": "", 4 | "chat_api_url": "", 5 | "chat_model": "", 6 | "enhancer_prompt": "", 7 | "drawing_prefixes": ["绘", "draw"], 8 | "image_output_dir": "./plugins/siliconflow2cow/images", 9 | "clean_interval": 3, 10 | "clean_check_interval": 3600 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Siliconflow2cow 插件(用于 chatgpt-on-wechat) 2 | 3 | ## 概述 4 | 5 | Siliconflow2cow 是一款强大的 chatgpt-on-wechat 插件,让用户能够通过简单的命令生成各种风格的图像。这个多功能插件支持多种模型,可进行文本到图像和图像到图像的转换,为用户提供丰富的图像生成选项。 6 | 7 | ## 主要特性 8 | 9 | - 支持多种图像生成模型(flux.d, flux.s, sd3, sdxl, sd2, sdt, sdxlt, sdxll) 10 | - 可自定义图像尺寸和比例 11 | - 支持文生图和图生图功能 12 | - 自动优化用户输入的提示词 13 | - 定期自动清理旧图片 14 | - 支持手动清理所有生成的图片 15 | 16 | ## 安装步骤 17 | 18 | 1. 确保您已安装 chatgpt-on-wechat。 19 | 2. 将 `siliconflow2cow` 目录复制到 chatgpt-on-wechat 的 `plugins` 文件夹中。 20 | 3. 安装所需依赖: 21 | ``` 22 | pip install -r siliconflow2cow/requirements.txt 23 | ``` 24 | 4. 在 `config.json` 文件中配置您的 API 令牌和其他设置。 25 | 26 | ## 配置说明 27 | 28 | 在 `config.json` 文件中添加以下配置: 29 | 30 | ```json 31 | { 32 | "auth_token": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 33 | "enhancer_auth_token": "", 34 | "chat_api_url": "", 35 | "chat_model": "", 36 | "enhancer_prompt": "", 37 | "drawing_prefixes": ["绘", "draw"], 38 | "image_output_dir": "./plugins/siliconflow2cow/images", 39 | "clean_interval": 3, 40 | "clean_check_interval": 3600 41 | } 42 | ``` 43 | 44 | - `auth_token`: 您的硅基流动 API 认证令牌 45 | - `enhancer_auth_token`:用于自定义提示词增强API的认证令牌。留空则使用 `auth_token` **非必填** 46 | - `chat_api_url`:用于自定义提示词增强的API URL "https://api.openai.com/v1/chat/completions"。 留空则为默认 "https://api.siliconflow.cn/v1/chat/completions"。 **非必填** 47 | - `chat_model`:用于自定义提示词增强的模型。默认为 "deepseek-ai/DeepSeek-V2-Chat" **非必填** 48 | - `enhancer_prompt`:用于自定义提示词增强的系统提示,留空走默认 **非必填** 49 | - `drawing_prefixes`: 触发绘图的命令前缀 50 | - `image_output_dir`: 生成图片的保存路径 51 | - `clean_interval`: 自动清理(默认3天)前的旧图片 52 | - `clean_check_interval`: 默认每小时检测一次图片是否需要清理(单位为s) 53 | 54 | ## 翻译模型选择 55 | 56 | 默认情况下,插件使用 DeepSeek 付费模型。您可以切换到免费模型,如: 57 | ``` 58 | Qwen/Qwen2-7B-Instruct (32K, 免费) 59 | Qwen/Qwen2-1.5B-Instruct (32K, 免费) 60 | Qwen/Qwen1.5-7B-Chat (32K, 免费) 61 | THUDM/glm-4-9b-chat (32K, 免费) 62 | THUDM/chatglm3-6b (32K, 免费) 63 | 01-ai/Yi-1.5-9B-Chat-16K (16K, 免费) 64 | 01-ai/Yi-1.5-6B-Chat (4K, 免费) 65 | internlm/internlm2_5-7b-chat (32K, 免费) 66 | 国际领先模型部分: 67 | google/gemma-2-9b-it (8K, 免费) 68 | meta-llama/Meta-Llama-3-8B-Instruct (8K, 免费) 69 | meta-llama/Meta-Llama-3.1-8B-Instruct (8K, 免费) 70 | mistralai/Mistral-7B-Instruct-v0.2 (32K,免费) 71 | ``` 72 | 73 | ## 优化建议 74 | 75 | 为提高图像质量,特别是解决颜色过度饱和的问题,可以考虑调整以下参数: 76 | 77 | 1. **推理步数** (`num_inference_steps`): 78 | - 标准模型:20-50 步 79 | - 快速模型(如 SDXL Turbo):4-10 步 80 | 81 | 2. **引导尺度** (`guidance_scale`): 82 | - 标准范围:5.0-8.0 83 | - 对于过度饱和的情况,尝试:3.0-5.0 84 | 85 | 3. **提示词优化**: 86 | - 使用具体、详细的描述 87 | - 包含艺术风格(例如,"油画风格") 88 | - 使用括号增加权重:"(蓝色眼睛:1.2)" 89 | 90 | 4. **模型特定配置**: 91 | 根据您使用的模型(FLUX、SD、SDXL Turbo 等)调整参数 92 | 93 | ## 使用方法 94 | 95 | 使用以下格式生成图像: 96 | 97 | ``` 98 | [前缀] [提示词] -m [模型] ---[宽高比] 99 | ``` 100 | 101 | 示例: 102 | ``` 103 | 绘RPG角色绘画风格。年轻的人类女性,有着清晰可见的骆驼耳朵,高高地举在头上。她有蓝色的眼睛。粉红色的头发扎成两条辫子。她脖子上戴着一个旧的奖章,上面画着一朵花。她的头上戴着一个简单的花冠。她穿着一件带有花卉图案的浅色波西米亚风格长裙-m flux.d ---16:9 104 | ``` 105 | **输入格式错误时(模型不存在、尺寸不存在时...),会使用默认模型flux.s默认尺寸1024x1024请求接口** 106 | image 107 | 108 | ![微信图片_20240920192537](https://github.com/user-attachments/assets/0f105708-2508-44ce-97b1-82037ea355a2) 109 | 110 | 111 | ### 支持的模型 112 | 113 | - flux.d: FLUX.1-dev 114 | - flux.s: FLUX.1-schnell 115 | - sd3: Stable Diffusion 3 Medium 116 | - sdxl: Stable Diffusion XL Base 1.0 117 | - sd2: Stable Diffusion 2.1 118 | - sdt: Stable Diffusion Turbo 119 | - sdxlt: Stable Diffusion XL Turbo 120 | - sdxll: SDXL-Lightning 121 | 122 | ### 可用宽高比 123 | 124 | - 1:1 (1024x1024) 125 | - 1:2 (1024x2048) 126 | - 2:1 (2048x1024) 127 | - 3:2 (1536x1024) 128 | - 2:3 (1024x1536) 129 | - 4:3 (1536x1152) 130 | - 3:4 (1152x1536) 131 | - 16:9 (2048x1152) 132 | - 9:16 (1152x2048) 133 | 134 | ### 图像到图像转换 135 | 136 | 在提示词中包含图片 URL: 137 | 138 | ``` 139 | 绘 将这张图片中的猫娘头上加上玫瑰 https://demo-cloudflare-imgbed.pages.dev/file/3a58a0d70ecf5439ec784.png -m sdxl ---9:16 140 | ``` 141 | image 142 | 143 | - 图生图有点奇葩,勉勉强强使用吧(第一张为原图,第二张为图生图) 144 | ![pintu-fulicat com-1724763611385](https://github.com/user-attachments/assets/46aa3223-36f9-4f36-bf06-ac48855851a5) 145 | 146 | 147 | ## 重要说明 148 | 149 | 1. 确保您有足够的 API 使用额度。 150 | 2. 请确保您有足够的存储空间来保存生成的图片。 151 | 3. 插件会自动优化您的提示词以产生更好的结果。 152 | 4. 请遵守API提供商的使用条款和内容政策。(*出现451ERROR为检测到违规提示词,sd2较易触发*) 153 | image 154 | 155 | 5. 定期清理功能会自动删除指定天数前的图片,请注意备份重要图片。 156 | 6. 使用 `绘clean_all` 命令时要小心,它会删除所有已生成的图片。 157 | ![pintu-fulicat com-1724779353863](https://github.com/user-attachments/assets/01e06fef-3f0c-4d9c-95d0-06f1f7e843e0) 158 | 159 | ## 故障排除 160 | 161 | 如果遇到问题: 162 | 163 | 1. 验证您的 API 令牌是否正确。 164 | 2. 确保您有稳定的网络连接。 165 | 3. 查看日志文件以获取详细的错误信息。 166 | 167 | ## 贡献 168 | 169 | 特别感谢 L 站的"逆向达人"提供的见解。 170 | [Workers 部署链接](https://linux.do/t/topic/185085) 171 | 172 | 我们欢迎您提交问题和拉取请求,以改进这个插件! 173 | -------------------------------------------------------------------------------- /siliconflow2cow.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import json 4 | import time 5 | import requests 6 | import base64 7 | from io import BytesIO 8 | from typing import List, Tuple 9 | from pathvalidate import sanitize_filename 10 | from PIL import Image 11 | from datetime import datetime, timedelta 12 | import threading 13 | 14 | import plugins 15 | from bridge.context import ContextType 16 | from bridge.reply import Reply, ReplyType 17 | from common.log import logger 18 | from plugins import * 19 | from config import conf 20 | 21 | 22 | @plugins.register( 23 | name="Siliconflow2cow", 24 | desire_priority=90, 25 | hidden=False, 26 | desc="A plugin for generating images using various models.", 27 | version="2.6.1", 28 | author="Lemodragon", 29 | ) 30 | class Siliconflow2cow(Plugin): 31 | def __init__(self): 32 | super().__init__() 33 | try: 34 | conf = super().load_config() 35 | if not conf: 36 | raise Exception("配置未找到。") 37 | 38 | self.auth_token = conf.get("auth_token") 39 | if not self.auth_token: 40 | raise Exception("在配置中未找到主认证令牌。") 41 | 42 | 43 | self.enhancer_auth_token = conf.get("enhancer_auth_token") or self.auth_token 44 | self.chat_api_url = conf.get("chat_api_url") or "https://api.siliconflow.cn/v1/chat/completions" 45 | self.chat_model = conf.get("chat_model") or "deepseek-ai/DeepSeek-V2-Chat" 46 | self.enhancer_prompt = conf.get("enhancer_prompt") or """As a Stable Diffusion Prompt expert, you will create prompts from keywords, often from databases like Danbooru. Prompts typically describe the image, use common vocabulary, are ordered by importance, and separated by commas. Avoid using "-" or ".", but spaces and natural language are acceptable. Avoid word repetition. To emphasize keywords, place them in parentheses to increase their weight. For example, "(flowers)" increases 'flowers' weight by 1.1x, while "(((flowers)))" increases it by 1.331x. Use "(flowers:1.5)" to increase 'flowers' weight by 1.5x. Only increase weights for important tags. Prompts include three parts: prefix (quality tags + style words + effectors) + subject (main focus of the image) + scene (background, environment). The prefix affects image quality. Tags like "masterpiece", "best quality" increase image detail. Style words like "illustration", "lensflare" define the image style. Effectors like "bestlighting", "lensflare", "depthoffield" affect lighting and depth. The subject is the main focus, like characters or scenes. Detailed subject description ensures rich, detailed images. Increase subject weight for clarity. For characters, describe facial, hair, body, clothing, pose features. The scene describes the environment. Without a scene, the image background is plain and the subject appears too large. Some subjects inherently include scenes (e.g., buildings, landscapes). Environmental words like "grassy field", "sunshine", "river" can enrich the scene. Your task is to design image generation prompts. Please follow these steps: 1. I will send you an image scene. You need to generate a detailed image description. 2. The image description must be in English, output as a Positive Prompt.""" 47 | 48 | 49 | if self.enhancer_auth_token == self.auth_token: 50 | logger.info("[Siliconflow2cow] 增强器使用默认的认证令牌") 51 | else: 52 | logger.info("[Siliconflow2cow] 使用自定义的增强器认证令牌") 53 | 54 | self.drawing_prefixes = conf.get("drawing_prefixes", ["绘", "draw"]) 55 | self.image_output_dir = conf.get("image_output_dir", "./plugins/siliconflow2cow/images") 56 | self.clean_interval = float(conf.get("clean_interval", 3)) # 天数 57 | self.clean_check_interval = int(conf.get("clean_check_interval", 3600)) # 秒数,默认1小时 58 | 59 | if not os.path.exists(self.image_output_dir): 60 | os.makedirs(self.image_output_dir) 61 | 62 | self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context 63 | 64 | # 启动定时清理任务 65 | self.schedule_next_run() 66 | 67 | logger.info(f"[Siliconflow2cow] 初始化成功,清理间隔设置为 {self.clean_interval} 天,检查间隔为 {self.clean_check_interval} 秒") 68 | except Exception as e: 69 | logger.error(f"[Siliconflow2cow] 初始化失败,错误:{e}") 70 | raise e 71 | 72 | def schedule_next_run(self): 73 | """安排下一次运行""" 74 | self.timer = threading.Timer(self.clean_check_interval, self.run_clean_task) 75 | self.timer.start() 76 | 77 | def run_clean_task(self): 78 | """运行清理任务并安排下一次运行""" 79 | self.clean_old_images() 80 | self.schedule_next_run() 81 | 82 | def on_handle_context(self, e_context: EventContext): 83 | if e_context["context"].type != ContextType.TEXT: 84 | return 85 | 86 | content = e_context["context"].content 87 | if not content.startswith(tuple(self.drawing_prefixes)): 88 | return 89 | 90 | logger.debug(f"[Siliconflow2cow] 收到消息: {content}") 91 | 92 | try: 93 | # 移除前缀 94 | for prefix in self.drawing_prefixes: 95 | if content.startswith(prefix): 96 | content = content[len(prefix):].strip() 97 | break 98 | 99 | if content.lower() == "clean_all": 100 | reply = self.clean_all_images() 101 | else: 102 | model_key, image_size, clean_prompt = self.parse_user_input(content) 103 | logger.debug(f"[Siliconflow2cow] 解析后的参数: 模型={model_key}, 尺寸={image_size}, 提示词={clean_prompt}") 104 | 105 | original_image_url = self.extract_image_url(clean_prompt) 106 | logger.debug(f"[Siliconflow2cow] 原始提示词中提取的图片URL: {original_image_url}") 107 | 108 | enhanced_prompt = self.enhance_prompt(clean_prompt) 109 | logger.debug(f"[Siliconflow2cow] 增强后的提示词: {enhanced_prompt}") 110 | 111 | image_url = self.generate_image(enhanced_prompt, original_image_url, model_key, image_size) 112 | logger.debug(f"[Siliconflow2cow] 生成的图片URL: {image_url}") 113 | 114 | if image_url: 115 | image_path = self.download_and_save_image(image_url) 116 | logger.debug(f"[Siliconflow2cow] 图片已保存到: {image_path}") 117 | 118 | with open(image_path, 'rb') as f: 119 | image_storage = BytesIO(f.read()) 120 | reply = Reply(ReplyType.IMAGE, image_storage) 121 | else: 122 | logger.error("[Siliconflow2cow] 生成图片失败") 123 | reply = Reply(ReplyType.ERROR, "生成图片失败。") 124 | 125 | e_context["reply"] = reply 126 | e_context.action = EventAction.BREAK_PASS 127 | except Exception as e: 128 | logger.error(f"[Siliconflow2cow] 发生错误: {e}") 129 | reply = Reply(ReplyType.ERROR, f"发生错误: {str(e)}") 130 | e_context["reply"] = reply 131 | e_context.action = EventAction.BREAK_PASS 132 | 133 | def parse_user_input(self, content: str) -> Tuple[str, str, str]: 134 | model_key = self.extract_model_key(content) 135 | image_size = self.extract_image_size(content) 136 | clean_prompt = self.clean_prompt_string(content, model_key) 137 | logger.debug(f"[Siliconflow2cow] 解析用户输入: 模型={model_key}, 尺寸={image_size}, 清理后的提示词={clean_prompt}") 138 | return model_key, image_size, clean_prompt 139 | 140 | def enhance_prompt(self, prompt: str) -> str: 141 | try: 142 | logger.debug(f"[Siliconflow2cow] 正在增强提示词: {prompt}") 143 | 144 | if not self.chat_api_url or not self.chat_model: 145 | logger.warning("[Siliconflow2cow] chat_api_url 或 chat_model 未设置,跳过提示词增强") 146 | return prompt 147 | 148 | auth_token = self.enhancer_auth_token or self.auth_token 149 | 150 | if not self.enhancer_prompt: 151 | logger.info("[Siliconflow2cow] 增强提示词为空,跳过提示词增强") 152 | return prompt 153 | 154 | response = requests.post( 155 | self.chat_api_url, 156 | headers={ 157 | "Content-Type": "application/json", 158 | "Authorization": f"Bearer {auth_token}" 159 | }, 160 | json={ 161 | "model": self.chat_model, 162 | "messages": [ 163 | {"role": "system", "content": self.enhancer_prompt}, 164 | {"role": "user", "content": prompt} 165 | ] 166 | } 167 | ) 168 | response.raise_for_status() 169 | enhanced_prompt = response.json()['choices'][0]['message']['content'] 170 | logger.debug(f"[Siliconflow2cow] 增强后的提示词: {enhanced_prompt}") 171 | return enhanced_prompt 172 | except Exception as e: 173 | logger.error(f"[Siliconflow2cow] 增强提示词失败: {e}") 174 | return prompt 175 | 176 | def generate_image(self, prompt: str, original_image_url: str, model_key: str, image_size: str) -> str: 177 | if original_image_url: 178 | logger.debug(f"[Siliconflow2cow] 检测到图片URL,使用图生图模式") 179 | return self.generate_image_by_img(prompt, original_image_url, model_key, image_size) 180 | else: 181 | logger.debug(f"[Siliconflow2cow] 未检测到图片URL,使用文生图模式") 182 | return self.generate_image_by_text(prompt, model_key, image_size) 183 | 184 | def generate_image_by_text(self, prompt: str, model_key: str, image_size: str) -> str: 185 | url = self.get_url_for_model(model_key) 186 | logger.debug(f"[Siliconflow2cow] 使用模型URL: {url}") 187 | 188 | width, height = map(int, image_size.split('x')) 189 | 190 | json_body = { 191 | "prompt": prompt, 192 | "width": width, 193 | "height": height 194 | } 195 | 196 | headers = { 197 | 'Authorization': f"Bearer {self.auth_token}", 198 | 'Accept': 'application/json', 199 | 'Content-Type': 'application/json' 200 | } 201 | 202 | if model_key == "flux.s": 203 | json_body.update({ 204 | "num_inference_steps": 30, 205 | "guidance_scale": 7.0 206 | }) 207 | elif model_key == "sd2": 208 | json_body.update({ 209 | "num_inference_steps": 40, 210 | "guidance_scale": 6.0 211 | }) 212 | elif model_key == "sd3": 213 | json_body.update({ 214 | "num_inference_steps": 45, 215 | "guidance_scale": 6.0 216 | }) 217 | elif model_key == "sdt": 218 | json_body.update({ 219 | "num_inference_steps": 6, 220 | "guidance_scale": 1.0, 221 | "cfg_scale": 1.0 222 | }) 223 | elif model_key == "sdxlt": 224 | json_body.update({ 225 | "num_inference_steps": 4, 226 | "guidance_scale": 1.0 227 | }) 228 | elif model_key == "sdxll": 229 | json_body.update({ 230 | "num_inference_steps": 4, 231 | "guidance_scale": 1.0 232 | }) 233 | elif model_key == "flux.d": 234 | json_body = { 235 | "model": "black-forest-labs/FLUX.1-dev", 236 | "prompt": prompt, 237 | "image_size": image_size, 238 | "num_inference_steps": 25, 239 | "seed": int(time.time()) 240 | } 241 | else: 242 | json_body.update({ 243 | "num_inference_steps": 50, 244 | "guidance_scale": 7.5 245 | }) 246 | 247 | logger.debug(f"[Siliconflow2cow] 发送请求体: {json_body}") 248 | try: 249 | response = requests.post(url, headers=headers, json=json_body) 250 | response.raise_for_status() 251 | json_response = response.json() 252 | logger.debug(f"[Siliconflow2cow] API响应: {json_response}") 253 | if model_key == "flux.d": 254 | return json_response['images'][0]['url'] 255 | else: 256 | return json_response['images'][0]['url'] 257 | except requests.exceptions.RequestException as e: 258 | logger.error(f"[Siliconflow2cow] API请求失败: {e}") 259 | if hasattr(e, 'response') and e.response is not None: 260 | if e.response.status_code == 400: 261 | error_message = e.response.json().get('error', {}).get('message', '未知错误') 262 | logger.error(f"[Siliconflow2cow] API错误信息: {error_message}") 263 | logger.error(f"[Siliconflow2cow] API响应内容: {e.response.text}") 264 | raise Exception(f"API请求失败: {str(e)}") 265 | 266 | def generate_image_by_img(self, prompt: str, image_url: str, model_key: str, image_size: str) -> str: 267 | url = self.get_img_url_for_model(model_key) 268 | logger.debug(f"[Siliconflow2cow] 使用图生图模型URL: {url}") 269 | img_prompt = self.remove_image_urls(prompt) 270 | 271 | base64_image = self.convert_image_to_base64(image_url) 272 | 273 | width, height = map(int, image_size.split('x')) 274 | 275 | json_body = { 276 | "prompt": img_prompt, 277 | "image": base64_image, 278 | "width": width, 279 | "height": height, 280 | "batch_size": 1 281 | } 282 | 283 | if model_key == "sdxl": 284 | json_body.update({ 285 | "num_inference_steps": 40, 286 | "guidance_scale": 7.5 287 | }) 288 | elif model_key == "sd2": 289 | json_body.update({ 290 | "num_inference_steps": 40, 291 | "guidance_scale": 8.5 292 | }) 293 | elif model_key == "sdxll": 294 | json_body.update({ 295 | "num_inference_steps": 4, 296 | "guidance_scale": 1.0 297 | }) 298 | elif model_key == "pm": 299 | json_body.update({ 300 | "style_name": "Photographic (Default)", 301 | "guidance_scale": 5, 302 | "style_strengh_radio": 20 303 | }) 304 | else: 305 | json_body.update({ 306 | "num_inference_steps": 50, 307 | "guidance_scale": 7.5 308 | }) 309 | 310 | headers = { 311 | 'Authorization': f"Bearer {self.auth_token}", 312 | 'Accept': 'application/json', 313 | 'Content-Type': 'application/json' 314 | } 315 | 316 | log_json_body = json_body.copy() 317 | log_json_body['image'] = '[BASE64_IMAGE_DATA]' 318 | logger.debug(f"[Siliconflow2cow] 发送图生图请求体: {log_json_body}") 319 | 320 | try: 321 | response = requests.post(url, headers=headers, json=json_body) 322 | response.raise_for_status() 323 | json_response = response.json() 324 | logger.debug(f"[Siliconflow2cow] API响应: {json_response}") 325 | return json_response['images'][0]['url'] 326 | except requests.exceptions.RequestException as e: 327 | logger.error(f"[Siliconflow2cow] API请求失败: {e}") 328 | if hasattr(e, 'response') and e.response is not None: 329 | if e.response.status_code == 400: 330 | error_message = e.response.json().get('error', {}).get('message', '未知错误') 331 | logger.error(f"[Siliconflow2cow] API错误信息: {error_message}") 332 | logger.error(f"[Siliconflow2cow] API响应内容: {e.response.text}") 333 | raise Exception(f"API请求失败: {str(e)}") 334 | 335 | def extract_model_key(self, prompt: str) -> str: 336 | match = re.search(r'-m ?(\S+)', prompt) 337 | model_key = match.group(1).strip() if match else "flux.s" 338 | logger.debug(f"[Siliconflow2cow] 提取的模型键: {model_key}") 339 | return model_key 340 | 341 | def extract_image_size(self, prompt: str) -> str: 342 | match = re.search(r'---(\d+:\d+)', prompt) 343 | if match: 344 | ratio = match.group(1).strip() 345 | size = self.RATIO_MAP.get(ratio, "1024x1024") 346 | else: 347 | size = "1024x1024" 348 | logger.debug(f"[Siliconflow2cow] 提取的图片尺寸: {size}") 349 | return size 350 | 351 | def clean_prompt_string(self, prompt: str, model_key: str) -> str: 352 | clean_prompt = re.sub(r' -m ?\S+', '', re.sub(r'---\d+:\d+', '', prompt)).strip() 353 | logger.debug(f"[Siliconflow2cow] 清理后的提示词: {clean_prompt}") 354 | return clean_prompt 355 | 356 | def extract_image_url(self, text: str) -> str: 357 | match = re.search(r'(https?://[^\s]+?\.(?:png|jpe?g|gif|bmp|webp|svg|tiff|ico))(?:\s|$)', text, re.IGNORECASE) 358 | url = match.group(1) if match else None 359 | logger.debug(f"[Siliconflow2cow] 提取的图片URL: {url}") 360 | return url 361 | 362 | def convert_image_to_base64(self, image_url: str) -> str: 363 | logger.debug(f"[Siliconflow2cow] 正在下载图片: {image_url}") 364 | response = requests.get(image_url) 365 | if response.status_code != 200: 366 | logger.error(f"[Siliconflow2cow] 下载图片失败,状态码: {response.status_code}") 367 | raise Exception('下载图片失败') 368 | base64_image = f"data:image/webp;base64,{base64.b64encode(response.content).decode('utf-8')}" 369 | logger.debug("[Siliconflow2cow] 图片已成功转换为base64") 370 | return base64_image 371 | 372 | def remove_image_urls(self, text: str) -> str: 373 | cleaned_text = re.sub(r'https?://\S+\.(?:png|jpe?g|gif|bmp|webp|svg|tiff|ico)(?:\s|$)', '', text, flags=re.IGNORECASE) 374 | logger.debug(f"[Siliconflow2cow] 移除图片URL后的文本: {cleaned_text}") 375 | return cleaned_text 376 | 377 | def get_url_for_model(self, model_key: str) -> str: 378 | URL_MAP = { 379 | "flux.d": "https://api.siliconflow.cn/v1/image/generations", 380 | "flux.s": "https://api.siliconflow.cn/v1/black-forest-labs/FLUX.1-schnell/text-to-image", 381 | "sd3": "https://api.siliconflow.cn/v1/stabilityai/stable-diffusion-3-medium/text-to-image", 382 | "sdxl": "https://api.siliconflow.cn/v1/stabilityai/stable-diffusion-xl-base-1.0/text-to-image", 383 | "sd2": "https://api.siliconflow.cn/v1/stabilityai/stable-diffusion-2-1/text-to-image", 384 | "sdt": "https://api.siliconflow.cn/v1/stabilityai/sd-turbo/text-to-image", 385 | "sdxlt": "https://api.siliconflow.cn/v1/stabilityai/sdxl-turbo/text-to-image", 386 | "sdxll": "https://api.siliconflow.cn/v1/ByteDance/SDXL-Lightning/text-to-image" 387 | } 388 | url = URL_MAP.get(model_key, URL_MAP["flux.s"]) 389 | logger.debug(f"[Siliconflow2cow] 选择的模型URL: {url}") 390 | return url 391 | 392 | def get_img_url_for_model(self, model_key: str) -> str: 393 | IMG_URL_MAP = { 394 | "sdxl": "https://api.siliconflow.cn/v1/stabilityai/stable-diffusion-xl-base-1.0/image-to-image", 395 | "sd2": "https://api.siliconflow.cn/v1/stabilityai/stable-diffusion-2-1/image-to-image", 396 | "sdxll": "https://api.siliconflow.cn/v1/ByteDance/SDXL-Lightning/image-to-image", 397 | "pm": "https://api.siliconflow.cn/v1/TencentARC/PhotoMaker/image-to-image" 398 | } 399 | url = IMG_URL_MAP.get(model_key, IMG_URL_MAP["sdxl"]) 400 | logger.debug(f"[Siliconflow2cow] 选择的图生图模型URL: {url}") 401 | return url 402 | 403 | RATIO_MAP = { 404 | "1:1": "1024x1024", 405 | "1:2": "1024x2048", 406 | "2:1": "2048x1024", 407 | "3:2": "1536x1024", 408 | "2:3": "1024x1536", 409 | "4:3": "1536x1152", 410 | "3:4": "1152x1536", 411 | "16:9": "2048x1152", 412 | "9:16": "1152x2048" 413 | } 414 | 415 | def download_and_save_image(self, image_url: str) -> str: 416 | logger.debug(f"[Siliconflow2cow] 正在下载并保存图片: {image_url}") 417 | response = requests.get(image_url) 418 | if response.status_code != 200: 419 | logger.error(f"[Siliconflow2cow] 下载图片失败,状态码: {response.status_code}") 420 | raise Exception('下载图片失败') 421 | 422 | image = Image.open(BytesIO(response.content)) 423 | 424 | filename = f"{int(time.time())}.png" 425 | file_path = os.path.join(self.image_output_dir, filename) 426 | 427 | image.save(file_path, format='PNG') 428 | 429 | logger.info(f"[Siliconflow2cow] 图片已保存到 {file_path}") 430 | return file_path 431 | 432 | def clean_all_images(self): 433 | """清理所有图片""" 434 | logger.info("[Siliconflow2cow] 开始清理所有图片") 435 | initial_count = len([name for name in os.listdir(self.image_output_dir) if os.path.isfile(os.path.join(self.image_output_dir, name))]) 436 | 437 | for filename in os.listdir(self.image_output_dir): 438 | file_path = os.path.join(self.image_output_dir, filename) 439 | if os.path.isfile(file_path): 440 | os.remove(file_path) 441 | logger.info(f"[Siliconflow2cow] 已删除图片: {file_path}") 442 | 443 | final_count = len([name for name in os.listdir(self.image_output_dir) if os.path.isfile(os.path.join(self.image_output_dir, name))]) 444 | 445 | logger.info("[Siliconflow2cow] 清理所有图片完成") 446 | return Reply(ReplyType.TEXT, f"清理完成:已删除 {initial_count - final_count} 张图片,当前目录下还有 {final_count} 张图片。") 447 | 448 | def clean_old_images(self): 449 | """清理指定天数前的图片""" 450 | logger.info(f"[Siliconflow2cow] 开始检查是否需要清理旧图片,清理间隔:{self.clean_interval}天") 451 | now = datetime.now() 452 | cleaned_count = 0 453 | for filename in os.listdir(self.image_output_dir): 454 | file_path = os.path.join(self.image_output_dir, filename) 455 | if os.path.isfile(file_path): 456 | file_time = datetime.fromtimestamp(os.path.getmtime(file_path)) 457 | if now - file_time > timedelta(days=self.clean_interval): 458 | os.remove(file_path) 459 | cleaned_count += 1 460 | logger.info(f"[Siliconflow2cow] 已删除旧图片: {file_path}") 461 | if cleaned_count > 0: 462 | logger.info(f"[Siliconflow2cow] 清理旧图片完成,共清理 {cleaned_count} 张图片") 463 | else: 464 | logger.info("[Siliconflow2cow] 没有需要清理的旧图片") 465 | 466 | def get_help_text(self, verbose=False, **kwargs): 467 | short_help = "简略指南:\n" 468 | short_help += f"1. 使用 {', '.join(self.drawing_prefixes)} 命令前缀\n" 469 | short_help += "2. 请求格式:[前缀] [提示词] -m [模型] ---[尺寸]\n" 470 | short_help += "3. 模型列表:flux.d, flux.s, sd3, sdxl, sd2, sdt, sdxlt, sdxll\n" 471 | short_help += f"4. 尺寸列表:{', '.join(self.RATIO_MAP.keys())}\n" 472 | if not verbose: 473 | return short_help 474 | 475 | help_text = "详细指南:\n" 476 | help_text += f"1. 使用 {', '.join(self.drawing_prefixes)} 作为命令前缀\n" 477 | help_text += "2. 在提示词后面添加 '-m' 来选择模型,例如:-m sdxl\n" 478 | help_text += "3. 使用 '---' 后跟比例来指定图片尺寸,例如:---16:9\n" 479 | help_text += "4. 如果要进行图生图,直接在提示词中包含图片URL\n" 480 | help_text += f"5. 输入 '{self.drawing_prefixes[0]}clean_all' 来清理所有图片(警告:这将删除所有已生成的图片)\n" 481 | help_text += f"示例:{self.drawing_prefixes[0]} 一只可爱的小猫 -m flux.s ---16:9\n" 482 | help_text += "注意:您的提示词将会被AI自动优化以产生更好的结果。\n" 483 | help_text += "注意:各模型的参数已经过调整以提高图像质量。\n" 484 | help_text += "可用的模型:flux.d, flux.s, sd3, sdxl, sd2, sdt, sdxlt, sdxll\n" 485 | help_text += f"可用的尺寸比例:{', '.join(self.RATIO_MAP.keys())}\n" 486 | help_text += f"图片将每{self.clean_interval}天自动清理一次。\n" 487 | return help_text 488 | --------------------------------------------------------------------------------