Skip to content

Commit

Permalink
Support react static in uvicorn backend (#382)
Browse files Browse the repository at this point in the history
* react static

* Prompt users to build web
  • Loading branch information
kivinju authored Aug 21, 2023
1 parent 33332ca commit 4a84d59
Show file tree
Hide file tree
Showing 30 changed files with 95 additions and 1,603 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ ELEVEN_LABS_API_KEY=<api key>
cp .env.example .env
```
- **Step 6**. Run server with `cli.py` or use uvicorn directly
```sh
# Build the web fronend.
python cli.py web-build
```
```sh
python cli.py run-uvicorn
# or
Expand All @@ -203,14 +207,15 @@ ELEVEN_LABS_API_KEY=<api key>
- **Step 7**. Run client:
- Use **GPT4** for better conversation and **Wear headphone** for best audio(avoid echo)
- There are two ways to access the web client:
- **Option 1**: Running the client in React.
- **Option 1** Open your web browser and navigate to http://localhost:8000 (NOT 0.0.0.0:8000)
- **Make sure you have ran `python cli.py web-build` before starting the server.**
- **Option 2**: Running the client in React.
```sh
cd client/web
npm install
npm start
```
After running these commands, a local development server will start, and your default web browser will open a new tab/window pointing to this server (usually http://localhost:3000).
- **Option 2** (legacy frontend): Open your web browser and navigate to http://localhost:8000 (NOT 0.0.0.0:8000)
- (Optional) Terminal client: Run the following command in your terminal
```sh
python client/cli.py
Expand Down
2 changes: 0 additions & 2 deletions alembic/env.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from realtime_ai_character.models.user import User
from realtime_ai_character.models.interaction import Interaction
from realtime_ai_character.database.base import Base # import the Base model
from sqlalchemy import engine_from_config
from sqlalchemy import pool
Expand Down
35 changes: 27 additions & 8 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

@click.group()
def cli():
assert sys.version_info > (3, 10), "Python version must be newer than 3.10"
assert sys.version_info > (3, 10), "Python version must be newer than 3.10"
pass


@click.command()
@click.option('--name', default="realtime-ai-character", help='The name to give to your Docker image.')
@click.option('--rebuild', is_flag=True, help='Flag to indicate whether to rebuild the Docker image.')
@click.option('--name', default="realtime-ai-character",
help='The name to give to your Docker image.')
@click.option('--rebuild', is_flag=True,
help='Flag to indicate whether to rebuild the Docker image.')
def docker_build(name, rebuild):
if rebuild or not image_exists(name):
click.secho(f"Building Docker image: {name}...", fg='green')
Expand All @@ -23,12 +25,15 @@ def docker_build(name, rebuild):
subprocess.run(["docker", "build", "-t", name, "."])
else:
click.secho(
f"Docker image: {name} already exists. Skipping build. To rebuild, use --rebuild option", fg='yellow')
f"Docker image: {name} already exists. Skipping build. " + \
"To rebuild, use --rebuild option", fg='yellow')


@click.command()
@click.option('--name', default="realtime-ai-character", help='The name of the Docker image to run.')
@click.option('--db-file', default=None, help='Path to the database file to mount inside the container.')
@click.option('--name', default="realtime-ai-character",
help='The name of the Docker image to run.')
@click.option('--db-file', default=None,
help='Path to the database file to mount inside the container.')
def docker_run(name, db_file):
click.secho(f"Running Docker image: {name}...", fg='green')
if not os.path.isfile('.env'):
Expand All @@ -46,7 +51,8 @@ def docker_run(name, db_file):


@click.command()
@click.option('--name', default="realtime-ai-character", help='The name of the Docker image to delete.')
@click.option('--name', default="realtime-ai-character",
help='The name of the Docker image to delete.')
def docker_delete(name):
if image_exists(name):
click.secho(f"Deleting Docker image: {name}...", fg='green')
Expand All @@ -60,7 +66,19 @@ def docker_delete(name):
def run_uvicorn(args):
click.secho("Running uvicorn server...", fg='green')
subprocess.run(["uvicorn", "realtime_ai_character.main:app",
"--ws-ping-interval", "60", "--ws-ping-timeout", "60", "--timeout-keep-alive", "60"] + list(args))
"--ws-ping-interval", "60",
"--ws-ping-timeout", "60",
"--timeout-keep-alive", "60"] + list(args))


@click.command()
def web_build():
# Build the web app to be served by FastAPI
click.secho("Building web app...", fg='green')
subprocess.run(["npm", "install"], cwd="client/web")
click.secho("Web app dependencies installed.", fg='green')
subprocess.run(["npm", "run", "build"], cwd="client/web")
click.secho("Web app built.", fg='green')


def image_exists(name):
Expand All @@ -73,6 +91,7 @@ def image_exists(name):
cli.add_command(docker_run)
cli.add_command(docker_delete)
cli.add_command(run_uvicorn)
cli.add_command(web_build)


if __name__ == '__main__':
Expand Down
3 changes: 1 addition & 2 deletions realtime_ai_character/database/connection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.orm import sessionmaker
from dotenv import load_dotenv
import os

Expand Down Expand Up @@ -29,7 +29,6 @@ def get_db():
if __name__ == "__main__":
print(SQLALCHEMY_DATABASE_URL)
from realtime_ai_character.models.user import User
from realtime_ai_character.models.interaction import Interaction
with SessionLocal() as session:
print(session.query(User).all())
session.delete(User(name="Test", email="[email protected]"))
Expand Down
34 changes: 31 additions & 3 deletions realtime_ai_character/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import warnings

from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, RedirectResponse

from realtime_ai_character.audio.speech_to_text import get_speech_to_text
from realtime_ai_character.audio.text_to_speech import get_text_to_speech
Expand All @@ -28,9 +29,36 @@

app.include_router(restful_router)
app.include_router(websocket_router)
app.mount("/static", StaticFiles(directory=os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'static')), name="static")

web_build_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'..', 'client', 'web', 'build')

if os.path.exists(web_build_path):
app.mount("/static/",
StaticFiles(directory=os.path.join(web_build_path, 'static')),
name="static")

@app.get("/", response_class=FileResponse)
async def read_index():
return FileResponse(os.path.join(web_build_path, 'index.html'))

@app.get("/{catchall:path}", response_class=FileResponse)
def read_static(request: Request):
path = request.path_params["catchall"]
file = os.path.join(web_build_path, path)

if os.path.exists(file):
return FileResponse(file)

return RedirectResponse("/")
else:
# If the web app is not built, prompt the user to build it
static_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
app.mount("/static/", StaticFiles(directory=static_path), name="static")

@app.get("/", response_class=FileResponse)
async def read_index():
return FileResponse(os.path.join(static_path, '404.html'))

# initializations
overwrite_chroma = os.getenv("OVERWRITE_CHROMA", 'True').lower() in ('true', '1')
Expand Down
15 changes: 3 additions & 12 deletions realtime_ai_character/restful_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
from fastapi import APIRouter, Depends, HTTPException, Request, \
status as http_status, UploadFile, File, Form
from google.cloud import storage
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
import firebase_admin
from firebase_admin import auth, credentials
from firebase_admin.exceptions import FirebaseError
Expand All @@ -25,9 +23,6 @@

router = APIRouter()

templates = Jinja2Templates(directory=os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'static'))

if os.getenv('USE_AUTH', ''):
cred = credentials.Certificate(os.environ.get('FIREBASE_CONFIG_PATH'))
firebase_admin.initialize_app(cred)
Expand Down Expand Up @@ -70,11 +65,6 @@ async def status():
return {"status": "ok", "message": "RealChar is running smoothly!"}


@router.get("/", response_class=HTMLResponse)
async def index(request: Request, user=Depends(get_current_user)):
return templates.TemplateResponse("index.html", {"request": request})


@router.get("/characters")
async def characters(user=Depends(get_current_user)):
def get_image_url(character):
Expand Down Expand Up @@ -311,10 +301,11 @@ async def quivr_info(user = Depends(get_current_user),
if not quivr_info:
return {"success": False}

return {"success": True, "api_key": quivr_info.quivr_api_key, "brain_id": quivr_info.quivr_brain_id}
return {"success": True, "api_key": quivr_info.quivr_api_key,
"brain_id": quivr_info.quivr_brain_id}

@router.post("/quivr_info")
async def quivr_info(update_quivr_info_request: UpdateQuivrInfoRequest,
async def quivr_info_update(update_quivr_info_request: UpdateQuivrInfoRequest,
user = Depends(get_current_user),
db: Session = Depends(get_db)):
if not user:
Expand Down
26 changes: 26 additions & 0 deletions realtime_ai_character/static/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background-color: black;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, sans-serif;
}
.prompt {
text-align: center;
font-size: 2em;
font-weight: bold;
color: white;
}
</style>
</head>
<body>
<div class="prompt">
<p>Please run `python cli.py web-build` to build the web first.</p>
</div>
</body>
</html>
Binary file removed realtime_ai_character/static/ai_helper.png
Binary file not shown.
Binary file removed realtime_ai_character/static/bruce.png
Binary file not shown.
15 changes: 0 additions & 15 deletions realtime_ai_character/static/connect.svg

This file was deleted.

9 changes: 0 additions & 9 deletions realtime_ai_character/static/continue-call.svg

This file was deleted.

Binary file removed realtime_ai_character/static/elon.png
Binary file not shown.
1 change: 0 additions & 1 deletion realtime_ai_character/static/end-call.svg

This file was deleted.

Binary file removed realtime_ai_character/static/favicon.ico
Binary file not shown.
Binary file not shown.
112 changes: 0 additions & 112 deletions realtime_ai_character/static/index.html

This file was deleted.

Binary file removed realtime_ai_character/static/jobs.png
Binary file not shown.
Binary file removed realtime_ai_character/static/logo.png
Binary file not shown.
Loading

0 comments on commit 4a84d59

Please sign in to comment.