Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ai-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
70 changes: 70 additions & 0 deletions src/AiCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand All @@ -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;
},
]
);
}
}
126 changes: 126 additions & 0 deletions src/MCP/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
18 changes: 17 additions & 1 deletion src/MCP/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'] );
Expand All @@ -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 = [];
Expand Down
40 changes: 36 additions & 4 deletions src/Tools/ImageTools.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
];
}

Expand All @@ -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 );
},
]
);

}
}