diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index 6e4f5c09..0d6cc732 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -119,7 +119,11 @@ def store_web(form_data: StoreWebForm, user=Depends(get_current_user)): loader = WebBaseLoader(form_data.url) data = loader.load() store_data_in_vector_db(data, form_data.collection_name) - return {"status": True, "collection_name": form_data.collection_name} + return { + "status": True, + "collection_name": form_data.collection_name, + "filename": form_data.url, + } except Exception as e: print(e) raise HTTPException( @@ -176,7 +180,11 @@ def store_doc( result = store_data_in_vector_db(data, collection_name) if result: - return {"status": True, "collection_name": collection_name} + return { + "status": True, + "collection_name": collection_name, + "filename": filename, + } else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, diff --git a/backend/apps/web/main.py b/backend/apps/web/main.py index 0030c25e..89616064 100644 --- a/backend/apps/web/main.py +++ b/backend/apps/web/main.py @@ -1,7 +1,16 @@ from fastapi import FastAPI, Depends from fastapi.routing import APIRoute from fastapi.middleware.cors import CORSMiddleware -from apps.web.routers import auths, users, chats, modelfiles, prompts, configs, utils +from apps.web.routers import ( + auths, + users, + chats, + documents, + modelfiles, + prompts, + configs, + utils, +) from config import WEBUI_VERSION, WEBUI_AUTH app = FastAPI() @@ -22,9 +31,8 @@ app.add_middleware( app.include_router(auths.router, prefix="/auths", tags=["auths"]) app.include_router(users.router, prefix="/users", tags=["users"]) app.include_router(chats.router, prefix="/chats", tags=["chats"]) -app.include_router(modelfiles.router, - prefix="/modelfiles", - tags=["modelfiles"]) +app.include_router(documents.router, prefix="/documents", tags=["documents"]) +app.include_router(modelfiles.router, prefix="/modelfiles", tags=["modelfiles"]) app.include_router(prompts.router, prefix="/prompts", tags=["prompts"]) app.include_router(configs.router, prefix="/configs", tags=["configs"]) diff --git a/backend/apps/web/models/documents.py b/backend/apps/web/models/documents.py new file mode 100644 index 00000000..0196c38b --- /dev/null +++ b/backend/apps/web/models/documents.py @@ -0,0 +1,124 @@ +from pydantic import BaseModel +from peewee import * +from playhouse.shortcuts import model_to_dict +from typing import List, Union, Optional +import time + +from utils.utils import decode_token +from utils.misc import get_gravatar_url + +from apps.web.internal.db import DB + +import json + +#################### +# Documents DB Schema +#################### + + +class Document(Model): + collection_name = CharField(unique=True) + name = CharField(unique=True) + title = CharField() + filename = CharField() + content = TextField(null=True) + user_id = CharField() + timestamp = DateField() + + class Meta: + database = DB + + +class DocumentModel(BaseModel): + collection_name: str + name: str + title: str + filename: str + content: Optional[str] = None + user_id: str + timestamp: int # timestamp in epoch + + +#################### +# Forms +#################### + + +class DocumentUpdateForm(BaseModel): + name: str + title: str + + +class DocumentForm(DocumentUpdateForm): + collection_name: str + filename: str + content: Optional[str] = None + + +class DocumentsTable: + def __init__(self, db): + self.db = db + self.db.create_tables([Document]) + + def insert_new_doc( + self, user_id: str, form_data: DocumentForm + ) -> Optional[DocumentModel]: + document = DocumentModel( + **{ + **form_data.model_dump(), + "user_id": user_id, + "timestamp": int(time.time()), + } + ) + + try: + result = Document.create(**document.model_dump()) + if result: + return document + else: + return None + except: + return None + + def get_doc_by_name(self, name: str) -> Optional[DocumentModel]: + try: + document = Document.get(Document.name == name) + return DocumentModel(**model_to_dict(document)) + except: + return None + + def get_docs(self) -> List[DocumentModel]: + return [ + DocumentModel(**model_to_dict(doc)) + for doc in Document.select() + # .limit(limit).offset(skip) + ] + + def update_doc_by_name( + self, name: str, form_data: DocumentUpdateForm + ) -> Optional[DocumentModel]: + try: + query = Document.update( + title=form_data.title, + name=form_data.name, + timestamp=int(time.time()), + ).where(Document.name == name) + query.execute() + + doc = Document.get(Document.name == form_data.name) + return DocumentModel(**model_to_dict(doc)) + except Exception as e: + print(e) + return None + + def delete_doc_by_name(self, name: str) -> bool: + try: + query = Document.delete().where((Document.name == name)) + query.execute() # Remove the rows, return number of rows removed. + + return True + except: + return False + + +Documents = DocumentsTable(DB) diff --git a/backend/apps/web/routers/documents.py b/backend/apps/web/routers/documents.py new file mode 100644 index 00000000..c64ee8f0 --- /dev/null +++ b/backend/apps/web/routers/documents.py @@ -0,0 +1,119 @@ +from fastapi import Depends, FastAPI, HTTPException, status +from datetime import datetime, timedelta +from typing import List, Union, Optional + +from fastapi import APIRouter +from pydantic import BaseModel +import json + +from apps.web.models.documents import ( + Documents, + DocumentForm, + DocumentUpdateForm, + DocumentModel, +) + +from utils.utils import get_current_user +from constants import ERROR_MESSAGES + +router = APIRouter() + +############################ +# GetDocuments +############################ + + +@router.get("/", response_model=List[DocumentModel]) +async def get_documents(user=Depends(get_current_user)): + return Documents.get_docs() + + +############################ +# CreateNewDoc +############################ + + +@router.post("/create", response_model=Optional[DocumentModel]) +async def create_new_doc(form_data: DocumentForm, user=Depends(get_current_user)): + if user.role != "admin": + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + + doc = Documents.get_doc_by_name(form_data.name) + if doc == None: + doc = Documents.insert_new_doc(user.id, form_data) + + if doc: + return doc + else: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.FILE_EXISTS, + ) + else: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.NAME_TAG_TAKEN, + ) + + +############################ +# GetDocByName +############################ + + +@router.get("/name/{name}", response_model=Optional[DocumentModel]) +async def get_doc_by_name(name: str, user=Depends(get_current_user)): + doc = Documents.get_doc_by_name(name) + + if doc: + return doc + else: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.NOT_FOUND, + ) + + +############################ +# UpdateDocByName +############################ + + +@router.post("/name/{name}/update", response_model=Optional[DocumentModel]) +async def update_doc_by_name( + name: str, form_data: DocumentUpdateForm, user=Depends(get_current_user) +): + if user.role != "admin": + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + + doc = Documents.update_doc_by_name(name, form_data) + if doc: + return doc + else: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.NAME_TAG_TAKEN, + ) + + +############################ +# DeleteDocByName +############################ + + +@router.delete("/name/{name}/delete", response_model=bool) +async def delete_doc_by_name(name: str, user=Depends(get_current_user)): + if user.role != "admin": + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + + result = Documents.delete_doc_by_name(name) + return result diff --git a/backend/config.py b/backend/config.py index 03718a06..2a96d018 100644 --- a/backend/config.py +++ b/backend/config.py @@ -58,7 +58,7 @@ if OPENAI_API_BASE_URL == "": # WEBUI_VERSION #################################### -WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.50") +WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.61") #################################### # WEBUI_AUTH (Required for security) diff --git a/backend/constants.py b/backend/constants.py index 0f7a46a0..c9bfaec5 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -18,6 +18,9 @@ class ERROR_MESSAGES(str, Enum): "Uh-oh! This username is already registered. Please choose another username." ) COMMAND_TAKEN = "Uh-oh! This command is already registered. Please choose another command string." + FILE_EXISTS = "Uh-oh! This file is already registered. Please choose another file." + + NAME_TAG_TAKEN = "Uh-oh! This name tag is already registered. Please choose another name tag string." INVALID_TOKEN = ( "Your session has expired or the token is invalid. Please sign in again." ) diff --git a/src/lib/apis/documents/index.ts b/src/lib/apis/documents/index.ts new file mode 100644 index 00000000..fb208ea4 --- /dev/null +++ b/src/lib/apis/documents/index.ts @@ -0,0 +1,177 @@ +import { WEBUI_API_BASE_URL } from '$lib/constants'; + +export const createNewDoc = async ( + token: string, + collection_name: string, + filename: string, + name: string, + title: string +) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/documents/create`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + collection_name: collection_name, + filename: filename, + name: name, + title: title + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const getDocs = async (token: string = '') => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/documents/`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const getDocByName = async (token: string, name: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/documents/name/${name}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +type DocUpdateForm = { + name: string; + title: string; +}; + +export const updateDocByName = async (token: string, name: string, form: DocUpdateForm) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/documents/name/${name}/update`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + name: form.name, + title: form.title + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const deleteDocByName = async (token: string, name: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/documents/name/${name}/delete`, { + method: 'DELETE', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; diff --git a/src/lib/components/AddFilesPlaceholder.svelte b/src/lib/components/AddFilesPlaceholder.svelte new file mode 100644 index 00000000..7cc51c6f --- /dev/null +++ b/src/lib/components/AddFilesPlaceholder.svelte @@ -0,0 +1,6 @@ +