openrag/frontend/src/app/api/[...path]/route.ts
Lucas Oliveira fcf7a302d0
feat: adds what is openrag prompt, refactors chat design, adds scroll to bottom on chat, adds streaming support (#283)
* Changed prompts to include info about OpenRAG, change status of As Dataframe and As Vector Store to false on OpenSearch component

* added markdown to onboarding step

* added className to markdown renderer

* changed onboarding step to not render span

* Added nudges to onboarding content

* Added onboarding style for nudges

* updated user message and assistant message designs

* updated route.ts to handle streaming messages

* created new useChatStreaming to handle streaming

* changed useChatStreaming to work with the chat page

* changed onboarding content to use default messages instead of onboarding steps, and to use the new hook to send messages

* added span to the markdown renderer on stream

* updated page to use new chat streaming hook

* disable animation on completed steps

* changed markdown renderer margins

* changed css to not display markdown links and texts on white always

* added isCompleted to assistant and user messages

* removed space between elements on onboarding step to ensure smoother animation

* removed opacity 50 on onboarding messages

* changed default api to be langflow on chat streaming

* added fade in and color transition

* added color transition

* Rendered onboarding with use-stick-to-bottom

* Added use stick to bottom on page

* fixed nudges design

* changed chat input design

* fixed nudges design

* made overflow be hidden on main

* Added overflow y auto on other pages

* Put animate on messages

* Add source to types

* Adds animate and delay props to messages
2025-10-22 14:03:23 -03:00

142 lines
No EOL
4.2 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ path: string[] }> }
) {
return proxyRequest(request, await params);
}
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ path: string[] }> }
) {
return proxyRequest(request, await params);
}
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ path: string[] }> }
) {
return proxyRequest(request, await params);
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ path: string[] }> }
) {
return proxyRequest(request, await params);
}
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ path: string[] }> }
) {
return proxyRequest(request, await params);
}
async function proxyRequest(
request: NextRequest,
params: { path: string[] }
) {
const backendHost = process.env.OPENRAG_BACKEND_HOST || 'localhost';
const backendSSL= process.env.OPENRAG_BACKEND_SSL || false;
const path = params.path.join('/');
const searchParams = request.nextUrl.searchParams.toString();
let backendUrl = `http://${backendHost}:8000/${path}${searchParams ? `?${searchParams}` : ''}`;
if (backendSSL) {
backendUrl = `https://${backendHost}:8000/${path}${searchParams ? `?${searchParams}` : ''}`;
}
try {
let body: string | ArrayBuffer | undefined = undefined;
let willSendBody = false;
if (request.method !== 'GET' && request.method !== 'HEAD') {
const contentType = request.headers.get('content-type') || '';
const contentLength = request.headers.get('content-length');
// For file uploads (multipart/form-data), preserve binary data
if (contentType.includes('multipart/form-data')) {
const buf = await request.arrayBuffer();
if (buf && buf.byteLength > 0) {
body = buf;
willSendBody = true;
}
} else {
// For JSON and other text-based content, use text
const text = await request.text();
if (text && text.length > 0) {
body = text;
willSendBody = true;
}
}
// Guard against incorrect non-zero content-length when there is no body
if (!willSendBody && contentLength) {
// We'll drop content-length/header below
}
}
const headers = new Headers();
// Copy relevant headers from the original request
for (const [key, value] of request.headers.entries()) {
const lower = key.toLowerCase();
if (
lower.startsWith('host') ||
lower.startsWith('x-forwarded') ||
lower.startsWith('x-real-ip') ||
lower === 'content-length' ||
(!willSendBody && lower === 'content-type')
) {
continue;
}
headers.set(key, value);
}
const init: RequestInit = {
method: request.method,
headers,
};
if (willSendBody) {
// Convert ArrayBuffer to Uint8Array to satisfy BodyInit in all environments
const bodyInit: BodyInit = typeof body === 'string' ? body : new Uint8Array(body as ArrayBuffer);
init.body = bodyInit;
}
const response = await fetch(backendUrl, init);
const responseHeaders = new Headers();
// Copy response headers
for (const [key, value] of response.headers.entries()) {
if (!key.toLowerCase().startsWith('transfer-encoding') &&
!key.toLowerCase().startsWith('connection')) {
responseHeaders.set(key, value);
}
}
// For streaming responses, pass the body directly without buffering
if (response.body) {
return new NextResponse(response.body, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders,
});
} else {
// Fallback for non-streaming responses
const responseBody = await response.text();
return new NextResponse(responseBody, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders,
});
}
} catch (error) {
console.error('Proxy error:', error);
return NextResponse.json(
{ error: 'Failed to proxy request' },
{ status: 500 }
);
}
}