Merge remote-tracking branch 'upstream/dev' into feat/oauth

This commit is contained in:
Jun Siang Cheah
2024-06-21 13:43:19 +01:00
133 changed files with 6387 additions and 1461 deletions

View File

@@ -77,6 +77,17 @@
}
loaded = true;
});
let sortKey = 'created_at'; // default sort key
let sortOrder = 'asc'; // default sort order
function setSortKey(key) {
if (sortKey === key) {
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
} else {
sortKey = key;
sortOrder = 'asc';
}
}
</script>
<ConfirmDialog
@@ -148,12 +159,66 @@
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400">
<tr>
<th scope="col" class="px-3 py-2"> {$i18n.t('Role')} </th>
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
<th scope="col" class="px-3 py-2"> {$i18n.t('Email')} </th>
<th scope="col" class="px-3 py-2"> {$i18n.t('Last Active')} </th>
<th scope="col" class="px-3 py-2"> {$i18n.t('Created at')} </th>
<th
scope="col"
class="px-3 py-2 cursor-pointer select-none"
on:click={() => setSortKey('role')}
>
{$i18n.t('Role')}
{#if sortKey === 'role'}
{sortOrder === 'asc' ? '▲' : '▼'}
{:else}
<span class="invisible"></span>
{/if}
</th>
<th
scope="col"
class="px-3 py-2 cursor-pointer select-none"
on:click={() => setSortKey('name')}
>
{$i18n.t('Name')}
{#if sortKey === 'name'}
{sortOrder === 'asc' ? '▲' : '▼'}
{:else}
<span class="invisible"></span>
{/if}
</th>
<th
scope="col"
class="px-3 py-2 cursor-pointer select-none"
on:click={() => setSortKey('email')}
>
{$i18n.t('Email')}
{#if sortKey === 'email'}
{sortOrder === 'asc' ? '▲' : '▼'}
{:else}
<span class="invisible"></span>
{/if}
</th>
<th
scope="col"
class="px-3 py-2 cursor-pointer select-none"
on:click={() => setSortKey('last_active_at')}
>
{$i18n.t('Last Active')}
{#if sortKey === 'last_active_at'}
{sortOrder === 'asc' ? '▲' : '▼'}
{:else}
<span class="invisible"></span>
{/if}
</th>
<th
scope="col"
class="px-3 py-2 cursor-pointer select-none"
on:click={() => setSortKey('created_at')}
>
{$i18n.t('Created at')}
{#if sortKey === 'created_at'}
{sortOrder === 'asc' ? '▲' : '▼'}
{:else}
<span class="invisible"></span>
{/if}
</th>
<th scope="col" class="px-3 py-2 text-right" />
</tr>
@@ -169,6 +234,11 @@
return name.includes(query);
}
})
.sort((a, b) => {
if (a[sortKey] < b[sortKey]) return sortOrder === 'asc' ? -1 : 1;
if (a[sortKey] > b[sortKey]) return sortOrder === 'asc' ? 1 : -1;
return 0;
})
.slice((page - 1) * 20, page * 20) as user}
<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-850 text-xs">
<td class="px-3 py-2 min-w-[7rem] w-28">

View File

@@ -1,6 +1,9 @@
<script lang="ts">
import Chat from '$lib/components/chat/Chat.svelte';
import { page } from '$app/stores';
import Chat from '$lib/components/chat/Chat.svelte';
import Help from '$lib/components/layout/Help.svelte';
</script>
<Help />
<Chat chatIdProp={$page.params.id} />

View File

@@ -0,0 +1,15 @@
<script>
import { showSidebar } from '$lib/stores';
import Playground from '$lib/components/workspace/Playground.svelte';
</script>
<div
class=" flex flex-col w-full min-h-screen max-h-screen {$showSidebar
? 'md:max-w-[calc(100%-260px)]'
: ''}"
>
<div class=" py-4 px-5 flex-1 max-h-full overflow-y-auto">
<Playground />
</div>
</div>

View File

@@ -1,11 +1,16 @@
<script lang="ts">
import { onMount, getContext } from 'svelte';
import { WEBUI_NAME, showSidebar } from '$lib/stores';
import { WEBUI_NAME, showSidebar, functions } from '$lib/stores';
import MenuLines from '$lib/components/icons/MenuLines.svelte';
import { page } from '$app/stores';
import { getFunctions } from '$lib/apis/functions';
const i18n = getContext('i18n');
onMount(async () => {
functions.set(await getFunctions(localStorage.token));
});
</script>
<svelte:head>
@@ -75,11 +80,13 @@
</a>
<a
class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/playground')
class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/functions')
? 'bg-gray-50 dark:bg-gray-850'
: ''} transition"
href="/workspace/playground">{$i18n.t('Playground')}</a
href="/workspace/functions"
>
{$i18n.t('Functions')}
</a>
</div>
</div>

