Download app.py from Crackershoot/CompleteProfileAI: direct link, hf CLI and curl.
- Browser
- Download file 11.4 kB
-
https://huggingface.co/spaces/Crackershoot/CompleteProfileAI/resolve/main/app.py
- Command line
-
hf download hf://spaces/Crackershoot/CompleteProfileAI/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Crackershoot/CompleteProfileAI/resolve/main/app.py
11.4 kB
| import os | |
| import io | |
| import json | |
| import random | |
| import logging | |
| from PIL import Image, ImageDraw | |
| import numpy as np | |
| import requests | |
| import gradio as gr | |
| # Force CPU execution configurations for PyTorch to prevent memory bloat on HF Spaces | |
| os.environ["OMP_NUM_THREADS"] = "1" | |
| os.environ["MKL_NUM_THREADS"] = "1" | |
| import torch | |
| from torchvision import transforms | |
| # --- Logging Setup --- | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") | |
| logger = logging.getLogger("CompleteProfileAI") | |
| # --- In-Memory ML Model Initialization --- | |
| DEVICE = "cpu" | |
| BIREFNET_MODEL = None | |
| try: | |
| from transformers import AutoModelForImageSegmentation | |
| logger.info("Initializing BiRefNet on CPU...") | |
| # Loading the official lightweight/general BiRefNet weights | |
| BIREFNET_MODEL = AutoModelForImageSegmentation.from_pretrained( | |
| "ZhengPeng7/BiRefNet", | |
| trust_remote_code=True | |
| ) | |
| BIREFNET_MODEL.to(DEVICE) | |
| BIREFNET_MODEL.eval() | |
| logger.info("BiRefNet successfully loaded and optimized for CPU.") | |
| except Exception as e: | |
| logger.error(f"Failed to load local BiRefNet model: {e}. Falling back to transparent bypass.") | |
| # --- Helper 1: Programmatic Studio Gradient Backdrops --- | |
| def create_gradient_backdrop(style="neutral_gray", size=(1024, 1024)): | |
| """Generates beautiful, professional linear gradient canvases directly in-memory.""" | |
| width, height = size | |
| base = Image.new("RGB", size) | |
| if style == "office_blue": | |
| color1 = (15, 32, 67) # Deep Corporate Navy | |
| color2 = (44, 83, 130) # Clean Soft Slate Blue | |
| elif style == "soft_teal": | |
| color1 = (11, 40, 41) # Dark Forest Teal | |
| color2 = (41, 108, 104) # Warm Modern Muted Teal | |
| else: # neutral_gray | |
| color1 = (25, 25, 25) # Rich Charcoal | |
| color2 = (85, 85, 85) # Soft Studio Medium Gray | |
| for y in range(height): | |
| ratio = y / height | |
| r = int(color1[0] * (1 - ratio) + color2[0] * ratio) | |
| g = int(color1 * (1 - ratio) + color2 * ratio) | |
| b = int(color1 * (1 - ratio) + color2 * ratio) | |
| # Apply the row color efficiently | |
| for x in range(width): | |
| base.putpixel((x, y), (r, g, b)) | |
| return base | |
| # --- Helper 2: Programmatic Abstract Fallback Banners --- | |
| def generate_fallback_banner(industry, color_palette_name): | |
| """Generates a beautiful geometric abstract banner programmatically if the API is offline.""" | |
| size = (1584, 396) | |
| image = Image.new("RGB", size) | |
| draw = ImageDraw.Draw(image) | |
| # Established Corporate Brand Palettes | |
| palettes = { | |
| "Corporate Blue": ((15, 32, 67), (44, 83, 130), (100, 149, 237)), | |
| "Creative Teal": ((11, 40, 41), (41, 108, 104), (127, 255, 212)), | |
| "Tech Slate": ((15, 15, 15), (50, 50, 60), (150, 150, 160)), | |
| "Creative Amber": ((60, 30, 10), (120, 60, 20), (255, 191, 0)), | |
| } | |
| colors = palettes.get(color_palette_name, palettes["Corporate Blue"]) | |
| c1, c2, c3 = colors | |
| # Set background gradient | |
| for y in range(396): | |
| ratio = y / 396 | |
| r = int(c1[0] * (1 - ratio) + c2[0] * ratio) | |
| g = int(c1 * (1 - ratio) + c2 * ratio) | |
| b = int(c1 * (1 - ratio) + c2 * ratio) | |
| draw.line([(0, y), (1584, y)], fill=(r, g, b)) | |
| # Generate abstract overlapping translucent shapes seeded by industry | |
| random.seed(hash(industry)) | |
| for _ in range(12): | |
| x1 = random.randint(0, 1584) | |
| y1 = random.randint(0, 396) | |
| x2 = x1 + random.randint(100, 450) | |
| y2 = y1 + random.randint(50, 300) | |
| x3 = x1 + random.randint(-200, 200) | |
| y3 = y1 + random.randint(-150, 150) | |
| # Overlay translucent polygon on top of the base | |
| shape_img = Image.new("RGBA", size) | |
| shape_draw = ImageDraw.Draw(shape_img) | |
| fill_color = random.choice([c2, c3]) + (random.randint(25, 75),) # RGB + Alpha | |
| shape_draw.polygon([(x1, y1), (x2, y2), (x3, y3)], fill=fill_color) | |
| image = Image.alpha_composite(image.convert("RGBA"), shape_img).convert("RGB") | |
| return image | |
| # --- CORE FUNCTION 1: AI Career Journalist (Tab 1) --- | |
| def optimize_linkedin_text(target_role, core_skills, achievement, style): | |
| api_key = os.environ.get("OPENAI_API_KEY") | |
| if not api_key: | |
| logger.warning("OPENAI_API_KEY environment variable not configured.") | |
| return ( | |
| "OpenAI API Key is missing. Please add it to your Space Secrets in settings.", | |
| "Please configure your OPENAI_API_KEY setting inside Hugging Face Secrets to activate the copywriter AI.", | |
| ["No Key Added"] | |
| ) | |
| # Initialize OpenAI Client (Compatible with v1.0.0+) | |
| from openai import OpenAI | |
| client = OpenAI(api_key=api_key) | |
| system_prompt = """You are the "AI Career Journalist," an elite Executive Recruiter and world-class LinkedIn Copywriter. Your mission is to interview job seekers and translate their messy, unstructured, conversational raw inputs into compelling, high-converting, and keyword-optimized LinkedIn profiles. | |
| Your copywriting philosophy: | |
| 1. Cut the Fluff: Avoid generic corporate corporate-speak. Be concrete. | |
| 2. Quantify Impact: Turn passive duties into active, measurable achievements. | |
| 3. Keep it Human: Write in a natural, professional first-person tone ("I am...", "I lead...") that sounds like a confident professional, not an LLM. | |
| You must strictly output your response in valid JSON format matching the schema requested. Do not write conversational preambles or postscripts.""" | |
| task_prompt = f"""Transform the following conversational user inputs into a polished LinkedIn Headline, "About" Summary, and Keyword List. | |
| ### Few-Shot Example: | |
| - USER INPUTS: | |
| * Target Role: Junior Software Engineer | |
| * Core Skills: Python, React, PostgreSQL, Git | |
| * Major Achievement: Built a campus tutoring app that was used by 300 students to schedule sessions. | |
| * Working Style: Collaborative, analytical, eager to solve complex logic. | |
| - AI OUTPUT JSON: | |
| {{ | |
| "headline": "Junior Software Engineer | Python & React | Building Impact-Driven Web Solutions", | |
| "summary": "I am a software engineer focused on building highly functional, user-centric web applications. My passion lies in translating complex logic into clean, performant code.\\n\\nRecently, I developed a campus tutoring scheduler using Python and React, which successfully streamlined session booking for over 300 active student users. I thrive in collaborative environments where continuous learning and analytical problem-solving are valued.\\n\\nSpecialties: Python, JavaScript (React), SQL (PostgreSQL), Git, API Integration, and Agile Methodologies.", | |
| "extracted_keywords": ["Software Engineering", "Full-Stack Development", "Python", "React.js", "PostgreSQL", "Database Design", "Agile Methodologies"], | |
| "status": "success", | |
| "error_message": "" | |
| }} | |
| ### Live Task: | |
| - USER INPUTS: | |
| * Target Role: {target_role} | |
| * Core Skills: {core_skills} | |
| * Major Achievement: {achievement} | |
| * Working Style: {style} | |
| Generate the JSON response following the exact schema shown in the examples. Your output MUST be pure JSON with no markdown wrapping (i.e. no ```json).""" | |
| try: | |
| response = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| response_format={ "type": "json_object" }, # Force structured JSON format | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": task_prompt} | |
| ], | |
| timeout=30 | |
| ) | |
| content = response.choices[0].message.content | |
| data = json.loads(content) | |
| if data.get("status") == "error": | |
| return "", data.get("error_message", "Error compiling data."), [] | |
| return data["headline"], data["summary"], data["extracted_keywords"] | |
| except Exception as e: | |
| logger.error(f"Error during OpenAI copy generation: {e}") | |
| return ( | |
| f"{target_role} | {core_skills.split(',')[0] if core_skills else 'Professional'}", | |
| f"An error occurred while calling the OpenAI service: {e}. Please ensure your API secrets are configured correctly.", | |
| [s.strip() for s in core_skills.split(",")] if core_skills else [] | |
| ) | |
| # --- CORE FUNCTION 2: Studio Headshot background Remover (Tab 2) --- | |
| def process_headshot(image, bg_type, gradient_preset, solid_color): | |
| if image is None: | |
| return None | |
| # Step 1: Preprocess size to safeguard against CPU RAM overflow | |
| max_size = 1024 | |
| w, h = image.size | |
| if w > max_size or h > max_size: | |
| ratio = min(max_size / w, max_size / h) | |
| new_size = (int(w * ratio), int(h * ratio)) | |
| image = image.resize(new_size, Image.Resampling.LANCZOS) | |
| logger.info(f"Resized input image to safe processing boundaries: {new_size}") | |
| orig_w, orig_h = image.size | |
| # Step 2: Extract Subject using Local BiRefNet Model | |
| if BIREFNET_MODEL is None: | |
| # Transparent Bypass Fallback if PyTorch fails to load | |
| logger.warning("BiRefNet uninitialized. Bypassing background extraction.") | |
| cut_subject = image.convert("RGBA") | |
| else: | |
| try: | |
| logger.info("Starting BiRefNet background segmentation loop...") | |
| # Normalize and prepare input tensor | |
| transform_image = transforms.Compose([ | |
| transforms.Resize((1024, 1024)), | |
| transforms.ToTensor(), | |
| transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) | |
| ]) | |
| img_rgb = image.convert("RGB") | |
| input_tensor = transform_image(img_rgb).unsqueeze(0).to(DEVICE) | |
| with torch.no_grad(): | |
| outputs = BIREFNET_MODEL(input_tensor) | |
| # Defensively unpack outputs based on tensor structure | |
| if hasattr(outputs, "logits"): | |
| pred = outputs.logits[-1] if isinstance(outputs.logits, list) else outputs.logits | |
| elif isinstance(outputs, (list, tuple)): | |
| pred = outputs[-1] | |
| else: | |
| pred = outputs | |
| if len(pred.shape) == 4: | |
| pred = pred.squeeze(0).squeeze(0) | |
| elif len(pred.shape) == 3: | |
| pred = pred.squeeze(0) | |
| # Generate alpha mask through sigmoid mapping | |
| pred = torch.sigmoid(pred).cpu().numpy() | |
| # Resize binary mask back to original bounds | |
| mask = Image.fromarray((pred * 255).astype(np.uint8)).resize((orig_w, orig_h), Image.Resampling.BILINEAR) | |
| cut_subject = image.convert("RGBA") | |
| cut_subject.putalpha(mask) | |
| logger.info("Successfully extracted headshot foreground subject.") | |
| except Exception as e: | |
| logger.error(f"Error during BiRefNet execution: {e}") | |
| cut_subject = image.convert("RGBA") | |
| # Step 3: Overlay Foreground onto Selected Studio Backdrop | |
| if bg_type == "Solid Color": | |
| hex_color = solid_color.lstrip('#') | |
| rgb_color = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) | |
| background = Image.new("RGB" | |