alaselababatunde commited on
Commit
e03f122
·
1 Parent(s): cb610aa

Configure multi-stage Docker build to serve frontend and fix API URL

Browse files
Files changed (3) hide show
  1. Dockerfile +14 -1
  2. frontend/src/App.tsx +4 -4
  3. main.py +30 -7
Dockerfile CHANGED
@@ -1,7 +1,17 @@
1
  # ==============================================================
2
- # Tech Disciples AI Backend — Dockerfile (for Hugging Face Spaces)
3
  # ==============================================================
4
 
 
 
 
 
 
 
 
 
 
 
5
  FROM python:3.10-slim
6
 
7
  # Environment setup
@@ -23,6 +33,9 @@ RUN pip install --no-cache-dir -r requirements.txt
23
  # Copy all source files
24
  COPY . .
25
 
 
 
 
26
  # Define Hugging Face token env var (Spaces injects the secret automatically)
27
  ENV HUGGINGFACEHUB_API_TOKEN=""
28
 
 
1
  # ==============================================================
2
+ # Tech Disciples AI — Dockerfile (for Hugging Face Spaces)
3
  # ==============================================================
4
 
5
+ # Stage 1: Build the React frontend
6
+ FROM node:18-alpine AS frontend-builder
7
+ WORKDIR /app/frontend
8
+ COPY frontend/package*.json ./
9
+ # Clean install to ensure all dependencies are properly resolved
10
+ RUN npm install --legacy-peer-deps
11
+ COPY frontend/ ./
12
+ RUN npm run build
13
+
14
+ # Stage 2: Set up the FastAPI backend
15
  FROM python:3.10-slim
16
 
17
  # Environment setup
 
33
  # Copy all source files
34
  COPY . .
35
 
36
+ # Copy the built frontend from Stage 1
37
+ COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
38
+
39
  # Define Hugging Face token env var (Spaces injects the secret automatically)
40
  ENV HUGGINGFACEHUB_API_TOKEN=""
41
 
frontend/src/App.tsx CHANGED
@@ -28,8 +28,8 @@ const App: React.FC = () => {
28
  setIsLoading(true);
29
 
30
  try {
31
- // Note: In development, make sure to point to your FastAPI backend URL if different
32
- const response = await axios.post('http://localhost:7860/ai-chat', {
33
  query: userMessage,
34
  session_id: sessionId,
35
  }, {
@@ -105,8 +105,8 @@ const App: React.FC = () => {
105
  {msg.role === 'user' ? <User size={16} /> : <Bot size={16} />}
106
  </div>
107
  <div className={`p-4 rounded-2xl ${msg.role === 'user'
108
- ? 'bg-purple-600/30 text-white rounded-tr-none border border-purple-500/20'
109
- : 'bg-white/5 text-white/90 rounded-tl-none border border-white/10 backdrop-blur-md'
110
  }`}>
111
  <div className="markdown-content prose prose-invert prose-sm max-w-none">
112
  <ReactMarkdown>{msg.content}</ReactMarkdown>
 
28
  setIsLoading(true);
29
 
30
  try {
31
+ // Note: Use relative path for Hugging Face deployment so it hits the same origin backend
32
+ const response = await axios.post('/ai-chat', {
33
  query: userMessage,
34
  session_id: sessionId,
35
  }, {
 
105
  {msg.role === 'user' ? <User size={16} /> : <Bot size={16} />}
106
  </div>
107
  <div className={`p-4 rounded-2xl ${msg.role === 'user'
108
+ ? 'bg-purple-600/30 text-white rounded-tr-none border border-purple-500/20'
109
+ : 'bg-white/5 text-white/90 rounded-tl-none border border-white/10 backdrop-blur-md'
110
  }`}>
111
  <div className="markdown-content prose prose-invert prose-sm max-w-none">
112
  <ReactMarkdown>{msg.content}</ReactMarkdown>
main.py CHANGED
@@ -5,7 +5,9 @@
5
  import os
6
  import logging
7
  from typing import List, Optional
8
- from fastapi import FastAPI, HTTPException, Header
 
 
9
  from fastapi.middleware.cors import CORSMiddleware
10
  from pydantic import BaseModel
11
  from dotenv import load_dotenv
@@ -99,12 +101,25 @@ class QueryInput(BaseModel):
99
  query: str
100
  session_id: Optional[str] = "default"
101
 
102
- # =====================================================
103
- # ROUTES
104
- # =====================================================
105
- @app.get("/")
 
 
 
 
 
 
 
 
 
 
106
  async def root():
107
- return {"message": "✅ Theo AI (TechDisciples CLCC) is online and ready for ministry."}
 
 
 
108
 
109
  @app.post("/ai-chat")
110
  async def ai_chat(data: QueryInput, x_api_key: str = Header(None)):
@@ -157,4 +172,12 @@ async def ai_chat(data: QueryInput, x_api_key: str = Header(None)):
157
 
158
  except Exception as e:
159
  logger.error(f"⚠️ Model runtime error: {e}")
160
- raise HTTPException(status_code=500, detail=f"Theo encountered an issue: {str(e)}")
 
 
 
 
 
 
 
 
 
5
  import os
6
  import logging
7
  from typing import List, Optional
8
+ from fastapi import FastAPI, HTTPException, Header, Request
9
+ from fastapi.responses import HTMLResponse, FileResponse
10
+ from fastapi.staticfiles import StaticFiles
11
  from fastapi.middleware.cors import CORSMiddleware
12
  from pydantic import BaseModel
13
  from dotenv import load_dotenv
 
101
  query: str
102
  session_id: Optional[str] = "default"
103
 
104
+ # Mount the frontend directory as static files
105
+ # In a typical Vite React app, the build output is in 'dist'.
106
+ # If it's just raw HTML/JS/CSS we point to 'frontend'.
107
+ frontend_dir = os.path.join(os.path.dirname(__file__), "frontend")
108
+ dist_dir = os.path.join(frontend_dir, "dist")
109
+
110
+ if os.path.isdir(dist_dir):
111
+ serve_dir = dist_dir
112
+ else:
113
+ serve_dir = frontend_dir
114
+
115
+ app.mount("/assets", StaticFiles(directory=os.path.join(serve_dir, "assets"), html=True), name="assets")
116
+
117
+ @app.get("/", response_class=HTMLResponse)
118
  async def root():
119
+ index_path = os.path.join(serve_dir, "index.html")
120
+ if os.path.exists(index_path):
121
+ return FileResponse(index_path)
122
+ return {"message": "✅ Theo AI (TechDisciples CLCC) API is online. Frontend not built yet."}
123
 
124
  @app.post("/ai-chat")
125
  async def ai_chat(data: QueryInput, x_api_key: str = Header(None)):
 
172
 
173
  except Exception as e:
174
  logger.error(f"⚠️ Model runtime error: {e}")
175
+ raise HTTPException(status_code=500, detail=f"Theo encountered an issue: {str(e)}")
176
+
177
+ # Catch-all route to serve the SPA index.html for unknown paths
178
+ @app.exception_handler(404)
179
+ async def custom_404_handler(request: Request, exc: HTTPException):
180
+ index_path = os.path.join(serve_dir, "index.html")
181
+ if os.path.exists(index_path) and not request.url.path.startswith("/api"):
182
+ return FileResponse(index_path)
183
+ return HTMLResponse(content="Not Found", status_code=404)