Add ACMCC app source, Supabase backend, and project config

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 20:19:26 -04:00
parent 313b51b412
commit 183fe0a93c
1422 changed files with 259271 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
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" } }
);
}
});