Janus-Pro-7B WebUI插件开发指南:扩展自定义功能模块

你是不是觉得Janus-Pro-7B的WebUI功能已经很丰富了,但总感觉还差点什么?比如,你想在生成文本前自动过滤掉敏感词,或者在图片生成后自动添加一个水印,又或者想在界面上加一个自己设计的统计面板。这些需求,官方的WebUI可能没有直接提供。

好消息是,Janus-Pro-7B的WebUI支持插件开发,这意味着你可以像搭积木一样,给它添加任何你想要的功能。今天,我就带你从零开始,亲手打造一个属于你自己的WebUI插件。整个过程比你想象的要简单,不需要你精通前端框架,只要会点Python,就能轻松上手。

1. 准备工作:认识插件架构

在动手写代码之前,我们先花几分钟了解一下Janus-Pro-7B WebUI的插件是怎么工作的。理解了它的设计思路,后面写代码就会顺畅很多。

简单来说,WebUI的插件系统就像一个“插座”和“插头”的体系。WebUI本身提供了很多标准的“插座”(我们称之为扩展点或钩子),你的插件就是一个个“插头”。当你把插头插到对应的插座上,你的代码就能在WebUI运行的特定时刻被调用,从而改变或增强它的行为。

1.1 插件能做什么?

一个插件通常可以干这几类事:

  • 添加新的处理步骤:比如在模型处理输入文本之前,你先对文本做点手脚(预处理);或者在模型输出结果之后,你再对结果加工一下(后处理)。
  • 扩展用户界面:在现有的WebUI页面上,增加新的按钮、输入框、选项卡或者一整块新的显示区域。
  • 集成外部服务:把WebUI和其他工具连起来,比如生成内容后自动保存到你的笔记软件,或者从数据库里读取预设的提示词。
  • 修改现有行为:改变WebUI某个默认功能的运作方式。

1.2 插件的基本结构

一个最简单的插件,其实就是一个Python文件,里面包含一个继承了特定基类的类。这个类里实现几个关键的方法,WebUI在启动时就会发现它,并把它“挂载”到相应的位置。

为了让你有个直观印象,我们先来看一个插件文件的骨架长什么样:

# my_awesome_plugin.py
import modules.scripts as scripts
from modules import scripts_postprocessing
from modules.ui_components import FormRow

class MyAwesomePlugin(scripts.Script):
    def title(self):
        return "我的超酷插件"

    def show(self, is_img2img):
        return scripts.AlwaysVisible

    def ui(self, is_img2img):
        # 在这里定义插件在界面上显示哪些控件
        with FormRow():
            # 例如,添加一个复选框
            my_checkbox = gr.Checkbox(label="启用我的功能", value=False)
        return [my_checkbox]

    def process(self, p, *args):
        # 在这里编写核心的处理逻辑
        # p 包含了所有的生成参数
        # args 对应上面 ui() 方法返回的控件值
        my_checkbox_value = args[0]
        if my_checkbox_value:
            # 如果用户勾选了复选框,就执行一些操作
            p.prompt = "[前缀] " + p.prompt
        # 处理完成后,返回修改后的参数对象 p
        return p

看不懂代码细节没关系,我们后面会一步步拆解。你现在只需要知道:一个插件 = 一个Python类 + 几个必须实现的方法

2. 开发第一个插件:文本前缀添加器

理论讲多了容易犯困,我们直接来实战。我们的第一个目标是做一个超级简单的插件:在用户输入提示词的开头,自动加上一个可自定义的前缀。

比如,用户输入“a cat”,如果前缀设置为“best quality, masterpiece, ”,那么最终发给模型的提示词就会变成“best quality, masterpiece, a cat”。这在需要统一画风或质量的场景下很有用。

2.1 创建插件文件

首先,找到你的Janus-Pro-7B WebUI安装目录。里面会有一个叫 extensions 的文件夹。所有第三方插件都放在这里。

  1. extensions 文件夹下,新建一个文件夹,名字就是你的插件名,比如 my-prefix-adder
  2. 进入这个新文件夹,创建一个Python文件,命名为 my_prefix_adder.py
  3. 用你喜欢的代码编辑器(比如VSCode、PyCharm)打开这个文件。

