mirror of
https://git.coastit.co.za/caitlin/CRM_ENVR.git
synced 2026-07-10 11:15:43 +02:00
Merge branch 'main' of https://git.coastit.co.za/caitlin/CRM_ENVR
This commit is contained in:
+61
-14
@@ -534,6 +534,19 @@ FB_SEARCHES = [
|
|||||||
"who can design a website for my",
|
"who can design a website for my",
|
||||||
"looking for someone to create a website",
|
"looking for someone to create a website",
|
||||||
"recommendations for a web designer",
|
"recommendations for a web designer",
|
||||||
|
"I need a website",
|
||||||
|
"I need a website built",
|
||||||
|
"need wordpress site",
|
||||||
|
"need a custom website",
|
||||||
|
"I need a new website",
|
||||||
|
"need an online store",
|
||||||
|
"need an online shop",
|
||||||
|
"need website help",
|
||||||
|
"who can help me with my website",
|
||||||
|
"looking for a website designer",
|
||||||
|
"need a shopify website",
|
||||||
|
"looking for website design",
|
||||||
|
"want to build a website for my",
|
||||||
]
|
]
|
||||||
|
|
||||||
VIEWPORTS = [
|
VIEWPORTS = [
|
||||||
@@ -1361,6 +1374,8 @@ async def ask_ollama(prompt: str) -> str:
|
|||||||
async def classify_leads(results: list[dict]) -> list[dict]:
|
async def classify_leads(results: list[dict]) -> list[dict]:
|
||||||
if not results:
|
if not results:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
# ── 1. AI classification ─────────────────────────────────────────
|
||||||
briefs = [r["title"][:200] for r in results]
|
briefs = [r["title"][:200] for r in results]
|
||||||
prompt = f"""Classify each post as LEAD or NOT.
|
prompt = f"""Classify each post as LEAD or NOT.
|
||||||
LEAD = someone REQUESTING/POSTING/WANTING a website built, designed, or created for them.
|
LEAD = someone REQUESTING/POSTING/WANTING a website built, designed, or created for them.
|
||||||
@@ -1376,7 +1391,7 @@ NOT LEAD:
|
|||||||
For each numbered post, answer ONLY "yes" (LEAD) or "no" (NOT LEAD):
|
For each numbered post, answer ONLY "yes" (LEAD) or "no" (NOT LEAD):
|
||||||
{chr(10).join(f'{i+1}. {t}' for i, t in enumerate(briefs))}
|
{chr(10).join(f'{i+1}. {t}' for i, t in enumerate(briefs))}
|
||||||
Return a JSON array like ["yes","no","yes"] matching the order above."""
|
Return a JSON array like ["yes","no","yes"] matching the order above."""
|
||||||
ai_succeeded = False
|
ai_leads: list[dict] = []
|
||||||
try:
|
try:
|
||||||
raw = await ask_ollama(prompt)
|
raw = await ask_ollama(prompt)
|
||||||
raw = raw.strip()
|
raw = raw.strip()
|
||||||
@@ -1389,19 +1404,14 @@ Return a JSON array like ["yes","no","yes"] matching the order above."""
|
|||||||
raw = raw.strip()
|
raw = raw.strip()
|
||||||
answers = json.loads(raw)
|
answers = json.loads(raw)
|
||||||
if isinstance(answers, list) and len(answers) == len(results):
|
if isinstance(answers, list) and len(answers) == len(results):
|
||||||
ai_succeeded = True
|
|
||||||
filtered = []
|
|
||||||
for i, ans in enumerate(answers):
|
for i, ans in enumerate(answers):
|
||||||
if isinstance(ans, str) and ans.lower() == 'yes':
|
if isinstance(ans, str) and ans.lower() == 'yes':
|
||||||
filtered.append(results[i])
|
ai_leads.append(results[i])
|
||||||
if filtered:
|
logger.info("AI classified %d/%d as LEAD", len(ai_leads), len(results))
|
||||||
return filtered[:10]
|
|
||||||
logger.info("AI classified all %d items as NOT LEAD — returning empty", len(results))
|
|
||||||
return []
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("AI classification failed, falling back to keyword filter: %s", e)
|
logger.warning("AI classification failed: %s", e)
|
||||||
|
|
||||||
if not ai_succeeded:
|
# ── 2. Keyword fallback (always runs) ────────────────────────────
|
||||||
web_terms = [
|
web_terms = [
|
||||||
"website", "web design", "web develop", "web dev",
|
"website", "web design", "web develop", "web dev",
|
||||||
"web designer", "web developer",
|
"web designer", "web developer",
|
||||||
@@ -1411,6 +1421,15 @@ Return a JSON array like ["yes","no","yes"] matching the order above."""
|
|||||||
"site for my", "site for my business",
|
"site for my", "site for my business",
|
||||||
"new website", "redesign my website",
|
"new website", "redesign my website",
|
||||||
"help with my website", "update my website",
|
"help with my website", "update my website",
|
||||||
|
"make a website", "make my website",
|
||||||
|
"website for my",
|
||||||
|
"online store", "online shop",
|
||||||
|
"build my site", "build a site",
|
||||||
|
"set up a website", "set up my website",
|
||||||
|
"custom website",
|
||||||
|
"shopify",
|
||||||
|
"my site",
|
||||||
|
"webpage", "web page",
|
||||||
]
|
]
|
||||||
request_terms = [
|
request_terms = [
|
||||||
"looking for", "need a", "need an", "looking to",
|
"looking for", "need a", "need an", "looking to",
|
||||||
@@ -1423,8 +1442,13 @@ Return a JSON array like ["yes","no","yes"] matching the order above."""
|
|||||||
"know a", "know any", "recommendation",
|
"know a", "know any", "recommendation",
|
||||||
"suggest", "looking for recommendations",
|
"suggest", "looking for recommendations",
|
||||||
"can anyone", "does anyone",
|
"can anyone", "does anyone",
|
||||||
|
"dm me", "pm me", "message me",
|
||||||
|
"quote for",
|
||||||
|
"can you help",
|
||||||
|
"how much",
|
||||||
|
"price for",
|
||||||
]
|
]
|
||||||
filtered = []
|
keyword_leads: list[dict] = []
|
||||||
for r in results:
|
for r in results:
|
||||||
t = r['title'].lower()
|
t = r['title'].lower()
|
||||||
has_web = any(kw in t for kw in web_terms)
|
has_web = any(kw in t for kw in web_terms)
|
||||||
@@ -1435,9 +1459,32 @@ Return a JSON array like ["yes","no","yes"] matching the order above."""
|
|||||||
'affordable web design package', 'web hosting',
|
'affordable web design package', 'web hosting',
|
||||||
'i do web design', 'i develop websites']):
|
'i do web design', 'i develop websites']):
|
||||||
continue
|
continue
|
||||||
filtered.append(r)
|
keyword_leads.append(r)
|
||||||
return filtered[:10]
|
|
||||||
return []
|
# ── 3. Merge: prefer AI leads, supplement with keywords to reach 5 ──
|
||||||
|
seen_titles: set[int] = set()
|
||||||
|
merged: list[dict] = []
|
||||||
|
for r in ai_leads + keyword_leads:
|
||||||
|
key = hash(r.get('title', ''))
|
||||||
|
if key not in seen_titles:
|
||||||
|
seen_titles.add(key)
|
||||||
|
merged.append(r)
|
||||||
|
|
||||||
|
# Fill to 5 with loose keyword matches (at least web OR request term)
|
||||||
|
if len(merged) < 5:
|
||||||
|
for r in results:
|
||||||
|
key = hash(r.get('title', ''))
|
||||||
|
if key in seen_titles:
|
||||||
|
continue
|
||||||
|
t = r['title'].lower()
|
||||||
|
if any(kw in t for kw in web_terms) or any(kw in t for kw in request_terms):
|
||||||
|
seen_titles.add(key)
|
||||||
|
merged.append(r)
|
||||||
|
if len(merged) >= 5:
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info("classify_leads: %d merged (%d AI + %d keyword) from %d raw", len(merged), len(ai_leads), len(keyword_leads), len(results))
|
||||||
|
return merged[:10]
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════
|
||||||
# FastAPI Endpoints
|
# FastAPI Endpoints
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -95,7 +95,7 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
|
|||||||
"relative flex h-10 w-10 items-center justify-center rounded-lg transition-colors",
|
"relative flex h-10 w-10 items-center justify-center rounded-lg transition-colors",
|
||||||
isActive
|
isActive
|
||||||
? "bg-sidebar-primary text-sidebar-primary-foreground"
|
? "bg-sidebar-primary text-sidebar-primary-foreground"
|
||||||
: "text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
: "text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-white"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<item.icon className="h-5 w-5" />
|
<item.icon className="h-5 w-5" />
|
||||||
@@ -104,7 +104,7 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
|
|||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right" className="ml-2">
|
<TooltipContent side="right" className="ml-2 text-white">
|
||||||
{item.label}
|
{item.label}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -143,12 +143,12 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
onClick={() => setFbDialogOpen(true)}
|
onClick={() => setFbDialogOpen(true)}
|
||||||
className="relative flex h-10 w-10 items-center justify-center rounded-lg transition-colors text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
className="relative flex h-10 w-10 items-center justify-center rounded-lg transition-colors text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-white"
|
||||||
>
|
>
|
||||||
<Facebook className="h-5 w-5" />
|
<Facebook className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right" className="ml-2">
|
<TooltipContent side="right" className="ml-2 text-white">
|
||||||
Facebook Accounts
|
Facebook Accounts
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function SystemMonitor({ collapsed }: SystemMonitorProps) {
|
|||||||
|
|
||||||
if (collapsed) {
|
if (collapsed) {
|
||||||
return (
|
return (
|
||||||
<div className="border-t border-sidebar-border px-3 py-2 flex justify-center gap-1.5">
|
<div className="border-t border-sidebar-border px-1 py-1.5 flex flex-col items-center text-[10px] leading-tight">
|
||||||
<span className={ramOver ? "text-red-400" : "text-[var(--theme-primary,#3b91f7)]"}>
|
<span className={ramOver ? "text-red-400" : "text-[var(--theme-primary,#3b91f7)]"}>
|
||||||
{rssMB}
|
{rssMB}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user