View File

@@ -0,0 +1,5 @@
<script>
import Functions from '$lib/components/workspace/Functions.svelte';
</script>
<Functions />

View File

@@ -0,0 +1,60 @@
<script>
import { toast } from 'svelte-sonner';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { functions, models } from '$lib/stores';
import { createNewFunction, getFunctions } from '$lib/apis/functions';
import FunctionEditor from '$lib/components/workspace/Functions/FunctionEditor.svelte';
import { getModels } from '$lib/apis';
let mounted = false;
let clone = false;
let func = null;
const saveHandler = async (data) => {
console.log(data);
const res = await createNewFunction(localStorage.token, {
id: data.id,
name: data.name,
meta: data.meta,
content: data.content
}).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success('Function created successfully');
functions.set(await getFunctions(localStorage.token));
models.set(await getModels(localStorage.token));
await goto('/workspace/functions');
}
};
onMount(() => {
if (sessionStorage.function) {
func = JSON.parse(sessionStorage.function);
sessionStorage.removeItem('function');
console.log(func);
clone = true;
}
mounted = true;
});
</script>
{#if mounted}
<FunctionEditor
id={func?.id ?? ''}
name={func?.name ?? ''}
meta={func?.meta ?? { description: '' }}
content={func?.content ?? ''}
{clone}
on:save={(e) => {
saveHandler(e.detail);
}}
/>
{/if}

View File

@@ -0,0 +1,68 @@
<script>
import { toast } from 'svelte-sonner';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { functions, models } from '$lib/stores';
import { updateFunctionById, getFunctions, getFunctionById } from '$lib/apis/functions';
import FunctionEditor from '$lib/components/workspace/Functions/FunctionEditor.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import { getModels } from '$lib/apis';
let func = null;
const saveHandler = async (data) => {
console.log(data);
const res = await updateFunctionById(localStorage.token, func.id, {
id: data.id,
name: data.name,
meta: data.meta,
content: data.content
}).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success('Function updated successfully');
functions.set(await getFunctions(localStorage.token));
models.set(await getModels(localStorage.token));
}
};
onMount(async () => {
console.log('mounted');
const id = $page.url.searchParams.get('id');
if (id) {
func = await getFunctionById(localStorage.token, id).catch((error) => {
toast.error(error);
goto('/workspace/functions');
return null;
});
console.log(func);
}
});
</script>
{#if func}
<FunctionEditor
edit={true}
id={func.id}
name={func.name}
meta={func.meta}
content={func.content}
on:save={(e) => {
saveHandler(e.detail);
}}
/>
{:else}
<div class="flex items-center justify-center h-full">
<div class=" pb-16">
<Spinner />
</div>
</div>
{/if}

View File

@@ -4,6 +4,8 @@
import { goto } from '$app/navigation';
import { settings, user, config, models, tools } from '$lib/stores';
import TurndownService from 'turndown';
import { onMount, tick, getContext } from 'svelte';
import { addNewModel, getModelById, getModelInfos } from '$lib/apis/models';
import { getModels } from '$lib/apis';
@@ -14,6 +16,7 @@
import Knowledge from '$lib/components/workspace/Models/Knowledge.svelte';
import ToolsSelector from '$lib/components/workspace/Models/ToolsSelector.svelte';
import { stringify } from 'postcss';
import { parseFile } from '$lib/utils/characters';
const i18n = getContext('i18n');
@@ -60,7 +63,10 @@
let knowledge = [];
$: if (name) {
id = name.replace(/\s+/g, '-').toLowerCase();
id = name
.replace(/\s+/g, '-')
.replace(/[^a-zA-Z0-9-]/g, '')
.toLowerCase();
}
const addUsage = (base_model_id) => {
@@ -213,9 +219,36 @@
accept="image/*"
on:change={() => {
let reader = new FileReader();
reader.onload = (event) => {
reader.onload = async (event) => {
let originalImageUrl = `${event.target.result}`;
let character = await parseFile(inputFiles[0]).catch((error) => {
return null;
});
console.log(character);
if (character && character.character) {
character = character.character;
console.log(character);
name = character.name;
const pattern = /<\/?[a-z][\s\S]*>/i;
if (character.summary.match(pattern)) {
const turndownService = new TurndownService();
info.meta.description = turndownService.turndown(character.summary);
} else {
info.meta.description = character.summary;
}
info.params.system = `Personality: ${character.personality}${
character?.scenario ? `\nScenario: ${character.scenario}` : ''
}${character?.greeting ? `\First Message: ${character.greeting}` : ''}${
character?.examples ? `\nExamples: ${character.examples}` : ''
}`;
}
const img = new Image();
img.src = originalImageUrl;
@@ -229,20 +262,20 @@
// Calculate the new width and height to fit within 100x100
let newWidth, newHeight;
if (aspectRatio > 1) {
newWidth = 100 * aspectRatio;
newHeight = 100;
newWidth = 250 * aspectRatio;
newHeight = 250;
} else {
newWidth = 100;
newHeight = 100 / aspectRatio;
newWidth = 250;
newHeight = 250 / aspectRatio;
}
// Set the canvas size
canvas.width = 100;
canvas.height = 100;
canvas.width = 250;
canvas.height = 250;
// Calculate the position to center the image
const offsetX = (100 - newWidth) / 2;
const offsetY = (100 - newHeight) / 2;
const offsetX = (250 - newWidth) / 2;
const offsetY = (250 - newHeight) / 2;
// Draw the image on the canvas
ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
@@ -408,10 +441,11 @@
</div>
{#if info.meta.description !== null}
<input
<textarea
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder={$i18n.t('Add a short description about what this model does')}
bind:value={info.meta.description}
row="3"
/>
{/if}
</div>

View File

@@ -5,7 +5,7 @@
import { onMount, getContext } from 'svelte';
import { page } from '$app/stores';
import { settings, user, config, models, tools } from '$lib/stores';
import { settings, user, config, models, tools, functions } from '$lib/stores';
import { splitStream } from '$lib/utils';
import { getModelInfos, updateModelById } from '$lib/apis/models';
@@ -16,6 +16,7 @@
import Tags from '$lib/components/common/Tags.svelte';
import Knowledge from '$lib/components/workspace/Models/Knowledge.svelte';
import ToolsSelector from '$lib/components/workspace/Models/ToolsSelector.svelte';
import FiltersSelector from '$lib/components/workspace/Models/FiltersSelector.svelte';
const i18n = getContext('i18n');
@@ -62,6 +63,7 @@
let knowledge = [];
let toolIds = [];
let filterIds = [];
const updateHandler = async () => {
loading = true;
@@ -86,6 +88,14 @@
}
}
if (filterIds.length > 0) {
info.meta.filterIds = filterIds;
} else {
if (info.meta.filterIds) {
delete info.meta.filterIds;
}
}
info.params.stop = params.stop ? params.stop.split(',').filter((s) => s.trim()) : null;
Object.keys(info.params).forEach((key) => {
if (info.params[key] === '' || info.params[key] === null) {
@@ -147,6 +157,10 @@
toolIds = [...model?.info?.meta?.toolIds];
}
if (model?.info?.meta?.filterIds) {
filterIds = [...model?.info?.meta?.filterIds];
}
if (model?.owned_by === 'openai') {
capabilities.usage = false;
}
@@ -190,20 +204,20 @@
// Calculate the new width and height to fit within 100x100
let newWidth, newHeight;
if (aspectRatio > 1) {
newWidth = 100 * aspectRatio;
newHeight = 100;
newWidth = 250 * aspectRatio;
newHeight = 250;
} else {
newWidth = 100;
newHeight = 100 / aspectRatio;
newWidth = 250;
newHeight = 250 / aspectRatio;
}
// Set the canvas size
canvas.width = 100;
canvas.height = 100;
canvas.width = 250;
canvas.height = 250;
// Calculate the position to center the image
const offsetX = (100 - newWidth) / 2;
const offsetY = (100 - newHeight) / 2;
const offsetX = (250 - newWidth) / 2;
const offsetY = (250 - newHeight) / 2;
// Draw the image on the canvas
ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
@@ -369,10 +383,11 @@
</div>
{#if info.meta.description !== null}
<input
<textarea
class="mt-1 px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder={$i18n.t('Add a short description about what this model does')}
bind:value={info.meta.description}
row="3"
/>
{/if}
</div>
@@ -533,6 +548,13 @@
<ToolsSelector bind:selectedToolIds={toolIds} tools={$tools} />
</div>
<div class="my-2">
<FiltersSelector
bind:selectedFilterIds={filterIds}
filters={$functions.filter((func) => func.type === 'filter')}
/>
</div>
<div class="my-2">
<div class="flex w-full justify-between mb-1">
<div class=" self-center text-sm font-semibold">{$i18n.t('Capabilities')}</div>

View File

@@ -1,5 +0,0 @@
<script>
import Playground from '$lib/components/workspace/Playground.svelte';
</script>
<Playground />