Changeset 3460288
- Timestamp:
- 02/12/2026 07:46:25 PM (6 weeks ago)
- Location:
- clipcloud-image-generation
- Files:
-
- 33 added
- 2 edited
-
tags/1.1.2 (added)
-
tags/1.1.2/assets (added)
-
tags/1.1.2/assets/css (added)
-
tags/1.1.2/assets/css/admin.css (added)
-
tags/1.1.2/assets/css/frontend.css (added)
-
tags/1.1.2/assets/css/list.css (added)
-
tags/1.1.2/assets/js (added)
-
tags/1.1.2/assets/js/admin.js (added)
-
tags/1.1.2/assets/js/notice.js (added)
-
tags/1.1.2/assets/js/status.js (added)
-
tags/1.1.2/clipcloud-image-generation.php (added)
-
tags/1.1.2/languages (added)
-
tags/1.1.2/languages/clipcloud-image-generation-da_DK.po (added)
-
tags/1.1.2/languages/clipcloud-image-generation-de_DE.po (added)
-
tags/1.1.2/languages/clipcloud-image-generation-es_ES.po (added)
-
tags/1.1.2/languages/clipcloud-image-generation-fr_FR.po (added)
-
tags/1.1.2/languages/clipcloud-image-generation-it_IT.po (added)
-
tags/1.1.2/languages/clipcloud-image-generation-ja.po (added)
-
tags/1.1.2/languages/clipcloud-image-generation-nl_NL.po (added)
-
tags/1.1.2/languages/clipcloud-image-generation-pl_PL.po (added)
-
tags/1.1.2/languages/clipcloud-image-generation-pt_BR.po (added)
-
tags/1.1.2/languages/clipcloud-image-generation-ru_RU.mo (added)
-
tags/1.1.2/languages/clipcloud-image-generation-ru_RU.po (added)
-
tags/1.1.2/languages/readme-da_DK.po (added)
-
tags/1.1.2/languages/readme-de_DE.po (added)
-
tags/1.1.2/languages/readme-es_ES.po (added)
-
tags/1.1.2/languages/readme-fr_FR.po (added)
-
tags/1.1.2/languages/readme-it_IT.po (added)
-
tags/1.1.2/languages/readme-ja.po (added)
-
tags/1.1.2/languages/readme-nl_NL.po (added)
-
tags/1.1.2/languages/readme-pl_PL.po (added)
-
tags/1.1.2/languages/readme-pt_BR.po (added)
-
tags/1.1.2/readme.txt (added)
-
trunk/clipcloud-image-generation.php (modified) (27 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
clipcloud-image-generation/trunk/clipcloud-image-generation.php
r3455057 r3460288 3 3 * Plugin Name: ClipCloud - Image Generation 4 4 * Description: Generates images using AI for your posts. 5 * Version: 1.1. 15 * Version: 1.1.2 6 6 * Author: ClipAI 7 7 * Text Domain: clipcloud-image-generation … … 23 23 // API base/paths. 24 24 const API_BASE = 'https://clipcloud.clipai.pro'; 25 const API_BASE_BACKUP = 'https://clipcloud-rumirror.clipai.pro'; 25 26 const EP_CREATION = '/api/creation'; // POST. 26 27 const EP_CREATION_BY_ID = '/api/creation/{creation_id}'; // GET. … … 116 117 wp_clear_scheduled_hook( self::CRON_HOOK_START ); 117 118 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 ); 118 125 } 119 126 … … 182 189 'cleanup_generated'=> false, 183 190 'speed_up' => true, 191 'api_base' => self::API_BASE, 192 'api_server_state' => '', 193 'api_server_checked_at' => 0, 184 194 ]; 185 195 } … … 191 201 private function save_opts( $opts ) { 192 202 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.'; 193 271 } 194 272 … … 283 361 } 284 362 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 ] 294 372 ); 295 373 … … 400 478 401 479 $out['post_types'] = $post_types_clean; 480 $out = $this->refresh_api_route_on_settings_save( $out ); 402 481 403 482 // Reset validation/cache by default to avoid stale "valid" flags. … … 421 500 $out['styles_labels'] = isset( $prev_opts['styles_labels'] ) && is_array( $prev_opts['styles_labels'] ) ? $prev_opts['styles_labels'] : []; 422 501 $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 ); 432 514 $code = is_wp_error( $res ) ? 0 : (int) wp_remote_retrieve_response_code( $res ); 433 515 … … 646 728 647 729 public function render_settings_page() { 730 $status_msg = $this->get_api_server_status_message(); 648 731 echo '<div class="wrap"><h1>' . esc_html__( 'ClipCloud - AI Image Generation', 'clipcloud-image-generation' ) . '</h1>'; 649 732 echo '<form method="post" action="options.php">'; 650 733 settings_fields( 'clipcloud_fi' ); 651 734 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>'; 652 736 submit_button(); 653 737 echo '</form></div>'; … … 1208 1292 } 1209 1293 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, '/' ); 1212 1300 } 1213 1301 … … 1315 1403 [ 1316 1404 'method' => 'GET', 1317 'timeout' => 30,1405 'timeout' => 15, 1318 1406 'redirection' => 2, 1319 1407 'limit_response_size' => 300000, … … 1425 1513 ), 1426 1514 'model' => self::ccfi_unpack( 1427 ' HkBWBqNN1d3otyY3WDG4oY9H',1515 'Cx1SAe1Dltes7Tw=', 1428 1516 '636c6970636c6f75643a696d6167653a7832' 1429 1517 ), … … 1583 1671 $heading = function_exists( 'mb_substr' ) ? mb_substr( (string) $heading_title, 0, 64 ) : substr( (string) $heading_title, 0, 64 ); 1584 1672 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 ); 1602 1684 } 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.'; 1624 1707 1625 1708 $r = $this->ccfi_llm_cfg(); 1626 1709 if ( '' === $r['url'] || '' === $r['model'] || '' === $r['key'] ) { 1627 return [ null, null ];1710 return [ null, null, null ]; 1628 1711 } 1629 1712 … … 1633 1716 [ 1634 1717 'role' => 'system', 1635 'content' => 'You are an assistant that creates concise prompts for image generation. Reply only with valid JSON.',1718 'content' => $system_text, 1636 1719 ], 1637 1720 [ … … 1650 1733 $encoded = wp_json_encode( $payload, JSON_UNESCAPED_UNICODE ); 1651 1734 if ( false === $encoded ) { 1652 return [ null, null ];1735 return [ null, null, null ]; 1653 1736 } 1654 1737 … … 1662 1745 ], 1663 1746 'body' => $encoded, 1664 'timeout' => 50,1747 'timeout' => 45, 1665 1748 'redirection' => 2, 1666 1749 'limit_response_size' => 180000, … … 1668 1751 ); 1669 1752 if ( is_wp_error( $res ) ) { 1670 return [ null, null ];1753 return [ null, null, null ]; 1671 1754 } 1672 1755 1673 1756 $code = wp_remote_retrieve_response_code( $res ); 1674 1757 if ( $code < 200 || $code >= 300 ) { 1675 return [ null, null ];1758 return [ null, null, null ]; 1676 1759 } 1677 1760 1678 1761 $body = json_decode( wp_remote_retrieve_body( $res ), true ); 1679 1762 if ( ! is_array( $body ) ) { 1680 return [ null, null ];1763 return [ null, null, null ]; 1681 1764 } 1682 1765 … … 1684 1767 $decoded = self::ccfi_parse_json_object( (string) $content ); 1685 1768 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; 1690 1781 $style = isset( $decoded['style'] ) && is_string( $decoded['style'] ) ? trim( $decoded['style'] ) : null; 1691 1782 … … 1700 1791 } 1701 1792 1702 return [ $prompt, $style ];1793 return [ $prompt, $style, $negative_prompt ]; 1703 1794 } 1704 1795 … … 1717 1808 } 1718 1809 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( 1723 1814 $title, 1724 1815 $is_auto_style, … … 1730 1821 $final_prompt = $p_prompt; 1731 1822 $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 ]; 1733 1825 } 1734 1826 … … 1740 1832 $final_style = $is_auto_style ? 'HD-HQ' : $sel; 1741 1833 1742 return [ $final_prompt, $final_style ];1834 return [ $final_prompt, $final_style, '' ]; 1743 1835 } 1744 1836 … … 2425 2517 $start_attempts = isset( $creation_attempts[ $slot ] ) ? (int) $creation_attempts[ $slot ] : 0; 2426 2518 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 ); 2428 2520 2429 2521 $payload = [ … … 2432 2524 'soft_id' => (int) self::SOFT_ID, 2433 2525 ]; 2526 if ( is_string( $final_negative_prompt ) && '' !== trim( $final_negative_prompt ) ) { 2527 $payload['negative_prompt'] = trim( $final_negative_prompt ); 2528 } 2434 2529 2435 2530 $res = wp_remote_request( … … 2460 2555 $body = json_decode( wp_remote_retrieve_body( $res ), true ); 2461 2556 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 ) { 2465 2560 $jobs[ $slot ] = $job_id_raw; 2466 2561 $states[ $slot ] = 'generating'; … … 2472 2567 } 2473 2568 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 } 2474 2605 } 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++; 2479 2610 if ( $start_attempts >= self::MAX_CREATION_ATTEMPTS ) { 2480 2611 $this->handle_slot_failure( $slot, $states, $jobs, $attempts_dummy ); … … 4139 4270 } 4140 4271 4272 register_activation_hook( __FILE__, [ 'ClipCloud_Featured_Image_Plugin', 'on_activation' ] ); 4141 4273 register_deactivation_hook( __FILE__, [ 'ClipCloud_Featured_Image_Plugin', 'on_deactivation' ] ); 4142 4274 register_uninstall_hook( __FILE__, [ 'ClipCloud_Featured_Image_Plugin', 'on_uninstall' ] ); -
clipcloud-image-generation/trunk/readme.txt
r3455057 r3460288 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.0 7 Stable tag: 1.1. 17 Stable tag: 1.1.2 8 8 License: GPL-2.0-or-later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 90 90 91 91 == Changelog == 92 = 1.1.2 = 93 * Another improvement to automatic prompt enhancement. 94 * Added backup server support. 95 92 96 = 1.1.1 = 93 97 * Improved automatic prompt enhancement.
Note: See TracChangeset
for help on using the changeset viewer.