import { NextRequest, NextResponse } from "next/server"; import { createClient } from "@/lib/supabase/server"; const GRADIO_URL = process.env.CLAUSEGUARD_GRADIO_URL || "https://gaurv007-clauseguard.hf.space"; /** * FIX v4.1: The chat endpoint now properly documents its limitations. * * ARCHITECTURE NOTE: * The Gradio ChatInterface uses gr.State to store RAG embeddings per-session. * This state is NOT accessible via the Gradio API from an external caller — * each API call creates a new session with empty state. * * For the Next.js web app, chat should either: * 1. Use the FastAPI backend (/api/chat) which manages its own RAG sessions, OR * 2. Embed the Gradio Space in an iframe for direct interaction * * This route attempts to use the Gradio API as a best-effort fallback, * but will clearly communicate to the user if the session is unavailable. */ export async function POST(req: NextRequest) { try { const supabase = await createClient(); const { data: { user } } = await supabase.auth.getUser(); if (!user) { return NextResponse.json({ error: "Unauthorized. Please log in." }, { status: 401 }); } const body = await req.json(); const { message, history, session_id } = body; if (!message) { return NextResponse.json( { error: "message is required" }, { status: 400 } ); } // FIX v4.1: Input validation if (message.length > 2000) { return NextResponse.json( { error: "Message too long (max 2000 characters)" }, { status: 400 } ); } // Try the FastAPI backend first (it has proper RAG session management) const apiUrl = process.env.CLAUSEGUARD_API_URL || ""; if (apiUrl && session_id) { try { const apiRes = await fetch(`${apiUrl}/api/chat`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message, session_id, history: history || [] }), }); if (apiRes.ok) { const data = await apiRes.json(); return NextResponse.json({ response: data.response }); } } catch { // Fall through to Gradio attempt } } // Fallback: Try the Gradio API const submitRes = await fetch(`${GRADIO_URL}/gradio_api/call/chat`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ data: [message] }), }); if (!submitRes.ok) { const errText = await submitRes.text().catch(() => ""); throw new Error(`Chat submit failed (${submitRes.status}): ${errText}`); } const { event_id } = await submitRes.json(); if (!event_id) throw new Error("No event_id from Gradio chat"); // Poll for result with timeout let resultText = ""; let attempts = 0; const maxAttempts = 30; while (attempts < maxAttempts) { const resultRes = await fetch( `${GRADIO_URL}/gradio_api/call/chat/${event_id}`, { headers: { Accept: "text/event-stream" } } ); if (!resultRes.ok) { throw new Error(`Chat result failed: ${resultRes.status}`); } resultText = await resultRes.text(); if (resultText.includes("event: complete")) break; if (resultText.includes("event: error")) { const errMatch = resultText.match(/event:\s*error\s*\ndata:\s*(.+)/); if (errMatch) throw new Error(`Chat error: ${errMatch[1]}`); throw new Error("Chat error from backend"); } await new Promise(r => setTimeout(r, 1000)); attempts++; } // Find the complete event data const dataMatch = resultText.match(/event:\s*complete\s*\ndata:\s*(.+)/); if (!dataMatch) { return NextResponse.json({ response: "⚠️ Chat is unavailable. The contract needs to be analyzed in the same session. " + "Please analyze a contract first in the Gradio Space, then use the chat tab there directly." }); } const responseData = JSON.parse(dataMatch[1]); const responseText = typeof responseData === "string" ? responseData : responseData[0] || ""; return NextResponse.json({ response: responseText }); } catch (error: any) { console.error("Chat error:", error.message); return NextResponse.json( { error: error.message || "Chat failed. Make sure you analyzed a contract first." }, { status: 500 } ); } }