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>
180 lines
6.9 KiB
TypeScript
180 lines
6.9 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.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" },
|
|
});
|
|
}
|
|
});
|