用 WordPress AI Client 构建图片生成插件

本文根据 Felix Arntz 于 2026 年 5 月 14 日发表于 WordPress Developer Blog 的文章整理并中文化。原文标题:How to build an image generation plugin with the WordPress AI Client。原文作者在文末感谢 @psykro、@juanmaguitar 与 @bph 参与审阅和校对。

目标与前提

本教程要在 WordPress 媒体库中加入一个“Generate Image File”按钮。用户点击后输入提示词,插件通过 WordPress AI Client 请求图像生成,先在界面预览结果,再由用户决定是否将图片保存为新的媒体附件。生成和保存拆成两个 REST API 操作,使用户能先检查图片再入库。

WordPress AI Client 的关键价值,是插件不直接绑定某一家模型服务。站点所有者在 WordPress 后台的 Settings > Connectors 中配置服务和凭据;插件只声明需要图像生成能力,由 WordPress 将请求路由给可用的提供方和模型。原文举例提到 Anthropic、Google、OpenAI 等服务。新增提供方或模型时,已有插件通常不必为每个厂商维护独立代码路径。

开始前需要:

  • WordPress 7.0 或更高版本;
  • 在 Settings > Connectors 配置了支持图像生成的 AI 提供方和模型;
  • Node.js 20 或更高版本,用于构建前端资源;
  • 基本的 WordPress 插件、REST API 和 PHP 开发经验。

使用文章所示 @wordpress/env 本地环境还需要 Docker,也可换用其他 WordPress 开发环境。

理解 AI Client 的调用方式

从提示构造器开始

每次调用从 wp_ai_client_prompt() 开始。它返回一个流式构造器,方法采用 WordPress 的 snake_case 命名,并封装底层 php-ai-client 库。

$builder = wp_ai_client_prompt()
    ->with_text( 'Summarize the benefits of caching in WordPress' )
    ->using_temperature( 0.7 );

$result = $builder->generate_text_result();

with_text() 设置提示内容;using_temperature() 是可选参数,用来调整输出随机性。调用生成方法后会得到 GenerativeAiResult 对象,其中包含生成内容,以及处理请求的提供方和模型等元数据。该对象可序列化,也可以直接交给 rest_ensure_response(),用于 REST API 响应。图像生成使用同一构造器模式,之后再配置输出格式、方向等选项。

偏好模型,不把模型写成硬依赖

插件可使用 using_model_preference() 按顺序提出首选模型。AI Client 会先尝试首选项;若都不可用,则回退到任何支持所需能力的模型。这样用户能用偏好模型,但不会因为站点没有该模型就无法使用插件。

$builder = wp_ai_client_prompt()
    ->with_text( 'Summarize the benefits of caching in WordPress' )
    ->using_temperature( 0.7 )
    ->using_model_preference( 'gpt-5.4', 'gemini-3.1-pro-preview', 'claude-opus-4.6' );

模型偏好不是强制要求。不要把插件设计成只能与某个特定厂商或型号配合;只有当某个模型特别适合用例时,才把它列为偏好,同时保留其他兼容模型的使用空间。

暴露功能前检查能力

并非每个站点都配置了 AI 提供方,也不是每个模型都支持所有能力。显示按钮或加载界面前,可以调用 is_supported_for_image_generation():

$is_available = wp_ai_client_prompt()
    ->with_text( 'test' )
    ->is_supported_for_image_generation();

该方法会根据构造器选项和可用模型声明的能力,判断站点是否有模型支持图像生成。它不发起外部 API 请求、不执行推理,也不分析提示词,因此不会产生推理成本。这个检查适合用于决定是否加载 UI,或在功能不可用时提示管理员去 Connectors 配置服务。仅安装 WordPress 7.0 并不意味着已配置可用的 AI 功能。

插件结构与入口

原文采用如下目录布局:

ai-client-imagegen/
├── plugin.php
├── includes/
│   ├── prompt.php
│   ├── rest-api.php
│   └── admin.php
├── src/
│   └── index.ts
├── build/
│   ├── index.js
│   └── index.asset.php
├── package.json
└── composer.json
  • plugin.php:入口文件,加载实现并注册钩子;
  • includes/prompt.php:统一构造 AI Client 图像提示;
  • includes/rest-api.php:生成图像和保存到媒体库的 REST 路由;
  • includes/admin.php:注册脚本,并在媒体库页面按条件加载;
  • src/index.ts:前端弹窗、API 调用与保存操作;
  • build/:由 @wordpress/scripts 生成的 JavaScript 和依赖清单。

文章中的一些代码片段为突出重点而从实际插件代码中略作删减。完整插件链接见原文所列 GitHub 项目:https://github.com/wptrainingteam/ai-client-imagegen 。原文没有逐行讲解前端约 590 行 TypeScript,而是建议将该项目中的 src/index.ts 放入本地对应目录。

