Changeset 3396741
- Timestamp:
- 11/16/2025 08:02:05 PM (4 months ago)
- Location:
- hippoo/trunk
- Files:
-
- 5 edited
-
app/ai.php (modified) (17 diffs)
-
app/web_api.php (modified) (1 diff)
-
assets/js/admin-script.js (modified) (3 diffs)
-
hippoo.php (modified) (2 diffs)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
hippoo/trunk/app/ai.php
r3396221 r3396741 7 7 8 8 const GPT_MODELS = ['gpt-5', 'gpt-4', 'gpt-4o', 'gpt-4o-mini']; 9 const GEMINI_MODELS = ['gemini-2.5-flash', 'gemini-2.5-pro']; 10 9 11 const DEFAULT_SYSTEM_PROMPT = 'Analyze product images and related descriptions to generate high-quality, engaging, and SEO-optimized product content. Focus on identifying key visual details such as type, color, material, and purpose to create accurate, natural, and persuasive descriptions suitable for e-commerce platforms.'; 10 12 const DEFAULT_DESCRIPTION_PROMPT = 'Review the provided product image and its details to understand the item’s appearance, features, and intended use. Then generate a compelling product title, short summary, detailed description, and a list of relevant SEO keywords based on your analysis.'; … … 18 20 add_filter('hippoo_settings_tab_contents', array($this, 'add_ai_tab_content')); 19 21 add_action('admin_init', array($this, 'settings_init')); 20 add_action('wp_ajax_hippoo_test_ai_connection', array($this, 'do_test_connection')); 22 add_action('wp_ajax_hippoo_test_ai_connection', array($this, 'do_test_connection')); 23 add_action('wp_ajax_hippoo_get_models_by_provider', array($this, 'get_models_by_provider')); 21 24 } 22 25 … … 174 177 } 175 178 176 177 179 public function section_ai_connect_render() 178 180 { … … 189 191 <select class="select" name="hippoo_ai_settings[ai_provider]"> 190 192 <option value="gpt" <?php selected($value, 'gpt'); ?>><?php esc_html_e('Chat GPT', 'hippoo'); ?></option> 193 <option value="gemini" <?php selected($value, 'gemini'); ?>><?php esc_html_e('Gemini', 'hippoo'); ?></option> 191 194 </select> 192 195 <?php … … 208 211 </select> 209 212 <?php 213 } elseif ($provider === 'gemini') { 214 ?> 215 <select class="select" name="hippoo_ai_settings[ai_model]"> 216 <?php foreach (self::GEMINI_MODELS as $model): ?> 217 <option value="<?php echo esc_attr($model); ?>" <?php selected($value, $model); ?>> 218 <?php echo esc_html($model); ?> 219 </option> 220 <?php endforeach; ?> 221 </select> 222 <?php 210 223 } else { 211 224 } … … 279 292 if ($provider === 'gpt') { 280 293 $result = $this->openai_test_connection($api_token); 294 } elseif ($provider === 'gemini') { 295 $result = $this->gemini_test_connection($api_token); 281 296 } else { 282 297 wp_send_json_error(__('Unsupported AI provider.', 'hippoo')); … … 287 302 } else { 288 303 wp_send_json_error(__('Failed to connect to AI service.', 'hippoo')); 304 } 305 } 306 307 public function get_models_by_provider() 308 { 309 check_ajax_referer('hippoo_nonce', 'nonce'); 310 311 $provider = sanitize_text_field($_POST['ai_provider']); 312 313 if ($provider === 'gpt') { 314 wp_send_json_success(self::GPT_MODELS); 315 } elseif ($provider === 'gemini') { 316 wp_send_json_success(self::GEMINI_MODELS); 317 } else { 318 wp_send_json_error(__('Invalid provider', 'hippoo')); 289 319 } 290 320 } … … 367 397 368 398 case 'gemini': 369 // $result = $this->gemini_generate_description(...);370 return new WP_Error('not_implemented', __('Gemini provider not implemented yet.', 'hippoo'), ['status' => 501]);399 $result = $this->gemini_generate_description($data); 400 break; 371 401 372 402 default: … … 405 435 406 436 case 'gemini': 407 // future: $result = $this->gemini_test_connection(...)408 return new WP_Error('not_implemented', __('Gemini provider not implemented yet.', 'hippoo'), ['status' => 501]);437 $result = $this->gemini_test_connection($api_token); 438 break; 409 439 410 440 default: … … 436 466 437 467 case 'gemini': 438 return new WP_Error('not_implemented', __('Gemini model list not implemented yet.', 'hippoo'), ['status' => 501]); 468 $result = self::GEMINI_MODELS; 469 break; 439 470 440 471 default: … … 619 650 } 620 651 652 private function check_rate_limit() 653 { 654 $ip = $_SERVER['REMOTE_ADDR']; 655 $transient_key = 'hippoo_ai_rate_limit_' . $ip; 656 $requests = get_transient($transient_key) ?: 0; 657 658 if ($requests >= 30) { // 30 requests per minute 659 return false; 660 } 661 662 set_transient($transient_key, $requests + 1, MINUTE_IN_SECONDS); 663 return true; 664 } 665 666 /* OpenAI */ 621 667 private function openai_generate_description($data) 622 668 { … … 655 701 656 702 $body = [ 657 'model' => $model,658 'messages' => $messages,703 'model' => $model, 704 'messages' => $messages, 659 705 'temperature' => $temperature, 660 706 ]; … … 668 714 $args = [ 669 715 'headers' => [ 670 'Content-Type' => 'application/json',716 'Content-Type' => 'application/json', 671 717 'Authorization' => "Bearer $api_token", 672 718 ], 673 'body' => json_encode($body),719 'body' => json_encode($body), 674 720 'timeout' => 120, 675 721 ]; … … 685 731 686 732 if ($status !== 200) { 687 $msg = $data_response['error']['message'] ?? 'Unexpected response from OpenAI.';733 $msg = $data_response['error']['message'] ?? __('Unexpected response from OpenAI.', 'hippoo'); 688 734 return new WP_Error('openai_error', $msg, ['status' => $status]); 689 735 } … … 701 747 $html = wp_kses_post($html); 702 748 749 if (!$html) { 750 return new WP_Error('openai_empty_output', __('OpenAI did not return any text. Try increasing max_tokens.', 'hippoo')); 751 } 752 703 753 $usage = $data_response['usage'] ?? []; 704 754 705 755 return [ 706 'html' => $html,707 'provider' => 'gpt',708 'model' => $model,709 'usage' => $usage,756 'html' => $html, 757 'provider' => 'gpt', 758 'model' => $model, 759 'usage' => $usage, 710 760 ]; 711 761 } … … 726 776 } 727 777 728 private function openai_get_models($api_token)729 {730 $response = wp_remote_get('https://api.openai.com/v1/models', [731 'headers' => ['Authorization' => 'Bearer ' . $api_token],732 'timeout' => 30,733 ]);734 735 if (is_wp_error($response)) {736 return new WP_Error('openai_error', $response->get_error_message(), ['status' => 500]);737 }738 739 $code = wp_remote_retrieve_response_code($response);740 $data = json_decode(wp_remote_retrieve_body($response), true);741 742 if ($code !== 200 || empty($data['data'])) {743 return rest_ensure_response([744 'provider' => 'gpt',745 'models' => self::GPT_MODELS,746 ]);747 }748 749 $models = array_map(fn($m) => $m['id'], $data['data']);750 $filtered = array_values(array_filter($models, fn($m) => str_starts_with($m, 'gpt')));751 752 return $filtered;753 }754 755 778 private function uses_max_completion_tokens($model) 756 779 { … … 761 784 } 762 785 763 private function check_rate_limit() 764 { 765 $ip = $_SERVER['REMOTE_ADDR']; 766 $transient_key = 'hippoo_ai_rate_limit_' . $ip; 767 $requests = get_transient($transient_key) ?: 0; 768 769 if ($requests >= 30) { // 30 requests per minute 786 /* Gemini */ 787 private function gemini_generate_description($data) 788 { 789 $model = $data['model'] ?: 'gemini-2.5-flash'; 790 $key = $data['api_token']; 791 $url = "https://generativelanguage.googleapis.com/v1/models/$model:generateContent?key=$key"; 792 793 $image_parts = array_map(function ($img) { 794 return [ 795 'inline_data' => [ 796 'mime_type' => mime_content_type($img), 797 'data' => base64_encode(file_get_contents($img)), 798 ], 799 ]; 800 }, $data['images']); 801 802 $body = [ 803 'contents' => [ 804 [ 805 "role" => "model", 806 "parts" => [ 807 ["text" => $data['system_prompt'] . "\n\nYou MUST only respond with a clean HTML block suitable for WordPress editor, no markdown, no backticks, no explanations."] 808 ] 809 ], 810 [ 811 "role" => "user", 812 "parts" => array_merge( 813 [ 814 ["text" => $data['description_prompt']] 815 ], 816 $image_parts 817 ) 818 ] 819 ], 820 821 "generationConfig" => [ 822 "temperature" => (float)$data['temperature'], 823 "maxOutputTokens" => (int)$data['max_tokens'] 824 ] 825 ]; 826 827 $response = wp_remote_post($url, [ 828 'headers' => ['Content-Type' => 'application/json'], 829 'body' => wp_json_encode($body), 830 'timeout' => 120, 831 ]); 832 833 if (is_wp_error($response)) { 834 return new WP_Error('gemini_request_failed', $response->get_error_message()); 835 } 836 837 $status = wp_remote_retrieve_response_code($response); 838 $body = json_decode(wp_remote_retrieve_body($response), true); 839 840 if ($status !== 200) { 841 $msg = $body['error']['message'] ?? __('Unexpected response from Gemini.', 'hippoo'); 842 return new WP_Error('gemini_error', $msg, ['status' => $status]); 843 } 844 845 $text = $body['candidates'][0]['content']['parts'][0]['text'] ?? ''; 846 847 if (!$text) { 848 return new WP_Error('gemini_empty_output', __('Gemini did not return any text. Try increasing max_tokens.', 'hippoo')); 849 } 850 851 $html = wp_kses_post(trim($text)); 852 $usage = $body['usageMetadata'] ?? []; 853 854 return [ 855 'html' => $html, 856 'provider' => 'gemini', 857 'model' => $model, 858 'usage' => $usage, 859 ]; 860 } 861 862 private function gemini_test_connection($api_token) 863 { 864 $response = wp_remote_get('https://generativelanguage.googleapis.com/v1/models?key=' . $api_token . '&pageSize=1', ['timeout' => 30]); 865 866 if (is_wp_error($response)) { 770 867 return false; 771 868 } 772 869 773 set_transient($transient_key, $requests + 1, MINUTE_IN_SECONDS); 774 return true; 870 return wp_remote_retrieve_response_code($response) === 200; 775 871 } 776 872 } -
hippoo/trunk/app/web_api.php
r3396221 r3396741 121 121 if (isset($data['token_id'])) { 122 122 $token_id = $data['token_id']; 123 124 if (!preg_match('/^[a-zA-Z0-9_-]+$/', $token_id)) {123 124 if (!preg_match('/^[a-zA-Z0-9_-]+$/', $token_id)) { 125 125 return new WP_REST_Response(['Message' => 'Invalid token.'], 400); 126 126 } 127 127 128 128 $file = hippoo_get_temp_dir() . 'hippoo_' . $token_id . '.json'; 129 129 $token = file_get_contents('php://input'); -
hippoo/trunk/assets/js/admin-script.js
r3396221 r3396741 151 151 }); 152 152 }); 153 153 154 154 /* AI Test Connection */ 155 155 $('#test-ai-connection').on('click', function() { … … 157 157 var originalText = button.text(); 158 158 159 159 160 button.text('Testing...').prop('disabled', true); 160 161 … … 183 184 }); 184 185 }); 186 187 $(document).on('change', 'select[name="hippoo_ai_settings[ai_provider]"]', function() { 188 var provider = $(this).val(); 189 190 $.ajax({ 191 url: hippoo.ajax_url, 192 method: 'POST', 193 data: { 194 action: 'hippoo_get_models_by_provider', 195 nonce: hippoo.nonce, 196 ai_provider: provider 197 }, 198 success: function(response) { 199 if (!response.success) { 200 console.error("Failed to load models"); 201 return; 202 } 203 204 var models = response.data; 205 var modelSelect = $('select[name="hippoo_ai_settings[ai_model]"]'); 206 207 modelSelect.empty(); 208 209 models.forEach(function(model) { 210 modelSelect.append('<option value="'+model+'">'+model+'</option>'); 211 }); 212 } 213 }); 214 }); 215 216 185 217 }); -
hippoo/trunk/hippoo.php
r3396221 r3396741 2 2 /** 3 3 * Plugin Name: Hippoo Mobile app for WooCommerce 4 * Version: 1.7. 04 * Version: 1.7.1 5 5 * Plugin URI: https://Hippoo.app/ 6 6 * Description: Best WooCommerce App Alternative – Manage orders and products on the go with real-time notifications, seamless order and product management, and powerful add-ons. Available for Android & iOS. 🚀. … … 30 30 } 31 31 32 define('hippoo_version', '1.7. 0');32 define('hippoo_version', '1.7.1'); 33 33 define('hippoo_path', dirname(__file__).DIRECTORY_SEPARATOR); 34 34 define('hippoo_main_file_path', __file__); -
hippoo/trunk/readme.txt
r3396221 r3396741 5 5 Requires at least: 5.3 6 6 Tested up to: 6.7 7 Stable tag: 1.7. 07 Stable tag: 1.7.1 8 8 License: GPL3 9 9 License URI: https://www.gnu.org/licenses/gpl-3.0.html … … 78 78 79 79 == Changelog == 80 * 1.7.1 – Add Gemini Support for AI features 80 81 * 1.7.0 – Introducing Hippoo AI for generating product content from photos 81 82 * 1.6.1 – Added REST API troubleshooter and support for customizable invoice templates.
Note: See TracChangeset
for help on using the changeset viewer.