├── .gitignore ├── README.md ├── __init__.py ├── config.json.template ├── requirements.txt └── sdwebui.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .vscode 4 | __pycache__/ 5 | venv* 6 | *.pyc 7 | config.json 8 | nohup.out 9 | tmp 10 | plugins.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 插件描述 2 | 3 | 本插件用于将画图请求转发给stable diffusion webui。 4 | 5 | ## 环境要求 6 | 7 | 使用前先安装stable diffusion webui,并在它的启动参数中添加 "--api"。 8 | 9 | 具体信息,请参考[文章](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/API)。 10 | 11 | 部署运行后,保证主机能够成功访问http://127.0.0.1:7860/docs 12 | 13 | 请**安装**本插件的依赖包```webuiapi``` 14 | 15 | ``` 16 | pip install webuiapi 17 | ``` 18 | 19 | ## 使用说明 20 | 21 | 请将`config.json.template`复制为`config.json`,并修改其中的参数和规则。 22 | 23 | PS: 如果修改了webui的`host`和`port`,也需要在配置文件中更改启动参数, 更多启动参数参考:https://github.com/mix1009/sdwebuiapi/blob/a1cb4c6d2f39389d6e962f0e6436f4aa74cd752c/webuiapi/webuiapi.py#L114 24 | ### 画图请求格式 25 | 26 | 用户的画图请求格式为: 27 | 28 | ``` 29 | <画图触发词><关键词1> <关键词2> ... <关键词n>: 30 | ``` 31 | 32 | - 本插件会对画图触发词后的关键词进行逐个匹配,如果触发了规则中的关键词,则会在画图请求中重载对应的参数。 33 | - 规则的匹配顺序参考`config.json`中的顺序,每个关键词最多被匹配到1次,如果多个关键词触发了重复的参数,重复参数以最后一个关键词为准。 34 | - 关键词中包含`help`或`帮助`,会打印出帮助文档。 35 | 36 | 第一个"**:**"号之后的内容会作为附加的**prompt**,接在最终的prompt后 37 | 38 | 例如: 画横版 高清 二次元:cat 39 | 40 | 会触发三个关键词 "横版", "高清", "二次元",prompt为"cat" 41 | 42 | 若默认参数是: 43 | ```json 44 | "width": 512, 45 | "height": 512, 46 | "enable_hr": false, 47 | "prompt": "8k" 48 | "negative_prompt": "nsfw", 49 | "sd_model_checkpoint": "perfectWorld_v2Baked" 50 | ``` 51 | 52 | "横版"触发的规则参数为: 53 | ```json 54 | "width": 640, 55 | "height": 384, 56 | ``` 57 | 58 | "高清"触发的规则参数为: 59 | ```json 60 | "enable_hr": true, 61 | "hr_scale": 1.6, 62 | ``` 63 | 64 | "二次元"触发的规则参数为: 65 | ```json 66 | "negative_prompt": "(low quality, worst quality:1.4),(bad_prompt:0.8), (monochrome:1.1), (greyscale)", 67 | "steps": 20, 68 | "prompt": "masterpiece, best quality", 69 | 70 | "sd_model_checkpoint": "meinamix_meinaV8" 71 | ``` 72 | 73 | 以上这些规则的参数会和默认参数合并。第一个":"后的内容cat会连接在prompt后。 74 | 75 | 得到最终参数为: 76 | ```json 77 | "width": 640, 78 | "height": 384, 79 | "enable_hr": true, 80 | "hr_scale": 1.6, 81 | "negative_prompt": "(low quality, worst quality:1.4),(bad_prompt:0.8), (monochrome:1.1), (greyscale)", 82 | "steps": 20, 83 | "prompt": "masterpiece, best quality, cat", 84 | 85 | "sd_model_checkpoint": "meinamix_meinaV8" 86 | ``` 87 | 88 | PS: 实际参数分为两部分: 89 | 90 | - 一部分是`params`,为画画的参数;参数名**必须**与webuiapi包中[txt2img api](https://github.com/mix1009/sdwebuiapi/blob/fb2054e149c0a4e25125c0cd7e7dca06bda839d4/webuiapi/webuiapi.py#L163)的参数名一致 91 | - 另一部分是`options`,指sdwebui的设置,使用的模型和vae需写在里面。它和(http://127.0.0.1:7860/sdapi/v1/options )所返回的键一致。 92 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .sdwebui import * -------------------------------------------------------------------------------- /config.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "start":{ 3 | "host" : "127.0.0.1", 4 | "port" : 7860, 5 | "use_https" : false 6 | }, 7 | "defaults": { 8 | "params": { 9 | "sampler_name": "DPM++ 2M Karras", 10 | "steps": 20, 11 | "width": 512, 12 | "height": 512, 13 | "cfg_scale": 7, 14 | "prompt":"masterpiece, best quality", 15 | "negative_prompt": "(low quality, worst quality:1.4),(bad_prompt:0.8), (monochrome:1.1), (greyscale)", 16 | "enable_hr": false, 17 | "hr_scale": 2, 18 | "hr_upscaler": "Latent", 19 | "hr_second_pass_steps": 15, 20 | "denoising_strength": 0.7 21 | }, 22 | "options": { 23 | "sd_model_checkpoint": "perfectWorld_v2Baked" 24 | } 25 | }, 26 | "rules": [ 27 | { 28 | "keywords": [ 29 | "横版", 30 | "壁纸" 31 | ], 32 | "params": { 33 | "width": 640, 34 | "height": 384 35 | }, 36 | "desc": "分辨率会变成640x384" 37 | }, 38 | { 39 | "keywords": [ 40 | "竖版" 41 | ], 42 | "params": { 43 | "width": 384, 44 | "height": 640 45 | } 46 | }, 47 | { 48 | "keywords": [ 49 | "高清" 50 | ], 51 | "params": { 52 | "enable_hr": true, 53 | "hr_scale": 1.6 54 | }, 55 | "desc": "出图分辨率长宽都会提高1.6倍" 56 | }, 57 | { 58 | "keywords": [ 59 | "二次元" 60 | ], 61 | "params": { 62 | "negative_prompt": "(low quality, worst quality:1.4),(bad_prompt:0.8), (monochrome:1.1), (greyscale)", 63 | "prompt": "masterpiece, best quality" 64 | }, 65 | "options": { 66 | "sd_model_checkpoint": "meinamix_meinaV8" 67 | }, 68 | "desc": "使用二次元风格模型出图" 69 | } 70 | ] 71 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # sdwebui plugin 2 | webuiapi>=0.6.2 3 | langid -------------------------------------------------------------------------------- /sdwebui.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | 3 | import io 4 | import json 5 | import os 6 | 7 | import webuiapi 8 | import langid 9 | from bridge.bridge import Bridge 10 | import plugins 11 | from bridge.context import ContextType 12 | from bridge.reply import Reply, ReplyType 13 | from common.log import logger 14 | from config import conf 15 | from plugins import * 16 | 17 | 18 | @plugins.register(name="sdwebui", desc="利用stable-diffusion webui来画图", version="2.1", author="lanvent") 19 | class SDWebUI(Plugin): 20 | def __init__(self): 21 | super().__init__() 22 | curdir = os.path.dirname(__file__) 23 | config_path = os.path.join(curdir, "config.json") 24 | try: 25 | with open(config_path, "r", encoding="utf-8") as f: 26 | config = json.load(f) 27 | self.rules = config["rules"] 28 | defaults = config["defaults"] 29 | self.default_params = defaults["params"] 30 | self.default_options = defaults["options"] 31 | self.start_args = config["start"] 32 | self.api = webuiapi.WebUIApi(**self.start_args) 33 | self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context 34 | logger.info("[SD] inited") 35 | except Exception as e: 36 | if isinstance(e, FileNotFoundError): 37 | logger.warn(f"[SD] init failed, {config_path} not found, ignore or see https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/sdwebui .") 38 | else: 39 | logger.warn("[SD] init failed, ignore or see https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/sdwebui .") 40 | raise e 41 | 42 | def on_handle_context(self, e_context: EventContext): 43 | 44 | if e_context['context'].type != ContextType.IMAGE_CREATE: 45 | return 46 | channel = e_context['channel'] 47 | if ReplyType.IMAGE in channel.NOT_SUPPORT_REPLYTYPE: 48 | return 49 | 50 | logger.debug("[SD] on_handle_context. content: %s" %e_context['context'].content) 51 | 52 | logger.info("[SD] image_query={}".format(e_context['context'].content)) 53 | reply = Reply() 54 | try: 55 | content = e_context['context'].content[:] 56 | # 解析用户输入 如"横版 高清 二次元:cat" 57 | if ":" in content: 58 | keywords, prompt = content.split(":", 1) 59 | else: 60 | keywords = content 61 | prompt = "" 62 | 63 | keywords = keywords.split() 64 | unused_keywords = [] 65 | if "help" in keywords or "帮助" in keywords: 66 | reply.type = ReplyType.INFO 67 | reply.content = self.get_help_text(verbose = True) 68 | else: 69 | rule_params = {} 70 | rule_options = {} 71 | for keyword in keywords: 72 | matched = False 73 | for rule in self.rules: 74 | if keyword in rule["keywords"]: 75 | for key in rule["params"]: 76 | rule_params[key] = rule["params"][key] 77 | if "options" in rule: 78 | for key in rule["options"]: 79 | rule_options[key] = rule["options"][key] 80 | matched = True 81 | break # 一个关键词只匹配一个规则 82 | if not matched: 83 | unused_keywords.append(keyword) 84 | logger.info("[SD] keyword not matched: %s" % keyword) 85 | 86 | params = {**self.default_params, **rule_params} 87 | options = {**self.default_options, **rule_options} 88 | params["prompt"] = params.get("prompt", "") 89 | if unused_keywords: 90 | if prompt: 91 | prompt += f", {', '.join(unused_keywords)}" 92 | else: 93 | prompt = ', '.join(unused_keywords) 94 | if prompt: 95 | lang = langid.classify(prompt)[0] 96 | if lang != "en": 97 | logger.info("[SD] translate prompt from {} to en".format(lang)) 98 | try: 99 | prompt = Bridge().fetch_translate(prompt, to_lang= "en") 100 | except Exception as e: 101 | logger.info("[SD] translate failed: {}".format(e)) 102 | logger.info("[SD] translated prompt={}".format(prompt)) 103 | params["prompt"] += f", {prompt}" 104 | if len(options) > 0: 105 | logger.info("[SD] cover options={}".format(options)) 106 | self.api.set_options(options) 107 | logger.info("[SD] params={}".format(params)) 108 | result = self.api.txt2img( 109 | **params 110 | ) 111 | reply.type = ReplyType.IMAGE 112 | b_img = io.BytesIO() 113 | result.image.save(b_img, format="PNG") 114 | reply.content = b_img 115 | e_context.action = EventAction.BREAK_PASS # 事件结束后,跳过处理context的默认逻辑 116 | except Exception as e: 117 | reply.type = ReplyType.ERROR 118 | reply.content = "[SD] "+str(e) 119 | logger.error("[SD] exception: %s" % e) 120 | e_context.action = EventAction.CONTINUE # 事件继续,交付给下个插件或默认逻辑 121 | finally: 122 | e_context['reply'] = reply 123 | 124 | def get_help_text(self, verbose = False, **kwargs): 125 | if not conf().get('image_create_prefix'): 126 | return "画图功能未启用" 127 | else: 128 | trigger = conf()['image_create_prefix'][0] 129 | help_text = "利用stable-diffusion来画图。\n" 130 | if not verbose: 131 | return help_text 132 | 133 | help_text += f"使用方法:\n使用\"{trigger}[关键词1] [关键词2]...:提示语\"的格式作画,如\"{trigger}横版 高清:cat\"\n" 134 | help_text += "目前可用关键词:\n" 135 | for rule in self.rules: 136 | keywords = [f"[{keyword}]" for keyword in rule['keywords']] 137 | help_text += f"{','.join(keywords)}" 138 | if "desc" in rule: 139 | help_text += f"-{rule['desc']}\n" 140 | else: 141 | help_text += "\n" 142 | return help_text --------------------------------------------------------------------------------