diff --git a/app/replicate_ai.py b/app/replicate_ai.py index 310c33b..e6d3c7d 100644 --- a/app/replicate_ai.py +++ b/app/replicate_ai.py @@ -141,31 +141,57 @@ Profiles found on site: {social_str} === WEB SEARCH RESULTS (use to find contacts, verify business identity) === {(search_results or "No results.")[:600]} -=== INSTRUCTIONS === -The client sells: web redesign, SEO, hosting migration, SSL renewal, -security audits, GDPR compliance, accessibility fixes, Google Ads, -maintenance contracts, AI tools for SMEs, GMB setup, social media management. +=== WHO WE ARE === +We are a full-service digital agency. We handle EVERYTHING web-related for SMEs: +new website builds, redesigns, landing pages, e-commerce, CMS migrations, speed +optimisation, mobile responsiveness, SSL/security, SEO (on-page + technical + +local), Google Ads, Google My Business setup & optimisation, social media +management (Instagram, Facebook, LinkedIn, TikTok), GDPR compliance, cookie +banners, accessibility fixes, hosting migrations, email setup, maintenance +contracts, and AI-powered tools. No job is too small or too large. -RULES — you MUST follow all of these: -1. A placeholder / minimal / blank site (few words, no images, no CMS) is one of - the BEST leads — they need a complete website build + all digital services. - Score it lead_quality=HOT or WARM and write an enthusiastic pitch. -2. pitch_angle is MANDATORY. Never leave it empty. Write 1 punchy Spanish sentence - tailored to the business type. Even "Hola, su web necesita una renovación - completa — podemos tenerla lista en 2 semanas." is better than nothing. -3. services_needed must list at LEAST 2 services. For a blank/placeholder site - always include "diseño web" and "posicionamiento SEO". -4. Use the WEB SEARCH RESULTS to find the real phone/email — put the best one - in best_contact_value. -5. Use copyright_year + Last-Modified to estimate site_last_updated. -6. Keep every string value SHORT (≤ 15 words). Arrays: max 4 items. - This keeps the JSON small and avoids truncation. +=== ASSESSMENT RULES === +Look at EVERY aspect of the site — quality, age, CMS, performance, SEO, GDPR, +social presence, GMB, contacts, hosting — and identify ALL the problems AND +opportunities. Then build the pitch around the most compelling angle for THIS +specific business. + +Lead scoring guide: +• HOT — blank/placeholder site, or ≥3 serious issues (expired SSL, no SEO, + no mobile, lorem ipsum, non-EU hosting, no GDPR, no social, site >3 yrs old) +• WARM — functional but clearly outdated or missing 1-2 key services +• COLD — modern, well-maintained site with few obvious gaps + +MANDATORY for EVERY assessment — no exceptions: +1. pitch_angle: A single, compelling cold-outreach sentence in Spanish, personalised + to this specific business name/type and its biggest weakness. Reference the actual + problem. Examples of good pitches: + - "Hola Salom Manacor, su web lleva sin actualizarse desde 2019 — en 3 semanas + le entregamos una nueva web con ficha en Google, redes sociales y posicionamiento + incluidos." + - "Detectamos que la web de [Negocio] no aparece en Google Maps ni tiene perfil + en Instagram — podemos solucionarlo esta semana." + - "Su certificado SSL vence en 12 días y su web no tiene aviso de cookies legal — + evite multas y pérdida de visitas con nuestro plan de mantenimiento." +2. outreach_email: A 3-4 sentence ready-to-send email in Spanish. First sentence + names the business and the most urgent problem. Second sentence explains the + impact (losing clients, Google ranking, legal risk). Third sentence introduces us + as the solution. Close with a call to action (llamada de 15 min, presupuesto + gratuito). Sign off: "Un saludo, [Agencia Digital]". +3. email_subject: A short, specific Spanish email subject line referencing the + business name and main issue (e.g. "Web de Salom Manacor — propuesta de mejora"). +4. services_needed: At least 2 specific services from our catalogue. +5. Use WEB SEARCH RESULTS to find real phone/email for best_contact_value. +6. Use copyright_year + Last-Modified to estimate site_last_updated. +7. Keep all string values concise (≤ 20 words each). Arrays: max 4 items. Respond ONLY with valid JSON, no markdown fences, no text outside the JSON: {{ "lead_quality": "HOT|WARM|COLD", - "lead_reasoning": "1-2 sentences why", - "pitch_angle": "1 punchy cold-outreach sentence in Spanish — NEVER empty", + "lead_reasoning": "1-2 sentences", + "pitch_angle": "1 punchy sentence in Spanish referencing the specific business problem", + "outreach_email": "ready-to-send 3-4 sentence email in Spanish", + "email_subject": "specific Spanish subject line", "services_needed": ["service1","service2"], "best_contact_channel": "email|phone|whatsapp|social|web_form|unknown", "best_contact_value": "real email/phone from page or search results", @@ -239,7 +265,7 @@ async def assess_domain(analysis: dict) -> dict: "top_p": 0.9, "temperature": 0.2, "thinking_level": "low", - "max_output_tokens": 4096, + "max_output_tokens": 6000, } } try: diff --git a/app/static/index.html b/app/static/index.html index a899e48..cab9a91 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -237,10 +237,16 @@ tr:hover td{background:rgba(255,255,255,.025)} - +
-
Cold Pitch (ES)
-
+
+
Outreach Email (ES)
+ +
+
+ Subject: +
+
@@ -657,6 +663,15 @@ function app() { }, async restartAiWorker() { await fetch('/api/ai/worker/restart',{method:'POST'}); this.notify('AI worker restarted','info'); await this.loadAiStatus(); }, + copyEmail() { + const subj = this.modal.ai.email_subject ? `Subject: ${this.modal.ai.email_subject}\n\n` : ''; + const body = this.modal.ai.outreach_email || this.modal.ai.pitch_angle || ''; + navigator.clipboard.writeText(subj + body).then( + () => this.notify('Email copied to clipboard', 'success'), + () => this.notify('Copy failed — select text manually', 'error'), + ); + }, + async resetAiStuck() { const r=await fetch('/api/ai/reset',{method:'POST'}); const d=await r.json(); this.notify(`Reset ${d.reset} stuck jobs → pending`,'success'); await this.loadAiStatus(); }, async startEnrich() { await fetch('/api/enrich/resume',{method:'POST'}); this.notify('Worker started','success'); await this.loadQueue(); }, async pauseEnrich() { await fetch('/api/enrich/pause',{method:'POST'}); this.notify('Worker paused','success'); await this.loadQueue(); },