Merge branch 'main' into feat/responsive-chat-bar

This commit is contained in:
Stan Girard 2023-06-14 21:36:00 +02:00 committed by GitHub
commit 75c0366e93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
90 changed files with 400 additions and 131 deletions

View File

@ -2,8 +2,9 @@
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8"
},
"python.formatting.provider": "none",
"python.formatting.provider": "black",
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"python.linting.enabled": true
}

View File

@ -0,0 +1,36 @@
from datetime import datetime
from fastapi import HTTPException
from pydantic import DateError
from utils.vectors import CommonsDep
async def verify_api_key(api_key: str, commons: CommonsDep):
try:
# Use UTC time to avoid timezone issues
current_date = datetime.utcnow().date()
result = commons['supabase'].table('api_keys').select('api_key', 'creation_time').filter('api_key', 'eq', api_key).filter('is_active', 'eq', True).execute()
if result.data is not None and len(result.data) > 0:
api_key_creation_date = datetime.strptime(result.data[0]['creation_time'], "%Y-%m-%dT%H:%M:%S").date()
# Check if the API key was created today: Todo remove this check and use deleted_time instead.
if api_key_creation_date == current_date:
return True
return False
except DateError:
return False
async def get_user_from_api_key(api_key: str, commons: CommonsDep):
# Lookup the user_id from the api_keys table
user_id_data = commons['supabase'].table('api_keys').select('user_id').filter('api_key', 'eq', api_key).execute()
if not user_id_data.data:
raise HTTPException(status_code=400, detail="Invalid API key.")
user_id = user_id_data.data[0]['user_id']
# Lookup the email from the users table. Todo: remove and use user_id for credentials
user_email_data = commons['supabase'].table('users').select('email').filter('user_id', 'eq', user_id).execute()
return {'email': user_email_data.data[0]['email']} if user_email_data.data else {'email': None}

View File

@ -1,34 +1,42 @@
import os
from auth.api_key_handler import verify_api_key, get_user_from_api_key
from auth.jwt_token_handler import decode_access_token, verify_token
from typing import Optional
from fastapi import HTTPException, Request, Depends
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from fastapi import Depends, Request, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from models.users import User
from .auth_handler import decode_access_token
from utils.vectors import CommonsDep
class JWTBearer(HTTPBearer):
class AuthBearer(HTTPBearer):
def __init__(self, auto_error: bool = True):
super().__init__(auto_error=auto_error)
async def __call__(self, request: Request):
async def __call__(self, request: Request, commons: CommonsDep):
credentials: Optional[HTTPAuthorizationCredentials] = await super().__call__(request)
if os.environ.get("AUTHENTICATE") == "false":
return True
if credentials:
if not credentials.scheme == "Bearer":
raise HTTPException(status_code=402, detail="Invalid authorization scheme.")
token = credentials.credentials
if not self.verify_jwt(token):
raise HTTPException(status_code=402, detail="Invalid token or expired token.")
return self.verify_jwt(token) # change this line
else:
self.check_scheme(credentials)
token = credentials.credentials
return await self.authenticate(token, commons)
def check_scheme(self, credentials):
if credentials and not credentials.scheme == "Bearer":
raise HTTPException(status_code=402, detail="Invalid authorization scheme.")
elif not credentials:
raise HTTPException(status_code=403, detail="Invalid authorization code.")
def verify_jwt(self, jwtoken: str):
payload = decode_access_token(jwtoken)
return payload
async def authenticate(self, token: str, commons: CommonsDep):
if os.environ.get("AUTHENTICATE") == "false":
return self.get_test_user()
elif verify_token(token):
return decode_access_token(token)
elif await verify_api_key(token, commons):
return await get_user_from_api_key(token, commons)
else:
raise HTTPException(status_code=402, detail="Invalid token or expired token.")
def get_test_user(self):
return {'email': 'test@example.com'} # replace with test user information
def get_current_user(credentials: dict = Depends(JWTBearer())) -> User:
def get_current_user(credentials: dict = Depends(AuthBearer())) -> User:
return User(email=credentials.get('email', 'none'))

View File

@ -24,6 +24,10 @@ def decode_access_token(token: str):
return payload
except JWTError as e:
return None
def verify_token(token: str):
payload = decode_access_token(token)
return payload is not None
def get_user_email_from_token(token: str):
payload = decode_access_token(token)

View File

@ -1,6 +1,6 @@
from langchain.prompts.prompt import PromptTemplate
prompt_template = """Use the following pieces of context to answer the question in the language of the question. If you don't know the answer, just say that you don't know, don't try to make up an answer.
prompt_template = """Your name is Quivr. You are a second brain. A person will ask you a question and you will provide a helpful answer. Write the answer in the same language as the question. If you don't know the answer, just say that you don't know. Don't try to make up an answer. Use the following context to answer the question:
{context}

View File