2.2 编写插件核心代码

现在,把下面的代码复制到 my_prefix_adder.py 文件中。我会在代码里加上详细的注释,告诉你每一部分是干什么的。

# my_prefix_adder.py
# 导入WebUI插件开发所需的必要模块
import modules.scripts as scripts
import gradio as gr
from modules import script_callbacks
from modules.ui_components import FormRow, FormGroup

# 我们的插件类,必须继承自 scripts.Script
class MyPrefixAdderScript(scripts.Script):
    """
    一个简单的插件示例:为所有提示词添加自定义前缀。
    """

    # 这个方法返回插件的显示名称,会出现在WebUI的下拉列表或按钮上
    def title(self):
        return "文本前缀添加器"

    # 这个方法决定插件何时显示。scripts.AlwaysVisible 表示始终显示。
    def show(self, is_img2img):
        return scripts.AlwaysVisible

    # 这是最重要的方法之一,用于创建插件的用户界面(UI)
    def ui(self, is_img2img):
        # 创建一个UI控件列表,最终要返回它
        controls = []

        # 使用FormGroup可以让相关控件在UI上分组显示,更美观
        with FormGroup(elem_id="my_prefix_group"):
            # 使用FormRow进行水平布局
            with FormRow():
                # 创建一个复选框,让用户决定是否启用本插件
                # label: 控件旁边显示的文字
                # value: 默认值,False表示默认不勾选
                enable_plugin = gr.Checkbox(label="启用前缀添加", value=False, elem_id="my_prefix_enable")
                controls.append(enable_plugin) # 记住这个控件,后面要用

            with FormRow():
                # 创建一个文本框,让用户输入想要添加的前缀
                # placeholder: 文本框内的提示文字
                prefix_text = gr.Textbox(label="自定义前缀",
                                         placeholder="例如:best quality, masterpiece, ",
                                         value="",
                                         elem_id="my_prefix_text")
                controls.append(prefix_text) # 记住这个控件

        # 返回所有需要记录值的UI控件列表
        return controls

    # 这是另一个核心方法,在生成流程中被调用,用于执行实际的处理逻辑
    # p: 一个包含所有生成参数(如提示词、步数、尺寸等)的对象
    # *args: 按顺序对应上面 ui() 方法返回的各个控件的当前值
    def process(self, p, *args):
        # 从args中解包出我们控件的值
        # args[0] 对应 controls[0],也就是 enable_checkbox
        # args[1] 对应 controls[1],也就是 prefix_textbox
        is_enabled = args[0]
        custom_prefix = args[1]

        # 检查插件是否被启用,以及前缀是否不为空
        if is_enabled and custom_prefix.strip():
            print(f"[文本前缀添加器] 正在工作,前缀为: '{custom_prefix}'")
            # 遍历所有的提示词(WebUI支持批量生成)
            for i in range(len(p.all_prompts)):
                # 在原有的提示词前加上我们自定义的前缀
                p.all_prompts[i] = custom_prefix + p.all_prompts[i]
            print(f"[文本前缀添加器] 处理完成。")
        else:
            print(f"[文本前缀添加器] 未启用或前缀为空,跳过处理。")

        # 必须返回修改后的参数对象 p
        return p

    # 后处理钩子(可选):在生成完成后执行,这里我们暂时不用
    # def postprocess(self, p, processed, *args):
    #     pass

2.3 安装并测试插件

代码写好了,怎么让它生效呢?非常简单。

  1. 重启WebUI:完全关闭正在运行的Janus-Pro-7B WebUI服务,然后重新启动它。
  2. 查看插件列表:启动后,在WebUI的顶部选项卡或设置页面中,找到“扩展”或“插件”标签页。你应该能看到“文本前缀添加器”出现在已安装的插件列表里,并且处于激活状态。
  3. 使用插件
    • 刷新你的WebUI主页面(文生图或图生图页面)。
    • 在提示词输入框附近,你应该能看到我们添加的“文本前缀添加器”折叠面板或一组新的控件。
    • 勾选“启用前缀添加”,在下面的文本框里输入“best quality, masterpiece, ”。
    • 在提示词框输入“a cat sitting on a sofa”。
    • 点击生成。观察控制台输出,你应该能看到类似 [文本前缀添加器] 正在工作... 的日志。
    • 生成的图片效果,应该会比不加前缀时有所提升(更偏向于高质量和大师作品风格)。

