diff --git a/frontend/app/api/v1/ppt/jobs/[job_id]/stream/route.ts b/frontend/app/api/v1/ppt/jobs/[job_id]/stream/route.ts new file mode 100644 index 0000000..7f42ffc --- /dev/null +++ b/frontend/app/api/v1/ppt/jobs/[job_id]/stream/route.ts @@ -0,0 +1,44 @@ +/** + * SSE Proxy for job progress stream. + * Next.js rewrites() don't handle SSE (text/event-stream) — they buffer responses + * and drop long-lived connections. This route handler streams directly without buffering. + */ +import { NextRequest } from 'next/server'; + +export const dynamic = 'force-dynamic'; + +const API_URL = process.env.API_INTERNAL_URL || 'http://api:8000'; + +export async function GET( + request: NextRequest, + { params }: { params: { job_id: string } } +) { + try { + const backendResponse = await fetch( + `${API_URL}/api/v1/ppt/jobs/${params.job_id}/stream`, + { + headers: { + Cookie: request.headers.get('cookie') || '', + Accept: 'text/event-stream', + 'Cache-Control': 'no-cache', + }, + } + ); + + if (!backendResponse.ok || !backendResponse.body) { + return new Response(null, { status: backendResponse.status }); + } + + return new Response(backendResponse.body, { + headers: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache, no-transform', + 'X-Accel-Buffering': 'no', + 'Connection': 'keep-alive', + }, + }); + } catch (error) { + console.error('[SSE] jobs/stream error:', error); + return new Response(null, { status: 502 }); + } +} diff --git a/frontend/app/api/v1/ppt/outlines/stream/[id]/route.ts b/frontend/app/api/v1/ppt/outlines/stream/[id]/route.ts new file mode 100644 index 0000000..f156be1 --- /dev/null +++ b/frontend/app/api/v1/ppt/outlines/stream/[id]/route.ts @@ -0,0 +1,44 @@ +/** + * SSE Proxy for outline generation stream. + * Next.js rewrites() don't handle SSE (text/event-stream) — they buffer responses + * and drop long-lived connections. This route handler streams directly without buffering. + */ +import { NextRequest } from 'next/server'; + +export const dynamic = 'force-dynamic'; + +const API_URL = process.env.API_INTERNAL_URL || 'http://api:8000'; + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const backendResponse = await fetch( + `${API_URL}/api/v1/ppt/outlines/stream/${params.id}`, + { + headers: { + Cookie: request.headers.get('cookie') || '', + Accept: 'text/event-stream', + 'Cache-Control': 'no-cache', + }, + } + ); + + if (!backendResponse.ok || !backendResponse.body) { + return new Response(null, { status: backendResponse.status }); + } + + return new Response(backendResponse.body, { + headers: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache, no-transform', + 'X-Accel-Buffering': 'no', + 'Connection': 'keep-alive', + }, + }); + } catch (error) { + console.error('[SSE] outlines/stream error:', error); + return new Response(null, { status: 502 }); + } +} diff --git a/frontend/app/api/v1/ppt/presentation/stream/[id]/route.ts b/frontend/app/api/v1/ppt/presentation/stream/[id]/route.ts new file mode 100644 index 0000000..a5e662b --- /dev/null +++ b/frontend/app/api/v1/ppt/presentation/stream/[id]/route.ts @@ -0,0 +1,44 @@ +/** + * SSE Proxy for presentation generation stream. + * Next.js rewrites() don't handle SSE (text/event-stream) — they buffer responses + * and drop long-lived connections. This route handler streams directly without buffering. + */ +import { NextRequest } from 'next/server'; + +export const dynamic = 'force-dynamic'; + +const API_URL = process.env.API_INTERNAL_URL || 'http://api:8000'; + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const backendResponse = await fetch( + `${API_URL}/api/v1/ppt/presentation/stream/${params.id}`, + { + headers: { + Cookie: request.headers.get('cookie') || '', + Accept: 'text/event-stream', + 'Cache-Control': 'no-cache', + }, + } + ); + + if (!backendResponse.ok || !backendResponse.body) { + return new Response(null, { status: backendResponse.status }); + } + + return new Response(backendResponse.body, { + headers: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache, no-transform', + 'X-Accel-Buffering': 'no', + 'Connection': 'keep-alive', + }, + }); + } catch (error) { + console.error('[SSE] presentation/stream error:', error); + return new Response(null, { status: 502 }); + } +}