diff --git a/ai-command.php b/ai-command.php index 169d925..b7aaecc 100644 --- a/ai-command.php +++ b/ai-command.php @@ -29,7 +29,7 @@ // TODO Register your tool here and add it to the collection $all_tools = [ - ...(new ImageTools($client))->get_tools(), + ...(new ImageTools($client, $server))->get_tools(), ...(new CommunityEvents($client))->get_tools(), ...(new MiscTools($server))->get_tools(), ...(new URLTools($server))->get_tools(), diff --git a/src/AiCommand.php b/src/AiCommand.php index 7caeca3..15605ac 100644 --- a/src/AiCommand.php +++ b/src/AiCommand.php @@ -72,6 +72,8 @@ private function register_tools($server) : void { $server->register_tool( $tool->get_data() ); } + $this->register_media_resources($server); + return; new FileTools( $server ); @@ -98,4 +100,72 @@ private function register_resources($server) { 'filePath' => './products.json', // Data will be fetched from products.json ]); } + + protected function register_media_resources( $server ) { + + $args = array( + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'posts_per_page' => - 1, + ); + + $media_items = get_posts( $args ); + + foreach ( $media_items as $media ) { + + $media_id = $media->ID; + $media_url = wp_get_attachment_url( $media_id ); + $media_type = get_post_mime_type( $media_id ); + $media_title = get_the_title( $media_id ); + + $server->register_resource( + [ + 'name' => 'media_' . $media_id, + 'uri' => 'media://' . $media_id, + 'description' => $media_title, + 'mimeType' => $media_type, + 'callable' => function () use ( $media_id, $media_url, $media_type ) { + $data = [ + 'id' => $media_id, + 'url' => $media_url, + 'filepath' => get_attached_file( $media_id ), + 'alt' => get_post_meta( $media_id, '_wp_attachment_image_alt', true ), + 'mime_type' => $media_type, + 'metadata' => wp_get_attachment_metadata( $media_id ), + ]; + + return $data; + }, + ] + ); + } + + // Also register a media collection resource + $server->register_resource( + [ + 'name' => 'media_collection', + 'uri' => 'data://media', + 'description' => 'Collection of all media items', + 'mimeType' => 'application/json', + 'callable' => function () { + + $args = array( + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'posts_per_page' => - 1, + 'fields' => 'ids', + ); + + $media_ids = get_posts( $args ); + $media_map = []; + + foreach ( $media_ids as $id ) { + $media_map[ $id ] = 'media://' . $id; + } + + return $media_map; + }, + ] + ); + } } diff --git a/src/MCP/Client.php b/src/MCP/Client.php index 2316bd0..2b50cb6 100644 --- a/src/MCP/Client.php +++ b/src/MCP/Client.php @@ -309,4 +309,130 @@ static function () { WP_CLI::error( $e->getMessage() ); } } + + public function modify_image_with_ai($prompt, $media_element) { + + + $mime_type = $media_element['mime_type']; + $image_path = $media_element['filepath']; + $image_contents = file_get_contents($image_path); + + // Convert image to base64 + $base64_image = base64_encode($image_contents); + + // API Configuration + $api_key = get_option('ais_google_api_key'); + + if(!$api_key) { + WP_CLI::error("Gemini API Key is not available"); + } + $model = 'gemini-2.0-flash-exp'; + $api_url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$api_key}"; + + // Prepare request payload + $payload = [ + 'contents' => [ + [ + 'role' => 'user', + 'parts' => [ + [ + 'text' => $prompt + ], + [ + 'inline_data' => [ + 'mime_type' => $mime_type, + 'data' => $base64_image + ] + ] + ] + ] + ], + 'generationConfig' => [ + 'responseModalities' => ['TEXT', 'IMAGE'] + ] + ]; + + // Convert payload to JSON + $json_payload = json_encode($payload); + + // Set up cURL request + $ch = curl_init($api_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json_payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($json_payload) + ]); + + // Execute request + $response = curl_exec($ch); + $error = curl_error($ch); + $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + // Handle errors + if ($error) { + WP_CLI::error("cURL Error: " . $error); + return false; + } + + if ($status_code >= 400) { + WP_CLI::error("API Error (Status $status_code): " . $response); + return false; + } + + // Process response + $response_data = json_decode($response, true); + + // Check for valid response + if (empty($response_data) || !isset($response_data['candidates'][0]['content']['parts'])) { + WP_CLI::error("Invalid API response format"); + return false; + } + + // Extract image data from response + $image_data = null; + foreach ($response_data['candidates'][0]['content']['parts'] as $part) { + if (isset($part['inlineData'])) { + $image_data = $part['inlineData']['data']; + $response_mime_type = $part['inlineData']['mimeType']; + break; + } + } + + if (!$image_data) { + WP_CLI::error("No image data in response"); + return false; + } + + // Decode base64 image + $binary_data = base64_decode($image_data); + if (false === $binary_data) { + WP_CLI::error("Failed to decode image data"); + return false; + } + + // Create temporary file for the image + $extension = explode('/', $response_mime_type)[1] ?? 'jpg'; + $filename = tempnam('/tmp', 'ai-generated-image'); + rename($filename, $filename . '.' . $extension); + $filename .= '.' . $extension; + + // Save image to the file + if (!file_put_contents($filename, $binary_data)) { + WP_CLI::error("Failed to save image to temporary file"); + return false; + } + + // Upload to media library + $image_id = \WP_CLI\AiCommand\MediaManager::upload_to_media_library($filename); + + if ($image_id) { + WP_CLI::success('Image generated with ID: ' . $image_id); + return $image_id; + } + + return false; +} } diff --git a/src/MCP/Server.php b/src/MCP/Server.php index 0bc3124..cff2080 100644 --- a/src/MCP/Server.php +++ b/src/MCP/Server.php @@ -211,10 +211,16 @@ private function read_resource( $uri ) { ]; } - private function get_resource_data( $mcp_resource ) { + public function get_resource_data( $mcp_resource ) { // Replace this with your actual logic to access the resource data // based on the resource definition. + if ( str_starts_with( $mcp_resource, 'media://' ) ) { + return $this->get_media_data( $mcp_resource ); + } + + + // Example: If the resource is a file, read the file contents. if ( isset( $mcp_resource['filePath'] ) ) { return file_get_contents( $mcp_resource['filePath'] ); @@ -230,6 +236,16 @@ private function get_resource_data( $mcp_resource ) { throw new Exception( 'Unable to access resource data.' ); } + private function get_media_data( $mcp_resource ) { + + foreach ( $this->resources as $resource ) { + if ( $resource['uri'] === $mcp_resource ) { + $callback_response = $resource['callable'](); + return $callback_response; + } + } + } + // TODO: use a dedicated JSON schema validator library private function validate_input( $input, $schema ): array { $errors = []; diff --git a/src/Tools/ImageTools.php b/src/Tools/ImageTools.php index 71a263d..655a9ba 100644 --- a/src/Tools/ImageTools.php +++ b/src/Tools/ImageTools.php @@ -2,18 +2,24 @@ namespace WP_CLI\AiCommand\Tools; use WP_CLI\AiCommand\Entity\Tool; +use WP_CLI\AiCommand\custom_tome_log; + class ImageTools { - private $client; + protected $client; + protected $server; - public function __construct($client) { + public function __construct($client, $server) { $this->client = $client; + $this->server = $server; } + public function get_tools(){ return [ - $this->image_generation_tool() + $this->image_generation_tool(), + $this->image_modification_tool() ]; } @@ -33,13 +39,39 @@ public function image_generation_tool() { 'required' => [ 'prompt' ], ], 'callable' => function ( $params ) { - return $this->client->get_image_from_ai_service( $params['prompt'] ); }, ] ); } + public function image_modification_tool() { + return new Tool( + [ + 'name' => 'modify_image', + 'description' => 'Modifies an image with a given image id and prompt.', + 'inputSchema' => [ + 'type' => 'object', + 'properties' => [ + 'prompt' => [ + 'type' => 'string', + 'description' => 'The prompt for generating the image.', + ], + 'media_id' => [ + 'type' => 'string', + 'description' => 'the id of the media element', + ], + ], + 'required' => [ 'prompt', 'media_id' ], + ], + 'callable' => function ( $params ) { + $media_uri = 'media://' . $params['media_id']; + $media_resource = $this->server->get_resource_data( $media_uri ); + return $this->client->modify_image_with_ai( $params['prompt'], $media_resource ); + }, + ] + ); + } }