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" }, }); } });