GithubHelp home page GithubHelp logo

navigaitor's Introduction

Next.js FastAPI Starter

Simple Next.js boilerplate that uses FastAPI as the API backend.


Introduction

This is a hybrid Next.js + Python app that uses Next.js as the frontend and FastAPI as the API backend. One great use case of this is to write Next.js apps that use Python AI libraries on the backend.

How It Works

The Python/FastAPI server is mapped into to Next.js app under /api/.

This is implemented using next.config.js rewrites to map any request to /api/:path* to the FastAPI API, which is hosted in the /api folder.

On localhost, the rewrite will be made to the 127.0.0.1:8000 port, which is where the FastAPI server is running.

In production, the FastAPI server is hosted as Python serverless functions on Vercel.

Demo

https://nextjs-fastapi-starter.vercel.app/

Deploy Your Own

You can clone & deploy it to Vercel with one click:

Deploy with Vercel

Developing Locally

You can clone & create this repo with the following command

npx create-next-app nextjs-fastapi --example "https://github.com/digitros/nextjs-fastapi"

Getting Started

First, install the dependencies:

npm install
# or
yarn
# or
pnpm install

Then, run the development server:

npm run dev
# or
yarn dev
# or
pnpm dev

Open http://localhost:3000 with your browser to see the result.

The FastApi server will be running on http://127.0.0.1:8000ย โ€“ feel free to change the port in package.json (you'll also need to update it in next.config.js).

Learn More

To learn more about Next.js, take a look at the following resources:

You can check out the Next.js GitHub repository - your feedback and contributions are welcome!

navigaitor's People

Contributors

kabilan108 avatar

Watchers

 avatar

navigaitor's Issues

Send message to API, parse response

// TODO: Send message to API, parse response

import { useState } from "react";

import { Conversation } from "@/components/dashboard/chat/conversation";
import HideablePanel from "@/components/dashboard/hideable-panel";
import MessageBox from "@/components/dashboard/chat/message-box";
import ChatOptions from "@/components/dashboard/chat/options";
import Drawer from "@/components/dashboard/drawer";

import { type Message as MessageType, Role } from "@/client/generated";
import { type SharedProps } from "@/lib/utils";

interface Props extends SharedProps {}

const RightPanel = () => <HideablePanel children={<ChatOptions />} />;

export const ChatDrawer = () => <Drawer children={<ChatOptions />} />;

export function Chat(props: Props) {
  console.log(props);
  const [conversation, addMessage] = useState<MessageType[]>([]);

  const handleSendMessage = async (userMessage: MessageType) => {
    console.log(`user said '${userMessage.content}'`);
    addMessage([...conversation, userMessage]);
    // TODO: Send message to API, parse response

    const assistantMessage = {
      role: Role.ASSISTANT,
      content: "Hello, I'm an **AI assistant**. How can I help you today?",
    };
    addMessage([...conversation, userMessage, assistantMessage]);
  };

  return (
    <main className="grid flex-1 gap-4 overflow-auto p-4 md:grid-cols-2 lg:grid-cols-3">
      <div className="relative flex h-full min-h-[50vh] flex-col rounded-xl bg-muted p-4 lg:col-span-2">
        <Conversation messages={conversation} />
        <MessageBox onSend={handleSendMessage} />
      </div>
      <RightPanel />
    </main>
  );
}

should be able to handle multiple files and metadata

# TODO: should be able to handle multiple files and metadata

from fastapi import APIRouter, Depends, UploadFile, HTTPException

from core.config import Settings, get_settings
from core.auth import get_current_user
from core import crud
from schema.auth import OAuthUserInDB
from schema.documents import Document, DocumentMetadataUpload
from schema.base import Response
from services import docstore, mongo


router = APIRouter()

default = {
    "name": "test",
    "document_type": "test",
    "tags": ["slides"],
}


