Merge branch 'main' of https://git.coastit.co.za/caitlin/CRM_ENVR
Build & Auto-Repair / build (push) Has been cancelled
Build & Auto-Repair / build (push) Has been cancelled
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useCallback, useRef, useEffect } from "react"
|
||||
import { AIChat } from "@/components/ai/ai-chat"
|
||||
import { useState, useCallback, useRef } from "react"
|
||||
import { AIChat, type AIChatHandle } from "@/components/ai/ai-chat"
|
||||
import { JobSelector } from "@/components/ai/job-selector"
|
||||
import { Bot, ChevronRight } from "lucide-react"
|
||||
|
||||
@@ -16,12 +16,50 @@ const tips = [
|
||||
export default function AIAssistantPage() {
|
||||
const [selectedJob, setSelectedJob] = useState<{ job_title: string; keywords: string[]; industry: string; description: string } | null>(null)
|
||||
const [recentPrompts, setRecentPrompts] = useState<string[]>([])
|
||||
const aiChatRef = useRef<{ fillInput: (text: string) => void } | null>(null)
|
||||
const [searching, setSearching] = useState(false)
|
||||
const aiChatRef = useRef<AIChatHandle | null>(null)
|
||||
|
||||
const handleJobSelect = useCallback((job: typeof selectedJob) => {
|
||||
setSelectedJob(job)
|
||||
}, [])
|
||||
|
||||
const handleSearch = useCallback(async (job: NonNullable<typeof selectedJob>) => {
|
||||
setSearching(true)
|
||||
const keyword = job.keywords[0]
|
||||
aiChatRef.current?.addAssistantMessage(`🔍 Searching Facebook for **${job.job_title}** leads...`)
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 360000)
|
||||
const statusId = setTimeout(() => {
|
||||
aiChatRef.current?.addAssistantMessage("⏳ Still searching Facebook (this can take up to 5 minutes)...")
|
||||
}, 45000)
|
||||
|
||||
try {
|
||||
const res = await fetch(`http://localhost:3008/scrape/facebook?force=true&query=${encodeURIComponent(keyword)}`, { method: "POST", signal: controller.signal })
|
||||
clearTimeout(timeoutId)
|
||||
clearTimeout(statusId)
|
||||
const data = await res.json()
|
||||
if (data.success && data.leads?.length > 0) {
|
||||
const leadsText = data.leads.map((lead: any, i: number) =>
|
||||
`**${i + 1}.** ${lead.author || "Unknown"}\n> ${(lead.content || "").slice(0, 300)}\n> 🔗 ${lead.url || "(no link available)"}`
|
||||
).join("\n\n")
|
||||
aiChatRef.current?.addAssistantMessage(`✅ Found **${data.leads.length}** leads:\n\n${leadsText}`)
|
||||
} else {
|
||||
const reason = data.error || data.flag_reason || "No leads found this time"
|
||||
aiChatRef.current?.addAssistantMessage(`⚠️ ${reason}`)
|
||||
}
|
||||
} catch (err: any) {
|
||||
clearTimeout(timeoutId)
|
||||
clearTimeout(statusId)
|
||||
const msg = err.name === "AbortError"
|
||||
? "❌ Scraper timed out after 5 minutes. Try again or check the scraper service."
|
||||
: `❌ ${err instanceof Error ? err.message : "Search failed"}`
|
||||
aiChatRef.current?.addAssistantMessage(msg)
|
||||
} finally {
|
||||
setSearching(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleTipClick = useCallback((tip: string) => {
|
||||
aiChatRef.current?.fillInput(tip)
|
||||
}, [])
|
||||
@@ -71,7 +109,7 @@ export default function AIAssistantPage() {
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-primary" />
|
||||
<span className="text-primary text-[10px] font-bold uppercase tracking-[0.15em]">Target Job</span>
|
||||
</div>
|
||||
<JobSelector onSelect={handleJobSelect} />
|
||||
<JobSelector onSelect={handleJobSelect} onSearch={handleSearch} searching={searching} />
|
||||
{selectedJob && (
|
||||
<div className="bg-card/50 border border-border rounded-xl p-3.5 mt-3 space-y-2">
|
||||
<h4 className="text-sm font-semibold text-foreground">{selectedJob.job_title}</h4>
|
||||
|
||||
Reference in New Issue
Block a user