Plugin Directory

Changeset 3396741


Ignore:
Timestamp:
11/16/2025 08:02:05 PM (4 months ago)
Author:
hippooo
Message:

1.7.1

Location:
hippoo/trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • hippoo/trunk/app/ai.php

    r3396221 r3396741  
    77
    88    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
    911    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.';
    1012    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.';
     
    1820        add_filter('hippoo_settings_tab_contents', array($this, 'add_ai_tab_content'));
    1921        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'));
    2124    }
    2225
     
    174177    }
    175178
    176 
    177179    public function section_ai_connect_render()
    178180    {
     
    189191        <select class="select" name="hippoo_ai_settings[ai_provider]">
    190192            <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>
    191194        </select>
    192195        <?php
     
    208211            </select>
    209212            <?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
    210223        } else {
    211224        }
     
    279292        if ($provider === 'gpt') {
    280293            $result = $this->openai_test_connection($api_token);
     294        } elseif ($provider === 'gemini') {
     295            $result = $this->gemini_test_connection($api_token);
    281296        } else {
    282297            wp_send_json_error(__('Unsupported AI provider.', 'hippoo'));
     
    287302        } else {
    288303            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'));
    289319        }
    290320    }
     
    367397
    368398            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;
    371401           
    372402            default:
     
    405435
    406436            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;
    409439
    410440            default:
     
    436466
    437467            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;
    439470
    440471            default:
     
    619650    }
    620651
     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 */
    621667    private function openai_generate_description($data)
    622668    {
     
    655701
    656702        $body = [
    657             'model'       => $model,
    658             'messages'    => $messages,
     703            'model' => $model,
     704            'messages' => $messages,
    659705            'temperature' => $temperature,
    660706        ];
     
    668714        $args = [
    669715            'headers' => [
    670                 'Content-Type'  => 'application/json',
     716                'Content-Type' => 'application/json',
    671717                'Authorization' => "Bearer $api_token",
    672718            ],
    673             'body'    => json_encode($body),
     719            'body' => json_encode($body),
    674720            'timeout' => 120,
    675721        ];
     
    685731
    686732        if ($status !== 200) {
    687             $msg = $data_response['error']['message'] ?? 'Unexpected response from OpenAI.';
     733            $msg = $data_response['error']['message'] ?? __('Unexpected response from OpenAI.', 'hippoo');
    688734            return new WP_Error('openai_error', $msg, ['status' => $status]);
    689735        }
     
    701747        $html = wp_kses_post($html);
    702748
     749        if (!$html) {
     750            return new WP_Error('openai_empty_output', __('OpenAI did not return any text. Try increasing max_tokens.', 'hippoo'));
     751        }
     752
    703753        $usage = $data_response['usage'] ?? [];
    704754
    705755        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,
    710760        ];
    711761    }
     
    726776    }
    727777
    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 
    755778    private function uses_max_completion_tokens($model)
    756779    {
     
    761784    }
    762785
    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)) {
    770867            return false;
    771868        }
    772869       
    773         set_transient($transient_key, $requests + 1, MINUTE_IN_SECONDS);
    774         return true;
     870        return wp_remote_retrieve_response_code($response) === 200;
    775871    }
    776872}
  • hippoo/trunk/app/web_api.php

    r3396221 r3396741  
    121121    if (isset($data['token_id'])) {
    122122        $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)) {
    125125            return new WP_REST_Response(['Message' => 'Invalid token.'], 400);
    126126        }
    127        
     127       
    128128        $file     = hippoo_get_temp_dir() . 'hippoo_' . $token_id . '.json';
    129129        $token    = file_get_contents('php://input');
  • hippoo/trunk/assets/js/admin-script.js

    r3396221 r3396741  
    151151        });
    152152    });
    153 
     153   
    154154    /* AI Test Connection */
    155155    $('#test-ai-connection').on('click', function() {
     
    157157        var originalText = button.text();
    158158       
     159       
    159160        button.text('Testing...').prop('disabled', true);
    160161       
     
    183184        });
    184185    });
     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
    185217});
  • hippoo/trunk/hippoo.php

    r3396221 r3396741  
    22/**
    33 * Plugin Name: Hippoo Mobile app for WooCommerce
    4  * Version: 1.7.0
     4 * Version: 1.7.1
    55 * Plugin URI: https://Hippoo.app/
    66 * 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. 🚀.
     
    3030}
    3131
    32 define('hippoo_version', '1.7.0');
     32define('hippoo_version', '1.7.1');
    3333define('hippoo_path', dirname(__file__).DIRECTORY_SEPARATOR);
    3434define('hippoo_main_file_path', __file__);
  • hippoo/trunk/readme.txt

    r3396221 r3396741  
    55Requires at least: 5.3
    66Tested up to: 6.7
    7 Stable tag: 1.7.0
     7Stable tag: 1.7.1
    88License: GPL3
    99License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    7878
    7979== Changelog ==
     80* 1.7.1 – Add Gemini Support for AI features
    8081* 1.7.0 – Introducing Hippoo AI for generating product content from photos
    8182* 1.6.1 – Added REST API troubleshooter and support for customizable invoice templates.
Note: See TracChangeset for help on using the changeset viewer.