FitMe-Agent / src /app.py
Ruyi Yang
Updated with offline image function
2f14ff0
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="/"
)