rowboat/apps/rowboatx/app/api/stream/route.ts
tusharmagar c637cb49ac Refactor RowboatX configuration and enhance editor features
- Updated `next.config.ts` to scope Turbopack to the app's directory.
- Modified `package.json` and `package-lock.json` to include new dependencies for Tiptap and markdown processing.
- Removed deprecated chat API and added new agent and config routes for file management.
- Introduced `JsonEditor` and `MarkdownViewer` components for improved content editing and display.
- Enhanced `TiptapMarkdownEditor` with additional toolbar options and markdown parsing capabilities.
- Updated layout and page components to integrate new editors and improve user experience.
2026-01-16 12:05:33 +05:30

113 lines
2.9 KiB
TypeScript

import { NextRequest } from 'next/server';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
const CLI_BASE_URL = process.env.CLI_BACKEND_URL || 'http://localhost:3000';
/**
* GET /api/stream
* Proxy SSE stream from CLI backend to frontend
*/
export async function GET(request: NextRequest) {
const encoder = new TextEncoder();
const customReadable = new ReadableStream({
async start(controller) {
let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
let isClosed = false;
// Handle client disconnect
request.signal.addEventListener('abort', () => {
isClosed = true;
reader?.cancel();
try {
controller.close();
} catch {
// Already closed, ignore
}
});
try {
// Connect to CLI backend SSE stream
const response = await fetch(`${CLI_BASE_URL}/stream`, {
headers: {
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
signal: request.signal, // Forward abort signal
});
if (!response.ok) {
throw new Error(`Failed to connect to backend: ${response.statusText}`);
}
const body = response.body;
if (!body) {
throw new Error('No response body');
}
reader = body.getReader();
// Read and forward stream
while (!isClosed) {
const { done, value } = await reader.read();
if (done) {
break;
}
// Only enqueue if controller is still open
if (!isClosed) {
try {
controller.enqueue(value);
} catch {
// Controller closed, stop reading
break;
}
}
}
} catch (error: unknown) {
// Only log non-abort errors
if ((error as Error).name !== 'AbortError') {
console.error('Stream error:', error);
}
// Try to send error message if controller is still open
if (!isClosed) {
try {
const errorMessage = `data: ${JSON.stringify({ type: 'error', error: String(error) })}\n\n`;
controller.enqueue(encoder.encode(errorMessage));
} catch {
// Controller already closed, ignore
}
}
} finally {
// Clean up
if (reader) {
try {
await reader.cancel();
} catch {
// Ignore cancel errors
}
}
if (!isClosed) {
try {
controller.close();
} catch {
// Already closed, ignore
}
}
}
},
});
return new Response(customReadable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}