From 032d7c7440c1bd0117d5d8e2046352d54330c851 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Tue, 26 Dec 2023 22:51:52 -0800 Subject: [PATCH] feat: modelfile backend & ollama version 0.0.0 whitelisted --- backend/apps/web/models/modelfiles.py | 12 +- backend/apps/web/routers/modelfiles.py | 26 ++- src/lib/apis/modelfiles/index.ts | 173 ++++++++++++++++++ src/lib/apis/ollama/index.ts | 68 +++++++ src/lib/utils/index.ts | 14 +- src/routes/(app)/+layout.svelte | 12 +- src/routes/(app)/modelfiles/+page.svelte | 46 ++--- .../(app)/modelfiles/create/+page.svelte | 32 ++-- src/routes/(app)/modelfiles/edit/+page.svelte | 59 +++--- 9 files changed, 346 insertions(+), 96 deletions(-) create mode 100644 src/lib/apis/modelfiles/index.ts diff --git a/backend/apps/web/models/modelfiles.py b/backend/apps/web/models/modelfiles.py index a572e4906..a0f9f1aa3 100644 --- a/backend/apps/web/models/modelfiles.py +++ b/backend/apps/web/models/modelfiles.py @@ -42,6 +42,14 @@ class ModelfileForm(BaseModel): modelfile: dict +class ModelfileTagNameForm(BaseModel): + tag_name: str + + +class ModelfileUpdateForm(ModelfileForm, ModelfileTagNameForm): + pass + + class ModelfileResponse(BaseModel): tag_name: str user_id: str @@ -57,11 +65,11 @@ class ModelfilesTable: def insert_new_modelfile( self, user_id: str, form_data: ModelfileForm ) -> Optional[ModelfileModel]: - if "title" in form_data.modelfile: + if "tagName" in form_data.modelfile: modelfile = ModelfileModel( **{ "user_id": user_id, - "tag_name": form_data.modelfile["title"], + "tag_name": form_data.modelfile["tagName"], "modelfile": json.dumps(form_data.modelfile), "timestamp": int(time.time()), } diff --git a/backend/apps/web/routers/modelfiles.py b/backend/apps/web/routers/modelfiles.py index eb1d49418..4563c4df2 100644 --- a/backend/apps/web/routers/modelfiles.py +++ b/backend/apps/web/routers/modelfiles.py @@ -11,6 +11,8 @@ from apps.web.models.users import Users from apps.web.models.modelfiles import ( Modelfiles, ModelfileForm, + ModelfileTagNameForm, + ModelfileUpdateForm, ModelfileResponse, ) @@ -77,13 +79,15 @@ async def create_new_modelfile(form_data: ModelfileForm, cred=Depends(bearer_sch ############################ -@router.get("/{tag_name}", response_model=Optional[ModelfileResponse]) -async def get_modelfile_by_tag_name(tag_name: str, cred=Depends(bearer_scheme)): +@router.post("/", response_model=Optional[ModelfileResponse]) +async def get_modelfile_by_tag_name( + form_data: ModelfileTagNameForm, cred=Depends(bearer_scheme) +): token = cred.credentials user = Users.get_user_by_token(token) if user: - modelfile = Modelfiles.get_modelfile_by_tag_name(tag_name) + modelfile = Modelfiles.get_modelfile_by_tag_name(form_data.tag_name) if modelfile: return ModelfileResponse( @@ -109,16 +113,16 @@ async def get_modelfile_by_tag_name(tag_name: str, cred=Depends(bearer_scheme)): ############################ -@router.post("/{tag_name}", response_model=Optional[ModelfileResponse]) +@router.post("/update", response_model=Optional[ModelfileResponse]) async def update_modelfile_by_tag_name( - tag_name: str, form_data: ModelfileForm, cred=Depends(bearer_scheme) + form_data: ModelfileUpdateForm, cred=Depends(bearer_scheme) ): token = cred.credentials user = Users.get_user_by_token(token) if user: if user.role == "admin": - modelfile = Modelfiles.get_modelfile_by_tag_name(tag_name) + modelfile = Modelfiles.get_modelfile_by_tag_name(form_data.tag_name) if modelfile: updated_modelfile = { **json.loads(modelfile.modelfile), @@ -126,7 +130,7 @@ async def update_modelfile_by_tag_name( } modelfile = Modelfiles.update_modelfile_by_tag_name( - tag_name, updated_modelfile + form_data.tag_name, updated_modelfile ) return ModelfileResponse( @@ -157,14 +161,16 @@ async def update_modelfile_by_tag_name( ############################ -@router.delete("/{tag_name}", response_model=bool) -async def delete_modelfile_by_tag_name(tag_name: str, cred=Depends(bearer_scheme)): +@router.delete("/delete", response_model=bool) +async def delete_modelfile_by_tag_name( + form_data: ModelfileTagNameForm, cred=Depends(bearer_scheme) +): token = cred.credentials user = Users.get_user_by_token(token) if user: if user.role == "admin": - result = Modelfiles.delete_modelfile_by_tag_name(tag_name) + result = Modelfiles.delete_modelfile_by_tag_name(form_data.tag_name) return result else: raise HTTPException( diff --git a/src/lib/apis/modelfiles/index.ts b/src/lib/apis/modelfiles/index.ts new file mode 100644 index 000000000..ad72eec90 --- /dev/null +++ b/src/lib/apis/modelfiles/index.ts @@ -0,0 +1,173 @@ +import { WEBUI_API_BASE_URL } from '$lib/constants'; + +export const createNewModelfile = async (token: string, modelfile: object) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/create`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + modelfile: modelfile + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const getModelfiles = async (token: string = '') => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/`, { + 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; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res.map((modelfile) => modelfile.modelfile); +}; + +export const getModelfileByTagName = async (token: string, tagName: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + tag_name: tagName + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res.modelfile; +}; + +export const updateModelfileByTagName = async ( + token: string, + tagName: string, + modelfile: object +) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/update`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + tag_name: tagName, + modelfile: modelfile + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const deleteModelfileByTagName = async (token: string, tagName: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/delete`, { + method: 'DELETE', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + tag_name: tagName + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; diff --git a/src/lib/apis/ollama/index.ts b/src/lib/apis/ollama/index.ts index 341871eec..198ea4184 100644 --- a/src/lib/apis/ollama/index.ts +++ b/src/lib/apis/ollama/index.ts @@ -134,3 +134,71 @@ export const generateChatCompletion = async ( return res; }; + +export const createModel = async ( + base_url: string = OLLAMA_API_BASE_URL, + token: string, + tagName: string, + content: string +) => { + let error = null; + + const res = await fetch(`${base_url}/create`, { + method: 'POST', + headers: { + 'Content-Type': 'text/event-stream', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + name: tagName, + modelfile: content + }) + }).catch((err) => { + error = err; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const deleteModel = async ( + base_url: string = OLLAMA_API_BASE_URL, + token: string, + tagName: string +) => { + let error = null; + + const res = await fetch(`${base_url}/delete`, { + method: 'DELETE', + headers: { + 'Content-Type': 'text/event-stream', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + name: tagName + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + console.log(json); + return true; + }) + .catch((err) => { + console.log(err); + error = err.error; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 9353d7f32..4ac670034 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -102,11 +102,11 @@ export const copyToClipboard = (text) => { }; export const checkVersion = (required, current) => { - return ( - current.localeCompare(required, undefined, { - numeric: true, - sensitivity: 'case', - caseFirst: 'upper' - }) < 0 - ); + return current === '0.0.0' + ? true + : current.localeCompare(required, undefined, { + numeric: true, + sensitivity: 'case', + caseFirst: 'upper' + }) < 0; }; diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index a04b043e2..5bd550899 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -8,6 +8,8 @@ const { saveAs } = fileSaver; import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama'; + import { getModelfiles } from '$lib/apis/modelfiles'; + import { getOpenAIModels } from '$lib/apis/openai'; import { user, showSettings, settings, models, modelfiles } from '$lib/stores'; @@ -95,11 +97,14 @@ console.log(); await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}')); - await models.set(await getModels()); + // await models.set(await getModels()); + // JSON.parse(localStorage.getItem('modelfiles') ?? '[]') + await modelfiles.set(await getModelfiles(localStorage.token)); + console.log($modelfiles); - await modelfiles.set(JSON.parse(localStorage.getItem('modelfiles') ?? '[]')); modelfiles.subscribe(async () => { // should fetch models + await models.set(await getModels()); }); await setOllamaVersion(); @@ -176,7 +181,8 @@