split frontend and backend containers

This commit is contained in:
phact 2025-08-01 10:12:39 -04:00
parent 3367a01419
commit a552406911
9 changed files with 209 additions and 25 deletions

View file

@ -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\

29
Dockerfile.backend Normal file
View file

@ -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"]

20
Dockerfile.frontend Normal file
View file

@ -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"]

View file

@ -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

View file

@ -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

View file

@ -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 }
);
}
}

View file

@ -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() {
</Card>
</div>
)
}
export default function AuthCallbackPage() {
return (
<Suspense fallback={
<div className="min-h-screen flex items-center justify-center bg-background">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<CardTitle className="flex items-center justify-center gap-2">
<Loader2 className="h-5 w-5 animate-spin" />
Loading...
</CardTitle>
<CardDescription>
Please wait while we process your request...
</CardDescription>
</CardHeader>
</Card>
</div>
}>
<AuthCallbackContent />
</Suspense>
)
}

View file

@ -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 (
<ProtectedRoute>
<ConnectorsPage />
<Suspense fallback={<div>Loading connectors...</div>}>
<ConnectorsPage />
</Suspense>
</ProtectedRoute>
)
}

View file

@ -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() {
</Card>
</div>
)
}
export default function LoginPage() {
return (
<Suspense fallback={
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="flex flex-col items-center gap-4">
<Loader2 className="h-8 w-8 animate-spin" />
<p className="text-muted-foreground">Loading...</p>
</div>
</div>
}>
<LoginPageContent />
</Suspense>
)
}