Spaces:
Runtime error
Runtime error
| import os | |
| import cv2 | |
| import base64 | |
| import requests | |
| import json | |
| import argparse | |
| import re | |
| import sys | |
| # --------------------------- | |
| # CONFIGURATION | |
| # --------------------------- | |
| API_URL = "https://openrouter.ai/api/v1/chat/completions" | |
| MODEL_NAME = os.environ.get("OPENROUTER_MODEL", "nvidia/nemotron-nano-12b-v2-vl:free") | |
| MEN_HAIRSTYLES = [ | |
| "Buzz Cut", "Crew Cut", "Fade", "Undercut", | |
| "Slick Back", "Side Part", "Quiff", "Pompadour", | |
| "French Crop", "Textured Fringe" | |
| ] | |
| def build_prompt(n): | |
| """Builds a prompt that instructs the model to return a JSON array | |
| containing exactly `n` hairstyle names (strings) chosen from the | |
| `MEN_HAIRSTYLES` list. The model must output ONLY the JSON array. | |
| """ | |
| styles_list = ', '.join(MEN_HAIRSTYLES) | |
| return f""" | |
| You are a professional MEN'S hairstylist. | |
| TASK: | |
| From the provided image, select the best {n} hairstyle names for this man. | |
| RULES: | |
| - Choose ONLY from this list: {styles_list} | |
| - Output MUST be valid JSON: an array of {n} strings, for example ["Buzz Cut", "Fade"] | |
| - Do NOT include explanations, reasons, or any extra text—ONLY the JSON array. | |
| """ | |
| def extract_styles_from_text(text, n): | |
| """Try to extract an ordered list of up to `n` hairstyles from text. | |
| First try to parse JSON; if that fails, search for known labels. | |
| """ | |
| text = text.strip() | |
| # Try JSON | |
| try: | |
| parsed = json.loads(text) | |
| if isinstance(parsed, list): | |
| result = [s for s in parsed if isinstance(s, str) and s in MEN_HAIRSTYLES] | |
| return result[:n] | |
| except Exception: | |
| pass | |
| # Fallback: find known hairstyles in order of appearance | |
| found = [] | |
| for m in re.finditer(r"\b(" + '|'.join(re.escape(s) for s in MEN_HAIRSTYLES) + r")\b", text, flags=re.IGNORECASE): | |
| name = m.group(0) | |
| # normalize to canonical casing from MEN_HAIRSTYLES | |
| for s in MEN_HAIRSTYLES: | |
| if name.lower() == s.lower() and s not in found: | |
| found.append(s) | |
| break | |
| if len(found) >= n: | |
| break | |
| return found[:n] | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Get hairstyle recommendations via OpenRouter API") | |
| parser.add_argument("--image", "-i", help="Path to image file") | |
| parser.add_argument("--n", "-n", type=int, default=1, help="Number of styles to return (default 1)") | |
| args = parser.parse_args() | |
| image_path = args.image or input("📂 Enter full path to the image file: ").strip() | |
| if not image_path: | |
| print("No image path provided.") | |
| sys.exit(1) | |
| image = cv2.imread(image_path) | |
| if image is None: | |
| print("Failed to read image.") | |
| sys.exit(1) | |
| _, buffer = cv2.imencode(".png", image) | |
| image_b64 = base64.b64encode(buffer).decode("utf-8") | |
| api_key = "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" | |
| prompt = build_prompt(args.n) | |
| payload = { | |
| "model": MODEL_NAME, | |
| "messages": [ | |
| { | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": prompt}, | |
| {"type": "image_url", "image_url": f"data:image/png;base64,{image_b64}"} | |
| ] | |
| } | |
| ], | |
| "temperature": 0.0, | |
| "max_tokens": 300 | |
| } | |
| headers = { | |
| "Authorization": f"Bearer {api_key}", | |
| "Content-Type": "application/json", | |
| "Accept": "application/json" | |
| } | |
| resp = requests.post(API_URL, headers=headers, json=payload, timeout=60) | |
| if resp.status_code != 200: | |
| print(f"API ERROR {resp.status_code}: {resp.text}") | |
| sys.exit(1) | |
| data = resp.json() | |
| try: | |
| message = data["choices"][0]["message"] | |
| except Exception: | |
| print("Unexpected API response format:", json.dumps(data)) | |
| sys.exit(1) | |
| # message.content might be a string or a list; normalize to text | |
| text_content = "" | |
| content = message.get("content") | |
| if isinstance(content, str): | |
| text_content = content | |
| elif isinstance(content, list): | |
| parts = [] | |
| for item in content: | |
| if isinstance(item, dict) and item.get("type") == "text": | |
| parts.append(item.get("text", "")) | |
| elif isinstance(item, str): | |
| parts.append(item) | |
| text_content = "\n".join(parts) | |
| else: | |
| text_content = str(content) | |
| styles = extract_styles_from_text(text_content, args.n) | |
| if not styles: | |
| # try reasoning_details fallback | |
| reasoning = message.get("reasoning_details") or [] | |
| if reasoning and isinstance(reasoning, list): | |
| rd_text = reasoning[0].get("text", "") | |
| styles = extract_styles_from_text(rd_text, args.n) | |
| # Output just the recommended styles as a JSON array (no extra text) | |
| print(json.dumps(styles)) | |
| if __name__ == "__main__": | |
| main() | |