simikkk commited on
Commit
8304a2c
·
verified ·
1 Parent(s): 19afe3a

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +209 -0
app.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import time
4
+ import hashlib
5
+ from collections import defaultdict
6
+ from pdf_parser import extract_text_from_pdf
7
+ from analyzer import analyze_cv
8
+ from report_generator import generate_pdf_report
9
+ from storage import check_license_key
10
+ from webhook import handle_stripe_webhook
11
+
12
+ # ─── Konfigurace ───────────────────────────────────────────────
13
+ STRIPE_SINGLE = os.environ.get("STRIPE_LINK_SINGLE") # Payment Link URL
14
+ STRIPE_PRO = os.environ.get("STRIPE_LINK_PRO") # Payment Link URL
15
+ SANDBOX_PASSWORD = os.environ.get("SANDBOX_PASSWORD") # Tajné heslo pro testování
16
+ IS_SANDBOX = os.environ.get("STRIPE_MODE", "sandbox") == "sandbox"
17
+
18
+ # ─── Rate limiting ──────────────────────────────────────────────
19
+ request_log = defaultdict(list)
20
+
21
+ def check_rate_limit(session_id: str) -> bool:
22
+ now = time.time()
23
+ request_log[session_id] = [t for t in request_log[session_id] if now - t < 3600]
24
+ if len(request_log[session_id]) >= 3:
25
+ return False
26
+ request_log[session_id].append(now)
27
+ return True
28
+
29
+ # ─── Hlavní analýza ────────────────────────────────────────────
30
+ def run_analysis(cv_file, cv_text_input, job_text, license_key, session_id):
31
+ cv_text = ""
32
+ if cv_file is not None:
33
+ cv_text = extract_text_from_pdf(cv_file)
34
+ if not cv_text and cv_text_input:
35
+ cv_text = cv_text_input.strip()
36
+ if not cv_text:
37
+ return "❌ Nahraj CV nebo vlož text.", None, free_upgrade_html()
38
+ if not job_text or len(job_text) < 50:
39
+ return "❌ Vlož popis pozice (alespoň 50 znaků).", None, free_upgrade_html()
40
+
41
+ license_info = check_license_key(license_key)
42
+ is_pro = license_info is not None and license_info.get("active")
43
+
44
+ if not is_pro:
45
+ if not check_rate_limit(session_id):
46
+ return "⚠️ Limit 3 analýzy/hodinu vyčerpán. Upgraduj nebo počkej.", None, upgrade_html()
47
+
48
+ analysis = analyze_cv(cv_text, job_text)
49
+ if "error" in analysis:
50
+ return f"❌ {analysis['error']}", None, ""
51
+
52
+ score = analysis.get("match_score", 0)
53
+ emoji = "🟢" if score >= 75 else "🟡" if score >= 50 else "🔴"
54
+
55
+ md = f"## {emoji} Match Score: {score}/100\n{analysis.get('score_explanation', '')}\n\n---\n\n"
56
+ md += "### 🎯 Tvoje 3 nejdůležitější akce\n"
57
+ for i, a in enumerate(analysis.get("top_3_actions", []), 1):
58
+ md += f"\n**{i}.** {a}"
59
+
60
+ md += "\n\n---\n\n### 🔑 Chybějící klíčová slova\n"
61
+ for kw in analysis.get("missing_keywords", []):
62
+ md += f"\n- **{kw['keyword']}** — {kw['why_it_matters']}"
63
+
64
+ md += "\n\n---\n\n### ✍️ Co posílit (přepiš vlastními slovy)\n"
65
+ for sec in analysis.get("weak_sections", []):
66
+ md += f"\n> *\"{sec.get('original_phrase', '')}\"*\n"
67
+ md += f"- **Problém:** {sec.get('problem', '')}\n"
68
+ md += f"- **Co sdělit:** {sec.get('what_to_convey_instead', '')}\n"
69
+ md += f"- **Směr:** {sec.get('example_direction', '')}\n"
70
+
71
+ md += "\n\n---\n\n### ⚠️ ATS varování\n"
72
+ for w in analysis.get("ats_warnings", []):
73
+ md += f"\n- {w}"
74
+
75
+ md += "\n\n---\n\n### 💼 LinkedIn headline nápady\n"
76
+ for h in analysis.get("linkedin_headline_ideas", []):
77
+ md += f"\n- {h}"
78
+
79
+ if not is_pro:
80
+ md += f"\n\n---\n*Free plán: 3 analýzy/hodinu. [Upgrade na Pro]({STRIPE_PRO})*"
81
+
82
+ pdf_path = generate_pdf_report(analysis, is_pro=is_pro)
83
+ return md, pdf_path, "" if is_pro else free_upgrade_html()
84
+
85
+ # ─── Stripe webhook handler ─────────────────────────────────────
86
+ def process_webhook(payload: str, sig_header: str):
87
+ """Volá se z Gradio endpoint přes gr.Request."""
88
+ result = handle_stripe_webhook(payload.encode(), sig_header)
89
+ if result["ok"] and result.get("key"):
90
+ return f"✅ Klíč vytvořen: {result['key']} pro {result['email']} ({result['plan']})"
91
+ elif result["ok"]:
92
+ return "✅ Event přijat (ignorován)"
93
+ else:
94
+ return f"❌ Chyba: {result['error']}"
95
+
96
+ # ─── Sandbox / testovací mód ────────────────────────────────────
97
+ def sandbox_simulate_payment(password: str, email: str, plan: str):
98
+ """Tajná funkce pro testování — přístup pouze heslem."""
99
+ if not SANDBOX_PASSWORD or password != SANDBOX_PASSWORD:
100
+ return "❌ Nesprávné heslo."
101
+ if not email or "@" not in email:
102
+ return "❌ Zadej platný email."
103
+ from storage import create_license_key
104
+ key = create_license_key(email, plan)
105
+ return f"✅ TEST klíč vytvořen:\n\n`{key}`\n\nPlan: {plan} | Email: {email}"
106
+
107
+ # ─── HTML komponenty ────────────────────────────────────────────
108
+ def free_upgrade_html():
109
+ return f"""
110
+ <div style="background:#f0f9ff;border:1px solid #bae6fd;border-radius:12px;padding:16px;margin-top:8px">
111
+ <p style="margin:0 0 8px;font-weight:600;color:#0369a1">🚀 Free plán: 3 analýzy/hodinu</p>
112
+ <p style="margin:0 0 12px;color:#475569;font-size:14px">Upgraduj pro neomezený přístup a čistý PDF bez watermarku.</p>
113
+ <a href="{STRIPE_SINGLE}" target="_blank" style="background:#0ea5e9;color:white;padding:8px 16px;border-radius:8px;text-decoration:none;margin-right:8px;font-size:14px">Jednorázová analýza — $2.99</a>
114
+ <a href="{STRIPE_PRO}" target="_blank" style="background:#7c3aed;color:white;padding:8px 16px;border-radius:8px;text-decoration:none;font-size:14px">Pro plán — $9.99/měsíc</a>
115
+ </div>"""
116
+
117
+ def upgrade_html():
118
+ return f"""
119
+ <div style="background:#fef3c7;border:1px solid #fcd34d;border-radius:12px;padding:16px;margin-top:8px">
120
+ <p style="margin:0 0 8px;font-weight:600;color:#92400e">⏳ Limit dosažen</p>
121
+ <p style="margin:0 0 12px;color:#475569;font-size:14px">3 analýzy za hodinu vyčerpány. Upgraduj nebo počkej hodinu.</p>
122
+ <a href="{STRIPE_PRO}" target="_blank" style="background:#7c3aed;color:white;padding:8px 16px;border-radius:8px;text-decoration:none;font-size:14px">Upgrade na Pro — $9.99/měsíc</a>
123
+ </div>"""
124
+
125
+ # ─── Gradio UI ──────────────────────────────────────────────────
126
+ with gr.Blocks(
127
+ theme=gr.themes.Soft(primary_hue="blue", secondary_hue="purple"),
128
+ title="ResumeLens AI"
129
+ ) as demo:
130
+
131
+ session_id = gr.State(lambda: str(time.time()))
132
+
133
+ # Sandbox badge (viditelný jen když IS_SANDBOX = True)
134
+ if IS_SANDBOX:
135
+ gr.HTML("""
136
+ <div style="background:#fef3c7;border:1px solid #f59e0b;text-align:center;padding:6px;font-size:13px;font-weight:600;color:#92400e">
137
+ ⚠️ SANDBOX MODUS — Stripe testovací platby aktivní
138
+ </div>""")
139
+
140
+ gr.HTML("""
141
+ <div style="text-align:center;padding:24px 0 8px">
142
+ <h1 style="font-size:2rem;font-weight:700;color:#1e293b;margin:0">🎯 ResumeLens AI</h1>
143
+ <p style="color:#64748b;margin:8px 0 0;font-size:1.1rem">
144
+ Upload your CV. Paste the job. Know exactly what to fix — in your own words.
145
+ </p>
146
+ <p style="color:#94a3b8;font-size:0.85rem;margin:6px 0 0">
147
+ We show you what's missing. You rewrite it yourself. No AI-generated text. ✓
148
+ </p>
149
+ </div>""")
150
+
151
+ with gr.Row():
152
+ with gr.Column(scale=1):
153
+ gr.Markdown("### 1️⃣ Tvoje CV")
154
+ cv_file = gr.File(label="Nahraj PDF", file_types=[".pdf"])
155
+ gr.Markdown("*nebo vlož text:*")
156
+ cv_text_input = gr.Textbox(label="Text CV", placeholder="Sem vlož obsah CV jako text...", lines=6)
157
+ with gr.Column(scale=1):
158
+ gr.Markdown("### 2️⃣ Popis pozice")
159
+ job_text = gr.Textbox(label="Popis pracovní nabídky", placeholder="Zkopíruj celý popis pozice z LinkedInu nebo jobs.cz...", lines=10)
160
+
161
+ with gr.Accordion("🔑 Máš Pro licenci? Zadej klíč", open=False):
162
+ license_key = gr.Textbox(label="Licenční klíč", placeholder="rl_xxxxxxxxxxxxxxxx", type="password")
163
+ gr.Markdown(f"Nemáš klíč? [Kup Pro plán]({STRIPE_PRO}) — klíč dostaneš automaticky emailem po platbě.")
164
+
165
+ analyze_btn = gr.Button("🔍 Analyzovat CV", variant="primary", size="lg")
166
+
167
+ with gr.Row():
168
+ with gr.Column(scale=2):
169
+ result_output = gr.Markdown()
170
+ with gr.Column(scale=1):
171
+ pdf_output = gr.File(label="📄 Stáhnout PDF report")
172
+
173
+ upgrade_output = gr.HTML()
174
+
175
+ analyze_btn.click(
176
+ fn=run_analysis,
177
+ inputs=[cv_file, cv_text_input, job_text, license_key, session_id],
178
+ outputs=[result_output, pdf_output, upgrade_output]
179
+ )
180
+
181
+ # ─── Skrytá sekce pro webhook (přístup přes URL parametr nebo přímé API) ───
182
+ # Tato sekce NENÍ viditelná v UI — slouží jako endpoint
183
+ with gr.Tab("⚙️", visible=False):
184
+ webhook_payload = gr.Textbox(label="Payload")
185
+ webhook_sig = gr.Textbox(label="Stripe-Signature")
186
+ webhook_btn = gr.Button("Process")
187
+ webhook_result = gr.Textbox(label="Result")
188
+ webhook_btn.click(fn=process_webhook, inputs=[webhook_payload, webhook_sig], outputs=[webhook_result])
189
+
190
+ # ─── Sandbox testovací panel (zaheslovaný) ─────────────────────────────────
191
+ with gr.Accordion("🔧 Developer Tools", open=False, visible=True):
192
+ gr.Markdown("*Přístup pouze pro administrátora.*")
193
+ sandbox_pw = gr.Textbox(label="Heslo", type="password", placeholder="Pouze pro vývojáře")
194
+ sandbox_email = gr.Textbox(label="Email zákazníka", placeholder="test@example.com")
195
+ sandbox_plan = gr.Radio(["single", "pro"], label="Plán", value="pro")
196
+ sandbox_btn = gr.Button("🧪 Simulovat platbu a vygenerovat klíč")
197
+ sandbox_result = gr.Textbox(label="Výsledek", interactive=False)
198
+ sandbox_btn.click(
199
+ fn=sandbox_simulate_payment,
200
+ inputs=[sandbox_pw, sandbox_email, sandbox_plan],
201
+ outputs=[sandbox_result]
202
+ )
203
+
204
+ gr.HTML("""
205
+ <div style="text-align:center;padding:20px 0 8px;border-top:1px solid #e2e8f0;margin-top:24px">
206
+ <p style="color:#94a3b8;font-size:12px">ResumeLens AI · Free tier: 3 analyses/hour · Your CV data is never stored</p>
207
+ </div>""")
208
+
209
+ demo.launch()