From a552406911df808eb9ba323f7df6b5ba2a94385d Mon Sep 17 00:00:00 2001 From: phact Date: Fri, 1 Aug 2025 10:12:39 -0400 Subject: [PATCH] split frontend and backend containers --- Dockerfile.app | 4 +- Dockerfile.backend | 29 ++++++++ Dockerfile.frontend | 20 ++++++ docker-compose.yml | 30 +++++--- frontend/next.config.ts | 8 --- frontend/src/app/api/[...path]/route.ts | 92 +++++++++++++++++++++++++ frontend/src/app/auth/callback/page.tsx | 26 ++++++- frontend/src/app/connectors/page.tsx | 6 +- frontend/src/app/login/page.tsx | 19 ++++- 9 files changed, 209 insertions(+), 25 deletions(-) create mode 100644 Dockerfile.backend create mode 100644 Dockerfile.frontend create mode 100644 frontend/src/app/api/[...path]/route.ts diff --git a/Dockerfile.app b/Dockerfile.app index c78e349a..c021d914 100644 --- a/Dockerfile.app +++ b/Dockerfile.app @@ -40,10 +40,10 @@ COPY src/ ./src/ RUN echo '#!/bin/bash\n\ set -e\n\ echo "Starting Python backend..."\n\ -uv run python src/app.py &\n\ +uv run python src/main.py &\n\ BACKEND_PID=$!\n\ echo "Waiting for backend to be ready..."\n\ -until curl -f http://localhost:8000/search -X POST -H "Content-Type: application/json" -d "{\"query\":\"test\"}" > /dev/null 2>&1; do\n\ +until curl -f http://localhost:8000/auth/me > /dev/null 2>&1; do\n\ echo "Backend not ready yet, waiting..."\n\ sleep 2\n\ done\n\ diff --git a/Dockerfile.backend b/Dockerfile.backend new file mode 100644 index 00000000..a009496e --- /dev/null +++ b/Dockerfile.backend @@ -0,0 +1,29 @@ +FROM python:3.13-slim + +# Install curl for uv installation +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + +# Install uv +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +ENV PATH="/root/.local/bin:$PATH" + +# Set working directory +WORKDIR /app + +# Copy Python dependencies +COPY pyproject.toml uv.lock ./ +RUN uv sync + +# Copy sample document and warmup script for docling +COPY documents/2506.08231v1.pdf ./ +COPY warm_up_docling.py ./ +RUN uv run python warm_up_docling.py && rm warm_up_docling.py 2506.08231v1.pdf + +# Copy Python source +COPY src/ ./src/ + +# Expose backend port +EXPOSE 8000 + +# Start backend in foreground +CMD ["uv", "run", "python", "src/main.py"] \ No newline at end of file diff --git a/Dockerfile.frontend b/Dockerfile.frontend new file mode 100644 index 00000000..a60e3d34 --- /dev/null +++ b/Dockerfile.frontend @@ -0,0 +1,20 @@ +FROM node:18-slim + +# Set working directory +WORKDIR /app + +# Copy frontend dependencies +COPY frontend/package*.json ./ +RUN npm install + +# Copy frontend source +COPY frontend/ ./ + +# Build frontend +RUN npm run build + +# Expose frontend port +EXPOSE 3000 + +# Start frontend in foreground +CMD ["npm", "start"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 04168f7b..c02489c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,12 +23,11 @@ services: ports: - "5601:5601" - gendb: - image: phact/gendb:latest - #build: - #context: . - #dockerfile: Dockerfile.app - container_name: gendb-app + gendb-backend: + build: + context: . + dockerfile: Dockerfile.backend + container_name: gendb-backend depends_on: - opensearch - langflow @@ -43,17 +42,30 @@ services: - OPENAI_API_KEY=${OPENAI_API_KEY} - NVIDIA_DRIVER_CAPABILITIES=compute,utility - NVIDIA_VISIBLE_DEVICES=all - ports: - - "3000:3000" + - GOOGLE_OAUTH_CLIENT_ID=${GOOGLE_OAUTH_CLIENT_ID} + - GOOGLE_OAUTH_CLIENT_SECRET=${GOOGLE_OAUTH_CLIENT_SECRET} volumes: - ./src:/app/src - - ./frontend/src:/app/frontend/src - ./pyproject.toml:/app/pyproject.toml - ./uv.lock:/app/uv.lock - ./documents:/app/documents gpus: all platform: linux/amd64 + gendb-frontend: + build: + context: . + dockerfile: Dockerfile.frontend + container_name: gendb-frontend + depends_on: + - gendb-backend + environment: + - GENDB_BACKEND_HOST=gendb-backend + ports: + - "3000:3000" + volumes: + - ./frontend/src:/app/src + langflow: volumes: - ./flows:/app/flows diff --git a/frontend/next.config.ts b/frontend/next.config.ts index ab7b4aa6..14dc986f 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,14 +1,6 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - async rewrites() { - return [ - { - source: '/api/:path*', - destination: 'http://localhost:8000/:path*', - }, - ]; - }, // Increase timeout for API routes experimental: { proxyTimeout: 300000, // 5 minutes diff --git a/frontend/src/app/api/[...path]/route.ts b/frontend/src/app/api/[...path]/route.ts new file mode 100644 index 00000000..a5dcc779 --- /dev/null +++ b/frontend/src/app/api/[...path]/route.ts @@ -0,0 +1,92 @@ +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.GENDB_BACKEND_HOST || 'localhost'; + const path = params.path.join('/'); + const searchParams = request.nextUrl.searchParams.toString(); + const backendUrl = `http://${backendHost}:8000/${path}${searchParams ? `?${searchParams}` : ''}`; + + try { + const body = request.method !== 'GET' && request.method !== 'HEAD' + ? await request.text() + : undefined; + + const headers = new Headers(); + + // Copy relevant headers from the original request + for (const [key, value] of request.headers.entries()) { + if (!key.toLowerCase().startsWith('host') && + !key.toLowerCase().startsWith('x-forwarded') && + !key.toLowerCase().startsWith('x-real-ip')) { + headers.set(key, value); + } + } + + const response = await fetch(backendUrl, { + method: request.method, + headers, + body, + }); + + const responseBody = await response.text(); + 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); + } + } + + 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 } + ); + } +} \ No newline at end of file diff --git a/frontend/src/app/auth/callback/page.tsx b/frontend/src/app/auth/callback/page.tsx index df40521a..d93c99ed 100644 --- a/frontend/src/app/auth/callback/page.tsx +++ b/frontend/src/app/auth/callback/page.tsx @@ -1,13 +1,13 @@ "use client" -import { useEffect, useState, useRef } from "react" +import { useEffect, useState, useRef, Suspense } from "react" import { useRouter, useSearchParams } from "next/navigation" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Loader2, CheckCircle, XCircle, ArrowLeft } from "lucide-react" import { useAuth } from "@/contexts/auth-context" -export default function AuthCallbackPage() { +function AuthCallbackContent() { const router = useRouter() const searchParams = useSearchParams() const { refreshAuth } = useAuth() @@ -211,4 +211,26 @@ export default function AuthCallbackPage() { ) +} + +export default function AuthCallbackPage() { + return ( + + + + + + Loading... + + + Please wait while we process your request... + + + + + }> + + + ) } \ No newline at end of file diff --git a/frontend/src/app/connectors/page.tsx b/frontend/src/app/connectors/page.tsx index 121c9707..a7600f94 100644 --- a/frontend/src/app/connectors/page.tsx +++ b/frontend/src/app/connectors/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, Suspense } from "react" import { useSearchParams } from "next/navigation" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" @@ -467,7 +467,9 @@ function ConnectorsPage() { export default function ProtectedConnectorsPage() { return ( - + Loading connectors...}> + + ) } \ No newline at end of file diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index fca2c5b0..ea095f3e 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -1,13 +1,13 @@ "use client" -import { useEffect } from "react" +import { useEffect, Suspense } from "react" import { useRouter, useSearchParams } from "next/navigation" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { useAuth } from "@/contexts/auth-context" import { Lock, LogIn, Loader2 } from "lucide-react" -export default function LoginPage() { +function LoginPageContent() { const { isLoading, isAuthenticated, login } = useAuth() const router = useRouter() const searchParams = useSearchParams() @@ -62,4 +62,19 @@ export default function LoginPage() { ) +} + +export default function LoginPage() { + return ( + +
+ +

Loading...

+
+ + }> + +
+ ) } \ No newline at end of file