[], 'version' => PLUME_VERSION, ]; \wp_enqueue_script( 'plume-images', PLUME_URL . 'assets/images/index.js', array_merge( $asset['dependencies'], [ 'wp-element', 'wp-api-fetch', 'wp-i18n' ] ), $asset['version'], true ); \wp_localize_script( 'plume-images', 'plumeData', [ 'nonce' => \wp_create_nonce( 'wp_rest' ), 'restUrl' => \esc_url_raw( \rest_url( 'plume/v1' ) ), 'isPaid' => TierManager::is_paid(), 'adminUrl' => \esc_url_raw( \admin_url() ), 'websiteUrl' => PLUME_WEBSITE_URL, ] ); \wp_enqueue_style( 'plume-images', PLUME_URL . 'assets/images/index.css', [], $asset['version'] ); } /** * Register the /plume/v1/images/generate REST route. * * @since 1.0.0 * @return void */ public static function register_routes(): void { \register_rest_route( 'plume/v1', '/images/generate', [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ self::class, 'handle_generate' ], 'permission_callback' => function () { return \current_user_can( 'upload_files' ); }, 'args' => [ 'prompt' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_textarea_field', ], 'aspect_ratio' => [ 'required' => false, 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'default' => '1:1', 'enum' => [ '1:1', '16:9', '4:3', '9:16' ], ], 'count' => [ 'required' => false, 'type' => 'integer', 'sanitize_callback' => 'absint', 'default' => 3, ], ], ] ); } /** * Generate one or more images and save them to the WordPress media library. * * Returns 201 when all images succeed, 207 when at least one fails but at * least one succeeds, and 500 when all fail. * * @since 1.0.0 * @param \WP_REST_Request $request Incoming REST request. * @return \WP_REST_Response */ public static function handle_generate( \WP_REST_Request $request ): \WP_REST_Response { $prompt = $request->get_param( 'prompt' ); $aspect_ratio = $request->get_param( 'aspect_ratio' ); $count = min( (int) $request->get_param( 'count' ), 3 ); $size_map = [ '1:1' => '1024x1024', '16:9' => '1792x1024', '4:3' => '1024x768', '9:16' => '1024x1792', ]; $options = [ 'aspect_ratio' => $aspect_ratio, 'size' => $size_map[ $aspect_ratio ] ?? '1024x1024', ]; $factory = new ProviderFactory( new ProviderSettings() ); $provider = $factory->make_image_provider(); $images = []; $errors = []; for ( $i = 0; $i < $count; $i++ ) { try { $attachment_id = $provider->generate_image( $prompt, $options ); $full = \wp_get_attachment_image_src( $attachment_id, 'full' ); $thumb = \wp_get_attachment_image_src( $attachment_id, 'thumbnail' ); $images[] = [ 'attachment_id' => $attachment_id, 'url' => $full ? \esc_url_raw( $full[0] ) : '', 'thumbnail_url' => $thumb ? \esc_url_raw( $thumb[0] ) : '', 'prompt' => $prompt, ]; } catch ( ProviderException $e ) { $errors[] = $e->getMessage(); } catch ( \Exception $e ) { $errors[] = $e->getMessage(); } } if ( empty( $images ) ) { return new \WP_REST_Response( [ 'error' => __( 'All image generation requests failed.', 'plume' ), 'details' => $errors, ], 500 ); } // No usage is logged here: images are generated with the user's own API key and never // pass through the managed proxy, so no managed credits are consumed. If a managed image // route is added, take the charge from the Worker's credits_charged, as chat does. $status = empty( $errors ) ? 201 : 207; return new \WP_REST_Response( [ 'images' => $images, 'errors' => $errors, 'provider' => $provider->get_slug(), 'count' => count( $images ), ], $status ); } }