mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Add ACMCC app source, Supabase backend, and project config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.99.1";
|
||||
|
||||
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 { documentId, fileUrl, title } = await req.json();
|
||||
if (!documentId || !fileUrl) {
|
||||
return new Response(JSON.stringify({ error: "documentId and fileUrl are required" }), {
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const LOVABLE_API_KEY = Deno.env.get("LOVABLE_API_KEY");
|
||||
if (!LOVABLE_API_KEY) {
|
||||
return new Response(JSON.stringify({ error: "LOVABLE_API_KEY not configured" }), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch the file content
|
||||
let fileContent = "";
|
||||
try {
|
||||
const fileResp = await fetch(fileUrl);
|
||||
if (!fileResp.ok) throw new Error(`Failed to fetch file: ${fileResp.status}`);
|
||||
|
||||
const contentType = fileResp.headers.get("content-type") || "";
|
||||
|
||||
if (contentType.includes("application/pdf")) {
|
||||
// For PDFs, extract text using base64 and Gemini's multimodal capability
|
||||
const arrayBuffer = await fileResp.arrayBuffer();
|
||||
const bytes = new Uint8Array(arrayBuffer);
|
||||
let binary = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
const base64 = btoa(binary);
|
||||
|
||||
const aiResp = 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-2.5-flash",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: `You are a document summarizer for a property management company. Generate a concise 2-4 sentence executive summary of the uploaded document. Focus on key findings, financial figures, action items, and important dates. Be professional and factual.`,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Please summarize this management report document titled "${title || 'Untitled'}".`,
|
||||
},
|
||||
{
|
||||
type: "image_url",
|
||||
image_url: {
|
||||
url: `data:application/pdf;base64,${base64}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!aiResp.ok) {
|
||||
if (aiResp.status === 429) {
|
||||
return new Response(JSON.stringify({ error: "Rate limit exceeded, please try again later." }), {
|
||||
status: 429, headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
if (aiResp.status === 402) {
|
||||
return new Response(JSON.stringify({ error: "AI credits exhausted. Please add funds." }), {
|
||||
status: 402, headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
const errText = await aiResp.text();
|
||||
console.error("AI error:", aiResp.status, errText);
|
||||
throw new Error(`AI gateway error: ${aiResp.status}`);
|
||||
}
|
||||
|
||||
const aiData = await aiResp.json();
|
||||
fileContent = aiData.choices?.[0]?.message?.content || "";
|
||||
} else {
|
||||
// For text-based files, read as text
|
||||
const textContent = await fileResp.text();
|
||||
const truncated = textContent.substring(0, 15000);
|
||||
|
||||
const aiResp = 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-2.5-flash",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: `You are a document summarizer for a property management company. Generate a concise 2-4 sentence executive summary. Focus on key findings, financial figures, action items, and important dates. Be professional and factual.`,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: `Please summarize this management report titled "${title || 'Untitled'}":\n\n${truncated}`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!aiResp.ok) {
|
||||
if (aiResp.status === 429) {
|
||||
return new Response(JSON.stringify({ error: "Rate limit exceeded." }), {
|
||||
status: 429, headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
if (aiResp.status === 402) {
|
||||
return new Response(JSON.stringify({ error: "AI credits exhausted." }), {
|
||||
status: 402, headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
throw new Error(`AI gateway error: ${aiResp.status}`);
|
||||
}
|
||||
|
||||
const aiData = await aiResp.json();
|
||||
fileContent = aiData.choices?.[0]?.message?.content || "";
|
||||
}
|
||||
} catch (fetchErr) {
|
||||
console.error("File fetch/AI error:", fetchErr);
|
||||
return new Response(JSON.stringify({ error: "Failed to process document for summary" }), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
// Save summary to database
|
||||
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
|
||||
const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
|
||||
const supabase = createClient(supabaseUrl, supabaseKey);
|
||||
|
||||
const { error: updateError } = await supabase
|
||||
.from("documents")
|
||||
.update({ ai_summary: fileContent })
|
||||
.eq("id", documentId);
|
||||
|
||||
if (updateError) {
|
||||
console.error("DB update error:", updateError);
|
||||
return new Response(JSON.stringify({ error: "Failed to save summary" }), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ summary: fileContent }), {
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("summarize-document error:", e);
|
||||
return new Response(JSON.stringify({ error: e instanceof Error ? e.message : "Unknown error" }), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user