feat: modelfile backend & ollama version 0.0.0 whitelisted

This commit is contained in:
Timothy J. Baek 2023-12-26 22:51:52 -08:00
parent 4221594778
commit 032d7c7440
9 changed files with 346 additions and 96 deletions

View file

@ -42,6 +42,14 @@ class ModelfileForm(BaseModel):
modelfile: dict modelfile: dict
class ModelfileTagNameForm(BaseModel):
tag_name: str
class ModelfileUpdateForm(ModelfileForm, ModelfileTagNameForm):
pass
class ModelfileResponse(BaseModel): class ModelfileResponse(BaseModel):
tag_name: str tag_name: str
user_id: str user_id: str
@ -57,11 +65,11 @@ class ModelfilesTable:
def insert_new_modelfile( def insert_new_modelfile(
self, user_id: str, form_data: ModelfileForm self, user_id: str, form_data: ModelfileForm
) -> Optional[ModelfileModel]: ) -> Optional[ModelfileModel]:
if "title" in form_data.modelfile: if "tagName" in form_data.modelfile:
modelfile = ModelfileModel( modelfile = ModelfileModel(
**{ **{
"user_id": user_id, "user_id": user_id,
"tag_name": form_data.modelfile["title"], "tag_name": form_data.modelfile["tagName"],
"modelfile": json.dumps(form_data.modelfile), "modelfile": json.dumps(form_data.modelfile),
"timestamp": int(time.time()), "timestamp": int(time.time()),
} }

View file

@ -11,6 +11,8 @@ from apps.web.models.users import Users
from apps.web.models.modelfiles import ( from apps.web.models.modelfiles import (
Modelfiles, Modelfiles,
ModelfileForm, ModelfileForm,
ModelfileTagNameForm,
ModelfileUpdateForm,
ModelfileResponse, 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]) @router.post("/", response_model=Optional[ModelfileResponse])
async def get_modelfile_by_tag_name(tag_name: str, cred=Depends(bearer_scheme)): async def get_modelfile_by_tag_name(
form_data: ModelfileTagNameForm, cred=Depends(bearer_scheme)
):
token = cred.credentials token = cred.credentials
user = Users.get_user_by_token(token) user = Users.get_user_by_token(token)
if user: if user:
modelfile = Modelfiles.get_modelfile_by_tag_name(tag_name) modelfile = Modelfiles.get_modelfile_by_tag_name(form_data.tag_name)
if modelfile: if modelfile:
return ModelfileResponse( 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( 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 token = cred.credentials
user = Users.get_user_by_token(token) user = Users.get_user_by_token(token)
if user: if user:
if user.role == "admin": 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: if modelfile:
updated_modelfile = { updated_modelfile = {
**json.loads(modelfile.modelfile), **json.loads(modelfile.modelfile),
@ -126,7 +130,7 @@ async def update_modelfile_by_tag_name(
} }
modelfile = Modelfiles.update_modelfile_by_tag_name( modelfile = Modelfiles.update_modelfile_by_tag_name(
tag_name, updated_modelfile form_data.tag_name, updated_modelfile
) )
return ModelfileResponse( return ModelfileResponse(
@ -157,14 +161,16 @@ async def update_modelfile_by_tag_name(
############################ ############################
@router.delete("/{tag_name}", response_model=bool) @router.delete("/delete", response_model=bool)
async def delete_modelfile_by_tag_name(tag_name: str, cred=Depends(bearer_scheme)): async def delete_modelfile_by_tag_name(
form_data: ModelfileTagNameForm, cred=Depends(bearer_scheme)
):
token = cred.credentials token = cred.credentials
user = Users.get_user_by_token(token) user = Users.get_user_by_token(token)
if user: if user:
if user.role == "admin": 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 return result
else: else:
raise HTTPException( raise HTTPException(

View file

@ -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;
};

View file

@ -134,3 +134,71 @@ export const generateChatCompletion = async (
return res; 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;
};

View file

@ -102,11 +102,11 @@ export const copyToClipboard = (text) => {
}; };
export const checkVersion = (required, current) => { export const checkVersion = (required, current) => {
return ( return current === '0.0.0'
current.localeCompare(required, undefined, { ? true
numeric: true, : current.localeCompare(required, undefined, {
sensitivity: 'case', numeric: true,
caseFirst: 'upper' sensitivity: 'case',
}) < 0 caseFirst: 'upper'
); }) < 0;
}; };

View file

@ -8,6 +8,8 @@
const { saveAs } = fileSaver; const { saveAs } = fileSaver;
import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama'; import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama';
import { getModelfiles } from '$lib/apis/modelfiles';
import { getOpenAIModels } from '$lib/apis/openai'; import { getOpenAIModels } from '$lib/apis/openai';
import { user, showSettings, settings, models, modelfiles } from '$lib/stores'; import { user, showSettings, settings, models, modelfiles } from '$lib/stores';
@ -95,11 +97,14 @@
console.log(); console.log();
await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}')); 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 () => { modelfiles.subscribe(async () => {
// should fetch models // should fetch models
await models.set(await getModels());
}); });
await setOllamaVersion(); await setOllamaVersion();
@ -176,7 +181,8 @@
<button <button
class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm" class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm"
on:click={async () => { on:click={async () => {
await setOllamaVersion(); location.href = '/';
// await setOllamaVersion();
}} }}
> >
Check Again Check Again

View file

@ -4,43 +4,29 @@
import toast from 'svelte-french-toast'; import toast from 'svelte-french-toast';
import { OLLAMA_API_BASE_URL } from '$lib/constants'; import { OLLAMA_API_BASE_URL } from '$lib/constants';
import { deleteModel } from '$lib/apis/ollama';
import { deleteModelfileByTagName, getModelfiles } from '$lib/apis/modelfiles';
const deleteModelHandler = async (tagName) => { const deleteModelHandler = async (tagName) => {
let success = null; let success = null;
const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/delete`, {
method: 'DELETE', success = await deleteModel(
headers: { $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
'Content-Type': 'text/event-stream', localStorage.token,
...($settings.authHeader && { Authorization: $settings.authHeader }), tagName
...($user && { Authorization: `Bearer ${localStorage.token}` }) );
},
body: JSON.stringify({ if (success) {
name: tagName toast.success(`Deleted ${tagName}`);
}) }
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
console.log(json);
toast.success(`Deleted ${tagName}`);
success = true;
return json;
})
.catch((err) => {
console.log(err);
toast.error(err.error);
return null;
});
return success; return success;
}; };
const deleteModelfilebyTagName = async (tagName) => { const deleteModelfile = async (tagName) => {
await deleteModelHandler(tagName); await deleteModelHandler(tagName);
await modelfiles.set($modelfiles.filter((modelfile) => modelfile.tagName != tagName)); await deleteModelfileByTagName(localStorage.token, tagName);
localStorage.setItem('modelfiles', JSON.stringify($modelfiles)); await modelfiles.set(await getModelfiles(localStorage.token));
}; };
const shareModelfile = async (modelfile) => { const shareModelfile = async (modelfile) => {
@ -167,7 +153,7 @@
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl" class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button" type="button"
on:click={() => { on:click={() => {
deleteModelfilebyTagName(modelfile.tagName); deleteModelfile(modelfile.tagName);
}} }}
> >
<svg <svg

View file

@ -8,6 +8,8 @@
import Advanced from '$lib/components/chat/Settings/Advanced.svelte'; import Advanced from '$lib/components/chat/Settings/Advanced.svelte';
import { splitStream } from '$lib/utils'; import { splitStream } from '$lib/utils';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import { createModel } from '$lib/apis/ollama';
import { createNewModelfile, getModelfiles } from '$lib/apis/modelfiles';
let loading = false; let loading = false;
@ -93,11 +95,14 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
}; };
const saveModelfile = async (modelfile) => { const saveModelfile = async (modelfile) => {
await modelfiles.set([ // await modelfiles.set([
...$modelfiles.filter((m) => m.tagName !== modelfile.tagName), // ...$modelfiles.filter((m) => m.tagName !== modelfile.tagName),
modelfile // modelfile
]); // ]);
localStorage.setItem('modelfiles', JSON.stringify($modelfiles)); // localStorage.setItem('modelfiles', JSON.stringify($modelfiles));
await createNewModelfile(localStorage.token, modelfile);
await modelfiles.set(await getModelfiles(localStorage.token));
}; };
const submitHandler = async () => { const submitHandler = async () => {
@ -128,17 +133,12 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
Object.keys(categories).filter((category) => categories[category]).length > 0 && Object.keys(categories).filter((category) => categories[category]).length > 0 &&
!$models.includes(tagName) !$models.includes(tagName)
) { ) {
const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/create`, { const res = await createModel(
method: 'POST', $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
headers: { localStorage.token,
'Content-Type': 'text/event-stream', tagName,
...($user && { Authorization: `Bearer ${localStorage.token}` }) content
}, );
body: JSON.stringify({
name: tagName,
modelfile: content
})
});
if (res) { if (res) {
const reader = res.body const reader = res.body

View file

@ -2,14 +2,20 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { toast } from 'svelte-french-toast'; import { toast } from 'svelte-french-toast';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { OLLAMA_API_BASE_URL } from '$lib/constants';
import { settings, db, user, config, modelfiles } from '$lib/stores';
import Advanced from '$lib/components/chat/Settings/Advanced.svelte';
import { splitStream } from '$lib/utils';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { settings, db, user, config, modelfiles } from '$lib/stores';
import { OLLAMA_API_BASE_URL } from '$lib/constants';
import { splitStream } from '$lib/utils';
import { createModel } from '$lib/apis/ollama';
import { getModelfiles, updateModelfileByTagName } from '$lib/apis/modelfiles';
import Advanced from '$lib/components/chat/Settings/Advanced.svelte';
let loading = false; let loading = false;
let filesInputElement; let filesInputElement;
@ -78,17 +84,20 @@
} }
}); });
const saveModelfile = async (modelfile) => { const updateModelfile = async (modelfile) => {
await modelfiles.set( // await modelfiles.set(
$modelfiles.map((e) => { // $modelfiles.map((e) => {
if (e.tagName === modelfile.tagName) { // if (e.tagName === modelfile.tagName) {
return modelfile; // return modelfile;
} else { // } else {
return e; // return e;
} // }
}) // })
); // );
localStorage.setItem('modelfiles', JSON.stringify($modelfiles)); // localStorage.setItem('modelfiles', JSON.stringify($modelfiles));
await updateModelfileByTagName(localStorage.token, modelfile.tagName, modelfile);
await modelfiles.set(await getModelfiles(localStorage.token));
}; };
const updateHandler = async () => { const updateHandler = async () => {
@ -106,18 +115,12 @@
content !== '' && content !== '' &&
Object.keys(categories).filter((category) => categories[category]).length > 0 Object.keys(categories).filter((category) => categories[category]).length > 0
) { ) {
const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/create`, { const res = await createModel(
method: 'POST', $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
headers: { localStorage.token,
'Content-Type': 'text/event-stream', tagName,
...($settings.authHeader && { Authorization: $settings.authHeader }), content
...($user && { Authorization: `Bearer ${localStorage.token}` }) );
},
body: JSON.stringify({
name: tagName,
modelfile: content
})
});
if (res) { if (res) {
const reader = res.body const reader = res.body
@ -178,7 +181,7 @@
} }
if (success) { if (success) {
await saveModelfile({ await updateModelfile({
tagName: tagName, tagName: tagName,
imageUrl: imageUrl, imageUrl: imageUrl,
title: title, title: title,