Initial commit: igny8 project

This commit is contained in:
igny8
2025-11-09 10:27:02 +00:00
commit 60b8188111
27265 changed files with 4360521 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
<?php
/**
* ==============================
* 📁 Folder Scope Declaration
* ==============================
* Folder: /ai/
* Purpose: AI content/image logic, parsers, prompt APIs
* Rules:
* - Can be reused globally across all modules
* - Contains all AI integration logic
* - OpenAI API integration and management
* - AI prompt libraries and templates
* - AI model configuration and rate limiting
*/

View File

@@ -0,0 +1,21 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : integration.php
* @location : /ai/integration.php
* @type : AI Integration
* @scope : Global
* @allowed : API configuration, connection management, authentication
* @reusability : Globally Reusable
* @notes : AI service configuration and connection management
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Include Runware API integration
require_once plugin_dir_path(__FILE__) . 'runware-api.php';

View File

@@ -0,0 +1,192 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : model-rates-config.php
* @location : /ai/model-rates-config.php
* @type : Config Array
* @scope : Global
* @allowed : Model pricing, cost calculations, rate configurations
* @reusability : Globally Reusable
* @notes : Central AI model pricing configuration
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Global model rates configuration
* Rates are per 1 million tokens for text models
* Per image for image generation models
* GPT-4.1, GPT-4o-mini, and GPT-4o models are supported
*/
$IGNY8_MODEL_RATES = [
'gpt-4.1' => ['in' => 2.00, 'out' => 8.00],
'gpt-4o-mini' => ['in' => 0.15, 'out' => 0.60],
'gpt-4o' => ['in' => 2.50, 'out' => 10.00]
];
/**
* Global image model rates configuration
* Rates are per image
*/
$IGNY8_IMAGE_MODEL_RATES = [
'dall-e-3' => 0.040,
'dall-e-2' => 0.020,
'gpt-image-1' => 0.042,
'gpt-image-1-mini' => 0.011
];
/**
* Get model rates for a specific model
*
* @param string $model Model name
* @return array Model rates array with 'in' and 'out' keys
*/
function igny8_get_model_rates($model) {
global $IGNY8_MODEL_RATES;
return $IGNY8_MODEL_RATES[$model] ?? $IGNY8_MODEL_RATES['gpt-4.1'];
}
/**
* Calculate API cost based on model and token usage
*
* @param string $model Model name
* @param int $input_tokens Number of input tokens
* @param int $output_tokens Number of output tokens
* @return array Cost breakdown with 'input_cost', 'output_cost', 'total_cost'
*/
function igny8_calculate_api_cost($model, $input_tokens, $output_tokens) {
$rates = igny8_get_model_rates($model);
// Debug logging
error_log("Igny8 Cost Calc Debug: Model=$model, Rates=" . json_encode($rates));
error_log("Igny8 Cost Calc Debug: Input tokens=$input_tokens, Output tokens=$output_tokens");
$input_cost = ($input_tokens / 1000000) * $rates['in'];
$output_cost = ($output_tokens / 1000000) * $rates['out'];
$total_cost = $input_cost + $output_cost;
error_log("Igny8 Cost Calc Debug: Input cost=$input_cost, Output cost=$output_cost, Total cost=$total_cost");
return [
'input_cost' => $input_cost,
'output_cost' => $output_cost,
'total_cost' => $total_cost,
'model' => $model,
'input_tokens' => $input_tokens,
'output_tokens' => $output_tokens
];
}
/**
* Format cost for display
*
* @param float $cost Cost amount
* @param int $decimals Number of decimal places
* @return string Formatted cost string
*/
function igny8_format_cost($cost, $decimals = 4) {
// Convert to cents for better readability
$cents = $cost * 100;
return number_format($cents, 2) . '¢';
}
/**
* Get image model rates for a specific model
*
* @param string $model Image model name
* @return float Image model rate per image
*/
function igny8_get_image_model_rates($model) {
global $IGNY8_IMAGE_MODEL_RATES;
return $IGNY8_IMAGE_MODEL_RATES[$model] ?? $IGNY8_IMAGE_MODEL_RATES['dall-e-3'];
}
/**
* Calculate image generation cost based on model
*
* @param string $model Image model name
* @param int $image_count Number of images generated
* @return array Cost breakdown with 'per_image_cost', 'total_cost'
*/
function igny8_calculate_image_cost($model, $image_count = 1) {
$per_image_rate = igny8_get_image_model_rates($model);
$total_cost = $per_image_rate * $image_count;
return [
'per_image_cost' => $per_image_rate,
'total_cost' => $total_cost,
'model' => $model,
'image_count' => $image_count
];
}
/**
* Get image model display name with pricing and typical uses
*
* @param string $model Image model name
* @return string Formatted model name with pricing and uses
*/
function igny8_get_image_model_display_name($model) {
$model_info = [
'dall-e-3' => [
'name' => 'DALL·E 3',
'uses' => 'High-quality image generation with advanced AI capabilities'
],
'dall-e-2' => [
'name' => 'DALL·E 2',
'uses' => 'Cost-effective image generation with good quality'
],
'gpt-image-1' => [
'name' => 'GPT Image 1 (Full)',
'uses' => 'Full-featured image generation with comprehensive capabilities'
],
'gpt-image-1-mini' => [
'name' => 'GPT Image 1 Mini',
'uses' => 'Lightweight, cost-effective image generation for bulk operations'
]
];
$rate = igny8_get_image_model_rates($model);
$info = $model_info[$model] ?? ['name' => strtoupper($model), 'uses' => 'Image generation'];
return sprintf(
'%s — $%.3f per image (%s)',
$info['name'], $rate, $info['uses']
);
}
/**
* Get model display name with pricing and typical uses
*
* @param string $model Model name
* @return string Formatted model name with pricing and uses
*/
function igny8_get_model_display_name($model) {
$model_info = [
'gpt-4.1' => [
'name' => 'GPT-4.1',
'uses' => 'Content creation, coding, analysis, high-quality content generation'
],
'gpt-4o-mini' => [
'name' => 'GPT-4o mini',
'uses' => 'Bulk tasks, lightweight AI, cost-effective for high-volume operations'
],
'gpt-4o' => [
'name' => 'GPT-4o',
'uses' => 'Advanced AI for general and multimodal tasks, faster than GPT-4.1'
]
];
$rates = igny8_get_model_rates($model);
$info = $model_info[$model] ?? ['name' => strtoupper($model), 'uses' => 'General purpose'];
return sprintf(
'%s — $%.2f / $%.2f per 1M tokens (%s)',
$info['name'], $rates['in'], $rates['out'], $info['uses']
);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,310 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : prompts-library.php
* @location : /ai/prompts-library.php
* @type : AI Integration
* @scope : Global
* @allowed : AI prompts, prompt management, AI templates
* @reusability : Globally Reusable
* @notes : Central AI prompts library for all modules
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Default clustering prompt template
*/
function igny8_get_default_clustering_prompt() {
return "Analyze the following keywords and group them into topic clusters.
Each cluster should include:
- \"name\": A clear, descriptive topic name
- \"description\": A brief explanation of what the cluster covers
- \"keywords\": A list of related keywords that belong to this cluster
Format the output as a JSON object with a \"clusters\" array.
Clustering rules:
- Group keywords based on strong semantic or topical relationships (intent, use-case, function, audience, etc.)
- Clusters should reflect how people actually search — problem ➝ solution, general ➝ specific, product ➝ benefit, etc.
- Avoid grouping keywords just because they share similar words — focus on meaning
- Include 310 keywords per cluster where appropriate
- Skip unrelated or outlier keywords that don't fit a clear theme
Keywords to process:
[IGNY8_KEYWORDS]";
}
/**
* Default ideas prompt template
*/
function igny8_get_default_ideas_prompt() {
return "Generate SEO-optimized, high-quality content ideas and detailed outlines for each of the following keyword clusters. Each idea must be valuable for SEO, have clear editorial flow, and include a structured long-form content outline that matches the standards of our content generation system.
==========================
CONTENT IDEA INPUT FORMAT
==========================
Clusters to analyze:
[IGNY8_CLUSTERS]
Keywords in each cluster:
[IGNY8_CLUSTER_KEYWORDS]
======================
OUTPUT FORMAT REQUIRED
======================
Return your response as JSON with an \"ideas\" array.
For each cluster, you must generate exactly 1 cluster_hub page and 24 supporting blog/article ideas based on unique keyword dimensions.
Each idea must include:
- \"title\": compelling blog/article title that naturally includes a primary keyword
- \"description\": detailed and structured content outline using proper H2/H3 breakdowns (see outline rules below)
- \"content_type\": the type of content (post, page)
- \"content_structure\": the editorial structure (cluster_hub, guide_tutorial, how_to, comparison, review, top_listicle, question)
- \"cluster_id\": ID of the cluster this idea belongs to
- \"estimated_word_count\": estimated total word count (range: 15002200 words)
- \"covered_keywords\": comma-separated list of keywords from the cluster that will be covered naturally in the content
=========================
OUTLINE (DESCRIPTION) RULES
=========================
Each content idea's \"description\" must follow the expected editorial structure based on both the content_type and content_structure fields. It should be formatted as a professional long-form content outline, suitable for direct use by AI or human writers.
1. STRUCTURE:
- INTRODUCTION SECTION: Start with 1 hook (italic, 30-40 words) followed by 2 intro paragraphs (50-60 words each)
- Use exactly 58 H2 sections (depending on content type)
- Each H2 section should contain 23 H3 subsections
- SECTION WORD COUNT: Each H2 section should be 250-300 words total
- CONTENT MIX: Vary content types within sections:
- Some sections: 1 paragraph + list/table + 1 paragraph
- Some sections: 2 paragraphs + list/table (as final element)
- Mix unordered lists, ordered lists, and tables strategically
- Use diverse formatting styles: data/stat mentions, expert quotes, comparative breakdowns
2. FORMATTING TYPES:
- content_type: \"paragraph\", \"list\", \"table\", \"blockquote\", or \"mixed\"
- Do not open sections or subheadings with bullet points or generic phrasing
- Use bullet lists or tables only after sufficient paragraph setup
- Tables should have defined columns (e.g., Feature, Benefit, Product Name, Price, Link)
- Blockquotes should contain expert insight, best practices, or unique POV
- Ensure each section varies in tone and structure from others
3. QUALITY & DEPTH:
- Write for depth and uniqueness, not surface-level filler
- Use primary keyword in title, intro, and at least 2 H2 sections
- Use secondary keywords naturally throughout outline
- Suggest informative, useful angles that solve a real problem or offer unique value
- Avoid repeated structure across all ideas — each outline should feel hand-edited
==========================
TONE & FLOW REQUIREMENTS
==========================
- Maintain a professional, editorial tone — not robotic or templated
- No generic intros like \"In today's article…\"
- Begin sections with direct, engaging sentences — not summaries of the heading
- Use real-world examples, relevant context, or recent data wherever useful
- Include ideas that could incorporate user intent like tutorials, comparisons, or decision support
==========================
FINAL RETURN FORMAT (JSON)
==========================
{
\"ideas\": [
{
\"title\": \"Best Organic Cotton Duvet Covers for All Seasons\",
\"description\": {
\"introduction\": {
\"hook\": \"Transform your sleep with organic cotton that blends comfort and sustainability.\",
\"paragraphs\": [
{
\"content_type\": \"paragraph\",
\"details\": \"Overview of organic cotton's rise in bedding industry.\"
},
{
\"content_type\": \"paragraph\",
\"details\": \"Why consumers prefer organic bedding over synthetic alternatives.\"
}
]
},
\"H2\": [
{
\"heading\": \"Why Choose Organic Cotton for Bedding?\",
\"subsections\": [
{
\"subheading\": \"Health and Skin Benefits\",
\"content_type\": \"paragraph\",
\"details\": \"Discuss hypoallergenic and chemical-free aspects.\"
},
{
\"subheading\": \"Environmental Sustainability\",
\"content_type\": \"list\",
\"details\": \"Bullet list of eco benefits like low water use, no pesticides.\"
},
{
\"subheading\": \"Long-Term Cost Savings\",
\"content_type\": \"table\",
\"details\": \"Table comparing durability and pricing over time.\"
}
]
},
{
\"heading\": \"Top Organic Cotton Duvet Brands\",
\"subsections\": [
{
\"subheading\": \"Brand 1 Overview\",
\"content_type\": \"paragraph\",
\"details\": \"Description of features, pricing, and audience fit.\"
},
{
\"subheading\": \"Brand 2 Overview\",
\"content_type\": \"paragraph\",
\"details\": \"Highlight what makes this brand stand out.\"
},
{
\"subheading\": \"Quick Comparison Table\",
\"content_type\": \"table\",
\"details\": \"Side-by-side feature and price comparison.\"
}
]
}
]
},
\"content_type\": \"post\",
\"content_structure\": \"review\",
\"cluster_id\": 12,
\"estimated_word_count\": 1800,
\"covered_keywords\": \"organic duvet covers, eco-friendly bedding, sustainable sheets\"
}
]
}
==============================
NOTES FOR EXECUTION ENGINE
==============================
- Make sure all outlines follow this structure and are unique in flow and format.
- Do not reuse same outline patterns across ideas.
- Emphasize depth, paragraph-first formatting, and mixed content presentation.
- Ensure final output is usable by long-form AI content systems and human writers alike.";
}
/**
* Default content generation prompt template
*/
function igny8_content_generation_prompt() {
return "You are an editorial content strategist. Your task is to generate a complete JSON response object that includes all the fields listed below, based on the provided content idea, keyword cluster, and keyword list.
Only the `content` field should contain HTML. All other fields must be plain JSON values.
==================
CONTENT OBJECT STRUCTURE
==================
{
\"title\": \"[Auto-generate a compelling blog title using the primary keyword]\",
\"meta_title\": \"[SEO-optimized meta title under 60 characters]\",
\"meta_description\": \"[SEO-friendly summary under 160 characters]\",
\"content\": \"[HTML body — see structure rules below]\",
\"word_count\": [Exact word count of the HTML content],
\"primary_keyword\": \"[Provided primary keyword]\",
\"secondary_keywords\": [List of provided secondary keywords],
\"keywords_used\": [List of all keywords actually used in the HTML content],
\"tags\": [List of 5 lowercase tags (24 words each)],
\"categories\": [\"Parent > Child\", \"Optional 2nd category if needed\"],
[IMAGE_PROMPTS]
}
===========================
CONTENT FORMAT & STRUCTURE
===========================
- Use only valid WP-supported HTML blocks: <h2>, <h3>, <p>, <ul>/<ol>, and <table>
- Do not add extra line breaks, empty tags, or inconsistent spacing
- Use proper table structure when using tables:
<table>
<thead>
<tr><th>col heading1</th><th>col heading2</th></tr>
</thead>
<tbody>
<tr>
<td>cell1</td><td>cell2</td>
</tr>
</tbody>
</table>
===========================
CONTENT FLOW RULES
===========================
**INTRODUCTION:**
- Start with 1 italicized hook (3040 words)
- Follow with 2 narrative paragraphs (each 5060 words; 23 sentences max)
- No headings allowed in intro
**H2 SECTIONS (58 total):**
Each section should be 250300 words and follow this format:
1. Two narrative paragraphs (80120 words each, 23 sentences)
2. One list or table (must come *after* a paragraph)
3. Optional closing paragraph (4060 words)
4. Insert 23 <h3> subsections naturally after main paragraphs
**Formatting Rules:**
- Vary use of unordered lists, ordered lists, and tables across sections
- Never begin any section or sub-section with a list or table
===========================
KEYWORD & SEO RULES
===========================
- **Primary keyword** must appear in:
- The title
- First paragraph of the introduction
- At least 2 H2 headings
- **Secondary keywords** must be used naturally, not forced
- **Tone & style guidelines:**
- No robotic or passive voice
- Avoid generic intros like \"In today's world…\"
- Don't repeat heading in opening sentence
- Vary sentence structure and length
===========================
IMAGE PROMPT RULES
===========================
- Provide detailed, specific, and relevant image prompts
- Each prompt should reflect the topic/subtopic in that section
- Avoid vague or generic prompts
===========================
INPUT VARIABLES
===========================
CONTENT IDEA DETAILS:
[IGNY8_IDEA]
KEYWORD CLUSTER:
[IGNY8_CLUSTER]
ASSOCIATED KEYWORDS:
[IGNY8_KEYWORDS]
===========================
OUTPUT FORMAT
===========================
Return ONLY the final JSON object.
Do NOT include any comments, formatting, or explanations.";
}

