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..ccd76194 --- /dev/null +++ b/backend/apps/web/models/documents.py @@ -0,0 +1,123 @@ +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 == name) + return DocumentModel(**model_to_dict(doc)) + except: + 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..42463f51 --- /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_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.DEFAULT(), + ) + else: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.COMMAND_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_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + + +############################ +# 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/src/lib/apis/documents/index.ts b/src/lib/apis/documents/index.ts new file mode 100644 index 00000000..37bdde04 --- /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}/prompts/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 @@ +