This commit is contained in:
parent
e3cd4fa080
commit
56ec151b70
|
@ -8,3 +8,5 @@ aiofiles
|
||||||
duckdb
|
duckdb
|
||||||
pyjwt[crypto]
|
pyjwt[crypto]
|
||||||
python-multipart
|
python-multipart
|
||||||
|
authlib
|
||||||
|
itsdangerous
|
|
@ -2,11 +2,16 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
|
base_url: str = "http://localhost:8000"
|
||||||
anyscale_api_key: str = "Awesome API"
|
anyscale_api_key: str = "Awesome API"
|
||||||
gcs_access: str = "access"
|
gcs_access: str = "access"
|
||||||
gcs_secret: str = "secret"
|
gcs_secret: str = "secret"
|
||||||
gcs_bucketname: str = "bucket"
|
gcs_bucketname: str = "bucket"
|
||||||
auth: bool = True
|
auth: bool = True
|
||||||
|
auth0_client_id: str = ""
|
||||||
|
auth0_client_secret: str = ""
|
||||||
|
auth0_domain: str = ""
|
||||||
|
app_secret_key: str = ""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_file=".env")
|
model_config = SettingsConfigDict(env_file=".env")
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,76 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI, status
|
from fastapi import FastAPI, status
|
||||||
|
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
|
from starlette.requests import Request
|
||||||
|
from authlib.integrations.starlette_client import OAuth, OAuthError
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
import hellocomputer
|
import hellocomputer
|
||||||
|
|
||||||
from .routers import analysis, files, sessions
|
from .routers import analysis, files, sessions
|
||||||
|
from .config import settings
|
||||||
|
|
||||||
static_path = Path(hellocomputer.__file__).parent / "static"
|
static_path = Path(hellocomputer.__file__).parent / "static"
|
||||||
|
|
||||||
|
oauth = OAuth()
|
||||||
|
oauth.register(
|
||||||
|
"auth0",
|
||||||
|
client_id=settings.auth0_client_id,
|
||||||
|
client_secret=settings.auth0_client_secret,
|
||||||
|
client_kwargs={"scope": "openid profile email", "verify": False},
|
||||||
|
server_metadata_url=f"https://{settings.auth0_domain}/.well-known/openid-configuration",
|
||||||
|
)
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
app.add_middleware(SessionMiddleware, secret_key=settings.app_secret_key)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def homepage(request: Request):
|
||||||
|
user = request.session.get("user")
|
||||||
|
if user:
|
||||||
|
print(json.dumps(user))
|
||||||
|
return RedirectResponse("/app")
|
||||||
|
|
||||||
|
with open(static_path / "login.html") as f:
|
||||||
|
return HTMLResponse(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/login")
|
||||||
|
async def login(request: Request):
|
||||||
|
return await oauth.auth0.authorize_redirect(
|
||||||
|
request,
|
||||||
|
redirect_uri="http://localhost:8000/callback",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/callback", methods=["GET", "POST"])
|
||||||
|
async def callback(request: Request):
|
||||||
|
try:
|
||||||
|
token = await oauth.auth0.authorize_access_token(request)
|
||||||
|
except OAuthError as error:
|
||||||
|
return HTMLResponse(f"<h1>{error.error}</h1>")
|
||||||
|
user = token.get("userinfo")
|
||||||
|
if user:
|
||||||
|
request.session["user"] = dict(user)
|
||||||
|
|
||||||
|
return RedirectResponse(url="/app")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/logout")
|
||||||
|
async def logout(request: Request):
|
||||||
|
request.session.pop("user", None)
|
||||||
|
return RedirectResponse(url="/")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/user")
|
||||||
|
async def user(request: Request):
|
||||||
|
user = request.session.get("user")
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
class HealthCheck(BaseModel):
|
class HealthCheck(BaseModel):
|
||||||
|
@ -44,7 +104,7 @@ app.include_router(sessions.router)
|
||||||
app.include_router(files.router)
|
app.include_router(files.router)
|
||||||
app.include_router(analysis.router)
|
app.include_router(analysis.router)
|
||||||
app.mount(
|
app.mount(
|
||||||
"/",
|
"/app",
|
||||||
StaticFiles(directory=static_path, html=True),
|
StaticFiles(directory=static_path, html=True),
|
||||||
name="static",
|
name="static",
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from typing import Annotated
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter
|
||||||
|
from starlette.requests import Request
|
||||||
from fastapi.responses import PlainTextResponse
|
from fastapi.responses import PlainTextResponse
|
||||||
|
|
||||||
from ..security import oauth2_scheme
|
|
||||||
|
|
||||||
# Scheme for the Authorization header
|
# Scheme for the Authorization header
|
||||||
|
|
||||||
|
@ -12,7 +11,9 @@ router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/new_session")
|
@router.get("/new_session")
|
||||||
async def get_new_session(token: Annotated[str, Depends(oauth2_scheme)]) -> str:
|
async def get_new_session(request: Request) -> str:
|
||||||
|
user = request.session.get("user")
|
||||||
|
print(user)
|
||||||
return str(uuid4())
|
return str(uuid4())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
|
@ -8,6 +8,7 @@
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -21,9 +22,17 @@
|
||||||
Hello, computer!
|
Hello, computer!
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<a href="#" class="list-group-item list-group-item-action bg-light">How to</a>
|
<a href="#" class="list-group-item list-group-item-action bg-light"><i
|
||||||
<a href="#" class="list-group-item list-group-item-action bg-light">File templates</a>
|
class="bi bi-question-circle"></i> How to</a>
|
||||||
<a href="#" class="list-group-item list-group-item-action bg-light">About</a>
|
<a href="#" class="list-group-item list-group-item-action bg-light"><i class="bi bi-file-ruled"></i>
|
||||||
|
File templates</a>
|
||||||
|
<a href="#" class="list-group-item list-group-item-action bg-light"><i class="bi bi-info-circle"></i>
|
||||||
|
About</a>
|
||||||
|
<a href="/config" class="list-group-item list-group-item-action bg-light"><i class="bi bi-toggles"></i>
|
||||||
|
Config</a>
|
||||||
|
<a href="/logout" class="list-group-item list-group-item-action bg-light"><i
|
||||||
|
class="bi bi-box-arrow-right"></i>
|
||||||
|
Logout</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -105,7 +114,7 @@
|
||||||
<div class="chat-messages">
|
<div class="chat-messages">
|
||||||
<!-- Messages will be appended here -->
|
<!-- Messages will be appended here -->
|
||||||
<div class="message bg-white p-2 mb-2 rounded">
|
<div class="message bg-white p-2 mb-2 rounded">
|
||||||
<img src="/img/assistant.webp" width="50px">
|
<img src="/app/img/assistant.webp" width="50px">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div id="spinner" class="spinner"></div>
|
<div id="spinner" class="spinner"></div>
|
||||||
<div id="result" class="hidden"></div>
|
<div id="result" class="hidden"></div>
|
||||||
|
|
43
src/hellocomputer/static/login.html
Normal file
43
src/hellocomputer/static/login.html
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Login Page</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.login-container {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 50px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.techie-font {
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="login-container text-center">
|
||||||
|
<h1 class="h3 mb-3 fw-normal techie-font">Hola, computer!</h1>
|
||||||
|
<img src="/app/img/assistant.webp" alt="Logo" class="logo img-fluid">
|
||||||
|
<a href="/login"><button type="submit" class="btn btn-primary w-100">Login</button></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -37,16 +37,16 @@ async function fetchResponse(message, newMessage) {
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
|
|
||||||
// Hide spinner and display result
|
// Hide spinner and display result
|
||||||
newMessage.innerHTML = '<img src="/img/assistant.webp" width="50px"> <div><pre>' + data + '</pre></div>';
|
newMessage.innerHTML = '<img src="/app/img/assistant.webp" width="50px"> <div><pre>' + data + '</pre></div>';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
newMessage.innerHTML = '<img src="/img/assistant.webp" width="50px">' + 'Error: ' + error.message;
|
newMessage.innerHTML = '<img src="/app/img/assistant.webp" width="50px">' + 'Error: ' + error.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addAIMessage(messageContent) {
|
function addAIMessage(messageContent) {
|
||||||
const newMessage = document.createElement('div');
|
const newMessage = document.createElement('div');
|
||||||
newMessage.classList.add('message', 'bg-white', 'p-2', 'mb-2', 'rounded');
|
newMessage.classList.add('message', 'bg-white', 'p-2', 'mb-2', 'rounded');
|
||||||
newMessage.innerHTML = '<img src="/img/assistant.webp" width="50px"> <div id="spinner" class="spinner"></div>';
|
newMessage.innerHTML = '<img src="/app/img/assistant.webp" width="50px"> <div id="spinner" class="spinner"></div>';
|
||||||
chatMessages.prepend(newMessage); // Add new message at the top
|
chatMessages.prepend(newMessage); // Add new message at the top
|
||||||
fetchResponse(messageContent, newMessage);
|
fetchResponse(messageContent, newMessage);
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ function addAIMessage(messageContent) {
|
||||||
function addAIManualMessage(m) {
|
function addAIManualMessage(m) {
|
||||||
const newMessage = document.createElement('div');
|
const newMessage = document.createElement('div');
|
||||||
newMessage.classList.add('message', 'bg-white', 'p-2', 'mb-2', 'rounded');
|
newMessage.classList.add('message', 'bg-white', 'p-2', 'mb-2', 'rounded');
|
||||||
newMessage.innerHTML = '<img src="/img/assistant.webp" width="50px"> <div>' + m + '</div>';
|
newMessage.innerHTML = '<img src="/app/img/assistant.webp" width="50px"> <div>' + m + '</div>';
|
||||||
chatMessages.prepend(newMessage); // Add new message at the top
|
chatMessages.prepend(newMessage); // Add new message at the top
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue