Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions backend.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM python:3.12-slim

WORKDIR /app

RUN apt-get update && apt-get install -y \
build-essential \
curl \
software-properties-common \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install --no-cache-dir fastapi uvicorn python-multipart

COPY . .

CMD ["python", "backend/api/main.py"]
156 changes: 156 additions & 0 deletions backend/api/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import sys
import os
from pathlib import Path
from fastapi import FastAPI, UploadFile, File, HTTPException, Body, Form
from fastapi.middleware.cors import CORSMiddleware
from typing import List, Dict, Any, Optional
import uvicorn
import asyncio
import shutil

# Add project root to sys.path
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))

from main_launcher import SecureInsightLauncher

app = FastAPI(title="NeuraX API")

# Enable CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

launcher = SecureInsightLauncher()

@app.on_event("startup")
async def startup_event():
# Run initialization in a separate thread to avoid blocking
def init():
if not launcher.validate_system():
print("System validation failed")
if not launcher.initialize_components():
print("Component initialization failed")

loop = asyncio.get_event_loop()
await loop.run_in_executor(None, init)

@app.get("/api/health")
async def health_check():
return launcher._perform_health_check()

@app.post("/api/upload")
async def upload_files(files: List[UploadFile] = File(...)):
results = []
upload_dir = Path("uploads")
upload_dir.mkdir(exist_ok=True)

for file in files:
temp_path = upload_dir / file.filename
with open(temp_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)

try:
# Process file using ingestion manager
result = launcher.ingestion_manager.process_file(str(temp_path))
if result:
# Add to vector store
if launcher.vector_store and launcher.embedding_manager:
# Depending on the content type, we might need to embed text or image
if result.get('content'):
embedding = launcher.embedding_manager.embed_text(result['content'])
result['embedding'] = embedding
elif result.get('image_path'):
embedding = launcher.embedding_manager.embed_image(result['image_path'])
result['embedding'] = embedding

launcher.vector_store.add_document(result)
results.append({"filename": file.filename, "status": "success", "id": result.get('metadata', {}).get('document_id')})
else:
results.append({"filename": file.filename, "status": "error", "message": "Processing failed"})
except Exception as e:
results.append({"filename": file.filename, "status": "error", "message": str(e)})
finally:
# We might want to keep the file if it's referenced by metadata,
# but for now let's follow the system's logic.
# Some processors might move the file or store it.
pass

return results

@app.get("/api/files")
async def list_files():
# If the vector store doesn't support listing, we'll return an empty list or cached list
return {"files": []}

@app.post("/api/query")
async def process_query(
query: str = Form(...),
image: Optional[UploadFile] = File(None)
):
try:
result = {}
if image:
upload_dir = Path("uploads/queries")
upload_dir.mkdir(parents=True, exist_ok=True)
temp_image_path = upload_dir / image.filename
with open(temp_image_path, "wb") as buffer:
shutil.copyfileobj(image.file, buffer)

result = launcher.query_processor.process_multimodal_query(query, str(temp_image_path))
else:
result = launcher.query_processor.process_text_query(query)

# Generate response using LLM if available
if launcher.llm_generator and result.get('results'):
context = result['results']
llm_response = launcher.llm_generator.generate_grounded_response(query, context)
result['generated_response'] = llm_response.response_text

# Generate citations
if launcher.citation_generator:
citations = launcher.citation_generator.generate_citations(llm_response.response_text, context)
result['citations'] = citations

return result
except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/analytics/metrics")
async def get_metrics():
if launcher.metrics_collector:
return launcher.metrics_collector.get_all_metrics()
return {}

@app.get("/api/knowledge-graph")
async def get_knowledge_graph():
if launcher.kg_manager:
return launcher.kg_manager.get_graph_stats()
return {}

@app.post("/api/feedback")
async def submit_feedback(feedback: Dict[str, Any] = Body(...)):
if launcher.feedback_system:
launcher.feedback_system.collect_feedback(**feedback)
return {"status": "success"}
return {"status": "error", "message": "Feedback system not available"}

@app.get("/api/config")
async def get_config():
try:
from config import LLM_CONFIG, SEARCH_CONFIG
return {
"llm": LLM_CONFIG,
"search": SEARCH_CONFIG
}
except ImportError:
return {}

if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
24 changes: 24 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
services:
backend:
build:
context: .
dockerfile: backend.Dockerfile
ports:
- "8000:8000"
volumes:
- ./vector_db:/app/vector_db
- ./uploads:/app/uploads
- ./logs:/app/logs
environment:
- PYTHONUNBUFFERED=1

frontend:
build:
context: ./neurax-frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_API_URL=http://backend:8000
depends_on:
- backend
41 changes: 41 additions & 0 deletions neurax-frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
30 changes: 30 additions & 0 deletions neurax-frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM node:20-alpine AS base

FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]
18 changes: 18 additions & 0 deletions neurax-frontend/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);

export default eslintConfig;
8 changes: 8 additions & 0 deletions neurax-frontend/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
output: 'standalone',
};

export default nextConfig;
Loading