import { NextRequest, NextResponse } from 'next/server'; // The target backend server base URL, derived from environment variable or defaulted. // This should match the logic in your frontend's page.tsx for consistency. const TARGET_SERVER_BASE_URL = process.env.SERVER_BASE_URL || 'http://localhost:8001'; // This is a fallback HTTP implementation that will be used if WebSockets are not available // or if there's an error with the WebSocket connection export async function POST(req: NextRequest) { try { const requestBody = await req.json(); // Assuming the frontend sends JSON // Note: This endpoint now uses the HTTP fallback instead of WebSockets // The WebSocket implementation is in src/utils/websocketClient.ts // This HTTP endpoint is kept for backward compatibility console.log('Using HTTP fallback for chat completion instead of WebSockets'); const targetUrl = `${TARGET_SERVER_BASE_URL}/chat/completions/stream`; // Make the actual request to the backend service const backendResponse = await fetch(targetUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream', // Indicate that we expect a stream }, body: JSON.stringify(requestBody), }); // If the backend service returned an error, forward that error to the client if (!backendResponse.ok) { const errorBody = await backendResponse.text(); const errorHeaders = new Headers(); backendResponse.headers.forEach((value, key) => { errorHeaders.set(key, value); }); return new NextResponse(errorBody, { status: backendResponse.status, statusText: backendResponse.statusText, headers: errorHeaders, }); } // Ensure the backend response has a body to stream if (!backendResponse.body) { return new NextResponse('Stream body from backend is null', { status: 500 }); } // Create a new ReadableStream to pipe the data from the backend to the client const stream = new ReadableStream({ async start(controller) { const reader = backendResponse.body!.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) { break; } controller.enqueue(value); } } catch (error) { console.error('Error reading from backend stream in proxy:', error); controller.error(error); } finally { controller.close(); reader.releaseLock(); // Important to release the lock on the reader } }, cancel(reason) { console.log('Client cancelled stream request:', reason); } }); // Set up headers for the response to the client const responseHeaders = new Headers(); // Copy the Content-Type from the backend response (e.g., 'text/event-stream') const contentType = backendResponse.headers.get('Content-Type'); if (contentType) { responseHeaders.set('Content-Type', contentType); } // It's good practice for streams not to be cached or transformed by intermediaries. responseHeaders.set('Cache-Control', 'no-cache, no-transform'); return new NextResponse(stream, { status: backendResponse.status, // Should be 200 for a successful stream start headers: responseHeaders, }); } catch (error) { console.error('Error in API proxy route (/api/chat/stream):', error); let errorMessage = 'Internal Server Error in proxy'; if (error instanceof Error) { errorMessage = error.message; } return new NextResponse(JSON.stringify({ error: errorMessage }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } } // Optional: Handle OPTIONS requests for CORS if you ever call this from a different origin // or use custom headers that trigger preflight requests. For same-origin, it's less critical. export async function OPTIONS() { return new NextResponse(null, { status: 204, // No Content headers: { 'Access-Control-Allow-Origin': '*', // Be more specific in production if needed 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', // Adjust as per client's request headers }, }); }