View File

@@ -0,0 +1,191 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : runware-api.php
* @location : /ai/runware-api.php
* @type : AI Integration
* @scope : Global
* @allowed : Runware API calls, image generation, AI processing
* @reusability : Globally Reusable
* @notes : Runware image generation API integration
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Generate image using Runware API
*
* @param string $prompt The image generation prompt
* @param string $model The Runware model to use (default: gen3a_turbo)
* @return array|WP_Error Response data or error
*/
function igny8_runway_generate_image($prompt, $model = 'gen3a_turbo') {
$api_key = get_option('igny8_runware_api_key', '');
if (empty($api_key)) {
return new WP_Error('no_api_key', 'Runware API key not configured');
}
$url = 'https://api.runwayml.com/v1/image/generations';
$headers = [
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
];
$body = [
'model' => $model,
'prompt' => $prompt,
'size' => '1024x1024',
'quality' => 'standard',
'n' => 1
];
$args = [
'method' => 'POST',
'headers' => $headers,
'body' => json_encode($body),
'timeout' => 60,
];
$response = wp_remote_post($url, $args);
if (is_wp_error($response)) {
igny8_log_ai_event('runway_api_error', 'error', [
'message' => $response->get_error_message(),
'prompt' => $prompt,
'model' => $model
]);
return $response;
}
$response_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
$response_data = json_decode($response_body, true);
if ($response_code !== 200) {
$error_message = isset($response_data['error']['message']) ? $response_data['error']['message'] : 'Unknown error';
igny8_log_ai_event('runway_api_error', 'error', [
'code' => $response_code,
'message' => $error_message,
'prompt' => $prompt,
'model' => $model
]);
return new WP_Error('api_error', $error_message);
}
// Log successful API call
igny8_log_ai_event('runway_api_success', 'success', [
'model' => $model,
'prompt_length' => strlen($prompt),
'cost' => 0.036, // Runware pricing
'service' => 'runware'
]);
return $response_data;
}
/**
* Download and save image from Runware response
*
* @param array $response_data The API response data
* @param string $filename The desired filename
* @return string|WP_Error Saved file path or error
*/
function igny8_runway_save_image($response_data, $filename) {
if (!isset($response_data['data'][0]['url'])) {
return new WP_Error('no_image_url', 'No image URL in response');
}
$image_url = $response_data['data'][0]['url'];
// Create uploads directory
$upload_dir = wp_upload_dir();
$igny8_dir = $upload_dir['basedir'] . '/igny8-ai-images/';
if (!file_exists($igny8_dir)) {
wp_mkdir_p($igny8_dir);
}
// Download image
$image_response = wp_remote_get($image_url);
if (is_wp_error($image_response)) {
return $image_response;
}
$image_data = wp_remote_retrieve_body($image_response);
$file_path = $igny8_dir . $filename;
$saved = file_put_contents($file_path, $image_data);
if ($saved === false) {
return new WP_Error('save_failed', 'Failed to save image file');
}
return $file_path;
}
/**
* Test Runware API connection
*
* @return array Test result
*/
function igny8_test_runway_connection() {
$test_prompt = 'A simple test image: a red circle on white background';
$response = igny8_runway_generate_image($test_prompt);
if (is_wp_error($response)) {
return [
'success' => false,
'message' => $response->get_error_message(),
'details' => 'Runware API connection failed'
];
}
return [
'success' => true,
'message' => 'Runware API connection successful',
'details' => 'Test image generation completed successfully'
];
}
/**
* Get available Runware models
*
* @return array Available models
*/
function igny8_get_runway_models() {
return [
'gen3a_turbo' => [
'name' => 'Gen-3 Alpha Turbo',
'description' => 'Fast, high-quality image generation',
'cost' => 0.055
],
'gen3a' => [
'name' => 'Gen-3 Alpha',
'description' => 'Standard quality image generation',
'cost' => 0.055
]
];
}
/**
* Log AI event for Runway API
*
* @param string $event Event type
* @param string $status Success/error status
* @param array $context Additional context data
*/
function igny8_log_runway_event($event, $status, $context = []) {
igny8_log_ai_event($event, $status, array_merge($context, [
'service' => 'runware',
'timestamp' => current_time('mysql')
]));
}

View File

@@ -0,0 +1,534 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : image-generation.php
* @location : /ai/writer/images/image-generation.php
* @type : AI Integration
* @scope : Global
* @allowed : Image generation, AI processing, media creation
* @reusability : Globally Reusable
* @notes : Image generation functions for all modules
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Generate featured image for post from post meta prompt
*
* @param int $post_id WordPress post ID
* @param string $image_size_type Type of image size to use (featured, desktop, mobile)
* @return array Result with success status and attachment_id or error
*/
function igny8_generate_featured_image_for_post($post_id, $image_size_type = 'featured') {
// Get post
$post = get_post($post_id);
if (!$post) {
return ['success' => false, 'error' => 'Post not found'];
}
// Get featured image prompt from post meta
$featured_image_prompt = get_post_meta($post_id, '_igny8_featured_image_prompt', true);
if (empty($featured_image_prompt)) {
return ['success' => false, 'error' => 'No featured image prompt found in post meta'];
}
// Get image generation settings from prompts page
$image_type = get_option('igny8_image_type', 'realistic');
$image_service = get_option('igny8_image_service', 'openai');
$image_format = get_option('igny8_image_format', 'jpg');
$negative_prompt = get_option('igny8_negative_prompt', 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title');
// Get image model settings based on service
$image_model = get_option('igny8_image_model', 'dall-e-3');
$runware_model = get_option('igny8_runware_model', 'runware:97@1');
$prompt_template = wp_unslash(get_option('igny8_image_prompt_template', 'Create a high-quality {image_type} image to use as a featured photo for a blog post titled "{post_title}". The image should visually represent the theme, mood, and subject implied by the image prompt: {image_prompt}. Focus on a realistic, well-composed scene that naturally communicates the topic without text or logos. Use balanced lighting, pleasing composition, and photographic detail suitable for lifestyle or editorial web content. Avoid adding any visible or readable text, brand names, or illustrative effects. **And make sure image is not blurry.**'));
// Get dimensions based on image size type and service
$dimensions = igny8_get_image_dimensions($image_size_type, $image_service);
$image_width = $dimensions['width'];
$image_height = $dimensions['height'];
// Get API keys
$openai_key = get_option('igny8_api_key', '');
$runware_key = get_option('igny8_runware_api_key', '');
$required_key = ($image_service === 'runware') ? $runware_key : $openai_key;
if (empty($required_key)) {
return ['success' => false, 'error' => ($image_service === 'runware' ? 'Runware' : 'OpenAI') . ' API key not configured'];
}
// Build final prompt
$prompt = str_replace(
['{image_type}', '{post_title}', '{image_prompt}'],
[$image_type, $post->post_title, $featured_image_prompt],
$prompt_template
);
try {
// Event 7: API request sent
error_log('Igny8: IMAGE_GEN_EVENT_7 - API request sent to ' . $image_service . ' for post: ' . $post_id);
if ($image_service === 'runware') {
// Runware API Call
$payload = [
[
'taskType' => 'authentication',
'apiKey' => $runware_key
],
[
'taskType' => 'imageInference',
'taskUUID' => wp_generate_uuid4(),
'positivePrompt' => $prompt,
'negativePrompt' => $negative_prompt,
'model' => $runware_model,
'width' => $image_width,
'height' => $image_height,
'steps' => 30,
'CFGScale' => 7.5,
'numberResults' => 1,
'outputFormat' => $image_format
]
];
$response = wp_remote_post('https://api.runware.ai/v1', [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode($payload),
'timeout' => 150, // Increased to 150 seconds for image generation
'httpversion' => '1.1',
'sslverify' => true
]);
if (is_wp_error($response)) {
error_log('Igny8: IMAGE_GEN_ERROR - Runware API request failed: ' . $response->get_error_message());
return ['success' => false, 'error' => 'Runware API Error: ' . $response->get_error_message()];
}
$response_body = wp_remote_retrieve_body($response);
$response_data = json_decode($response_body, true);
error_log('Igny8: IMAGE_GEN - Runware API response: ' . substr($response_body, 0, 200));
if (isset($response_data['data'][0]['imageURL'])) {
$image_url = $response_data['data'][0]['imageURL'];
// Event 8: Image URL received
error_log('Igny8: IMAGE_GEN_EVENT_8 - Image URL received from ' . $image_service . ' for post: ' . $post_id);
// Generate filename
$filename = sanitize_file_name($post->post_title) . '_featured_' . time() . '.' . $image_format;
// Event 9: Image saved to WordPress
error_log('Igny8: IMAGE_GEN_EVENT_9 - Saving image to WordPress for post: ' . $post_id);
// Download image from Runware URL
require_once(ABSPATH . 'wp-admin/includes/media.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/image.php');
error_log('Igny8: IMAGE_GEN - Downloading image from URL: ' . $image_url);
$temp_file = download_url($image_url);
if (is_wp_error($temp_file)) {
error_log('Igny8: IMAGE_GEN_EVENT_9_ERROR - Failed to download image: ' . $temp_file->get_error_message());
return ['success' => false, 'error' => 'Failed to download image: ' . $temp_file->get_error_message()];
}
error_log('Igny8: IMAGE_GEN - Image downloaded to temp file: ' . $temp_file);
// Prepare file array for media_handle_sideload
$file_array = [
'name' => $filename,
'tmp_name' => $temp_file
];
// Upload to WordPress media library
error_log('Igny8: IMAGE_GEN - Uploading to media library with media_handle_sideload');
$attachment_id = media_handle_sideload($file_array, $post_id, $post->post_title . ' Featured Image');
// Clean up temp file
if (file_exists($temp_file)) {
@unlink($temp_file);
}
if (is_wp_error($attachment_id)) {
error_log('Igny8: IMAGE_GEN_EVENT_9_ERROR - media_handle_sideload failed: ' . $attachment_id->get_error_message());
return ['success' => false, 'error' => 'Failed to upload image: ' . $attachment_id->get_error_message()];
}
error_log('Igny8: IMAGE_GEN - Successfully created attachment ID: ' . $attachment_id);
if (!is_wp_error($attachment_id)) {
// Set as featured image
set_post_thumbnail($post_id, $attachment_id);
// Generate attachment metadata
$attachment_data = wp_generate_attachment_metadata($attachment_id, get_attached_file($attachment_id));
wp_update_attachment_metadata($attachment_id, $attachment_data);
// Get attachment URL
$attachment_url = wp_get_attachment_url($attachment_id);
error_log('Igny8: IMAGE_GEN_EVENT_9_SUCCESS - Image saved successfully, attachment ID: ' . $attachment_id);
return [
'success' => true,
'attachment_id' => $attachment_id,
'image_url' => $attachment_url,
'provider' => 'runware'
];
} else {
error_log('Igny8: IMAGE_GEN_EVENT_9_ERROR - Failed to save image: ' . $attachment_id->get_error_message());
return ['success' => false, 'error' => 'Failed to register image: ' . $attachment_id->get_error_message()];
}
} else {
error_log('Igny8: IMAGE_GEN_EVENT_8_ERROR - No image URL in response');
$error_msg = isset($response_data['errors'][0]['message']) ? $response_data['errors'][0]['message'] : 'Unknown Runware API error';
return ['success' => false, 'error' => $error_msg];
}
} else {
// OpenAI API Call with selected model
$data = [
'model' => $image_model,
'prompt' => $prompt,
'n' => 1,
'size' => $image_width . 'x' . $image_height
];
$args = [
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $openai_key,
],
'body' => json_encode($data),
'timeout' => 60,
];
$response = wp_remote_post('https://api.openai.com/v1/images/generations', $args);
if (is_wp_error($response)) {
return ['success' => false, 'error' => 'OpenAI API Error: ' . $response->get_error_message()];
}
$response_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
if ($response_code === 200) {
$body = json_decode($response_body, true);
if (isset($body['data'][0]['url'])) {
$image_url = $body['data'][0]['url'];
$revised_prompt = $body['data'][0]['revised_prompt'] ?? null;
// Generate filename (OpenAI always returns PNG)
$filename = sanitize_file_name($post->post_title) . '_featured_' . time() . '.png';
// Download and register in WordPress Media Library
$attachment_id = wp_insert_attachment([
'post_mime_type' => 'image/png',
'post_title' => $post->post_title . ' Featured Image',
'post_content' => '',
'post_status' => 'inherit'
], $image_url, $post_id);
if (!is_wp_error($attachment_id)) {
// Set as featured image
set_post_thumbnail($post_id, $attachment_id);
// Get attachment URL
$attachment_url = wp_get_attachment_url($attachment_id);
return [
'success' => true,
'attachment_id' => $attachment_id,
'image_url' => $attachment_url,
'provider' => 'openai',
'revised_prompt' => $revised_prompt
];
} else {
return ['success' => false, 'error' => 'Failed to register image: ' . $attachment_id->get_error_message()];
}
}
} else {
return ['success' => false, 'error' => 'HTTP ' . $response_code . ' error'];
}
}
} catch (Exception $e) {
return ['success' => false, 'error' => 'Exception: ' . $e->getMessage()];
}
return ['success' => false, 'error' => 'Unknown error occurred'];
}
/**
* Generate single article image for post from post meta prompts
*
* @param int $post_id WordPress post ID
* @param string $device_type Device type: 'desktop' or 'mobile'
* @param int $index Image index (1-based)
* @return array Result with success status and attachment_id or error
*/
function igny8_generate_single_article_image($post_id, $device_type = 'desktop', $index = 1) {
// Get post
$post = get_post($post_id);
if (!$post) {
return ['success' => false, 'error' => 'Post not found'];
}
// Get article image prompts from post meta
$article_images_data = get_post_meta($post_id, '_igny8_article_images_data', true);
// Debug: Log the raw data to see what's actually stored
error_log('IGNY8 DEBUG: Raw article_images_data: ' . substr($article_images_data, 0, 200) . '...');
$article_images = json_decode($article_images_data, true);
// Check for JSON decode errors
if (json_last_error() !== JSON_ERROR_NONE) {
error_log('IGNY8 DEBUG: JSON decode error: ' . json_last_error_msg());
error_log('IGNY8 DEBUG: Raw data causing error: ' . $article_images_data);
// Try to clean the data by stripping HTML tags
$cleaned_data = wp_strip_all_tags($article_images_data);
$article_images = json_decode($cleaned_data, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log('IGNY8 DEBUG: Still invalid JSON after cleaning: ' . json_last_error_msg());
return ['success' => false, 'error' => 'Invalid JSON in article images data: ' . json_last_error_msg()];
} else {
error_log('IGNY8 DEBUG: Successfully cleaned and parsed JSON');
}
}
if (empty($article_images) || !is_array($article_images)) {
return ['success' => false, 'error' => 'No article image prompts found in post meta'];
}
// Find the prompt for the requested index
$image_key = 'prompt-img-' . $index;
$prompt = '';
foreach ($article_images as $image_data) {
if (isset($image_data[$image_key])) {
$prompt = $image_data[$image_key];
break;
}
}
if (empty($prompt)) {
return ['success' => false, 'error' => 'No prompt found for image index ' . $index];
}
// Get image generation settings
$image_type = get_option('igny8_image_type', 'realistic');
$image_service = get_option('igny8_image_service', 'openai');
$image_format = get_option('igny8_image_format', 'jpg');
$negative_prompt = get_option('igny8_negative_prompt', 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title');
// Get image model settings based on service
$image_model = get_option('igny8_image_model', 'dall-e-3');
$runware_model = get_option('igny8_runware_model', 'runware:97@1');
// Get dimensions based on device type and service
$dimensions = igny8_get_image_dimensions($device_type, $image_service);
$image_width = $dimensions['width'];
$image_height = $dimensions['height'];
// Get API keys
$openai_key = get_option('igny8_api_key', '');
$runware_key = get_option('igny8_runware_api_key', '');
$required_key = ($image_service === 'runware') ? $runware_key : $openai_key;
if (empty($required_key)) {
return ['success' => false, 'error' => ($image_service === 'runware' ? 'Runware' : 'OpenAI') . ' API key not configured'];
}
// Enhance prompt if needed
$full_prompt = $prompt;
if (strlen($prompt) < 50 || strpos($prompt, 'Create') !== 0) {
$section = "Section " . $index;
$full_prompt = "Create a high-quality {$image_type} image for the section titled '{$section}'. {$prompt}";
}
try {
error_log('Igny8: ARTICLE_IMAGE_GEN - Generating ' . $device_type . ' image for post: ' . $post_id . ', index: ' . $index);
if ($image_service === 'runware') {
// Runware API Call
$payload = [
[
'taskType' => 'authentication',
'apiKey' => $runware_key
],
[
'taskType' => 'imageInference',
'taskUUID' => wp_generate_uuid4(),
'positivePrompt' => $full_prompt,
'negativePrompt' => $negative_prompt,
'model' => $runware_model,
'width' => $image_width,
'height' => $image_height,
'steps' => 30,
'CFGScale' => 7.5,
'numberResults' => 1,
'outputFormat' => $image_format
]
];
$response = wp_remote_post('https://api.runware.ai/v1', [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode($payload),
'timeout' => 150,
'httpversion' => '1.1',
'sslverify' => true
]);
if (is_wp_error($response)) {
error_log('Igny8: ARTICLE_IMAGE_GEN_ERROR - Runware API request failed: ' . $response->get_error_message());
return ['success' => false, 'error' => 'Runware API Error: ' . $response->get_error_message()];
}
$response_body = wp_remote_retrieve_body($response);
$response_data = json_decode($response_body, true);
if (isset($response_data['data'][0]['imageURL'])) {
$image_url = $response_data['data'][0]['imageURL'];
// Generate filename
$filename = sanitize_file_name($post->post_title) . '_' . $device_type . '_' . $index . '_' . time() . '.' . $image_format;
// Download image from Runware URL
require_once(ABSPATH . 'wp-admin/includes/media.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/image.php');
$temp_file = download_url($image_url);
if (is_wp_error($temp_file)) {
return ['success' => false, 'error' => 'Failed to download image: ' . $temp_file->get_error_message()];
}
// Prepare file array for media_handle_sideload
$file_array = [
'name' => $filename,
'tmp_name' => $temp_file
];
// Upload to WordPress media library
$attachment_id = media_handle_sideload($file_array, $post_id, $post->post_title . ' ' . ucfirst($device_type) . ' Image ' . $index);
// Clean up temp file
if (file_exists($temp_file)) {
@unlink($temp_file);
}
if (is_wp_error($attachment_id)) {
return ['success' => false, 'error' => 'Failed to upload image: ' . $attachment_id->get_error_message()];
}
// Generate attachment metadata
$attachment_data = wp_generate_attachment_metadata($attachment_id, get_attached_file($attachment_id));
wp_update_attachment_metadata($attachment_id, $attachment_data);
// Add custom metadata
update_post_meta($attachment_id, '_igny8_image_type', $device_type);
update_post_meta($attachment_id, '_igny8_provider', 'runware');
update_post_meta($attachment_id, '_igny8_section', 'Section ' . $index);
// Get attachment URL
$attachment_url = wp_get_attachment_url($attachment_id);
error_log('Igny8: ARTICLE_IMAGE_GEN_SUCCESS - ' . $device_type . ' image generated, attachment ID: ' . $attachment_id);
return [
'success' => true,
'attachment_id' => $attachment_id,
'image_url' => $attachment_url,
'provider' => 'runware',
'device_type' => $device_type,
'index' => $index
];
} else {
$error_msg = isset($response_data['errors'][0]['message']) ? $response_data['errors'][0]['message'] : 'Unknown Runware API error';
return ['success' => false, 'error' => $error_msg];
}
} else {
// OpenAI API Call with selected model
$data = [
'model' => $image_model,
'prompt' => $full_prompt,
'n' => 1,
'size' => '1024x1024' // OpenAI only supports square
];
$args = [
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $openai_key,
],
'body' => json_encode($data),
'timeout' => 60,
];
$response = wp_remote_post('https://api.openai.com/v1/images/generations', $args);
if (is_wp_error($response)) {
return ['success' => false, 'error' => 'OpenAI API Error: ' . $response->get_error_message()];
}
$response_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
if ($response_code === 200) {
$body = json_decode($response_body, true);
if (isset($body['data'][0]['url'])) {
$image_url = $body['data'][0]['url'];
$revised_prompt = $body['data'][0]['revised_prompt'] ?? null;
// Generate filename (OpenAI always returns PNG)
$filename = sanitize_file_name($post->post_title) . '_' . $device_type . '_' . $index . '_' . time() . '.png';
// Download and register in WordPress Media Library
$attachment_id = wp_insert_attachment([
'post_mime_type' => 'image/png',
'post_title' => $post->post_title . ' ' . ucfirst($device_type) . ' Image ' . $index,
'post_content' => '',
'post_status' => 'inherit'
], $image_url, $post_id);
if (!is_wp_error($attachment_id)) {
// Add custom metadata
update_post_meta($attachment_id, '_igny8_image_type', $device_type);
update_post_meta($attachment_id, '_igny8_provider', 'openai');
update_post_meta($attachment_id, '_igny8_section', 'Section ' . $index);
// Get attachment URL
$attachment_url = wp_get_attachment_url($attachment_id);
return [
'success' => true,
'attachment_id' => $attachment_id,
'image_url' => $attachment_url,
'provider' => 'openai',
'device_type' => $device_type,
'index' => $index,
'revised_prompt' => $revised_prompt
];
} else {
return ['success' => false, 'error' => 'Failed to register image: ' . $attachment_id->get_error_message()];
}
}
} else {
return ['success' => false, 'error' => 'HTTP ' . $response_code . ' error'];
}
}
} catch (Exception $e) {
return ['success' => false, 'error' => 'Exception: ' . $e->getMessage()];
}
return ['success' => false, 'error' => 'Unknown error occurred'];
}