Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- Dockerfile +6 -0
- ai_engine.py +38 -0
- database.py +13 -0
- main.py +56 -0
- requirements.txt +8 -0
- scraper.py +9 -0
Dockerfile
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9-slim
|
| 2 |
+
WORKDIR /app
|
| 3 |
+
COPY requirements.txt .
|
| 4 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 5 |
+
COPY . .
|
| 6 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
ai_engine.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import os
|
| 3 |
+
import json
|
| 4 |
+
|
| 5 |
+
# Configure the Gemini API
|
| 6 |
+
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
|
| 7 |
+
|
| 8 |
+
# Initialize the model (Flash is the free, fast model)
|
| 9 |
+
model = genai.GenerativeModel(
|
| 10 |
+
model_name='gemini-1.5-flash',
|
| 11 |
+
generation_config={"response_mime_type": "application/json"} # Forces perfect JSON!
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
def analyze_lead(name: str, company: str, company_summary: str) -> dict:
|
| 15 |
+
prompt = f"""
|
| 16 |
+
You are a B2B sales expert. Analyze this lead.
|
| 17 |
+
Lead: {name} at {company}. Info: {company_summary}
|
| 18 |
+
|
| 19 |
+
Return ONLY this JSON structure:
|
| 20 |
+
{{
|
| 21 |
+
"score": <integer 1-10>,
|
| 22 |
+
"score_reason": "<one sentence why this score>",
|
| 23 |
+
"cold_email": "<a 100-word personalized cold email pitching AI automation services. Start with Hi {name},. End by asking for a 10-min chat.>"
|
| 24 |
+
}}
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
response = model.generate_content(prompt)
|
| 29 |
+
# Because we forced JSON mime type, this is 100% safe to parse
|
| 30 |
+
return json.loads(response.text)
|
| 31 |
+
|
| 32 |
+
except Exception as e:
|
| 33 |
+
# Fallback if something goes wrong
|
| 34 |
+
return {
|
| 35 |
+
"score": 0,
|
| 36 |
+
"score_reason": f"AI Error: {str(e)}",
|
| 37 |
+
"cold_email": "Error generating email."
|
| 38 |
+
}
|
database.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from supabase import create_client
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
supabase = create_client(os.getenv("SUPABASE_URL", ""), os.getenv("SUPABASE_KEY", ""))
|
| 5 |
+
|
| 6 |
+
def save_lead(data: dict):
|
| 7 |
+
return supabase.table("leads").insert(data).execute()
|
| 8 |
+
|
| 9 |
+
def get_pending_leads():
|
| 10 |
+
return supabase.table("leads").select("*").eq("status", "pending").execute().data
|
| 11 |
+
|
| 12 |
+
def update_lead_status(lead_id: int, status: str):
|
| 13 |
+
return supabase.table("leads").update({"status": status}).eq("id", lead_id).execute()
|
main.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, UploadFile, File
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import io
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
from scraper import scrape_company
|
| 9 |
+
from ai_engine import analyze_lead
|
| 10 |
+
from database import save_lead, get_pending_leads, update_lead_status
|
| 11 |
+
|
| 12 |
+
load_dotenv()
|
| 13 |
+
|
| 14 |
+
app = FastAPI()
|
| 15 |
+
|
| 16 |
+
app.add_middleware(
|
| 17 |
+
CORSMiddleware,
|
| 18 |
+
allow_origins=["*"], # Allow all origins for Vercel integration
|
| 19 |
+
allow_credentials=True,
|
| 20 |
+
allow_methods=["*"],
|
| 21 |
+
allow_headers=["*"],
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
@app.post("/process-csv")
|
| 25 |
+
async def process_csv(file: UploadFile = File(...)):
|
| 26 |
+
contents = await file.read()
|
| 27 |
+
df = pd.read_csv(io.BytesIO(contents))
|
| 28 |
+
|
| 29 |
+
for _, row in df.iterrows():
|
| 30 |
+
summary = scrape_company(row["url"])
|
| 31 |
+
result = analyze_lead(row["name"], row["company"], summary)
|
| 32 |
+
|
| 33 |
+
save_lead({
|
| 34 |
+
"name": row["name"],
|
| 35 |
+
"company": row["company"],
|
| 36 |
+
"url": row["url"],
|
| 37 |
+
"email": row.get("email", ""),
|
| 38 |
+
"company_summary": summary,
|
| 39 |
+
"score": result["score"],
|
| 40 |
+
"score_reason": result["score_reason"],
|
| 41 |
+
"cold_email": result["cold_email"]
|
| 42 |
+
})
|
| 43 |
+
return {"status": "success"}
|
| 44 |
+
|
| 45 |
+
@app.get("/get-leads")
|
| 46 |
+
def fetch_leads():
|
| 47 |
+
return get_pending_leads()
|
| 48 |
+
|
| 49 |
+
@app.post("/update-lead/{lead_id}")
|
| 50 |
+
def update_lead(lead_id: int, status: str = "approved"):
|
| 51 |
+
update_lead_status(lead_id, status)
|
| 52 |
+
return {"status": "updated"}
|
| 53 |
+
|
| 54 |
+
@app.get("/")
|
| 55 |
+
def read_root():
|
| 56 |
+
return {"message": "Lead Qualifier API is running!"}
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
google-generativeai
|
| 4 |
+
supabase
|
| 5 |
+
requests
|
| 6 |
+
python-dotenv
|
| 7 |
+
python-multipart
|
| 8 |
+
pandas
|
scraper.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
|
| 3 |
+
def scrape_company(url: str) -> str:
|
| 4 |
+
try:
|
| 5 |
+
# Using Jina AI reader to bypass JS blocking and get clean markdown
|
| 6 |
+
res = requests.get(f"https://r.jina.ai/{url}", timeout=15)
|
| 7 |
+
return res.text[:800] if res.status_code == 200 else "No description found."
|
| 8 |
+
except Exception as e:
|
| 9 |
+
return f"Scraping error: {str(e)}"
|