mirror of
https://github.com/open-webui/open-webui
synced 2025-06-26 18:26:48 +00:00
Merge remote-tracking branch 'upstream/dev' into feat/oauth
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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} />
|
||||
|
||||
15
src/routes/(app)/playground/+page.svelte
Normal file
15
src/routes/(app)/playground/+page.svelte
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
5
src/routes/(app)/workspace/functions/+page.svelte
Normal file
5
src/routes/(app)/workspace/functions/+page.svelte
Normal file
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import Functions from '$lib/components/workspace/Functions.svelte';
|
||||
</script>
|
||||
|
||||
<Functions />
|
||||
60
src/routes/(app)/workspace/functions/create/+page.svelte
Normal file
60
src/routes/(app)/workspace/functions/create/+page.svelte
Normal 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}
|
||||
68
src/routes/(app)/workspace/functions/edit/+page.svelte
Normal file
68
src/routes/(app)/workspace/functions/edit/+page.svelte
Normal 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}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<script>
|
||||
import Playground from '$lib/components/workspace/Playground.svelte';
|
||||
</script>
|
||||
|
||||
<Playground />
|
||||
Reference in New Issue
Block a user