mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
118 lines
4.6 KiB
TypeScript
118 lines
4.6 KiB
TypeScript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
|
|
|
const corsHeaders = {
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Headers":
|
|
"authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version",
|
|
};
|
|
|
|
serve(async (req) => {
|
|
if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
|
|
|
|
try {
|
|
const LOVABLE_API_KEY = Deno.env.get("LOVABLE_API_KEY");
|
|
if (!LOVABLE_API_KEY) throw new Error("LOVABLE_API_KEY is not configured");
|
|
|
|
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
|
|
const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
|
|
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
|
|
|
const { question, association_id, history } = await req.json();
|
|
if (!question || !association_id) {
|
|
return new Response(JSON.stringify({ error: "question and association_id are required" }), {
|
|
status: 400,
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
// Fetch FAQs for the association
|
|
const { data: faqs } = await supabase
|
|
.from("association_faqs")
|
|
.select("question, answer")
|
|
.eq("association_id", association_id)
|
|
.order("sort_order");
|
|
|
|
const faqList = (faqs || [])
|
|
.filter((f: any) => f.answer)
|
|
.map((f: any, i: number) => `${i + 1}. Q: ${f.question}\n A: ${f.answer}`)
|
|
.join("\n\n");
|
|
|
|
// Fetch association name for context
|
|
const { data: assoc } = await supabase
|
|
.from("associations")
|
|
.select("name")
|
|
.eq("id", association_id)
|
|
.single();
|
|
|
|
const assocName = assoc?.name || "the community";
|
|
|
|
const systemPrompt = `You are a friendly and helpful community assistant for ${assocName}.
|
|
|
|
You are available on the community's public page to help visitors and residents with questions.
|
|
|
|
You have a knowledge base of pre-determined questions and answers. Try to match the visitor's question to one of these FAQs and provide the answer. You can rephrase answers naturally but keep the same information.
|
|
|
|
If the question is clearly covered by one or more FAQs, answer it using that information.
|
|
If the question is NOT covered by any FAQ, or you're unsure, politely let them know you don't have that information and suggest they contact management directly using the contact information on this page.
|
|
|
|
Do NOT make up answers. Only answer from the provided FAQ knowledge base.
|
|
|
|
Here are the available FAQs:
|
|
${faqList || "No FAQs have been configured yet."}`;
|
|
|
|
// Build messages with conversation history
|
|
const messages: any[] = [{ role: "system", content: systemPrompt }];
|
|
if (history && Array.isArray(history)) {
|
|
for (const msg of history.slice(-10)) {
|
|
messages.push({ role: msg.role, content: msg.content });
|
|
}
|
|
}
|
|
messages.push({ role: "user", content: question });
|
|
|
|
const aiResponse = await fetch("https://ai.gateway.lovable.dev/v1/chat/completions", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${LOVABLE_API_KEY}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
model: "google/gemini-3-flash-preview",
|
|
messages,
|
|
}),
|
|
});
|
|
|
|
if (!aiResponse.ok) {
|
|
if (aiResponse.status === 429) {
|
|
return new Response(JSON.stringify({ error: "Rate limited, please try again later." }), {
|
|
status: 429,
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
if (aiResponse.status === 402) {
|
|
return new Response(JSON.stringify({ error: "Service temporarily unavailable." }), {
|
|
status: 402,
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
const errText = await aiResponse.text();
|
|
console.error("AI gateway error:", aiResponse.status, errText);
|
|
throw new Error("AI gateway error");
|
|
}
|
|
|
|
const aiData = await aiResponse.json();
|
|
const answer = aiData.choices?.[0]?.message?.content || "I'm sorry, I couldn't process your question. Please try again.";
|
|
|
|
return new Response(
|
|
JSON.stringify({ answer }),
|
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
);
|
|
} catch (e) {
|
|
console.error("ai-public-chat error:", e);
|
|
return new Response(
|
|
JSON.stringify({ error: e instanceof Error ? e.message : "Unknown error" }),
|
|
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
);
|
|
}
|
|
});
|