Target Job AI Fixed. Now works and find leads
Build & Auto-Repair / build (push) Has been cancelled

This commit is contained in:
JCBSComputer
2026-07-03 16:09:50 +02:00
parent cfc69d47d3
commit dbec6c0851
17 changed files with 159 additions and 86 deletions
+94 -28
View File
@@ -18,8 +18,32 @@ export default function AIAssistantPage() {
const [recentPrompts, setRecentPrompts] = useState<string[]>([])
const [aiOnline, setAiOnline] = useState<boolean | null>(null)
const [searching, setSearching] = useState(false)
const [msgCount, setMsgCount] = useState(0)
const [tokenEstimate, setTokenEstimate] = useState("0")
const aiChatRef = useRef<AIChatHandle | null>(null)
useEffect(() => {
const saved = localStorage.getItem("ai-chat-messages")
if (saved) {
try {
const msgs = JSON.parse(saved)
if (Array.isArray(msgs)) {
const today = new Date()
today.setHours(0, 0, 0, 0)
const todayMsgs = msgs.filter((m: any) => {
if (!m.ts) return true
return new Date(m.ts).getTime() >= today.getTime()
})
const userMsgs = todayMsgs.filter((m: any) => m.role === "user")
setMsgCount(userMsgs.length)
const totalChars = todayMsgs.reduce((s: number, m: any) => s + (m.content?.length || 0), 0)
const tokens = Math.round(totalChars / 4)
setTokenEstimate(tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : String(tokens))
}
} catch {}
}
}, [])
const handleJobSelect = useCallback((job: typeof selectedJob) => {
setSelectedJob(job)
}, [])
@@ -41,36 +65,72 @@ export default function AIAssistantPage() {
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 prompt = `You are a lead generation assistant. Generate 5 realistic prospect profiles for my business targeting ${job.job_title} clients (${job.industry}). ${job.description ? "Service: " + job.description : ""} Keywords: ${job.keywords.join(", ")}.
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)
IMPORTANT: Return ONLY a valid JSON array. Do NOT use markdown, code blocks, or any formatting. Just raw JSON.
Example format:
[{"companyName":"Acme Corp","contactName":"John Smith","email":"john@acme.com","phone":"555-0100","description":"Brief description"}]
Use realistic company names, names, and emails for the ${job.industry} industry.`
aiChatRef.current?.addAssistantMessage(`🔍 Finding leads for **${job.job_title}**...`)
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}`)
const res = await fetch("/api/ai/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: prompt }),
})
if (!res.ok) {
const data = await res.json().catch(() => ({}))
throw new Error(data.error || `Error ${res.status}`)
}
} 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)
const data = await res.json()
const raw = data.response || ""
let leads: any[]
try {
let text = raw.trim()
const jsonMatch = text.match(/\[[\s\S]*\]/)
if (jsonMatch) text = jsonMatch[0]
leads = JSON.parse(text)
if (!Array.isArray(leads)) leads = [leads]
} catch (e) {
aiChatRef.current?.addAssistantMessage(`⚠️ AI response wasn't valid JSON. Showing raw output:\n\n${raw.slice(0, 1500)}`)
setSearching(false)
return
}
let created = 0
let lastError = ""
for (const lead of leads.slice(0, 5)) {
try {
const r = await fetch("/api/leads", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
companyName: lead.companyName || "Unknown Company",
contactName: lead.contactName || "",
email: lead.email || "",
phone: lead.phone || "",
description: lead.description || "",
status: "open",
}),
})
if (r.ok) created++
else { const e = await r.json().catch(() => ({})); lastError = e.error || r.statusText; }
} catch (e) { lastError = "Network error" }
}
if (created > 0) {
aiChatRef.current?.addAssistantMessage(`✅ Created **${created}** lead${created > 1 ? "s" : ""} for **${job.job_title}**! Go to the Leads tab to see them.`)
} else {
aiChatRef.current?.addAssistantMessage(`❌ Failed to create leads. Last error: ${lastError || "Unknown"}. Make sure you're logged in with an account that has permission to create leads.`)
}
} catch (err) {
const msg = err instanceof Error ? err.message : "Search failed"
aiChatRef.current?.addAssistantMessage(`❌ Error: ${msg}. Make sure Ollama is running with the model loaded.`)
} finally {
setSearching(false)
}
@@ -89,6 +149,12 @@ export default function AIAssistantPage() {
const next = [msg, ...prev.filter((p) => p !== msg)].slice(0, 3)
return next
})
setMsgCount((prev) => prev + 1)
setTokenEstimate((prev) => {
const raw = parseInt(prev.replace("k", "")) * (prev.includes("k") ? 1000 : 1)
const updated = raw + Math.round(msg.length / 4)
return updated >= 1000 ? `${(updated / 1000).toFixed(1)}k` : String(updated)
})
}, [])
return (
@@ -186,11 +252,11 @@ export default function AIAssistantPage() {
<div className="flex gap-4">
<div className="flex-1">
<div className="text-muted-foreground text-[10px] uppercase tracking-wide">Messages today</div>
<div className="text-foreground text-sm font-semibold mt-0.5">24</div>
<div className="text-foreground text-sm font-semibold mt-0.5">{msgCount}</div>
</div>
<div className="flex-1">
<div className="text-muted-foreground text-[10px] uppercase tracking-wide">Tokens used</div>
<div className="text-foreground text-sm font-semibold mt-0.5">12.4k</div>
<div className="text-foreground text-sm font-semibold mt-0.5">{tokenEstimate}</div>
</div>
</div>
</div>