恭喜你!你的第一个插件已经成功运行了。它虽然简单,但包含了插件开发的所有核心要素:UI定义、参数获取、逻辑处理。

3. 深入进阶:开发一个后处理图片插件

只会改文字不过瘾?我们来点更实用的:开发一个后处理插件,在图片生成完成后,自动为它添加一个简单的文本水印。

这个例子会涉及到图片处理,我们会用到PIL(Python Imaging Library)这个库。确保你的环境已经安装了它(pip install Pillow)。

3.1 创建图片水印插件

extensions 文件夹下再新建一个文件夹,比如 my-image-watermark,然后创建 my_image_watermark.py 文件。

# my_image_watermark.py
import modules.scripts as scripts
import gradio as gr
from modules import script_callbacks, processing, images
from modules.ui_components import FormRow, FormGroup
from PIL import Image, ImageDraw, ImageFont
import os

class MyImageWatermarkScript(scripts.Script):

    def title(self):
        return "图片水印添加器"

    def show(self, is_img2img):
        return scripts.AlwaysVisible

    def ui(self, is_img2img):
        controls = []
        with FormGroup(elem_id="my_watermark_group"):
            with FormRow():
                enable_watermark = gr.Checkbox(label="添加文字水印", value=False)
                controls.append(enable_watermark)

            with FormRow():
                watermark_text = gr.Textbox(label="水印文字", value="My AI Art", placeholder="输入水印内容")
                controls.append(watermark_text)

            with FormRow():
                # 滑块控件,用于选择水印文字大小
                text_size = gr.Slider(minimum=10, maximum=100, step=1, value=36, label="字体大小")
                controls.append(text_size)

            with FormRow():
                # 滑块控件,用于选择水印不透明度
                text_opacity = gr.Slider(minimum=0.1, maximum=1.0, step=0.1, value=0.5, label="不透明度")
                controls.append(text_opacity)

            with FormRow():
                # 单选按钮,选择水印位置
                position = gr.Radio(choices=["右下角", "左下角", "右上角", "左上角", "居中"], value="右下角", label="水印位置")
                controls.append(position)

        return controls

    # 注意:我们这次重写的是 postprocess 方法,它在图片生成后被调用
    def postprocess(self, p, processed, *args):
        """
        p: 处理参数对象
        processed: 处理结果对象,里面包含了生成的所有图片(processed.images)
        *args: UI控件的值
        """
        is_enabled, watermark_text, text_size, opacity, position = args

        if not is_enabled or not watermark_text.strip():
            return processed # 如果未启用,直接返回原结果

        print(f"[图片水印添加器] 开始为 {len(processed.images)} 张图片添加水印。")

        # 尝试加载一个字体,如果失败则使用默认字体
        try:
            # 你可以指定一个字体文件路径,例如:
            # font = ImageFont.truetype("/path/to/your/font.ttf", text_size)
            font = ImageFont.load_default()
        except:
            font = ImageFont.load_default()

        watermarked_images = []
        for img in processed.images:
            # 将WebUI的图片对象转换为PIL Image对象
            pil_image = images.from_numpy(img)
            draw = ImageDraw.Draw(pil_image, 'RGBA') # 使用RGBA模式以支持透明度

            # 计算文字尺寸和位置
            # 注意:load_default()字体可能无法准确获取文本尺寸,这里是一个简单估算
            # 对于复杂需求,建议使用指定字体文件
            text_bbox = draw.textbbox((0, 0), watermark_text, font=font)
            text_width = text_bbox[2] - text_bbox[0]
            text_height = text_bbox[3] - text_bbox[1]

            img_width, img_height = pil_image.size

            # 根据选择的位置计算坐标
            padding = 10
            if position == "右下角":
                x = img_width - text_width - padding
                y = img_height - text_height - padding
            elif position == "左下角":
                x = padding
                y = img_height - text_height - padding
            elif position == "右上角":
                x = img_width - text_width - padding
                y = padding
            elif position == "左上角":
                x = padding
                y = padding
            else: # 居中
                x = (img_width - text_width) / 2
                y = (img_height - text_height) / 2

            # 绘制半透明文字(白色带透明度)
            draw.text((x, y), watermark_text, font=font, fill=(255, 255, 255, int(255 * opacity)))

            # 将处理后的PIL Image转换回numpy数组,并添加到新列表
            watermarked_images.append(images.to_numpy(pil_image))

        # 用添加了水印的图片列表替换原来的图片列表
        processed.images = watermarked_images
        print(f"[图片水印添加器] 水印添加完成。")
        return processed

