Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,12 +9,15 @@ import plotly.express as px
|
|
| 9 |
from plotly.subplots import make_subplots
|
| 10 |
import plotly.graph_objects as go
|
| 11 |
from datetime import date, timedelta
|
|
|
|
| 12 |
|
| 13 |
# =====================================================
|
| 14 |
# CONFIG / SECRETS
|
| 15 |
# =====================================================
|
| 16 |
API_TOKEN = os.getenv("LEADFEEDER_API_TOKEN")
|
| 17 |
APP_PASSWORD = os.getenv("APP_PASSWORD")
|
|
|
|
|
|
|
| 18 |
|
| 19 |
if not API_TOKEN or not APP_PASSWORD:
|
| 20 |
print("⚠️ WARNING: Secrets missing. App will launch but API calls will fail.")
|
|
@@ -64,15 +67,6 @@ def load_campaign_rules():
|
|
| 64 |
return DEFAULT_CAMPAIGNS
|
| 65 |
return DEFAULT_CAMPAIGNS
|
| 66 |
|
| 67 |
-
def save_campaign_rules(rules_text):
|
| 68 |
-
try:
|
| 69 |
-
rules = json.loads(rules_text)
|
| 70 |
-
with open(CAMPAIGN_CONFIG_FILE, "w") as f:
|
| 71 |
-
json.dump(rules, f, indent=4)
|
| 72 |
-
return "✅ Configuration Saved!", rules
|
| 73 |
-
except json.JSONDecodeError:
|
| 74 |
-
return "❌ Error: Invalid JSON.", None
|
| 75 |
-
|
| 76 |
def categorize_quality(score):
|
| 77 |
if pd.isna(score): return "Unknown"
|
| 78 |
try:
|
|
@@ -101,30 +95,23 @@ def apply_business_logic(df):
|
|
| 101 |
if "lead_quality_score" in df.columns:
|
| 102 |
df["Quality_Group"] = df["lead_quality_score"].apply(categorize_quality)
|
| 103 |
|
| 104 |
-
# 2. Campaign Logic (
|
| 105 |
rules = load_campaign_rules()
|
| 106 |
|
| 107 |
def resolve_campaign(row):
|
| 108 |
-
#
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
#
|
| 113 |
-
if
|
| 114 |
-
match = get_campaign_match(
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
-
# SCENARIO 2: The path is missing (unenriched preview) OR it's a generic Homepage
|
| 118 |
-
# Here we fallback to website and industry so your dashboard actually populates!
|
| 119 |
-
website = str(row.get("website_url", "")).strip().lower()
|
| 120 |
-
industry = str(row.get("primary_industry", "")).strip().lower()
|
| 121 |
-
|
| 122 |
-
match = get_campaign_match(website, rules)
|
| 123 |
-
if match: return match
|
| 124 |
-
|
| 125 |
-
match = get_campaign_match(industry, rules)
|
| 126 |
-
if match: return match
|
| 127 |
-
|
| 128 |
return "Uncategorized"
|
| 129 |
|
| 130 |
df["Campaign"] = df.apply(resolve_campaign, axis=1)
|
|
@@ -491,7 +478,6 @@ with gr.Blocks(title="Leadfeeder Campaign Pro") as demo:
|
|
| 491 |
# --- TAB 4: SETTINGS ---
|
| 492 |
with gr.Tab("⚙️ Campaign Settings"):
|
| 493 |
gr.Markdown("### Manage Campaign Groups")
|
| 494 |
-
gr.Markdown("Select a campaign below and paste your keywords separated by commas.")
|
| 495 |
|
| 496 |
init_rules = load_campaign_rules()
|
| 497 |
camp_choices = list(init_rules.keys()) + ["+ Create New Campaign"]
|
|
@@ -513,7 +499,7 @@ with gr.Blocks(title="Leadfeeder Campaign Pro") as demo:
|
|
| 513 |
|
| 514 |
camp_dropdown.change(update_ui_on_select, inputs=[camp_dropdown], outputs=[new_camp_name, kw_input])
|
| 515 |
|
| 516 |
-
save_config_btn = gr.Button("💾 Save Configuration", variant="primary")
|
| 517 |
config_status = gr.Markdown()
|
| 518 |
|
| 519 |
def save_easy_config(selected_camp, new_name, kw_string):
|
|
@@ -527,12 +513,33 @@ with gr.Blocks(title="Leadfeeder Campaign Pro") as demo:
|
|
| 527 |
if not target_camp:
|
| 528 |
return "❌ Error: Campaign name cannot be empty.", gr.update()
|
| 529 |
|
|
|
|
| 530 |
rules[target_camp] = clean_kws
|
|
|
|
|
|
|
| 531 |
with open(CAMPAIGN_CONFIG_FILE, "w") as f:
|
| 532 |
json.dump(rules, f, indent=4)
|
| 533 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
updated_choices = list(rules.keys()) + ["+ Create New Campaign"]
|
| 535 |
-
return
|
| 536 |
|
| 537 |
save_config_btn.click(save_easy_config, inputs=[camp_dropdown, new_camp_name, kw_input], outputs=[config_status, camp_dropdown])
|
| 538 |
|
|
|
|
| 9 |
from plotly.subplots import make_subplots
|
| 10 |
import plotly.graph_objects as go
|
| 11 |
from datetime import date, timedelta
|
| 12 |
+
from huggingface_hub import HfApi # ADDED: For permanent saving to Hugging Face
|
| 13 |
|
| 14 |
# =====================================================
|
| 15 |
# CONFIG / SECRETS
|
| 16 |
# =====================================================
|
| 17 |
API_TOKEN = os.getenv("LEADFEEDER_API_TOKEN")
|
| 18 |
APP_PASSWORD = os.getenv("APP_PASSWORD")
|
| 19 |
+
HF_TOKEN = os.getenv("HF_TOKEN") # ADDED: Your Hugging Face write token
|
| 20 |
+
SPACE_ID = os.getenv("SPACE_ID") # ADDED: Automatically provided by HF Spaces
|
| 21 |
|
| 22 |
if not API_TOKEN or not APP_PASSWORD:
|
| 23 |
print("⚠️ WARNING: Secrets missing. App will launch but API calls will fail.")
|
|
|
|
| 67 |
return DEFAULT_CAMPAIGNS
|
| 68 |
return DEFAULT_CAMPAIGNS
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
def categorize_quality(score):
|
| 71 |
if pd.isna(score): return "Unknown"
|
| 72 |
try:
|
|
|
|
| 95 |
if "lead_quality_score" in df.columns:
|
| 96 |
df["Quality_Group"] = df["lead_quality_score"].apply(categorize_quality)
|
| 97 |
|
| 98 |
+
# 2. Campaign Logic (Smart Fallback)
|
| 99 |
rules = load_campaign_rules()
|
| 100 |
|
| 101 |
def resolve_campaign(row):
|
| 102 |
+
# Priority 1: Landing Page
|
| 103 |
+
if row.get("landing_page_path"):
|
| 104 |
+
match = get_campaign_match(row["landing_page_path"], rules)
|
| 105 |
+
if match: return match
|
| 106 |
+
# Priority 2: Website URL
|
| 107 |
+
if row.get("website_url"):
|
| 108 |
+
match = get_campaign_match(row["website_url"], rules)
|
| 109 |
+
if match: return match
|
| 110 |
+
# Priority 3: Industry
|
| 111 |
+
if row.get("primary_industry"):
|
| 112 |
+
match = get_campaign_match(row["primary_industry"], rules)
|
| 113 |
+
if match: return match
|
| 114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
return "Uncategorized"
|
| 116 |
|
| 117 |
df["Campaign"] = df.apply(resolve_campaign, axis=1)
|
|
|
|
| 478 |
# --- TAB 4: SETTINGS ---
|
| 479 |
with gr.Tab("⚙️ Campaign Settings"):
|
| 480 |
gr.Markdown("### Manage Campaign Groups")
|
|
|
|
| 481 |
|
| 482 |
init_rules = load_campaign_rules()
|
| 483 |
camp_choices = list(init_rules.keys()) + ["+ Create New Campaign"]
|
|
|
|
| 499 |
|
| 500 |
camp_dropdown.change(update_ui_on_select, inputs=[camp_dropdown], outputs=[new_camp_name, kw_input])
|
| 501 |
|
| 502 |
+
save_config_btn = gr.Button("💾 Save Configuration to Hugging Face", variant="primary")
|
| 503 |
config_status = gr.Markdown()
|
| 504 |
|
| 505 |
def save_easy_config(selected_camp, new_name, kw_string):
|
|
|
|
| 513 |
if not target_camp:
|
| 514 |
return "❌ Error: Campaign name cannot be empty.", gr.update()
|
| 515 |
|
| 516 |
+
# 1. Update the dictionary
|
| 517 |
rules[target_camp] = clean_kws
|
| 518 |
+
|
| 519 |
+
# 2. Save file locally first
|
| 520 |
with open(CAMPAIGN_CONFIG_FILE, "w") as f:
|
| 521 |
json.dump(rules, f, indent=4)
|
| 522 |
|
| 523 |
+
status_message = f"✅ Saved locally! Updated keywords for '{target_camp}'."
|
| 524 |
+
|
| 525 |
+
# 3. Push to Hugging Face automatically
|
| 526 |
+
if HF_TOKEN and SPACE_ID:
|
| 527 |
+
try:
|
| 528 |
+
api = HfApi(token=HF_TOKEN)
|
| 529 |
+
api.upload_file(
|
| 530 |
+
path_or_fileobj=CAMPAIGN_CONFIG_FILE,
|
| 531 |
+
path_in_repo=CAMPAIGN_CONFIG_FILE,
|
| 532 |
+
repo_id=SPACE_ID,
|
| 533 |
+
repo_type="space"
|
| 534 |
+
)
|
| 535 |
+
status_message = f"✅ Saved securely to Hugging Face Cloud! Updated keywords for '{target_camp}'."
|
| 536 |
+
except Exception as e:
|
| 537 |
+
status_message = f"⚠️ Saved locally, but failed to push to Hugging Face (Check HF_TOKEN). Error: {e}"
|
| 538 |
+
elif not HF_TOKEN:
|
| 539 |
+
status_message = f"⚠️ Saved locally. To make this permanent on Hugging Face, add an HF_TOKEN secret in your Space settings."
|
| 540 |
+
|
| 541 |
updated_choices = list(rules.keys()) + ["+ Create New Campaign"]
|
| 542 |
+
return status_message, gr.update(choices=updated_choices, value=target_camp)
|
| 543 |
|
| 544 |
save_config_btn.click(save_easy_config, inputs=[camp_dropdown, new_camp_name, kw_input], outputs=[config_status, camp_dropdown])
|
| 545 |
|