入口文件声明插件信息,确认 WordPress 环境存在,并在 AI Client 函数不存在时提前退出。随后加载实现文件并注册钩子:

<?php
/**
 * Plugin Name: AI Client ImageGen
 * Plugin URI: https://github.com/wptrainingteam/ai-client-imagegen
 * Description: Generates images in the WordPress Media Library using the built-in AI Client.
 * Requires at least: 7.0
 * Requires PHP: 7.4
 * Version: 1.0.0
 * Author: Your Name
 * License: GPL-2.0-or-later
 * Text Domain: ai-client-imagegen
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
    return;
}

require_once __DIR__ . '/includes/prompt.php';
require_once __DIR__ . '/includes/rest-api.php';
require_once __DIR__ . '/includes/admin.php';

add_action( 'rest_api_init', 'aicig_register_rest_routes' );
add_action( 'init', 'aicig_register_assets' );
add_action( 'admin_enqueue_scripts', 'aicig_enqueue_media_assets' );

Requires at least: 7.0 用于声明最低 WordPress 版本;function_exists() 则是运行时保护。原文代码中的 Author: Your Name 是示例占位文本,实际插件应填写作者信息。

复用图像提示构造器

在 includes/prompt.php 中,将提示文本和图像输出选项集中到一个函数。可选方向值用于提示期望的媒体比例;未指定时不设置方向。FileTypeEnum::inline() 要求结果以可内嵌数据形式返回,便于前端预览。

<?php
use WordPress\AiClient\Files\Enums\FileTypeEnum;
use WordPress\AiClient\Files\Enums\MediaOrientationEnum;

function aicig_get_image_generation_prompt( string $prompt, string $orientation = '' ): WP_AI_Client_Prompt_Builder {
    $builder = wp_ai_client_prompt()
        ->with_text( $prompt )
        ->as_output_file_type( FileTypeEnum::inline() );

    if ( $orientation ) {
        $builder->as_output_media_orientation( MediaOrientationEnum::from( $orientation ) );
    }

    return $builder;
}

该函数返回构造器而不是立即执行请求。REST 回调之后调用 generate_image_result();管理员界面则复用同一函数做能力检查。提示构造不指定服务商或模型,路由由 AI Client 处理。MediaOrientationEnum::from() 接收 square、landscape 或 portrait 等方向值。

注册 REST API

插件将两个端点放在 ai-client-imagegen/v1 命名空间下:

  1. POST /generate-image:接收提示词和可选方向,生成图像并返回 AI Client 结果;
  2. POST /upload-image:接收 Base64 图像、文件名和可选 MIME 类型,将图片保存为媒体附件。

两个端点都使用 current_user_can( ‘upload_files’ ) 作为权限检查。原文将路由注册和处理逻辑拆开说明;下面把文章中的片段放在一起,展示彼此之间的调用关系。

生成图像

生成路由要求 prompt 为字符串,方向可选且限定为 square、landscape 或 portrait。处理器从请求读取参数,调用共享提示构造器,再由 generate_image_result() 执行请求。

function aicig_register_rest_routes(): void {
    register_rest_route(
        'ai-client-imagegen/v1',
        '/generate-image',
        array(
            'methods'             => WP_REST_Server::CREATABLE,
            'callback'            => 'aicig_rest_generate_image',
            'permission_callback' => static function () {
                return current_user_can( 'upload_files' );
            },
            'args'                => array(
                'prompt' => array(
                    'type'     => 'string',
                    'required' => true,
                ),
                'orientation' => array(
                    'type'     => 'string',
                    'required' => false,
                    'enum'     => array( 'square', 'landscape', 'portrait' ),
                ),
            ),
        )
    );
}

function aicig_rest_generate_image( WP_REST_Request $request ) {
    $prompt      = $request->get_param( 'prompt' );
    $orientation = $request->get_param( 'orientation' );
    $builder = aicig_get_image_generation_prompt(
        $prompt,
        $orientation ?? ''
    );

    return rest_ensure_response(
        $builder->generate_image_result()
    );
}

GenerativeAiResult 可以序列化,因此 rest_ensure_response() 能直接将结果转换为 JSON 响应,其中包含图像数据和提供方、模型等元数据。若发生错误,generate_image_result() 返回的 WP_Error 也可作为 REST 响应处理。

保存到媒体库

用户确认预览后,前端把图像的 Base64 数据、文件名和 MIME 类型发送给保存端点。回调严格解码 Base64;无效数据返回 400 错误。文件写入失败时返回 500 错误。写入成功后,创建附件、生成缩略图和元数据,最终响应附件 ID 与 URL。

function aicig_register_rest_routes(): void {
    // Other REST route registration for generating an image.
    register_rest_route(
        'ai-client-imagegen/v1',
        '/upload-image',
        array(
            'methods'             => WP_REST_Server::CREATABLE,
            'callback'            => 'aicig_rest_upload_image',
            'permission_callback' => static function () {
                return current_user_can( 'upload_files' );
            },
            'args'                => array(
                'image_base64' => array(
                    'type'     => 'string',
                    'required' => true,
                ),
                'file_name' => array(
                    'type'              => 'string',
                    'required'          => true,
                    'sanitize_callback' => 'sanitize_file_name',
                ),
                'mime_type' => array(
                    'type'              => 'string',
                    'required'          => false,
                    'default'           => 'image/png',
                    'sanitize_callback' => 'sanitize_mime_type',
                ),
            ),
        )
    );
}

function aicig_rest_upload_image( WP_REST_Request $request ) {
    $image_base64 = $request->get_param( 'image_base64' );
    $file_name    = $request->get_param( 'file_name' );
    $mime_type    = $request->get_param( 'mime_type' );

    $decoded = base64_decode( $image_base64, true );
    if ( false === $decoded ) {
        return new WP_Error(
            'invalid_image_data',
            __( 'The provided image data is not valid base64.', 'ai-client-imagegen' ),
            array( 'status' => 400 )
        );
    }

    $upload = wp_upload_bits( $file_name, null, $decoded );
    if ( ! empty( $upload['error'] ) ) {
        return new WP_Error(
            'upload_failed',
            $upload['error'],
            array( 'status' => 500 )
        );
    }

    $attachment_data = array(
        'post_mime_type' => $mime_type,
        'post_title'     => sanitize_file_name(
            pathinfo( $file_name, PATHINFO_FILENAME )
        ),
        'post_status'    => 'inherit',
    );

    $attachment_id = wp_insert_attachment(
        $attachment_data,
        $upload['file']
    );

    if ( is_wp_error( $attachment_id ) ) {
        return $attachment_id;
    }

    require_once ABSPATH . 'wp-admin/includes/image.php';

    $metadata = wp_generate_attachment_metadata(
        $attachment_id,
        $upload['file']
    );

    wp_update_attachment_metadata( $attachment_id, $metadata );

    return rest_ensure_response(
        array(
            'id'  => $attachment_id,
            'url' => wp_get_attachment_url( $attachment_id ),
        )
    );
}

文件名和 MIME 类型分别通过 sanitize_file_name()、sanitize_mime_type() 清理。创建媒体附件是标准 WordPress 流程:wp_upload_bits() 写文件,wp_insert_attachment() 创建记录,wp_generate_attachment_metadata() 与 wp_update_attachment_metadata() 保存尺寸等元数据。原文把两个路由示例分开展示;在实际文件中,应将两个 register_rest_route() 调用合并到同一个 aicig_register_rest_routes() 定义中,而不是重复定义同名函数。

后台脚本注册与按需加载

在 includes/admin.php 中,先注册打包后的脚本。index.asset.php 由 @wordpress/scripts 自动生成,包含依赖和基于内容的版本标识;若文件暂时不存在,原文示例提供空依赖和 1.0.0 版本的回退值。

function aicig_register_assets(): void {
    $asset_file = plugin_dir_path( __DIR__ ) . 'build/index.asset.php';

    if ( file_exists( $asset_file ) ) {
        $asset = require $asset_file;
    } else {
        $asset = array(
            'dependencies' => array(),
            'version'      => '1.0.0',
        );
    }

    wp_register_script(
        'aicig-imagegen',
        plugins_url( 'build/index.js', __DIR__ ),
        $asset['dependencies'],
        $asset['version'],
        array( 'strategy' => 'defer' )
    );
}

然后仅在媒体库、且当前用户有上传权限、且站点存在图像生成能力时,加载前端脚本:

function aicig_enqueue_media_assets( string $hook_suffix ): void {
    if ( 'upload.php' !== $hook_suffix ) {
        return;
    }

    if ( ! current_user_can( 'upload_files' ) ) {
        return;
    }

    if ( ! aicig_get_image_generation_prompt( 'test' )->is_supported_for_image_generation() ) {
        // Show an admin notice, or handle the unsupported case in another way.
        return;
    }

    wp_enqueue_script( 'aicig-imagegen' );
}

三道条件依次是:

  1. 页面必须是媒体库(upload.php);
  2. 用户必须有 upload_files 权限;
  3. AI Client 必须报告存在可用的图像生成模型。

前两道检查与 REST API 的权限条件一致,避免出现界面可见、操作却无权完成的情况。第三道检查复用提示构造函数;传入 test 只是满足构造器输入,支持检查不会分析提示词,也不会真正生成图像。若没有可用模型,可在提前返回的位置显示提示,引导管理员检查 Connectors 设置。

前端界面

前端文件是约 590 行 TypeScript,原文没有逐行展开。它使用原生 DOM 操作创建弹窗,通过 @wordpress/api-fetch 发送请求、用 @wordpress/i18n 支持国际化,显示图像预览以及提供方和模型信息,并处理保存到媒体库的操作。用户点击保存前可以检查结果,保存时还可以定制文件名。

原文选择轻量的原生 DOM 写法,是因为这个独立弹窗不需要额外框架;界面更复杂时,可考虑使用 WordPress 内置的 React。前端完整实现应从原文提供的项目源码取得:https://github.com/wptrainingteam/ai-client-imagegen/blob/main/src/index.ts 。这篇教程没有包含该文件的完整代码,因此此处不补写未展示的实现细节。

构建与本地运行

在项目根目录添加 package.json,声明前端运行时依赖和构建工具:

{
    "name": "ai-client-imagegen",
    "private": true,
    "license": "GPL-2.0-or-later",
    "dependencies": {
        "@wordpress/api-fetch": "^7.41.0",
        "@wordpress/i18n": "^6.14.0"
    },
    "devDependencies": {
        "@wordpress/env": "^10.27.0",
        "@wordpress/scripts": "^31.0.0",
        "typescript": "^5.8.2"
    },
    "scripts": {
        "build": "wp-scripts build",
        "wp-env": "wp-env"
    }
}

再添加 .wp-env.json,使用 WordPress 7.0 并将当前目录作为启用的插件:

{
    "core": "https://wordpress.org/wordpress-7.0.zip",
    "plugins": [ "." ]
}

安装依赖并生成前端构建文件:

npm install
npm run build

@wordpress/scripts 会把 src/index.ts 编译为 build/index.js,并生成 build/index.asset.php 依赖清单。PHP 部分不需要单独构建,因为 AI Client 随 WordPress Core 提供。

若用 @wordpress/env 启动本地开发站点:

npm run wp-env start

该环境基于 Docker,默认在 http://localhost:8888 提供站点;原文给出的默认登录凭据是 admin / password。启动后按以下顺序操作:

  1. 在 Settings > Connectors 配置支持图像生成的服务;
  2. 打开 Media > Library;
  3. 点击“Generate Image File”,输入提示词并生成图片;
  4. 检查预览,满意后点击保存按钮,并可修改文件名。

如果看到的是提示而非生成按钮,说明当前已配置的提供方不支持图像生成,应检查 Connectors 设置。以上是原文的运行步骤;本文没有实际运行插件或验证生成结果。

后续扩展:把现有图片交给 AI 编辑

原文指出,图像编辑可沿用几乎相同的提示构造方式,只需额外把既有图像作为 File DTO 输入:

use WordPress\AiClient\Files\DTO\File;
use WordPress\AiClient\Files\Enums\FileTypeEnum;
use WordPress\AiClient\Files\Enums\MediaOrientationEnum;

function aicig_get_image_editing_prompt( string $prompt, File $image_file, string $orientation = '' ): WP_AI_Client_Prompt_Builder {
    $builder = wp_ai_client_prompt()
        ->with_text( $prompt )
        ->with_file( $image_file )
        ->as_output_file_type( FileTypeEnum::inline() );

    if ( $orientation ) {
        $builder->as_output_media_orientation( MediaOrientationEnum::from( $orientation ) );
    }

    return $builder;
}

with_file() 将源图片传给 AI Client,再由 Client 路由到支持图像编辑的提供方和模型。可以在生成端点增加可选图片参数,并按参数有无选择生成或编辑提示构造函数;前端则需要相应设计编辑入口和交互。

原文还提出几种延伸方向:

  • 为一个提示词生成多张变体,让用户挑选;
  • 利用文本生成功能,根据图片内容自动生成更有描述性的文件名;
  • 从媒体库选择已有图片,输入指令后请求 AI 编辑。

小结

这个示例中,真正负责 AI 接入的部分很小:一个统一构造提示的函数,以及调用 generate_image_result() 的 REST 处理器。其余工作仍是熟悉的 WordPress 插件开发,包括路由注册、媒体附件创建、权限判断和后台脚本加载。AI Client 接管服务商通信、认证、模型选择与结果规范化,使插件能把注意力放在媒体库中的用户流程上。

wp_ai_client_prompt() 提供统一入口;模型偏好让插件表达理想选项而不强制绑定;is_supported_for_image_generation() 能在不发起推理请求的前提下确认能力;拆分“生成预览”和“保存媒体”则把最终入库的决定留给用户。基于同一结构,还可以继续探索图片编辑、变体生成与智能命名。

来源信息

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容