@ -9,7 +9,7 @@ logger = get_logger(__name__)
openai_api_key = os.environ.get("OPENAI_API_KEY")
openai.api_key = openai_api_key
summary_llm = guidance.llms.OpenAI('gpt-3.5-turbo', caching=False)
summary_llm = guidance.llms.OpenAI('gpt-3.5-turbo-0613', caching=False)
def llm_summerize(document):
@ -40,7 +40,7 @@ def llm_evaluate_summaries(question, summaries, model):
if not model.startswith('gpt'):
logger.info(
f'Model {model} not supported. Using gpt-3.5-turbo instead.')
model = 'gpt-3.5-turbo'
model = 'gpt-3.5-turbo-0613'
logger.info(f'Evaluating summaries with {model}')
evaluation_llm = guidance.llms.OpenAI(model, caching=False)
evaluation = guidance("""

View File

@ -10,6 +10,7 @@ from routes.explore_routes import explore_router
from routes.misc_routes import misc_router
from routes.upload_routes import upload_router
from routes.user_routes import user_router
from routes.api_key_routes import api_key_router
logger = get_logger(__name__)
@ -29,3 +30,4 @@ app.include_router(explore_router)
app.include_router(misc_router)
app.include_router(upload_router)
app.include_router(user_router)
app.include_router(api_key_router)

View File

@ -5,11 +5,11 @@ from pydantic import BaseModel
class ChatMessage(BaseModel):
model: str = "gpt-3.5-turbo"
model: str = "gpt-3.5-turbo-0613"
question: str
# A list of tuples where each tuple is (speaker, text)
history: List[Tuple[str, str]]
temperature: float = 0.0
max_tokens: int = 256
use_summarization: bool = False
chat_id: Optional[UUID] = None,
chat_id: Optional[UUID] = None,

View File

@ -19,3 +19,4 @@ guidance==0.0.53
python-jose==3.3.0
google_cloud_aiplatform==1.25.0
transformers==4.30.1
asyncpg==0.27.0

View File

@ -0,0 +1,82 @@
from datetime import datetime
import time
from typing import List
from pydantic import BaseModel
from auth.auth_bearer import AuthBearer, get_current_user
from fastapi import APIRouter, Depends
from utils.vectors import fetch_user_id_from_credentials
from models.users import User
from utils.vectors import CommonsDep
from uuid import uuid4
from secrets import token_hex
from asyncpg.exceptions import UniqueViolationError
from logger import get_logger
logger = get_logger(__name__)
class ApiKeyInfo(BaseModel):
key_id: str
creation_time: str
class ApiKey(BaseModel):
api_key: str
api_key_router = APIRouter()
@api_key_router.post("/api-key", response_model=ApiKey, dependencies=[Depends(AuthBearer())])
async def create_api_key(commons: CommonsDep, current_user: User = Depends(get_current_user)):
date = time.strftime("%Y%m%d")
user_id = fetch_user_id_from_credentials(commons, date, {"email": current_user.email})
"""Create new API key for current user."""
new_key_id = str(uuid4())
new_api_key = token_hex(16)
api_key_inserted = False
while not api_key_inserted:
try:
# Attempt to insert new API key into database
commons['supabase'].table('api_keys').insert([{
"key_id": new_key_id,
"user_id": user_id,
"api_key": new_api_key,
"creation_time": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
"is_active": True
}]).execute()
api_key_inserted = True
except UniqueViolationError:
# Generate a new API key if the current one is already in use
new_api_key = token_hex(16)
logger.info(f"Created new API key for user {current_user.email}.")
return {"api_key": new_api_key}
@api_key_router.delete("/api-key/{key_id}", dependencies=[Depends(AuthBearer())])
async def delete_api_key(key_id: str, commons: CommonsDep, current_user: User = Depends(get_current_user)):
"""Delete (deactivate) an API key for current user."""
commons['supabase'].table('api_keys').update({
"is_active": False,
"deleted_time": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
}).match({"key_id": key_id, "user_id": current_user.user_id}).execute()
return {"message": "API key deleted."}
@api_key_router.get("/api-keys", response_model=List[ApiKeyInfo], dependencies=[Depends(AuthBearer())])
async def get_api_keys(commons: CommonsDep, current_user: User = Depends(get_current_user)):
"""Get all active API keys for the current user. Return only the key_id and the creation_time."""
user_id = fetch_user_id_from_credentials(commons, time.strftime("%Y%m%d"), {"email": current_user.email})
response = commons['supabase'].table('api_keys').select("key_id, creation_time").filter('user_id', 'eq', user_id).filter('is_active', 'eq', True).execute()
return response.data

View File

@ -2,7 +2,7 @@ import os
import time
from uuid import UUID
from auth.auth_bearer import JWTBearer, get_current_user
from auth.auth_bearer import AuthBearer, get_current_user
from fastapi import APIRouter, Depends, Request
from models.chats import ChatMessage
from models.users import User
@ -30,7 +30,7 @@ def fetch_user_stats(commons, user, date):
return userItem
# get all chats
@chat_router.get("/chat", dependencies=[Depends(JWTBearer())])
@chat_router.get("/chat", dependencies=[Depends(AuthBearer())])
async def get_chats(commons: CommonsDep, current_user: User = Depends(get_current_user)):
date = time.strftime("%Y%m%d")
user_id = fetch_user_id_from_credentials(commons, date, {"email": current_user.email})
@ -38,7 +38,7 @@ async def get_chats(commons: CommonsDep, current_user: User = Depends(get_curren
return {"chats": chats}
# get one chat
@chat_router.get("/chat/{chat_id}", dependencies=[Depends(JWTBearer())])
@chat_router.get("/chat/{chat_id}", dependencies=[Depends(AuthBearer())])
async def get_chats(commons: CommonsDep, chat_id: UUID):
chats = get_chat_details(commons, chat_id)
if len(chats) > 0:
@ -47,7 +47,7 @@ async def get_chats(commons: CommonsDep, chat_id: UUID):
return {"error": "Chat not found"}
# delete one chat
@chat_router.delete("/chat/{chat_id}", dependencies=[Depends(JWTBearer())])
@chat_router.delete("/chat/{chat_id}", dependencies=[Depends(AuthBearer())])
async def delete_chat(commons: CommonsDep, chat_id: UUID):
delete_chat_from_db(commons, chat_id)
return {"message": f"{chat_id} has been deleted."}
@ -91,11 +91,11 @@ def chat_handler(request, commons, chat_id, chat_message, email, is_new_chat=Fal
# update existing chat
@chat_router.put("/chat/{chat_id}", dependencies=[Depends(JWTBearer())])
@chat_router.put("/chat/{chat_id}", dependencies=[Depends(AuthBearer())])
async def chat_endpoint(request: Request, commons: CommonsDep, chat_id: UUID, chat_message: ChatMessage, current_user: User = Depends(get_current_user)):
return chat_handler(request, commons, chat_id, chat_message, current_user.email)
# create new chat
@chat_router.post("/chat", dependencies=[Depends(JWTBearer())])
@chat_router.post("/chat", dependencies=[Depends(AuthBearer())])
async def chat_endpoint(request: Request, commons: CommonsDep, chat_message: ChatMessage, current_user: User = Depends(get_current_user)):
return chat_handler(request, commons, None, chat_message, current_user.email, is_new_chat=True)

View File

@ -2,7 +2,7 @@ import os
import shutil
from tempfile import SpooledTemporaryFile
from auth.auth_bearer import JWTBearer, get_current_user
from auth.auth_bearer import AuthBearer, get_current_user
from crawl.crawler import CrawlWebsite
from fastapi import APIRouter, Depends, Request, UploadFile
from models.users import User
@ -23,7 +23,7 @@ def get_unique_user_data(commons, user):
user_unique_vectors = [dict(t) for t in set(tuple(d.items()) for d in documents)]
return user_unique_vectors
@crawl_router.post("/crawl/", dependencies=[Depends(JWTBearer())])
@crawl_router.post("/crawl/", dependencies=[Depends(AuthBearer())])
async def crawl_endpoint(request: Request,commons: CommonsDep, crawl_website: CrawlWebsite, enable_summarization: bool = False, current_user: User = Depends(get_current_user)):
max_brain_size = os.getenv("MAX_BRAIN_SIZE")
if request.headers.get('Openai-Api-Key'):

View File

@ -1,4 +1,4 @@
from auth.auth_bearer import JWTBearer, get_current_user
from auth.auth_bearer import AuthBearer, get_current_user
from fastapi import APIRouter, Depends
from models.users import User
from utils.vectors import CommonsDep
@ -13,15 +13,15 @@ def get_unique_user_data(commons, user):
unique_data = [dict(t) for t in set(tuple(d.items()) for d in documents)]
return unique_data
@explore_router.get("/explore", dependencies=[Depends(JWTBearer())])
@explore_router.get("/explore", dependencies=[Depends(AuthBearer())])
async def explore_endpoint(commons: CommonsDep, current_user: User = Depends(get_current_user)):
unique_data = get_unique_user_data(commons, current_user)
unique_data.sort(key=lambda x: int(x['size']), reverse=True)
return {"documents": unique_data}
@explore_router.delete("/explore/{file_name}", dependencies=[Depends(JWTBearer())])
async def delete_endpoint(commons: CommonsDep, file_name: str, credentials: dict = Depends(JWTBearer())):
@explore_router.delete("/explore/{file_name}", dependencies=[Depends(AuthBearer())])
async def delete_endpoint(commons: CommonsDep, file_name: str, credentials: dict = Depends(AuthBearer())):
user = User(email=credentials.get('email', 'none'))
# Cascade delete the summary from the database first, because it has a foreign key constraint
commons['supabase'].table("summaries").delete().match(
@ -30,7 +30,7 @@ async def delete_endpoint(commons: CommonsDep, file_name: str, credentials: dict
{"metadata->>file_name": file_name, "user_id": user.email}).execute()
return {"message": f"{file_name} of user {user.email} has been deleted."}
@explore_router.get("/explore/{file_name}", dependencies=[Depends(JWTBearer())])
@explore_router.get("/explore/{file_name}", dependencies=[Depends(AuthBearer())])
async def download_endpoint(commons: CommonsDep, file_name: str, current_user: User = Depends(get_current_user)):
response = commons['supabase'].table("vectors").select(
"metadata->>file_name, metadata->>file_size, metadata->>file_extension, metadata->>file_url", "content").match({"metadata->>file_name": file_name, "user_id": current_user.email}).execute()

View File

@ -1,6 +1,6 @@
import os
from auth.auth_bearer import JWTBearer, get_current_user
from auth.auth_bearer import AuthBearer, get_current_user
from fastapi import APIRouter, Depends, Request, UploadFile
from models.users import User
from utils.file import convert_bytes, get_file_size
@ -23,7 +23,7 @@ def calculate_remaining_space(request, max_brain_size, max_brain_size_with_own_k
remaining_free_space = float(max_brain_size_with_own_key) - current_brain_size if request.headers.get('Openai-Api-Key') else float(max_brain_size) - current_brain_size
return remaining_free_space
@upload_router.post("/upload", dependencies=[Depends(JWTBearer())])
@upload_router.post("/upload", dependencies=[Depends(AuthBearer())])
async def upload_file(request: Request, commons: CommonsDep, file: UploadFile, enable_summarization: bool = False, current_user: User = Depends(get_current_user)):
max_brain_size = os.getenv("MAX_BRAIN_SIZE")
max_brain_size_with_own_key = os.getenv("MAX_BRAIN_SIZE_WITH_KEY",209715200)

View File

@ -2,7 +2,7 @@ import os
import time
from fastapi import APIRouter, Depends, Request
from auth.auth_bearer import JWTBearer, get_current_user
from auth.auth_bearer import AuthBearer, get_current_user
from models.users import User
from utils.vectors import CommonsDep
@ -27,7 +27,7 @@ def get_user_request_stats(commons, email):
'*').filter("email", "eq", email).execute()
return requests_stats.data
@user_router.get("/user", dependencies=[Depends(JWTBearer())])
@user_router.get("/user", dependencies=[Depends(AuthBearer())])
async def get_user_endpoint(request: Request, commons: CommonsDep, current_user: User = Depends(get_current_user)):
user_vectors = get_user_vectors(commons, current_user.email)

37
docs/docs/backend/api.md Normal file
View File

@ -0,0 +1,37 @@
---
sidebar_position: 2
---
# API Backend Documentation
## Overview
This documentation outlines the key points and usage instructions for interacting with the API backend. Please follow the guidelines below to use the backend services effectively.
## Usage Instructions
1. Standalone Backend
- The backend can now be used independently without the frontend application.
- Users can interact with the API endpoints directly using API testing tools like Postman.
2. Generating API Key
- To access the backend services, you need to sign in to the frontend application.
- Once signed in, navigate to the `/config` page to generate a new API key.
- The API key will be required to authenticate your requests to the backend.
3. Authenticating Requests
- When making requests to the backend API, include the following header:
- `Authorization: Bearer {api_key}`
- Replace `{api_key}` with the generated API key obtained from the frontend.
4. Future Plans
- The development team has plans to introduce additional features and improvements.
- These include the ability to delete API keys and view the list of active keys.
- The GitHub roadmap will provide more details on upcoming features, including addressing active issues.
5. API Key Expiration
- Each API key has a daily expiration.
- The expiration is based on Coordinated Universal Time (UTC) to avoid timezone issues.
- After the expiration time, typically at midnight UTC, you will need to regenerate the API key to continue using the backend services.
- Once the capability to delete keys is implemented, you will have the option to delete keys manually.
Please refer to the official GitHub repository and the project roadmap for more information and updates on the backend services.

View File

@ -1,4 +1,4 @@
import Button from "@/app/components/ui/Button";
import Button from "@/lib/components/ui/Button";
import { useGoogleLogin } from "./hooks/useGoogleLogin";

View File

@ -1,6 +1,6 @@
"use client";
import Button from "@/app/components/ui/Button";
import { useSupabase } from "@/app/supabase-provider";
import Button from "@/lib/components/ui/Button";
import { useToast } from "@/lib/hooks/useToast";
import { useState } from "react";

View File

@ -1,10 +1,10 @@
"use client";
import Button from "@/app/components/ui/Button";
import Card from "@/app/components/ui/Card";
import { Divider } from "@/app/components/ui/Divider";
import Field from "@/app/components/ui/Field";
import PageHeading from "@/app/components/ui/PageHeading";
import { useSupabase } from "@/app/supabase-provider";
import Button from "@/lib/components/ui/Button";
import Card from "@/lib/components/ui/Card";
import { Divider } from "@/lib/components/ui/Divider";
import Field from "@/lib/components/ui/Field";
import PageHeading from "@/lib/components/ui/PageHeading";
import { useToast } from "@/lib/hooks/useToast";
import Link from "next/link";
import { redirect } from "next/navigation";

View File

@ -1,8 +1,8 @@
"use client";
import Button from "@/app/components/ui/Button";
import Card from "@/app/components/ui/Card";
import PageHeading from "@/app/components/ui/PageHeading";
import { useSupabase } from "@/app/supabase-provider";
import Button from "@/lib/components/ui/Button";
import Card from "@/lib/components/ui/Card";
import PageHeading from "@/lib/components/ui/PageHeading";
import { useToast } from "@/lib/hooks/useToast";
import Link from "next/link";
import { useRouter } from "next/navigation";

View File

@ -1,9 +1,9 @@
"use client";
import Button from "@/app/components/ui/Button";
import Card from "@/app/components/ui/Card";
import Field from "@/app/components/ui/Field";
import PageHeading from "@/app/components/ui/PageHeading";
import { useSupabase } from "@/app/supabase-provider";
import Button from "@/lib/components/ui/Button";
import Card from "@/lib/components/ui/Card";
import Field from "@/lib/components/ui/Field";
import PageHeading from "@/lib/components/ui/PageHeading";
import { useToast } from "@/lib/hooks/useToast";
import Link from "next/link";
import { useState } from "react";

View File

@ -1,3 +1,4 @@
import Card from "@/lib/components/ui/Card";
import { FC, ReactNode } from "react";
import {
GiArtificialIntelligence,
@ -7,7 +8,6 @@ import {
GiLockedDoor,
GiOpenBook,
} from "react-icons/gi";
import Card from "../components/ui/Card";
const Features: FC = () => {
return (

View File

@ -1,9 +1,9 @@
"use client";
import Button from "@/lib/components/ui/Button";
import { motion, useScroll, useSpring, useTransform } from "framer-motion";
import Link from "next/link";
import { FC, useRef } from "react";
import { MdNorthEast } from "react-icons/md";
import Button from "../components/ui/Button";
const Hero: FC = () => {
const targetRef = useRef<HTMLDivElement | null>(null);

View File

@ -1,8 +1,8 @@
"use client";
import PageHeading from "@/lib/components/ui/PageHeading";
import useChatsContext from "@/lib/context/ChatsProvider/hooks/useChatsContext";
import { UUID } from "crypto";
import { useEffect } from "react";
import PageHeading from "../../components/ui/PageHeading";
import useChatsContext from "../ChatsProvider/hooks/useChatsContext";
import { ChatInput, ChatMessages } from "../components";
interface ChatPageProps {

View File

@ -1,7 +1,7 @@
"use client";
import Button from "@/lib/components/ui/Button";
import Link from "next/link";
import { MdSettings } from "react-icons/md";
import Button from "../../../../components/ui/Button";
export function ConfigButton() {
return (

View File

@ -1,7 +1,7 @@
"use client";
import Button from "@/lib/components/ui/Button";
import { useSpeech } from "@/lib/context/ChatsProvider/hooks/useSpeech";
import { MdMic, MdMicOff } from "react-icons/md";
import Button from "../../../../components/ui/Button";
import { useSpeech } from "../../../ChatsProvider/hooks/useSpeech";
export function MicButton() {
const { isListening, speechSupported, startListening } = useSpeech();

View File

@ -1,6 +1,6 @@
"use client";
import useChatsContext from "@/app/chat/ChatsProvider/hooks/useChatsContext";
import Button from "../../../../components/ui/Button";
import Button from "@/lib/components/ui/Button";
import useChatsContext from "@/lib/context/ChatsProvider/hooks/useChatsContext";
import { ConfigButton } from "./ConfigButton";
import { MicButton } from "./MicButton";

View File

@ -1,7 +1,7 @@
"use client";
import Card from "@/app/components/ui/Card";
import Card from "@/lib/components/ui/Card";
import useChatsContext from "@/lib/context/ChatsProvider/hooks/useChatsContext";
import { FC, useEffect, useRef } from "react";
import useChatsContext from "../../ChatsProvider/hooks/useChatsContext";
import ChatMessage from "./ChatMessage";
export const ChatMessages: FC = () => {

View File

@ -5,7 +5,7 @@ import { usePathname } from "next/navigation";
import { FC } from "react";
import { FiTrash2 } from "react-icons/fi";
import { MdChatBubbleOutline } from "react-icons/md";
import { Chat } from "../../types";
import { Chat } from "../../../../lib/types/Chat";
interface ChatsListItemProps {
chat: Chat;

View File

@ -3,7 +3,7 @@ import { cn } from "@/lib/utils";
import { MotionConfig, motion } from "framer-motion";
import { useState } from "react";
import { MdChevronRight } from "react-icons/md";
import useChatsContext from "../../ChatsProvider/hooks/useChatsContext";
import useChatsContext from "@/lib/context/ChatsProvider/hooks/useChatsContext";
import ChatsListItem from "./ChatsListItem";
import { NewChatButton } from "./NewChatButton";
export function ChatsList() {

View File

@ -1,8 +1,8 @@
"use client";
import { ChatsProvider } from "@/lib/context/ChatsProvider/chats-provider";
import { redirect } from "next/navigation";
import { FC, ReactNode } from "react";
import { useSupabase } from "../supabase-provider";
import { ChatsProvider } from "./ChatsProvider/chats-provider";
import { ChatsList } from "./components";
interface LayoutProps {

View File

@ -0,0 +1,60 @@
"use client";
import Button from "@/lib/components/ui/Button";
import { useAxios } from "@/lib/useAxios";
import { useState } from "react";
export const ApiKeyConfig = (): JSX.Element => {
const [apiKey, setApiKey] = useState("");
const { axiosInstance } = useAxios();
const handleCreateClick = async () => {
try {
const response = await axiosInstance.post("/api-key"); // replace with your api-key endpoint URL
setApiKey(response.data.api_key);
} catch (error) {
console.error("Error creating API key: ", error);
}
};
const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text);
} catch (err) {
console.error("Failed to copy:", err);
}
};
const handleCopyClick = () => {
if (apiKey) {
copyToClipboard(apiKey);
}
};
return (
<>
<div className="border-b border-gray-300 w-full max-w-xl mb-8">
<p className="text-center text-gray-600 uppercase tracking-wide font-semibold">
API Key Config
</p>
</div>
<div className="flex justify-between items-center">
<div className="flex items-center space-x-4">
{!apiKey && (
<Button variant="secondary" onClick={handleCreateClick}>
Create New Key
</Button>
)}
</div>
{apiKey && (
<div className="flex items-center space-x-4">
<span className="text-gray-600">{apiKey}</span>
<Button variant="secondary" onClick={handleCopyClick}>
📋
</Button>
</div>
)}
</div>
</>
);
};

View File

@ -1,8 +1,8 @@
"use client";
import Field from "@/lib/components/ui/Field";
import { BrainConfig } from "@/lib/context/BrainConfigProvider/types";
import { UseFormRegister } from "react-hook-form";
import Field from "../../components/ui/Field";
interface BackendConfigProps {
register: UseFormRegister<BrainConfig>;

View File

@ -1,7 +1,7 @@
"use client";
import Button from "@/lib/components/ui/Button";
import { useRouter } from "next/navigation";
import Button from "../../components/ui/Button";
import { useConfig } from "../hooks/useConfig";
import { BackendConfig } from "./BackendConfig";
import { ModelConfig } from "./ModelConfig";

View File

@ -1,18 +1,19 @@
"use client";
import Field from "@/lib/components/ui/Field";
import {
BrainConfig,
Model,
PaidModels,
anthropicModels,
models,
paidModels,
} from "@/lib/context/BrainConfigProvider/types";
import { UseFormRegister } from "react-hook-form";
import Field from "../../components/ui/Field";
interface ModelConfigProps {
register: UseFormRegister<BrainConfig>;
model: Model;
model: Model | PaidModels;
openAiKey: string | undefined;
temperature: number;
maxTokens: number;
@ -25,6 +26,24 @@ export const ModelConfig = ({
temperature,
maxTokens,
}: ModelConfigProps): JSX.Element => {
const defineMaxTokens = (model: Model | PaidModels): number => {
//At the moment is evaluating only models from OpenAI
switch (model) {
case "gpt-3.5-turbo":
return 3000;
case "gpt-3.5-turbo-0613":
return 3000;
case "gpt-3.5-turbo-16k":
return 14000;
case "gpt-4":
return 6000;
case "gpt-4-0613":
return 6000;
default:
return 3000;
}
};
return (
<>
<div className="border-b border-gray-300 mt-8 mb-8">
@ -85,8 +104,8 @@ export const ModelConfig = ({
<input
type="range"
min="256"
max="3000"
step="1"
max={defineMaxTokens(model)}
step="32"
value={maxTokens}
{...register("maxTokens")}
/>

View File

@ -1,8 +1,8 @@
"use client";
import { useSupabase } from "@/app/supabase-provider";
import Button from "@/lib/components/ui/Button";
import Link from "next/link";
import Button from "../../components/ui/Button";
export const UserAccountSection = (): JSX.Element => {
const { session } = useSupabase();

View File

@ -3,6 +3,7 @@ import { redirect } from "next/navigation";
import { useSupabase } from "../supabase-provider";
import { ConfigForm, ConfigTitle } from "./components";
import { ApiKeyConfig } from "./components/ApiKeyConfig";
// TODO: Use states instead of NEXTJS router to open and close modal
export default function ConfigPage() {
@ -17,6 +18,7 @@ export default function ConfigPage() {
<section className="w-full outline-none pt-10 flex flex-col gap-5 items-center justify-center p-6">
<ConfigTitle />
<ConfigForm />
<ApiKeyConfig />
</section>
</main>
);

View File

@ -1,6 +1,9 @@
"use client";
import Ellipsis from "@/app/components/ui/Ellipsis";
import { useSupabase } from "@/app/supabase-provider";
import Button from "@/lib/components/ui/Button";
import { AnimatedCard } from "@/lib/components/ui/Card";
import Ellipsis from "@/lib/components/ui/Ellipsis";
import Modal from "@/lib/components/ui/Modal";
import { useToast } from "@/lib/hooks/useToast";
import { useAxios } from "@/lib/useAxios";
import {
@ -10,10 +13,7 @@ import {
forwardRef,
useState,
} from "react";
import Button from "../../components/ui/Button";
import { AnimatedCard } from "../../components/ui/Card";
import Modal from "../../components/ui/Modal";
import { Document } from "../types";
import { Document } from "../../../lib/types/Document";
import DocumentData from "./DocumentData";
interface DocumentProps {

View File

@ -1,14 +1,15 @@
"use client";
import Button from "@/lib/components/ui/Button";
import Spinner from "@/lib/components/ui/Spinner";
import { Document } from "@/lib/types/Document";
import { useAxios } from "@/lib/useAxios";
import { AnimatePresence, motion } from "framer-motion";
import Link from "next/link";
import { redirect } from "next/navigation";
import { useEffect, useState } from "react";
import Button from "../components/ui/Button";
import Spinner from "../components/ui/Spinner";
import { useSupabase } from "../supabase-provider";
import DocumentItem from "./DocumentItem";
import { Document } from "./types";
export default function ExplorePage() {
const [documents, setDocuments] = useState<Document[]>([]);

View File

@ -1,11 +1,11 @@
import Footer from "@/lib/components/Footer";
import { NavBar } from "@/lib/components/NavBar";
import { ToastProvider } from "@/lib/components/ui/Toast";
import { createServerComponentSupabaseClient } from "@supabase/auth-helpers-nextjs";
import { Analytics } from "@vercel/analytics/react";
import { Inter } from "next/font/google";
import { cookies, headers } from "next/headers";
import { BrainConfigProvider } from "../lib/context/BrainConfigProvider/brain-config-provider";
import Footer from "./components/Footer";
import { NavBar } from "./components/NavBar";
import { ToastProvider } from "./components/ui/Toast";
import "./globals.css";
import SupabaseProvider from "./supabase-provider";

View File

@ -1,7 +1,7 @@
"use client";
import Button from "@/app/components/ui/Button";
import Card from "@/app/components/ui/Card";
import Field from "@/app/components/ui/Field";
import Button from "@/lib/components/ui/Button";
import Card from "@/lib/components/ui/Card";
import Field from "@/lib/components/ui/Field";
import { useCrawler } from "./hooks/useCrawler";
export const Crawler = (): JSX.Element => {

View File

@ -1,7 +1,7 @@
"use client";
import Button from "@/lib/components/ui/Button";
import Card from "@/lib/components/ui/Card";
import { AnimatePresence } from "framer-motion";
import Button from "../../../components/ui/Button";
import Card from "../../../components/ui/Card";
import FileComponent from "./components/FileComponent";
import { useFileUploader } from "./hooks/useFileUploader";

View File

@ -1,8 +1,8 @@
"use client";
import Button from "@/lib/components/ui/Button";
import { Divider } from "@/lib/components/ui/Divider";
import PageHeading from "@/lib/components/ui/PageHeading";
import Link from "next/link";
import Button from "../components/ui/Button";
import { Divider } from "../components/ui/Divider";
import PageHeading from "../components/ui/PageHeading";
import { Crawler } from "./components/Crawler";
import { FileUploader } from "./components/FileUploader";

View File

@ -1,5 +1,5 @@
import { GiBrain } from "react-icons/gi";
import { UserStats } from "../types";
import { UserStats } from "../../../lib/types/User";
export const BrainConsumption = (userStats: UserStats): JSX.Element => {
const { current_brain_size, max_brain_size } = userStats;

View File

@ -1,5 +1,5 @@
import { HTMLAttributes } from "react";
import { UserStats } from "../types";
import { UserStats } from "../../../lib/types/User";
interface DateComponentProps extends HTMLAttributes<HTMLSpanElement> {
date: UserStats["date"];

View File

@ -1,10 +1,10 @@
"use client";
import Button from "@/app/components/ui/Button";
import Button from "@/lib/components/ui/Button";
import { cn } from "@/lib/utils";
import Link from "next/link";
import prettyBytes from "pretty-bytes";
import { HTMLAttributes } from "react";
import { UserStats } from "../types";
import { UserStats } from "../../../lib/types/User";
import { BrainConsumption } from "./BrainConsumption";
import { DateComponent } from "./Date";
import BrainSpaceChart from "./Graphs/BrainSpaceChart";

View File

@ -1,11 +1,11 @@
"use client";
import Spinner from "@/lib/components/ui/Spinner";
import { useAxios } from "@/lib/useAxios";
import { redirect } from "next/navigation";
import { useEffect, useState } from "react";
import Spinner from "../components/ui/Spinner";
import { UserStats } from "../../lib/types/User";
import { useSupabase } from "../supabase-provider";
import { UserStatistics } from "./components/UserStatistics";
import { UserStats } from "./types";
export default function UserPage() {
const [userStats, setUserStats] = useState<UserStats>();

View File

@ -1,4 +1,4 @@
import Button from "@/app/components/ui/Button";
import Button from "@/lib/components/ui/Button";
import Link from "next/link";
import { usePathname } from "next/navigation";

View File

@ -1,7 +1,7 @@
"use client";
import Button from "@/lib/components/ui/Button";
import { FC, useEffect, useLayoutEffect, useState } from "react";
import { MdDarkMode, MdLightMode } from "react-icons/md";
import Button from "../../../../ui/Button";
export const DarkModeToggle: FC = () => {
const [dark, setDark] = useState(false);

View File

@ -1,10 +1,10 @@
"use client";
import { useSupabase } from "@/app/supabase-provider";
import Button from "@/lib/components/ui/Button";
import { cn } from "@/lib/utils";
import Link from "next/link";
import { Dispatch, FC, HTMLAttributes, SetStateAction } from "react";
import { MdPerson, MdSettings } from "react-icons/md";
import Button from "../../../ui/Button";
import { AuthButtons } from "./components/AuthButtons";
import { DarkModeToggle } from "./components/DarkModeToggle";
import { NavLink } from "./components/NavLink";

View File

@ -4,7 +4,7 @@ import { FC } from "react";
import { Header } from "./components/Header";
import { Logo } from "./components/Logo";
import { MobileMenu } from "./components/MobileMenu";
import { NavItems } from "./components/NavItems/";
import { NavItems } from "./components/NavItems";
export const NavBar: FC = () => {
return (

View File

@ -13,7 +13,7 @@ export const BrainConfigContext = createContext<ConfigContext | undefined>(
);
const defaultBrainConfig: BrainConfig = {
model: "gpt-3.5-turbo",
model: "gpt-3.5-turbo-0613",
temperature: 0,
maxTokens: 500,
keepLocal: true,

View File

@ -19,8 +19,9 @@ export type ConfigContext = {
};
// export const openAiModels = ["gpt-3.5-turbo", "gpt-4"] as const; ## TODO activate GPT4 when not in demo mode
export const openAiModels = ["gpt-3.5-turbo"] as const;
export const openAiPaidModels = ["gpt-3.5-turbo", "gpt-4"] as const;
export const openAiModels = ["gpt-3.5-turbo","gpt-3.5-turbo-0613","gpt-3.5-turbo-16k"] as const;
export const openAiPaidModels = ["gpt-3.5-turbo","gpt-3.5-turbo-0613","gpt-3.5-turbo-16k","gpt-4","gpt-4-0613"] as const;
export const anthropicModels = [
// "claude-v1",

View File

@ -4,7 +4,7 @@ import { useAxios } from "@/lib/useAxios";
import { UUID } from "crypto";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { Chat, ChatMessage } from "../../types";
import { Chat, ChatMessage } from "../../../types/Chat";
export default function useChats() {
const [allChats, setAllChats] = useState<Chat[]>([]);
@ -34,24 +34,27 @@ export default function useChats() {
text: "Error occured while fetching your chats",
});
}
}, []);
}, [axiosInstance, publish]);
const fetchChat = useCallback(async (chatId?: UUID) => {
if (!chatId) return;
try {
console.log(`Fetching chat ${chatId}`);
const response = await axiosInstance.get<Chat>(`/chat/${chatId}`);
console.log(response.data);
const fetchChat = useCallback(
async (chatId?: UUID) => {
if (!chatId) return;
try {
console.log(`Fetching chat ${chatId}`);
const response = await axiosInstance.get<Chat>(`/chat/${chatId}`);
console.log(response.data);
setChat(response.data);
} catch (error) {
console.error(error);
publish({
variant: "danger",
text: `Error occured while fetching ${chatId}`,
});
}
}, []);
setChat(response.data);
} catch (error) {
console.error(error);
publish({
variant: "danger",
text: `Error occured while fetching ${chatId}`,
});
}
},
[axiosInstance, publish]
);
type ChatResponse = Omit<Chat, "chatId"> & { chatId: UUID | undefined };

View File

@ -1,5 +1,5 @@
import { isSpeechRecognitionSupported } from "@/lib/helpers/isSpeechRecognitionSupported";
import { useEffect, useState } from "react";
import { isSpeechRecognitionSupported } from "../../helpers/isSpeechRecognitionSupported";
import useChatsContext from "./useChatsContext";
export const useSpeech = () => {

View File

@ -1,5 +1,5 @@
import { ToastContext } from "@/lib/components/ui/Toast/domain/ToastContext";
import { useContext } from "react";
import { ToastContext } from "../../app/components/ui/Toast/domain/ToastContext";
export const useToast = () => {
const { publish } = useContext(ToastContext);

View File

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -5,6 +5,7 @@ module.exports = {
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./lib/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {

View File

@ -103,3 +103,14 @@ BEGIN
LIMIT match_count;
END;
$$;
-- Create api_keys table
CREATE TABLE IF NOT EXISTS api_keys(
key_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES users(user_id),
api_key TEXT UNIQUE,
creation_time TIMESTAMP DEFAULT current_timestamp,
deleted_time TIMESTAMP,
is_active BOOLEAN DEFAULT true
);