Plugin Directory

Changeset 3460288


Ignore:
Timestamp:
02/12/2026 07:46:25 PM (6 weeks ago)
Author:
clipai
Message:

Version 1.1.2 Release

Location:
clipcloud-image-generation
Files:
33 added
2 edited

Legend:

Unmodified
Added
Removed
  • clipcloud-image-generation/trunk/clipcloud-image-generation.php

    r3455057 r3460288  
    33 * Plugin Name: ClipCloud - Image Generation
    44 * Description: Generates images using AI for your posts.
    5  * Version:     1.1.1
     5 * Version:     1.1.2
    66 * Author:      ClipAI
    77 * Text Domain: clipcloud-image-generation
     
    2323    // API base/paths.
    2424    const API_BASE          = 'https://clipcloud.clipai.pro';
     25    const API_BASE_BACKUP   = 'https://clipcloud-rumirror.clipai.pro';
    2526    const EP_CREATION       = '/api/creation';               // POST.
    2627    const EP_CREATION_BY_ID = '/api/creation/{creation_id}'; // GET.
     
    116117        wp_clear_scheduled_hook( self::CRON_HOOK_START );
    117118        wp_clear_scheduled_hook( self::CRON_HOOK_WATCHDOG );
     119    }
     120
     121    public static function on_activation() {
     122        $opts = wp_parse_args( get_option( self::OPT_KEY, [] ), self::defaults() );
     123        $opts = self::refresh_api_route( $opts );
     124        update_option( self::OPT_KEY, $opts );
    118125    }
    119126
     
    182189            'cleanup_generated'=> false,
    183190            'speed_up'         => true,
     191            'api_base'         => self::API_BASE,
     192            'api_server_state' => '',
     193            'api_server_checked_at' => 0,
    184194        ];
    185195    }
     
    191201    private function save_opts( $opts ) {
    192202        update_option( self::OPT_KEY, $opts );
     203    }
     204
     205    private function get_effective_api_base( $opts = null ) {
     206        $o = is_array( $opts ) ? $opts : $this->get_opts();
     207        $base = isset( $o['api_base'] ) ? trim( (string) $o['api_base'] ) : '';
     208        if ( in_array( $base, [ self::API_BASE, self::API_BASE_BACKUP ], true ) ) {
     209            return $base;
     210        }
     211        return self::API_BASE;
     212    }
     213
     214    private static function probe_api_base_static( $base_url, $timeout_sec ) {
     215        $res = wp_remote_request(
     216            untrailingslashit( (string) $base_url ),
     217            [
     218                'method'              => 'GET',
     219                'timeout'             => (int) $timeout_sec,
     220                'redirection'         => 2,
     221                'limit_response_size' => 2048,
     222            ]
     223        );
     224
     225        if ( is_wp_error( $res ) ) {
     226            return false;
     227        }
     228
     229        $code = (int) wp_remote_retrieve_response_code( $res );
     230        return in_array( $code, [ 200, 404 ], true );
     231    }
     232
     233    private static function refresh_api_route( $opts ) {
     234        $out = is_array( $opts ) ? $opts : [];
     235        $out['api_server_checked_at'] = time();
     236
     237        if ( self::probe_api_base_static( self::API_BASE, 15 ) ) {
     238            $out['api_base']         = self::API_BASE;
     239            $out['api_server_state'] = 'primary';
     240            return $out;
     241        }
     242
     243        if ( self::probe_api_base_static( self::API_BASE_BACKUP, 12 ) ) {
     244            $out['api_base']         = self::API_BASE_BACKUP;
     245            $out['api_server_state'] = 'backup';
     246            return $out;
     247        }
     248
     249        $out['api_base']         = self::API_BASE;
     250        $out['api_server_state'] = 'down';
     251        return $out;
     252    }
     253
     254    private function refresh_api_route_on_settings_save( $opts ) {
     255        return self::refresh_api_route( $opts );
     256    }
     257
     258    private function get_api_server_status_message( $opts = null ) {
     259        $o = is_array( $opts ) ? $opts : $this->get_opts();
     260        $state = isset( $o['api_server_state'] ) ? sanitize_key( (string) $o['api_server_state'] ) : '';
     261
     262        if ( 'backup' === $state ) {
     263            return '🟡 Backup ClipCloud server is in use.';
     264        }
     265        if ( 'down' === $state ) {
     266            return '⚠️ 🔴 ClipCloud servers are unavailable. Please contact your hosting provider and ask: "Why is there no access to clipcloud.clipai.pro?"';
     267        }
     268
     269        // Also used before the first check on a fresh install.
     270        return '🟢 Primary ClipCloud server is in use.';
    193271    }
    194272
     
    283361        }
    284362
    285         $res = wp_remote_post(
    286             'https://clipcloud-wp.clipai.pro/wp-cron.php',
    287             [
    288                 'timeout' => 7,
    289                 'body'    => [
    290                     'task' => $task,
    291                     'site' => $site,
    292                 ],
    293             ]
     363            $res = wp_remote_post(
     364                'https://clipcloud-wp.clipai.pro/wp-cron.php',
     365                [
     366                    'timeout' => 7,
     367                    'body'    => [
     368                        'task' => $task,
     369                        'site' => $site,
     370                    ],
     371                ]
    294372        );
    295373
     
    400478
    401479                    $out['post_types'] = $post_types_clean;
     480                    $out               = $this->refresh_api_route_on_settings_save( $out );
    402481
    403482                    // Reset validation/cache by default to avoid stale "valid" flags.
     
    421500                        $out['styles_labels']    = isset( $prev_opts['styles_labels'] ) && is_array( $prev_opts['styles_labels'] ) ? $prev_opts['styles_labels'] : [];
    422501                        $out['styles_labels_ts'] = isset( $prev_opts['styles_labels_ts'] ) ? (int) $prev_opts['styles_labels_ts'] : 0;
    423                     } else {
    424                         $res  = wp_remote_request(
    425                             $this->build_url( self::EP_STYLES ),
    426                             [
    427                                 'method'  => 'GET',
    428                                 'headers' => $this->http_headers( $out['api_key'] ),
    429                                 'timeout' => 7,
    430                             ]
    431                         );
     502                        } else {
     503                            $res  = wp_remote_request(
     504                                $this->build_url(
     505                                    self::EP_STYLES,
     506                                    $this->get_effective_api_base( $out )
     507                                ),
     508                                [
     509                                    'method'  => 'GET',
     510                                    'headers' => $this->http_headers( $out['api_key'] ),
     511                                    'timeout' => 10,
     512                                ]
     513                            );
    432514                        $code = is_wp_error( $res ) ? 0 : (int) wp_remote_retrieve_response_code( $res );
    433515
     
    646728
    647729    public function render_settings_page() {
     730        $status_msg = $this->get_api_server_status_message();
    648731        echo '<div class="wrap"><h1>' . esc_html__( 'ClipCloud - AI Image Generation', 'clipcloud-image-generation' ) . '</h1>';
    649732        echo '<form method="post" action="options.php">';
    650733        settings_fields( 'clipcloud_fi' );
    651734        do_settings_sections( 'clipcloud_fi' );
     735        echo '<p style="margin:12px 0 8px 0;font-size:14px;line-height:1.4;">' . esc_html( $status_msg ) . '</p>';
    652736        submit_button();
    653737        echo '</form></div>';
     
    12081292    }
    12091293
    1210     private function build_url( $path ) {
    1211         return rtrim( self::API_BASE, '/' ) . '/' . ltrim( $path, '/' );
     1294    private function build_url( $path, $base_override = '' ) {
     1295        $base = is_string( $base_override ) && '' !== trim( $base_override )
     1296            ? trim( $base_override )
     1297            : $this->get_effective_api_base();
     1298
     1299        return rtrim( $base, '/' ) . '/' . ltrim( $path, '/' );
    12121300    }
    12131301
     
    13151403            [
    13161404                'method'              => 'GET',
    1317                 'timeout'             => 30,
     1405                'timeout'             => 15,
    13181406                'redirection'         => 2,
    13191407                'limit_response_size' => 300000,
     
    14251513            ),
    14261514            'model' => self::ccfi_unpack(
    1427                 'HkBWBqNN1d3otyY3WDG4oY9H',
     1515                'Cx1SAe1Dltes7Tw=',
    14281516                '636c6970636c6f75643a696d6167653a7832'
    14291517            ),
     
    15831671        $heading    = function_exists( 'mb_substr' ) ? mb_substr( (string) $heading_title, 0, 64 ) : substr( (string) $heading_title, 0, 64 );
    15841672
    1585         if ( $heading ) {
    1586             if ( $is_auto_style ) {
    1587                 $z = implode( ', ', self::autosuggest_styles() );
    1588                 $q = sprintf(
    1589                     'Generate image prompt for article named "%1$s" with heading named "%2$s". Return JSON with "prompt" and "style" parameters. You only need to specify what exactly should be shown in the picture. Do not specify what should not be there. For the "style" parameter, select a style for this image from: %3$s',
    1590                     $base_title,
    1591                     $heading,
    1592                     $z
    1593                 );
    1594             } else {
    1595                 $q = sprintf(
    1596                     'Generate image prompt for article named "%1$s" with heading named "%2$s". Return JSON with "prompt" parameter. You only need to specify what exactly should be shown in the picture. Do not specify what should not be there. User chosen style is %3$s.',
    1597                     $base_title,
    1598                     $heading,
    1599                     $chosen_style
    1600                 );
    1601             }
     1673        $styles_list = implode( ',', self::autosuggest_styles() );
     1674        $context     = $base_title;
     1675        if ( '' !== $heading ) {
     1676            $context .= ' — ' . $heading;
     1677        }
     1678
     1679        if ( $is_auto_style ) {
     1680            $style_instruction = sprintf(
     1681                'Select "style" only from: %s. Use specific styles only when they are truly needed for this article. Prefer HQ styles (especially FullHD-HQ variants) for people or complex multi-object scenes.',
     1682                $styles_list
     1683            );
    16021684        } else {
    1603             if ( $is_auto_style ) {
    1604                 $z = implode( ', ', self::autosuggest_styles() );
    1605                 $q = sprintf(
    1606                     'Generate image prompt for article named "%s". Return JSON with "prompt" and "style" parameters. You only need to specify what exactly should be shown in the picture. Do not specify what should not be there. For the "style" parameter, select a style for this image from: %s',
    1607                     $base_title,
    1608                     $z
    1609                 );
    1610             } else {
    1611                 $q = sprintf(
    1612                     'Generate image prompt for article named "%s". Return JSON with "prompt" parameter. You only need to specify what exactly should be shown in the picture. Do not specify what should not be there. User chosen style is %s.',
    1613                     $base_title,
    1614                     $chosen_style
    1615                 );
    1616             }
    1617         }
    1618 
    1619         if ( $is_auto_style ) {
    1620             $q .= ' Return only a strict JSON object with keys "prompt" (string) and "style" (string).';
    1621         } else {
    1622             $q .= ' Return only a strict JSON object with key "prompt" (string).';
    1623         }
     1685            $style_instruction = sprintf(
     1686                'The style is predefined by the user: "%s". Keep this exact style value in "style". Do not choose another style and do not provide style-selection advice.',
     1687                $chosen_style
     1688            );
     1689        }
     1690
     1691        $system_text = sprintf(
     1692            'You are an assistant who gives the result of task execution strictly in the specified form. ' .
     1693            'Your goal is to select a prompt and parameters for generating images using another AI. ' .
     1694            'Describe what and how should be depicted in the image. ' .
     1695            'Your entire output must be in English, regardless of input language. ' .
     1696            'Avoid requests that could add text, letters, or digits to the image. ' .
     1697            'Avoid prohibited content (for example NSFW). ' .
     1698            '%1$s ' .
     1699            'You were given the article or query title: "%2$s". ' .
     1700            'Return exactly one valid JSON object with this structure: ' .
     1701            '{"query":"describe what should be in the image","negative_prompt":"optional: what should not appear","style":"style value"}',
     1702            $style_instruction,
     1703            $context
     1704        );
     1705
     1706        $q = 'Create the JSON result now. Return only JSON, no extra text.';
    16241707
    16251708        $r = $this->ccfi_llm_cfg();
    16261709        if ( '' === $r['url'] || '' === $r['model'] || '' === $r['key'] ) {
    1627             return [ null, null ];
     1710            return [ null, null, null ];
    16281711        }
    16291712
     
    16331716                [
    16341717                    'role'    => 'system',
    1635                     'content' => 'You are an assistant that creates concise prompts for image generation. Reply only with valid JSON.',
     1718                    'content' => $system_text,
    16361719                ],
    16371720                [
     
    16501733        $encoded = wp_json_encode( $payload, JSON_UNESCAPED_UNICODE );
    16511734        if ( false === $encoded ) {
    1652             return [ null, null ];
     1735            return [ null, null, null ];
    16531736        }
    16541737
     
    16621745                ],
    16631746                'body'                => $encoded,
    1664                 'timeout'             => 50,
     1747                'timeout'             => 45,
    16651748                'redirection'         => 2,
    16661749                'limit_response_size' => 180000,
     
    16681751        );
    16691752        if ( is_wp_error( $res ) ) {
    1670             return [ null, null ];
     1753            return [ null, null, null ];
    16711754        }
    16721755
    16731756        $code = wp_remote_retrieve_response_code( $res );
    16741757        if ( $code < 200 || $code >= 300 ) {
    1675             return [ null, null ];
     1758            return [ null, null, null ];
    16761759        }
    16771760
    16781761        $body = json_decode( wp_remote_retrieve_body( $res ), true );
    16791762        if ( ! is_array( $body ) ) {
    1680             return [ null, null ];
     1763            return [ null, null, null ];
    16811764        }
    16821765
     
    16841767        $decoded = self::ccfi_parse_json_object( (string) $content );
    16851768        if ( ! is_array( $decoded ) ) {
    1686             return [ null, null ];
    1687         }
    1688 
    1689         $prompt = isset( $decoded['prompt'] ) && is_string( $decoded['prompt'] ) ? trim( $decoded['prompt'] ) : null;
     1769            return [ null, null, null ];
     1770        }
     1771
     1772        $prompt = null;
     1773        if ( isset( $decoded['query'] ) && is_string( $decoded['query'] ) ) {
     1774            $prompt = trim( $decoded['query'] );
     1775        } elseif ( isset( $decoded['prompt'] ) && is_string( $decoded['prompt'] ) ) {
     1776            // Backward-compatible fallback.
     1777            $prompt = trim( $decoded['prompt'] );
     1778        }
     1779
     1780        $negative_prompt = isset( $decoded['negative_prompt'] ) && is_string( $decoded['negative_prompt'] ) ? trim( $decoded['negative_prompt'] ) : null;
    16901781        $style  = isset( $decoded['style'] ) && is_string( $decoded['style'] ) ? trim( $decoded['style'] ) : null;
    16911782
     
    17001791        }
    17011792
    1702         return [ $prompt, $style ];
     1793        return [ $prompt, $style, $negative_prompt ];
    17031794    }
    17041795
     
    17171808            }
    17181809
    1719             return [ $final_prompt, $final_style ];
    1720         }
    1721 
    1722         list( $p_prompt, $p_style ) = $this->ccfi_enhance_prompt(
     1810            return [ $final_prompt, $final_style, '' ];
     1811        }
     1812
     1813        list( $p_prompt, $p_style, $p_negative_prompt ) = $this->ccfi_enhance_prompt(
    17231814            $title,
    17241815            $is_auto_style,
     
    17301821            $final_prompt = $p_prompt;
    17311822            $final_style  = $is_auto_style ? ( $p_style ?: 'HD-HQ' ) : $sel;
    1732             return [ $final_prompt, $final_style ];
     1823            $final_negative_prompt = $p_negative_prompt ? $p_negative_prompt : '';
     1824            return [ $final_prompt, $final_style, $final_negative_prompt ];
    17331825        }
    17341826
     
    17401832        $final_style = $is_auto_style ? 'HD-HQ' : $sel;
    17411833
    1742         return [ $final_prompt, $final_style ];
     1834        return [ $final_prompt, $final_style, '' ];
    17431835    }
    17441836
     
    24252517            $start_attempts = isset( $creation_attempts[ $slot ] ) ? (int) $creation_attempts[ $slot ] : 0;
    24262518
    2427             list( $final_prompt, $final_style ) = $this->prepare_prompt_and_style( $post_id, $o, $heading_text );
     2519            list( $final_prompt, $final_style, $final_negative_prompt ) = $this->prepare_prompt_and_style( $post_id, $o, $heading_text );
    24282520
    24292521            $payload = [
     
    24322524                'soft_id'   => (int) self::SOFT_ID,
    24332525            ];
     2526            if ( is_string( $final_negative_prompt ) && '' !== trim( $final_negative_prompt ) ) {
     2527                $payload['negative_prompt'] = trim( $final_negative_prompt );
     2528            }
    24342529
    24352530            $res = wp_remote_request(
     
    24602555            $body = json_decode( wp_remote_retrieve_body( $res ), true );
    24612556
    2462             if ( $code >= 200 && $code < 300 && is_array( $body ) ) {
    2463                 $job_id_raw = isset( $body[ self::FIELD_CREATION_ID ] ) ? (string) $body[ self::FIELD_CREATION_ID ] : '';
    2464                     if ( '' !== $job_id_raw ) {
     2557                if ( $code >= 200 && $code < 300 && is_array( $body ) ) {
     2558                    $job_id_raw = isset( $body[ self::FIELD_CREATION_ID ] ) ? (string) $body[ self::FIELD_CREATION_ID ] : '';
     2559                        if ( '' !== $job_id_raw ) {
    24652560                    $jobs[ $slot ]   = $job_id_raw;
    24662561                    $states[ $slot ] = 'generating';
     
    24722567                    }
    24732568                    continue;
     2569                        }
     2570                }
     2571
     2572                // Some API nodes may reject optional negative_prompt; retry once without it.
     2573                if ( isset( $payload['negative_prompt'] ) ) {
     2574                    $retry_payload = $payload;
     2575                    unset( $retry_payload['negative_prompt'] );
     2576
     2577                    $retry_res = wp_remote_request(
     2578                        $this->build_url( self::EP_CREATION ),
     2579                        [
     2580                            'method'  => 'POST',
     2581                            'headers' => $this->http_headers( $o['api_key'], true ),
     2582                            'timeout' => self::HTTP_TIMEOUT_SEC,
     2583                            'body'    => wp_json_encode( $retry_payload, JSON_UNESCAPED_UNICODE ),
     2584                        ]
     2585                    );
     2586
     2587                    if ( ! is_wp_error( $retry_res ) ) {
     2588                        $retry_code = wp_remote_retrieve_response_code( $retry_res );
     2589                        $retry_body = json_decode( wp_remote_retrieve_body( $retry_res ), true );
     2590
     2591                        if ( $retry_code >= 200 && $retry_code < 300 && is_array( $retry_body ) ) {
     2592                            $job_id_raw = isset( $retry_body[ self::FIELD_CREATION_ID ] ) ? (string) $retry_body[ self::FIELD_CREATION_ID ] : '';
     2593                            if ( '' !== $job_id_raw ) {
     2594                                $jobs[ $slot ]   = $job_id_raw;
     2595                                $states[ $slot ] = 'generating';
     2596                                $started         = true;
     2597                                $started_slots++;
     2598                                if ( isset( $creation_attempts[ $slot ] ) ) {
     2599                                    unset( $creation_attempts[ $slot ] );
     2600                                    $creation_attempts_changed = true;
     2601                                }
     2602                                continue;
     2603                            }
     2604                        }
    24742605                    }
    2475             }
    2476 
    2477             // If we reach here the slot failed.
    2478             $start_attempts++;
     2606                }
     2607
     2608                // If we reach here the slot failed.
     2609                $start_attempts++;
    24792610            if ( $start_attempts >= self::MAX_CREATION_ATTEMPTS ) {
    24802611                $this->handle_slot_failure( $slot, $states, $jobs, $attempts_dummy );
     
    41394270}
    41404271
     4272register_activation_hook( __FILE__, [ 'ClipCloud_Featured_Image_Plugin', 'on_activation' ] );
    41414273register_deactivation_hook( __FILE__, [ 'ClipCloud_Featured_Image_Plugin', 'on_deactivation' ] );
    41424274register_uninstall_hook( __FILE__, [ 'ClipCloud_Featured_Image_Plugin', 'on_uninstall' ] );
  • clipcloud-image-generation/trunk/readme.txt

    r3455057 r3460288  
    55Tested up to: 6.9
    66Requires PHP: 7.0
    7 Stable tag: 1.1.1
     7Stable tag: 1.1.2
    88License: GPL-2.0-or-later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    9090
    9191== Changelog ==
     92= 1.1.2 =
     93* Another improvement to automatic prompt enhancement.
     94* Added backup server support.
     95
    9296= 1.1.1 =
    9397* Improved automatic prompt enhancement.
Note: See TracChangeset for help on using the changeset viewer.