# TODO: add conversation id
# TODO: should be able to handle multiple files and metadata
# TODO: file ingestion (chunking, metadata extraction, etc.)
@router.post("/upload", response_model=Response)
async def upload_document(
    file: UploadFile,
    metadata: DocumentMetadataUpload | None = None,
    user: OAuthUserInDB = Depends(get_current_user),
    s3: docstore.Client = Depends(docstore.create_client),
    db: mongo.AsyncClient = Depends(mongo.get_db),
    settings: Settings = Depends(get_settings),
) -> dict:
    conversation_id = None
    doc = await docstore.upload_doc(
        user_id=user.id,
        conversation_id=conversation_id,
        bucket=settings.AWS_BUCKET,
        metadata=metadata.model_dump() if metadata else {},
        file=file,
        client=s3,
        db=db,
    )
    return {"message": "Document uploaded", "data": {"document_id": doc.id}}


@router.delete("/{document_id}", response_model=Response)
async def delete_document(
    document_id: str,
    user: OAuthUserInDB = Depends(get_current_user),
    s3: docstore.Client = Depends(docstore.create_client),
    db: mongo.AsyncClient = Depends(mongo.get_db),
    settings: Settings = Depends(get_settings),
) -> dict:
    await docstore.delete_doc(
        user_id=user.id,
        doc_id=document_id,
        bucket=settings.AWS_BUCKET,
        client=s3,
        db=db,
    )
    return {"message": "Document deleted"}


@router.get("")
async def list_documents(
    user: OAuthUserInDB = Depends(get_current_user),
    db: mongo.AsyncClient = Depends(mongo.get_db),
) -> list[Document]:
    return await docstore.list_docs(user_id=user.id, db=db)


@router.get("/{document_id}")
async def get_document(
    document_id: str,
    user: OAuthUserInDB = Depends(get_current_user),
    db: mongo.AsyncClient = Depends(mongo.get_db),
) -> Document:
    return await docstore.get_doc(user_id=user.id, doc_id=document_id, db=db)


@router.put("/{document_id}")
async def update_document(
    document_id: str,
    document_update: dict,
    user: OAuthUserInDB = Depends(get_current_user),
    db: mongo.AsyncClient = Depends(mongo.get_db),
) -> Response:
    try:
        doc = await crud.update_document(
            db=db,
            doc_id=document_id,
            user_id=user.id,
            doc_update=document_update,
        )
        return {"message": "Document updated", "data": doc}
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Error updating document: {e}")

Use a secure secret key

# TODO: Use a secure secret key

from starlette.middleware.sessions import SessionMiddleware
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI, Request

import time

from core.config import settings, setup_logging
from api import api_router

setup_logging(settings)
app = FastAPI(
    title="navigAItor-server",
    openapi_url=f"{settings.API_V1_STR}/openapi.json",
    # docs_url=f"{settings.API_V1_STR}/docs",
    # redoc_url=f"{settings.API_V1_STR}/redoc",
    version="0.1.0",
)

# TODO: Use a secure secret key
app.add_middleware(SessionMiddleware, secret_key=settings.SESSION_TOKEN)

if settings.CLIENT_URL:
    app.add_middleware(
        CORSMiddleware,
        allow_origins=[settings.CLIENT_URL],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )


@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response


app.include_router(api_router, prefix=settings.API_V1_STR)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000, log_level="debug")

use folders: user_id/conversation_id/file_id

# TODO: use folders: user_id/conversation_id/file_id

from fastapi import Depends, UploadFile, HTTPException
from bson.objectid import ObjectId
import boto3
import uuid

from core.config import Settings, get_settings
from schema.files import CreateFile, FileInDB, raise_upload_error
from services import mongo

Client = boto3.client


def create_s3_client(settings: Settings = Depends(get_settings)) -> Client:
    return boto3.client(
        "s3",
        region_name=settings.AWS_REGION,
        aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
        aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
    )


def generate_file_name(file_name: str) -> str:
    """Generate unique file name"""
    ext = file_name.split(".")[-1]
    return f"{uuid.uuid4()}.{ext}"


# TODO: use folders: user_id/conversation_id/file_id
async def upload_file(
    user_id: str,
    conversation_id: str,
    bucket: str,
    file: UploadFile,
    client: Client,
    db: mongo.AsyncClient,
):
    """Create file metadata"""

    s3_key = generate_file_name(file.filename)

    try:
        client.upload_fileobj(file.file, bucket, s3_key)
    except Exception as e:
        raise_upload_error(e)

    metadata = CreateFile(
        name=file.filename,
        type=file.content_type,
        s3_key=s3_key,
        user_id=user_id,
        conversation_id=conversation_id,
    )
    try:
        inserted = await db.files.insert_one(metadata.model_dump())
    except Exception as e:
        raise_upload_error(e)

    file = FileInDB(**metadata.model_dump(), _id=inserted.inserted_id)

    return file


async def find_file(file_id: str, user_id: str, db: mongo.AsyncClient) -> FileInDB:
    """Find file by id"""
    file = await db.files.find_one({"_id": ObjectId(file_id)})
    if not file:
        raise HTTPException(status_code=404, detail="File not found")
    if file["user_id"] != user_id:
        raise HTTPException(status_code=401, detail="Unauthorized")
    return FileInDB(**file)


async def delete_file(
    user_id: str,
    file_id: str,
    bucket: str,
    client: Client,
    db: mongo.AsyncClient,
) -> dict:
    """Delete file from S3"""
    file = await find_file(file_id, user_id, db)
    client.delete_object(Bucket=bucket, Key=file.s3_key)
    await db.files.delete_one({"_id": ObjectId(file_id)})
    return {"message": "File deleted"}


async def list_files(user_id: str, db: mongo.AsyncClient) -> list[FileInDB]:
    """List files for user"""
    files = await db.files.find({"user_id": user_id}).to_list(None)
    return [FileInDB(**file) for file in files]

file ingestion (chunking, metadata extraction, etc.)

# TODO: file ingestion (chunking, metadata extraction, etc.)

from fastapi import APIRouter, Depends, UploadFile, File

from core.config import Settings, get_settings
from core.auth import get_current_user
from schema.auth import OAuthUserInDB
from schema.files import FileInDB
from services import fs, mongo

router = APIRouter()


# TODO: add conversation id
# TODO: file ingestion (chunking, metadata extraction, etc.)
@router.post("/upload")
async def upload_file(
    file: UploadFile = File(...),
    user: OAuthUserInDB = Depends(get_current_user),
    s3: fs.Client = Depends(fs.create_s3_client),
    db: mongo.AsyncClient = Depends(mongo.get_db),
    settings: Settings = Depends(get_settings),
) -> dict:
    conversation_id = None
    file = await fs.upload_file(
        user_id=user.id,
        conversation_id=conversation_id,
        bucket=settings.AWS_BUCKET,
        file=file,
        client=s3,
        db=db,
    )
    return {"file_id": file.id}


@router.delete("/{file_id}")
async def delete_file(
    file_id: str,
    user: OAuthUserInDB = Depends(get_current_user),
    s3: fs.Client = Depends(fs.create_s3_client),
    db: mongo.AsyncClient = Depends(mongo.get_db),
    settings: Settings = Depends(get_settings),
) -> dict:
    return await fs.delete_file(
        user_id=user.id,
        file_id=file_id,
        bucket=settings.AWS_BUCKET,
        client=s3,
        db=db,
    )


@router.post("/list")
async def list_files(
    user: OAuthUserInDB = Depends(get_current_user),
    db: mongo.AsyncClient = Depends(mongo.get_db),
) -> list[FileInDB]:
    return await fs.list_files(user_id=user.id, db=db)

dialog needs to get the document passed to it

maybe use Sheet instead of Dialog

see https://github\.com/shadcn\-ui/ui/issues/1905

// TODO: dialog needs to get the document passed to it

import { useState } from "react";

import Card from "@/components/dashboard/card";

import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";

import { type Document, DocumentType } from "@/lib/utils";

const METADATA_FIELDS = [
  { key: "name", name: "Name", className: "" },
  { key: "document_type", name: "Type", className: "" },
  { key: "tags", name: "Tags", className: "" },
];

const BadgeColors = {
  document_type: {
    [DocumentType.SLIDES]: "bg-[#e9795d]",
    [DocumentType.DOCUMENT]: "bg-[#f4a261]",
    [DocumentType.RECORDING]: "bg-[#e9c46a]",
    test: "bg-[#2a9d8f]",
  },
};

// TODO: dialog needs to get the document passed to it
// maybe use Sheet instead of Dialog
// see https://github.com/shadcn-ui/ui/issues/1905

const DocumentEntry = ({
  document,
  handleTriggerDialog,
}: {
  document: Document;
  handleTriggerDialog: () => void;
}) => {
  const handleRowClick = () => {
    handleTriggerDialog();
  };

  return (
    <TableRow onClick={handleRowClick}>
      {METADATA_FIELDS.map((field) => {
        return (
          <TableCell key={field.key} className={field.className}>
            {field.key === "document_type" ? (
              <Badge
                className={BadgeColors[field.key][document.metadata[field.key]]}
              >
                {document.metadata[field.key]}
              </Badge>
            ) : (
              document.metadata[field.key]
            )}
          </TableCell>
        );
      })}
    </TableRow>
  );
};

const DocumentTable = ({
  documents,
  isOpenDialog,
  setIsOpenDialog,
}: {
  documents: Document[];
  isOpenDialog: boolean;
  setIsOpenDialog: (value: boolean) => void;
}) => {
  const handleTriggerDialog = () => {
    setIsOpenDialog(!isOpenDialog);
  };

  return (
    <Table>
      <TableHeader>
        <TableRow>
          {METADATA_FIELDS.map((field) => (
            <TableHead key={field.key} className={field.className}>
              {field.name}
            </TableHead>
          ))}
        </TableRow>
      </TableHeader>
      <TableBody>
        <Dialog open={isOpenDialog} onOpenChange={handleTriggerDialog}>
          {documents.map((document) => (
            <DocumentEntry
              key={document.id}
              document={document}
              handleTriggerDialog={handleTriggerDialog}
            />
          ))}
          <DialogContent>
            <DialogHeader>
              <DialogTitle>Document X</DialogTitle>
            </DialogHeader>
          </DialogContent>
        </Dialog>
      </TableBody>
    </Table>
  );
};

interface Props {
  documents: Document[];
}

export default function Documents({ documents }: Props) {
  const [isOpenDialog, setIsOpenDialog] = useState(false);

  return (
    <Card
      title="Knowledge Base"
      description="Manage your personal knowledge base."
      className="flex flex-col gap-4"
    >
      <DocumentTable
        documents={documents}
        isOpenDialog={isOpenDialog}
        setIsOpenDialog={setIsOpenDialog}
      />
    </Card>
  );
}

use folders: user_id/file_id

# TODO: use folders: user_id/file_id

    )


def generate_file_name(user_id: str, file_name: str) -> str:
    """Generate unique file name"""
    ext = file_name.split(".")[-1]
    return f"{user_id}/{uuid.uuid4()}.{ext}"


# TODO: use folders: user_id/file_id
async def upload_file(
    user_id: str,
    conversation_id: str,

add conversation id

# TODO: add conversation id

from fastapi import APIRouter, Depends, UploadFile, File

from core.config import Settings, get_settings
from core.auth import get_current_user
from schema.auth import OAuthUserInDB
from schema.files import FileInDB
from services import fs, mongo

router = APIRouter()


# TODO: add conversation id
# TODO: file ingestion (chunking, metadata extraction, etc.)
@router.post("/upload")
async def upload_file(
    file: UploadFile = File(...),
    user: OAuthUserInDB = Depends(get_current_user),
    s3: fs.Client = Depends(fs.create_s3_client),
    db: mongo.AsyncClient = Depends(mongo.get_db),
    settings: Settings = Depends(get_settings),
) -> dict:
    conversation_id = None
    file = await fs.upload_file(
        user_id=user.id,
        conversation_id=conversation_id,
        bucket=settings.AWS_BUCKET,
        file=file,
        client=s3,
        db=db,
    )
    return {"file_id": file.id}


@router.delete("/{file_id}")
async def delete_file(
    file_id: str,
    user: OAuthUserInDB = Depends(get_current_user),
    s3: fs.Client = Depends(fs.create_s3_client),
    db: mongo.AsyncClient = Depends(mongo.get_db),
    settings: Settings = Depends(get_settings),
) -> dict:
    return await fs.delete_file(
        user_id=user.id,
        file_id=file_id,
        bucket=settings.AWS_BUCKET,
        client=s3,
        db=db,
    )


@router.post("/list")
async def list_files(
    user: OAuthUserInDB = Depends(get_current_user),
    db: mongo.AsyncClient = Depends(mongo.get_db),
) -> list[FileInDB]:
    return await fs.list_files(user_id=user.id, db=db)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.