import sys import os import re from datetime import datetime import shutil import requests # Add requests library for API calls # Add the src directory to Python path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import gradio as gr import asyncio from utils.taobao_crawler import TaobaoCrawler from utils.data_processor import DataProcessor from agents.fashion_agent import FashionAgent import pandas as pd import json from PIL import Image import io import base64 import numpy as np import torch # Add OpenAI API support from openai import OpenAI from dotenv import load_dotenv, find_dotenv # Add imports needed for agent from autogen_agentchat.messages import TextMessage from autogen_core import CancellationToken # Load environment variables load_dotenv(find_dotenv(), override=True) # Global variables crawler = None data_processor = None fashion_agent = None clothing_data = None current_mode = "taobao" # Default recommendation mode MODEL_OPTIONS = [ "gpt-4o", "gpt-4o-mini", "o3-mini", "AI21-Jamba-1.5-Large", "AI21-Jamba-1.5-Mini", "Codestral-2501", "Cohere-command-r", "Ministral-3B", "Mistral-Large-2411", "Mistral-Nemo", "Mistral-small" ] # Initialize OpenAI client openai_client = None openai_model = "gpt-4.1-mini" # Default model def initialize_openai_client(): """Initialize OpenAI client""" global openai_client api_key = os.getenv("OPENAI_API_KEY") if not api_key: print("Warning: OPENAI_API_KEY environment variable not found") return False openai_client = OpenAI(api_key=api_key) return True def set_openai_model(model_name): """Set OpenAI model""" global openai_model openai_model = model_name print(f"OpenAI model set to: {openai_model}") async def start_crawler(): """Start crawler and return login page URL""" global crawler try: crawler = TaobaoCrawler() # Direct login if crawler.login(): return "Login successful!" return "Login failed, please try again." except Exception as e: print(f"Error in start_crawler: {str(e)}") return f"Error occurred: {str(e)}" async def check_login(): """Check login status""" if crawler: return "Logged in" return "Not logged in" async def process_data(): """Process crawled data""" global data_processor, clothing_data if not crawler: return [] # Initialize data processor data_processor = DataProcessor(data_dir="data") # Get data items = crawler.get_purchase_history(days=30) if items: try: # Convert items to DataFrame items_df = pd.DataFrame(items) # Process data clothing_data = data_processor.process_data(items_df) # Close browser crawler.close() # Return image URL list return get_image_urls() except Exception as e: print(f"Error processing data: {str(e)}") return [] return [] def get_image_urls(): """Get all image URLs""" if clothing_data is not None: return list(clothing_data['image_url'].values) return [] def update_model(selected_model): # Set OpenAI model (for image analysis) if selected_model in ["gpt-4.1-mini", "gpt-4.1-turbo", "gpt-4.0-turbo", "gpt-4o", "gpt-4o-mini"]: set_openai_model(selected_model) # Also keep the original GitHub model setting (for other functions) os.environ["GITHUB_MODEL"] = selected_model return f"Current selected model: {selected_model}" def upload_to_imgbb(image_path): """Upload image to ImgBB and get public URL""" try: # Use user-provided API Key imgbb_key = os.getenv("IMGBB_KEY", "1fd2bb0a1db93995e5736b5e275f7f28") # User-provided API Key with open(image_path, 'rb') as f: image_data = f.read() # Convert image to base64 encoding base64_image = base64.b64encode(image_data).decode('utf-8') # Upload to ImgBB res = requests.post( 'https://api.imgbb.com/1/upload', data={ 'key': imgbb_key, 'image': base64_image, } ) # Print response for debugging print(f"ImgBB API Response: {res.text}") if res.status_code == 200: response_json = res.json() if response_json.get('success'): # Return only the image URL return response_json['data']['url'] else: return f"ImgBB upload failed: {response_json.get('error', {}).get('message', 'Unknown error')}" else: return f"ImgBB upload failed, status code: {res.status_code}" except Exception as e: print(f"ImgBB upload error: {str(e)}") return f"Error during ImgBB upload process: {str(e)}" async def get_recommendation(style_preference: str, temperature: float = None, mood: str = None, tab: str = "taobao"): """Get clothing recommendations Args: style_preference: Style preference temperature: Temperature mood: Mood tab: Tab type, 'taobao' uses CSV data, 'physical' uses TXT data """ global clothing_data result_images = [] try: # Initialize recommendation agent if tab == "taobao": # In taobao mode, use global clothing_data (DataFrame) if clothing_data is None: return [], "Please process Taobao data first" fashion_agent = FashionAgent(clothing_data) print(f"Using Taobao CSV data with {len(clothing_data)} records") elif tab == "physical": # In physical mode, read image analysis text result txt_path = "data/upload_process_txt.txt" # Check if file exists if not os.path.exists(txt_path): return [], f"File does not exist: {txt_path}, please upload and analyze an image first" try: with open(txt_path, 'r', encoding='utf-8') as file: text_description = file.read() print(f"Successfully read image analysis text, content length: {len(text_description)} characters") if len(text_description.strip()) == 0: return [], "Image analysis text is empty, please upload and analyze an image again" fashion_agent = FashionAgent(text_description) except Exception as e: return [], f"Failed to read image analysis text: {str(e)}" else: return [], f"Invalid tab type: {tab}. Please use 'taobao' or 'physical'." # Get recommendation results result = await fashion_agent.process_request( style_preference=style_preference, temperature=temperature, mood=mood ) # Parse image URLs from the recommendation result recommended_images = re.findall(r'https?://[^\s]+\.jpg', result) # Replace image URLs with placeholders when printing print_result = re.sub(r'https?://[^\s]+\.jpg', '[IMAGE_URL]', result) print("LLM result:", print_result[:100] + "..." if len(print_result) > 100 else print_result) return recommended_images, result except Exception as e: print(f"Error during recommendation process: {str(e)}") import traceback traceback.print_exc() return [], f"Error getting recommendation: {str(e)}" async def analyze_image_with_openai(image_url): """Use OpenAI API to analyze image and generate detailed description""" global openai_client, openai_model try: # Ensure OpenAI client is initialized if openai_client is None: if not initialize_openai_client(): return "Error: OpenAI API key not set, please set OPENAI_API_KEY in environment variables" print(f"Using OpenAI API ({openai_model}) to analyze image: {image_url}") # Create API request response = openai_client.responses.create( model=openai_model, input=[{ "role": "user", "content": [ {"type": "input_text", "text": "what's in this image?"}, { "type": "input_image", "image_url": image_url }, ], }], ) # Extract analysis result analysis_result = response.output_text print(f"OpenAI analysis complete, result length: {len(analysis_result)}") return analysis_result except Exception as e: print(f"OpenAI image analysis error: {str(e)}") return f"Error during image analysis process: {str(e)}" async def process_uploaded_images(image_path): """Process uploaded images and analyze clothing features""" try: if not image_path: return "Please upload an image" print(f"Received image path: {image_path}") # Use ImgBB to upload image imgbb_url = upload_to_imgbb(image_path) # Check if ImgBB upload was successful - if the returned URL is a string and doesn't start with error message if isinstance(imgbb_url, str) and not imgbb_url.startswith("ImgBB upload failed") and not imgbb_url.startswith("Error during ImgBB upload process"): # Upload successful, use OpenAI API for image analysis print("Image upload successful, starting OpenAI analysis...") result_message = await analyze_image_with_openai(imgbb_url) # Ensure data directory exists os.makedirs("data", exist_ok=True) # Save analysis result to file for get_recommendation function to use try: with open("data/upload_process_txt.txt", "w", encoding="utf-8") as f: f.write(result_message) print(f"Analysis result saved to file, length: {len(result_message)} characters") except Exception as e: print(f"Failed to save analysis result to file: {str(e)}") else: result_message = f"ImgBB upload failed: {imgbb_url}" return result_message except Exception as e: print(f"Error processing image: {str(e)}") return f"Error processing image: {str(e)}" # Non-async wrapper function for direct calling of async functions def sync_get_recommendation(style, temp, mood): """Synchronous wrapper function for Gradio button click event""" global current_mode print(f"Using {current_mode} mode for clothing recommendation") try: # Use asyncio.new_event_loop().run_until_complete to run async function loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) images, text = loop.run_until_complete(get_recommendation(style, temp, mood, tab=current_mode)) loop.close() return images, text except Exception as e: print(f"Recommendation process error: {str(e)}") return [], f"Error during recommendation process: {str(e)}" # Create Gradio interface with gr.Blocks() as demo: gr.Markdown("# Fashion Recommendation System") with gr.Row(): # Left Column with gr.Column(scale=1): # Using Tabs to organize different data acquisition methods with gr.Tabs() as tabs: # Tab 1: Online Wardrobe (Taobao Data) with gr.Tab("Online Wardrobe (Taobao)") as taobao_tab: gr.Markdown("## Get Taobao Purchase History") start_button = gr.Button("Login to Taobao") login_status = gr.Textbox(label="Login Status", interactive=False) process_button = gr.Button("Get Clothing Data") # Display Taobao clothing images gr.Markdown("## My Taobao Clothing") taobao_gallery = gr.Gallery(label="Taobao Clothing", show_label=False) # Tab 2: Offline Wardrobe (Image Upload) with gr.Tab("Offline Wardrobe (Physical Clothes)") as physical_tab: gr.Markdown("## Upload Clothing Image") image_upload = gr.Image( label="Upload Clothing Image", type="filepath" # Return local file path ) upload_button = gr.Button("Analyze Image") # Display analysis results gr.Markdown("## Clothing Analysis Results") analysis_text = gr.Textbox( label="Clothing Analysis", interactive=False, lines=10, placeholder="Waiting for analysis results..." ) # Display current recommendation mode current_mode_text = gr.Markdown("**Current Mode: Taobao**") # Recommendation Settings (shared by both tabs) gr.Markdown("## Recommendation Settings") style_input = gr.Textbox(label="Desired Style") temperature_input = gr.Number(label="Current Temperature (Optional)") mood_input = gr.Textbox(label="Current Mood (Optional)") model_dropdown = gr.Dropdown( choices=MODEL_OPTIONS, value=os.getenv('API_HOST', 'github'), label="Select Model" ) recommend_button = gr.Button("Get Recommendations") # Right Column with gr.Column(scale=1): # Recommendation Results gr.Markdown("## Recommended Outfits") recommendation_gallery = gr.Gallery(label="Recommended Outfits", show_label=False) recommendation_text = gr.Textbox(label="Recommendation Explanation", interactive=False, lines=10) # Bind events start_button.click( fn=start_crawler, outputs=login_status ) process_button.click( fn=process_data, outputs=taobao_gallery ) upload_button.click( fn=process_uploaded_images, inputs=image_upload, outputs=analysis_text ) # Use the selected mode for recommendations recommend_button.click( fn=sync_get_recommendation, inputs=[style_input, temperature_input, mood_input], outputs=[recommendation_gallery, recommendation_text] ) model_dropdown.change( fn=update_model, inputs=model_dropdown, outputs=[] ) # Define function to update mode def update_mode(mode): global current_mode current_mode = mode return f"**Current Mode: {mode.capitalize()}**" # Automatically update recommendation mode when tabs are switched taobao_tab.select( fn=lambda: update_mode("taobao"), inputs=None, outputs=current_mode_text ) physical_tab.select( fn=lambda: update_mode("physical"), inputs=None, outputs=current_mode_text ) # run on hugging face spaces and local machine if __name__ == "__main__": print(f"Using API_HOST: {os.getenv('API_HOST', 'github')}") print(f"Using GITHUB_MODEL: {os.getenv('GITHUB_MODEL', 'gpt-4o')}") # Detect running environment if os.getenv('SPACE_ID'): # Hugging Face Spaces environment demo.launch() else: # Local environment os.environ["HTTP_PROXY"] = "http://127.0.0.1:2802" os.environ["HTTPS_PROXY"] = "http://127.0.0.1:2802" os.environ["NO_PROXY"] = "127.0.0.1,localhost" # Local run configuration is_share = False # Can be modified as needed server_name = "0.0.0.0" if is_share else "127.0.0.1" # Add a static file service to make uploads directory accessible via web demo.queue().launch( server_name=server_name, share=is_share, show_error=True, favicon_path=None, # Set static file directory root_path="/" )