3.2 测试水印插件

  1. 同样,重启WebUI。
  2. 在文生图页面,找到新出现的“图片水印添加器”控件组。
  3. 勾选“添加文字水印”,输入水印文字(如“Created with Janus”),调整大小、透明度和位置。
  4. 输入提示词,生成一张图片。
  5. 查看结果,生成的图片上应该已经带上了你设置的水印。

通过这个例子,你学会了如何使用 postprocess 钩子来操作生成后的图片,并且使用了更丰富的UI控件(滑块、单选按钮)。插件的能力边界正在被你一步步拓宽。

4. 实用技巧与问题排查

开发插件时,你可能会遇到一些小麻烦。这里分享几个实用的技巧和常见问题的解决方法。

4.1 调试与日志输出

插件开发离不开调试。最直接的方法就是使用 print() 函数将信息输出到WebUI的控制台。就像我们在前面代码里做的:

print(f"[我的插件] 当前参数是: {some_variable}")

这能帮你清楚地知道代码执行到了哪一步,变量的值是什么。

4.2 理解WebUI的对象结构

插件强大的关键在于你能操作WebUI内部的数据。两个最重要的对象是:

  • p (在 process 中)StableDiffusionProcessing 类的实例。它包含了生成所需的一切参数:p.prompt(提示词),p.negative_prompt(负面提示词),p.steps(步数),p.cfg_scale(引导系数),p.seed(种子)等等。修改这些属性就能影响生成过程。
  • processed (在 postprocess 中)Processed 类的实例。最重要的属性是 processed.images,这是一个包含所有生成图片(numpy数组格式)的列表。process 方法返回的 p 对象,最终也会被转换为 processed 对象。

多翻阅WebUI的源代码(特别是 modules/processing.pymodules/images.py),是理解这些对象的最佳途径。

4.3 常见问题与解决

  • 插件不显示:检查你的插件文件夹是否放在正确的 extensions 目录下。检查 script.py 文件中的类名是否正确,以及 title() 方法是否返回了非空字符串。重启WebUI。
  • 控件值获取错误:确保 ui() 方法返回的控件列表顺序,与 process()postprocess() 方法中 *args 的解包顺序完全一致。
  • 导入错误:确保你的插件文件开头正确导入了所需的模块(如 modules.scripts)。如果用了第三方库(如PIL),请确保已通过pip安装。
  • 性能问题:在 processpostprocess 中执行非常耗时的操作(如下载文件、复杂计算)会拖慢整个生成流程。尽量保持插件逻辑高效,或者考虑使用异步操作。

5. 总结

走完这两个插件的开发旅程,你应该已经感受到,为Janus-Pro-7B WebUI扩展功能并没有那么神秘。核心就是找到那个对的“钩子”(processpostprocess),然后在里面编写你的逻辑。

从简单的文本处理到图片后处理,你手中的工具已经可以应对很多定制化需求了。你可以尝试把两者结合起来,比如做一个“智能提示词优化+自动添加签名水印”的一站式插件。更进一步的,你可以研究 script_callbacks 模块,它提供了更多精细化的钩子,允许你在WebUI生命周期的几乎任何阶段注入代码,比如在页面加载时添加新的选项卡。

最好的学习方式就是动手去实现一个你真正需要的功能。从一个小点子开始,遇到问题就查源码、看日志、在开发者社区里交流。当你成功运行起自己开发的插件,并看到它完美地融入WebUI工作时,那种成就感是非常棒的。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