phi-3-shampoo-analyzer / phi-shampoo-analyzer.php
wmounger's picture
Upload 6 files
70a185d verified
<?php
/*
Plugin Name: Phi Shampoo Analyzer
Plugin URI: https://example.com/
Description: Uses Microsoft Phi-3.5 models via Hugging Face Inference API for shampoo ingredient analysis.
Version: 1.0
Author: Monica Hsueh
License: GPL2
*/
/*
Model Information:
--------------------------
Text Model: microsoft/Phi-3.5-mini-instruct
Vision Model: microsoft/Phi-3.5-vision-instruct
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Define plugin constants
define('PHI_SHAMPOO_VERSION', '1.0');
define('PHI_SHAMPOO_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('PHI_SHAMPOO_PLUGIN_URL', plugin_dir_url(__FILE__));
define('PHI_SHAMPOO_PLUGIN_BASENAME', plugin_basename(__FILE__));
// Define model constants
define('PHI_SHAMPOO_TEXT_MODEL', 'microsoft/Phi-3.5-mini-instruct');
define('PHI_SHAMPOO_VISION_MODEL', 'microsoft/Phi-3.5-vision-instruct');
define('PHI_SHAMPOO_HF_TEXT_API_URL', 'https://api-inference.huggingface.co/models/' . PHI_SHAMPOO_TEXT_MODEL);
define('PHI_SHAMPOO_HF_VISION_API_URL', 'https://api-inference.huggingface.co/models/' . PHI_SHAMPOO_VISION_MODEL);
/**
* Plugin activation hook
*/
function phi_shampoo_activate() {
// Create default pages with shortcodes
phi_shampoo_create_pages();
// Set default settings
if (!get_option('phi_shampoo_test_mode')) {
update_option('phi_shampoo_test_mode', true); // Setting to true for initial testing
}
// Flush rewrite rules
flush_rewrite_rules();
}
register_activation_hook(__FILE__, 'phi_shampoo_activate');
/**
* Plugin deactivation hook
*/
function phi_shampoo_deactivate() {
// Flush rewrite rules on deactivation
flush_rewrite_rules();
}
register_deactivation_hook(__FILE__, 'phi_shampoo_deactivate');
/**
* Create required pages with shortcodes
*/
function phi_shampoo_create_pages() {
// Create analyzer page if it doesn't exist
$analyzer_page_id = wp_insert_post(array(
'post_title' => 'Phi Shampoo Analyzer',
'post_content' => '[phi_shampoo_analyzer]',
'post_status' => 'publish',
'post_type' => 'page',
'comment_status' => 'closed'
));
// Save page ID to settings
update_option('phi_shampoo_analyzer_page_id', $analyzer_page_id);
}
/**
* Enqueue scripts and styles
*/
function phi_shampoo_enqueue_scripts() {
// Register and enqueue CSS
wp_register_style('phi-shampoo-style', PHI_SHAMPOO_PLUGIN_URL . 'assets/css/style.css', array(), PHI_SHAMPOO_VERSION);
wp_enqueue_style('phi-shampoo-style');
// Register and enqueue JavaScript
wp_register_script('phi-shampoo-script', PHI_SHAMPOO_PLUGIN_URL . 'assets/js/phi-shampoo.js', array('jquery'), PHI_SHAMPOO_VERSION, true);
wp_enqueue_script('phi-shampoo-script');
// Localize the script with the ajax URL
wp_localize_script('phi-shampoo-script', 'phiShampoo', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('phi_shampoo_nonce'),
'analyzePage' => get_permalink(get_option('phi_shampoo_analyzer_page_id'))
));
}
add_action('wp_enqueue_scripts', 'phi_shampoo_enqueue_scripts');
/**
* Write to debug log
*/
function phi_shampoo_log($message) {
if (defined('WP_DEBUG') && WP_DEBUG) {
$log_file = WP_CONTENT_DIR . '/debug.log';
$timestamp = current_time('mysql');
if (is_array($message) || is_object($message)) {
error_log('[' . $timestamp . '] Phi Shampoo: ' . print_r($message, true) . "\n", 3, $log_file);
} else {
error_log('[' . $timestamp . '] Phi Shampoo: ' . $message . "\n", 3, $log_file);
}
}
}
/**
* Add settings link to plugins page
*/
function phi_shampoo_add_settings_link($links) {
$settings_link = '<a href="' . admin_url('admin.php?page=phi-shampoo-settings') . '">' . __('Settings', 'phi-shampoo-analyzer') . '</a>';
array_unshift($links, $settings_link);
return $links;
}
add_filter('plugin_action_links_' . PHI_SHAMPOO_PLUGIN_BASENAME, 'phi_shampoo_add_settings_link');
/**
* Register shortcodes
*/
function phi_shampoo_register_shortcodes() {
add_shortcode('phi_shampoo_analyzer', 'phi_shampoo_analyzer_shortcode');
}
add_action('init', 'phi_shampoo_register_shortcodes');
/**
* Shampoo analyzer shortcode
*/
function phi_shampoo_analyzer_shortcode() {
ob_start();
?>
<div class="phi-shampoo-analyzer">
<h2>Phi Shampoo Analyzer</h2>
<p>Enter shampoo ingredients below or upload an image of the ingredient list.</p>
<form id="phi-analyzer-form" method="post" enctype="multipart/form-data">
<?php wp_nonce_field('phi_shampoo_analyze', 'phi_shampoo_nonce'); ?>
<div class="phi-form-group">
<label for="phi-ingredients">Ingredients:</label>
<textarea id="phi-ingredients" name="ingredients" rows="5" placeholder="Enter ingredients separated by commas..."></textarea>
</div>
<div class="phi-form-group">
<label for="phi-image">Or upload image:</label>
<input type="file" id="phi-image" name="ingredient_image" accept="image/*">
</div>
<div class="phi-form-group">
<label for="phi-allergies">Known Allergies (optional):</label>
<input type="text" id="phi-allergies" name="allergies" placeholder="e.g., Parabens, Sulfates, Fragrances">
</div>
<div class="phi-form-group">
<label for="phi-skin-type">Skin Type (optional):</label>
<select id="phi-skin-type" name="skin_type">
<option value="">Select Skin Type</option>
<option value="Normal">Normal</option>
<option value="Dry">Dry</option>
<option value="Oily">Oily</option>
<option value="Combination">Combination</option>
<option value="Sensitive">Sensitive</option>
</select>
</div>
<div class="phi-form-submit">
<button type="submit" class="phi-submit-button">Analyze</button>
</div>
</form>
<div id="phi-result" class="phi-result" style="display: none;">
<h3>Analysis Result</h3>
<div id="phi-result-content"></div>
</div>
<div id="phi-loading" class="phi-loading" style="display: none;">
<p>Analyzing ingredients... Please wait.</p>
</div>
</div>
<script>
jQuery(document).ready(function($) {
$('#phi-analyzer-form').on('submit', function(e) {
e.preventDefault();
$('#phi-loading').show();
$('#phi-result').hide();
var formData = new FormData(this);
formData.append('action', 'phi_analyze_shampoo');
$.ajax({
url: phiShampoo.ajax_url,
type: 'POST',
data: formData,
contentType: false,
processData: false,
success: function(response) {
$('#phi-loading').hide();
if (response.success) {
$('#phi-result-content').html(response.data.result);
$('#phi-result').show();
} else {
$('#phi-result-content').html('<p class="phi-error">Error: ' + response.data.message + '</p>');
$('#phi-result').show();
}
},
error: function() {
$('#phi-loading').hide();
$('#phi-result-content').html('<p class="phi-error">Error: Could not connect to the server.</p>');
$('#phi-result').show();
}
});
});
});
</script>
<?php
return ob_get_clean();
}
/**
* AJAX handler for shampoo analysis
*/
function phi_shampoo_ajax_analyze() {
// Check nonce
if (!isset($_POST['phi_shampoo_nonce']) || !wp_verify_nonce($_POST['phi_shampoo_nonce'], 'phi_shampoo_analyze')) {
wp_send_json_error(array('message' => 'Security check failed.'));
}
$ingredients = '';
$image_uploaded = false;
// Handle image upload
if (!empty($_FILES['ingredient_image']['tmp_name'])) {
$image_uploaded = true;
$tmp_name = $_FILES['ingredient_image']['tmp_name'];
// Process image to extract ingredients
$result = phi_shampoo_process_image($tmp_name);
if ($result['success']) {
$ingredients = $result['ingredients'];
} else {
wp_send_json_error(array('message' => 'Could not extract ingredients from image: ' . $result['error']));
}
} else if (!empty($_POST['ingredients'])) {
$ingredients = sanitize_textarea_field($_POST['ingredients']);
} else {
wp_send_json_error(array('message' => 'Please provide ingredients or upload an image.'));
}
// Gather additional data
$allergies = isset($_POST['allergies']) ? sanitize_text_field($_POST['allergies']) : '';
$skin_type = isset($_POST['skin_type']) ? sanitize_text_field($_POST['skin_type']) : '';
// Analyze ingredients
$analysis_data = array(
'ingredients' => $ingredients,
'allergies' => $allergies,
'skin_type' => $skin_type
);
$analysis = phi_shampoo_analyze_ingredients($analysis_data);
if ($analysis['success']) {
$html_result = '<div class="phi-analysis">';
if ($image_uploaded) {
$html_result .= '<p><strong>Extracted Ingredients:</strong> ' . esc_html($ingredients) . '</p>';
}
$html_result .= '<div class="phi-analysis-content">' . nl2br(esc_html($analysis['result'])) . '</div>';
$html_result .= '</div>';
wp_send_json_success(array('result' => $html_result));
} else {
wp_send_json_error(array('message' => 'Analysis failed: ' . $analysis['error']));
}
}
add_action('wp_ajax_phi_analyze_shampoo', 'phi_shampoo_ajax_analyze');
add_action('wp_ajax_nopriv_phi_analyze_shampoo', 'phi_shampoo_ajax_analyze');
/**
* Process image to extract ingredients using Phi-3.5 Vision
*/
function phi_shampoo_process_image($image_path) {
// Check if in test mode
$test_mode = get_option('phi_shampoo_test_mode', false);
if ($test_mode) {
phi_shampoo_log("Test mode active - returning test ingredients from image");
return array(
'success' => true,
'ingredients' => "Water, Sodium Laureth Sulfate, Cocamidopropyl Betaine, Sodium Chloride, Glycol Distearate, Dimethiconol, Fragrance, Carbomer, Guar Hydroxypropyltrimonium Chloride, Tetrasodium EDTA, Citric Acid, Sodium Benzoate, Methylchloroisothiazolinone, Methylisothiazolinone"
);
}
// Get API token
$api_token = phi_shampoo_get_api_token();
if (empty($api_token)) {
phi_shampoo_log("Error: Missing API token for Hugging Face");
return array(
'success' => false,
'error' => 'Missing API token. Please configure it in plugin settings.',
'ingredients' => ''
);
}
// Check if file exists
if (!file_exists($image_path)) {
phi_shampoo_log("Error: Image file not found at path: " . $image_path);
return array(
'success' => false,
'error' => 'Image file not found.',
'ingredients' => ''
);
}
// Read and encode image
$image_data = file_get_contents($image_path);
$base64_image = base64_encode($image_data);
// Make API request
$api_url = defined('PHI_SHAMPOO_HF_VISION_API_URL') ? PHI_SHAMPOO_HF_VISION_API_URL : '';
$response = wp_remote_post($api_url, array(
'headers' => array(
'Authorization' => 'Bearer ' . $api_token,
'Content-Type' => 'application/json'
),
'body' => json_encode(array(
'inputs' => array(
'image' => $base64_image,
'text' => "Extract all the ingredients from this shampoo product label. Format them as a comma-separated list."
)
)),
'timeout' => 60
));
// Handle response
if (is_wp_error($response)) {
phi_shampoo_log("API Error: " . $response->get_error_message());
return array(
'success' => false,
'error' => $response->get_error_message(),
'ingredients' => ''
);
}
$body = wp_remote_retrieve_body($response);
$status_code = wp_remote_retrieve_response_code($response);
phi_shampoo_log("API Status: " . $status_code . ", Response: " . substr($body, 0, 200) . "...");
if ($status_code !== 200) {
$error_message = "API Error: Received status code " . $status_code;
$decoded = json_decode($body, true);
if ($decoded && isset($decoded['error'])) {
$error_message .= " - " . $decoded['error'];
}
return array(
'success' => false,
'error' => $error_message,
'ingredients' => ''
);
}
$decoded = json_decode($body, true);
if (isset($decoded['generated_text'])) {
return array(
'success' => true,
'ingredients' => $decoded['generated_text']
);
} else {
return array(
'success' => false,
'error' => 'Unexpected response format',
'ingredients' => ''
);
}
}
/**
* Analyze ingredients using Phi-3.5 Text model
*/
function phi_shampoo_analyze_ingredients($data) {
// Check if in test mode
$test_mode = get_option('phi_shampoo_test_mode', false);
if ($test_mode) {
return phi_shampoo_generate_test_analysis($data);
}
// Get API token
$api_token = phi_shampoo_get_api_token();
if (empty($api_token)) {
return array(
'success' => false,
'error' => 'Missing API token. Please configure it in plugin settings.',
'result' => 'Error: API token not configured.'
);
}
// Check if ingredients are provided
if (empty($data['ingredients'])) {
return array(
'success' => false,
'error' => 'No ingredients provided for analysis.',
'result' => 'Error: No ingredients to analyze.'
);
}
// Build prompt for the model
$prompt = "You are a shampoo ingredient analyzer. Please analyze the following shampoo ingredients for safety and potential allergic reactions:\n\n";
$prompt .= "Ingredients:\n" . $data['ingredients'] . "\n\n";
// Add user information if available
if (!empty($data['allergies'])) {
$prompt .= "User has allergies to: " . $data['allergies'] . "\n\n";
}
if (!empty($data['skin_type'])) {
$prompt .= "User has " . $data['skin_type'] . " skin type.\n\n";
}
// Add analysis instructions
$prompt .= "Please provide a detailed analysis including:\n";
$prompt .= "1. Overall safety assessment (safe or not safe for this user)\n";
$prompt .= "2. Potentially harmful ingredients with explanations\n";
$prompt .= "3. Specific concerns based on the user's health profile\n";
$prompt .= "4. Alternative recommendations if needed\n";
$prompt .= "\nBegin your response with a clear 'SAFE TO USE' or 'NOT RECOMMENDED' statement.\n";
// Log the prompt
phi_shampoo_log("Analysis prompt: " . $prompt);
// Make API request
$api_url = defined('PHI_SHAMPOO_HF_TEXT_API_URL') ? PHI_SHAMPOO_HF_TEXT_API_URL : '';
$response = wp_remote_post($api_url, array(
'headers' => array(
'Authorization' => 'Bearer ' . $api_token,
'Content-Type' => 'application/json'
),
'body' => json_encode(array(
'inputs' => $prompt,
'parameters' => array(
'max_new_tokens' => 512,
'temperature' => 0.7,
'do_sample' => true
)
)),
'timeout' => 60
));
// Handle response
if (is_wp_error($response)) {
phi_shampoo_log("API Error: " . $response->get_error_message());
return array(
'success' => false,
'error' => $response->get_error_message(),
'result' => 'Error: API request failed. ' . $response->get_error_message()
);
}
$body = wp_remote_retrieve_body($response);
$status_code = wp_remote_retrieve_response_code($response);
phi_shampoo_log("API Status: " . $status_code . ", Response: " . substr($body, 0, 200) . "...");
if ($status_code !== 200) {
$error_message = "API Error: Received status code " . $status_code;
$decoded = json_decode($body, true);
if ($decoded && isset($decoded['error'])) {
$error_message .= " - " . $decoded['error'];
}
return array(
'success' => false,
'error' => $error_message,
'result' => 'Error: ' . $error_message
);
}
$decoded = json_decode($body, true);
if (isset($decoded['generated_text'])) {
return array(
'success' => true,
'result' => $decoded['generated_text']
);
} else {
return array(
'success' => false,
'error' => 'Unexpected response format',
'result' => 'Error: Could not parse the AI response.'
);
}
}
/**
* Generate test analysis results (used when test mode is enabled)
*/
function phi_shampoo_generate_test_analysis($data) {
$ingredients = $data['ingredients'];
$has_allergies = !empty($data['allergies']);
if (strpos(strtolower($ingredients), 'sulfate') !== false ||
strpos(strtolower($ingredients), 'methylisothiazolinone') !== false) {
$result = "NOT RECOMMENDED\n\n";
$result .= "Based on the ingredients analysis, this shampoo contains some potentially problematic ingredients for your specific health profile.\n\n";
if (strpos(strtolower($ingredients), 'sodium laureth sulfate') !== false ||
strpos(strtolower($ingredients), 'sodium lauryl sulfate') !== false) {
$result .= "• Sodium Laureth/Lauryl Sulfate: A strong detergent that can be irritating, especially for sensitive skin or those with certain allergies.\n";
}
if (strpos(strtolower($ingredients), 'methylisothiazolinone') !== false) {
$result .= "• Methylisothiazolinone: A preservative that is a known allergen and has been associated with skin sensitization.\n";
}
if ($has_allergies) {
$result .= "\nBased on your reported allergies, this product contains ingredients that may trigger a reaction. Consider alternative formulations labeled 'sulfate-free' and 'preservative-free'.\n";
}
$result .= "\nRecommendation: Look for shampoos with gentler cleansing agents like Cocamidopropyl Hydroxysultaine, Sodium Cocoyl Isethionate or Coco Glucoside.";
} else {
$result = "SAFE TO USE\n\n";
$result .= "Based on the ingredients analysis, this shampoo appears to be safe for general use. The formula contains:\n\n";
$result .= "• Mild cleansing agents that effectively clean without excessive drying\n";
$result .= "• Conditioning agents to help maintain hair moisture\n";
if (strpos(strtolower($ingredients), 'fragrance') !== false ||
strpos(strtolower($ingredients), 'parfum') !== false) {
$result .= "• Fragrances are present, which could potentially cause sensitivity in some individuals\n";
}
$result .= "\nNo major red flags were identified in this formulation that would likely trigger adverse reactions.";
}
return array(
'success' => true,
'result' => $result
);
}
/**
* Get the Hugging Face API token
* Returns the API token from WordPress options or an empty string if not set
*/
function phi_shampoo_get_api_token() {
$token = get_option('phi_shampoo_hf_api_token', '');
// For extra security, you can decrypt the token here if you stored it encrypted
return $token;
}
/**
* Admin menu
*/
function phi_shampoo_admin_menu() {
add_menu_page(
'Phi Shampoo Analyzer',
'Phi Shampoo',
'manage_options',
'phi-shampoo-settings',
'phi_shampoo_settings_page',
'dashicons-admin-generic'
);
}
add_action('admin_menu', 'phi_shampoo_admin_menu');
/**
* Admin settings page
*/
function phi_shampoo_settings_page() {
// Save settings if submitted
if (isset($_POST['phi_shampoo_save_settings']) && isset($_POST['phi_shampoo_settings_nonce'])) {
if (wp_verify_nonce($_POST['phi_shampoo_settings_nonce'], 'phi_shampoo_settings')) {
// Sanitize and save the API token
if (isset($_POST['phi_shampoo_hf_api_token'])) {
$api_token = sanitize_text_field($_POST['phi_shampoo_hf_api_token']);
update_option('phi_shampoo_hf_api_token', $api_token);
}
// Save test mode setting
$test_mode = isset($_POST['phi_shampoo_test_mode']) ? true : false;
update_option('phi_shampoo_test_mode', $test_mode);
echo '<div class="notice notice-success is-dismissible"><p>Settings saved successfully!</p></div>';
} else {
echo '<div class="notice notice-error is-dismissible"><p>Security check failed. Settings not saved.</p></div>';
}
}
// Get current settings
$api_token = get_option('phi_shampoo_hf_api_token', '');
$test_mode = get_option('phi_shampoo_test_mode', true);
?>
<div class="wrap">
<h1>Phi Shampoo Analyzer Settings</h1>
<form method="post" action="">
<?php wp_nonce_field('phi_shampoo_settings', 'phi_shampoo_settings_nonce'); ?>
<table class="form-table">
<tr>
<th scope="row">Hugging Face API Token</th>
<td>
<input type="password" name="phi_shampoo_hf_api_token" value="<?php echo esc_attr($api_token); ?>" class="regular-text" />
<p class="description">
Enter your Hugging Face API token.
<a href="https://huggingface.co/settings/tokens" target="_blank">Get a token here</a>.
</p>
</td>
</tr>
<tr>
<th scope="row">Test Mode</th>
<td>
<label>
<input type="checkbox" name="phi_shampoo_test_mode" <?php checked($test_mode, true); ?> />
Enable test mode (uses pre-defined responses instead of API calls)
</label>
</td>
</tr>
</table>
<div class="phi-shampoo-settings-info">
<h2>Important Information</h2>
<p>To use this plugin, make sure:</p>
<ul>
<li>You have a valid Hugging Face account</li>
<li>You have <a href="https://huggingface.co/settings/inference-providers" target="_blank">enabled the HF Inference API</a> in your Hugging Face account</li>
<li>Your API token has the necessary permissions (at least "Read" access)</li>
</ul>
</div>
<p class="submit">
<input type="submit" name="phi_shampoo_save_settings" class="button-primary" value="Save Settings" />
</p>
</form>
</div>
<?php
}