refac
This commit is contained in:
@@ -6,10 +6,10 @@
|
|||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { onMount, getContext } from 'svelte';
|
import { onMount, getContext, tick } from 'svelte';
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
import { WEBUI_NAME, knowledge } from '$lib/stores';
|
import { WEBUI_NAME, knowledge, user } from '$lib/stores';
|
||||||
import {
|
import {
|
||||||
getKnowledgeBases,
|
getKnowledgeBases,
|
||||||
deleteKnowledgeById,
|
deleteKnowledgeById,
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
} from '$lib/apis/knowledge';
|
} from '$lib/apis/knowledge';
|
||||||
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { capitalizeFirstLetter } from '$lib/utils';
|
||||||
|
|
||||||
import DeleteConfirmDialog from '../common/ConfirmDialog.svelte';
|
import DeleteConfirmDialog from '../common/ConfirmDialog.svelte';
|
||||||
import ItemMenu from './Knowledge/ItemMenu.svelte';
|
import ItemMenu from './Knowledge/ItemMenu.svelte';
|
||||||
@@ -24,9 +25,9 @@
|
|||||||
import Search from '../icons/Search.svelte';
|
import Search from '../icons/Search.svelte';
|
||||||
import Plus from '../icons/Plus.svelte';
|
import Plus from '../icons/Plus.svelte';
|
||||||
import Spinner from '../common/Spinner.svelte';
|
import Spinner from '../common/Spinner.svelte';
|
||||||
import { capitalizeFirstLetter } from '$lib/utils';
|
|
||||||
import Tooltip from '../common/Tooltip.svelte';
|
import Tooltip from '../common/Tooltip.svelte';
|
||||||
import XMark from '../icons/XMark.svelte';
|
import XMark from '../icons/XMark.svelte';
|
||||||
|
import ViewSelector from './common/ViewSelector.svelte';
|
||||||
|
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
||||||
@@ -34,14 +35,25 @@
|
|||||||
let selectedItem = null;
|
let selectedItem = null;
|
||||||
let showDeleteConfirm = false;
|
let showDeleteConfirm = false;
|
||||||
|
|
||||||
|
let tagsContainerElement: HTMLDivElement;
|
||||||
|
let viewOption = '';
|
||||||
|
|
||||||
let fuse = null;
|
let fuse = null;
|
||||||
|
|
||||||
let knowledgeBases = [];
|
let knowledgeBases = [];
|
||||||
|
|
||||||
|
let items = [];
|
||||||
let filteredItems = [];
|
let filteredItems = [];
|
||||||
|
|
||||||
$: if (knowledgeBases.length > 0) {
|
const setFuse = async () => {
|
||||||
// Added a check for non-empty array, good practice
|
items = knowledgeBases.filter(
|
||||||
fuse = new Fuse(knowledgeBases, {
|
(item) =>
|
||||||
|
viewOption === '' ||
|
||||||
|
(viewOption === 'created' && item.user_id === $user?.id) ||
|
||||||
|
(viewOption === 'shared' && item.user_id !== $user?.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
fuse = new Fuse(items, {
|
||||||
keys: [
|
keys: [
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
@@ -50,16 +62,24 @@
|
|||||||
],
|
],
|
||||||
threshold: 0.3
|
threshold: 0.3
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
setFilteredItems();
|
||||||
|
};
|
||||||
|
|
||||||
|
$: if (knowledgeBases.length > 0 && viewOption !== undefined) {
|
||||||
|
// Added a check for non-empty array, good practice
|
||||||
|
setFuse();
|
||||||
} else {
|
} else {
|
||||||
fuse = null; // Reset fuse if knowledgeBases is empty
|
fuse = null; // Reset fuse if knowledgeBases is empty
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (fuse) {
|
const setFilteredItems = () => {
|
||||||
filteredItems = query
|
filteredItems = query ? fuse.search(query).map((result) => result.item) : items;
|
||||||
? fuse.search(query).map((e) => {
|
};
|
||||||
return e.item;
|
|
||||||
})
|
$: if (query !== undefined && fuse) {
|
||||||
: knowledgeBases;
|
setFilteredItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteHandler = async (item) => {
|
const deleteHandler = async (item) => {
|
||||||
@@ -75,6 +95,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
viewOption = localStorage?.workspaceViewOption || '';
|
||||||
knowledgeBases = await getKnowledgeBaseList(localStorage.token);
|
knowledgeBases = await getKnowledgeBaseList(localStorage.token);
|
||||||
loaded = true;
|
loaded = true;
|
||||||
});
|
});
|
||||||
@@ -94,18 +115,31 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1 my-1.5">
|
<div class="flex flex-col gap-1 mt-1.5 my-1">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div class="flex md:self-center text-xl font-medium px-0.5 items-center">
|
<div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0">
|
||||||
{$i18n.t('Knowledge')}
|
<div>
|
||||||
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
|
{$i18n.t('Knowledge')}
|
||||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
|
</div>
|
||||||
>{filteredItems.length}</span
|
|
||||||
|
<div class="text-lg font-medium text-gray-500 dark:text-gray-500">
|
||||||
|
{filteredItems.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-full justify-end gap-1.5">
|
||||||
|
<a
|
||||||
|
class=" px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-medium text-sm flex items-center"
|
||||||
|
href="/workspace/knowledge/create"
|
||||||
>
|
>
|
||||||
|
<Plus className="size-3" strokeWidth="2.5" />
|
||||||
|
|
||||||
|
<div class=" hidden md:block md:ml-1 text-xs">{$i18n.t('New Knowledge')}</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" flex w-full space-x-2">
|
<div class=" flex w-full space-x-2 py-0.5">
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1">
|
||||||
<div class=" self-center ml-1 mr-3">
|
<div class=" self-center ml-1 mr-3">
|
||||||
<Search className="size-3.5" />
|
<Search className="size-3.5" />
|
||||||
@@ -128,22 +162,35 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
class=" px-2 py-2 rounded-xl hover:bg-gray-700/10 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition font-medium text-sm flex items-center space-x-1"
|
|
||||||
aria-label={$i18n.t('Create Knowledge')}
|
|
||||||
on:click={() => {
|
|
||||||
goto('/workspace/knowledge/create');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus className="size-3.5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-5 grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
<div
|
||||||
|
class=" flex w-full bg-transparent overflow-x-auto scrollbar-none -mx-1"
|
||||||
|
on:wheel={(e) => {
|
||||||
|
if (e.deltaY !== 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.currentTarget.scrollLeft += e.deltaY;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-1.5 whitespace-nowrap"
|
||||||
|
bind:this={tagsContainerElement}
|
||||||
|
>
|
||||||
|
<ViewSelector
|
||||||
|
bind:value={viewOption}
|
||||||
|
onChange={async (value) => {
|
||||||
|
localStorage.workspaceViewOption = value;
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- The Aleph dreams itself into being, and the void learns its own name -->
|
||||||
|
<div class=" my-2 mb-5 grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
||||||
{#each filteredItems as item}
|
{#each filteredItems as item}
|
||||||
<button
|
<button
|
||||||
class=" flex space-x-4 cursor-pointer text-left w-full px-4 py-3 border border-gray-50 dark:border-gray-850 hover:bg-black/5 dark:hover:bg-white/5 transition rounded-2xl"
|
class=" flex space-x-4 cursor-pointer text-left w-full px-4 py-3 border border-gray-50 dark:border-gray-850 hover:bg-black/5 dark:hover:bg-white/5 transition rounded-2xl"
|
||||||
|
|||||||
@@ -248,7 +248,7 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1 mt-1.5 my-1">
|
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3">
|
||||||
<input
|
<input
|
||||||
id="models-import-input"
|
id="models-import-input"
|
||||||
bind:this={modelsImportInputElement}
|
bind:this={modelsImportInputElement}
|
||||||
@@ -334,7 +334,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<a
|
<a
|
||||||
class=" px-2 py-1 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-medium text-sm flex items-center"
|
class=" px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-medium text-sm flex items-center"
|
||||||
href="/workspace/models/create"
|
href="/workspace/models/create"
|
||||||
>
|
>
|
||||||
<Plus className="size-3" strokeWidth="2.5" />
|
<Plus className="size-3" strokeWidth="2.5" />
|
||||||
@@ -343,8 +343,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class=" flex flex-1 items-center w-full space-x-2 py-0.5">
|
<div
|
||||||
|
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100 dark:border-gray-850"
|
||||||
|
>
|
||||||
|
<div class="px-3 flex flex-1 items-center w-full space-x-2 py-0.5 pb-2 px-0.5">
|
||||||
<div class="flex flex-1 items-center">
|
<div class="flex flex-1 items-center">
|
||||||
<div class=" self-center ml-1 mr-3">
|
<div class=" self-center ml-1 mr-3">
|
||||||
<Search className="size-3.5" />
|
<Search className="size-3.5" />
|
||||||
@@ -369,213 +373,240 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class=" flex w-full bg-transparent overflow-x-auto scrollbar-none -mx-1"
|
|
||||||
on:wheel={(e) => {
|
|
||||||
if (e.deltaY !== 0) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.currentTarget.scrollLeft += e.deltaY;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="flex gap-1 w-fit text-center text-sm rounded-full bg-transparent px-1.5 whitespace-nowrap"
|
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none"
|
||||||
bind:this={tagsContainerElement}
|
on:wheel={(e) => {
|
||||||
|
if (e.deltaY !== 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.currentTarget.scrollLeft += e.deltaY;
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ViewSelector
|
|
||||||
bind:value={viewOption}
|
|
||||||
onChange={async (value) => {
|
|
||||||
localStorage.workspaceViewOption = value;
|
|
||||||
|
|
||||||
await tick();
|
|
||||||
setTags();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{#if (tags ?? []).length > 0}
|
|
||||||
<TagSelector
|
|
||||||
bind:value={selectedTag}
|
|
||||||
items={tags.map((tag) => {
|
|
||||||
return { value: tag, label: tag };
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" my-2 mb-5 gap-2 grid lg:grid-cols-2 xl:grid-cols-3" id="model-list">
|
|
||||||
{#each filteredModels as model (model.id)}
|
|
||||||
<div
|
<div
|
||||||
class=" flex flex-col cursor-pointer w-full px-4 py-3 border border-gray-50 dark:border-gray-850 dark:hover:bg-white/5 hover:bg-black/5 rounded-2xl transition"
|
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-0.5 whitespace-nowrap"
|
||||||
id="model-item-{model.id}"
|
bind:this={tagsContainerElement}
|
||||||
>
|
>
|
||||||
<div class="flex gap-4 mt-1 mb-0.5">
|
<ViewSelector
|
||||||
<div class=" w-10">
|
bind:value={viewOption}
|
||||||
<div
|
onChange={async (value) => {
|
||||||
class=" rounded-full object-cover {model.is_active
|
localStorage.workspaceViewOption = value;
|
||||||
? ''
|
|
||||||
: 'opacity-50 dark:opacity-50'} "
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={model?.meta?.profile_image_url ?? `${WEBUI_BASE_URL}/static/favicon.png`}
|
|
||||||
alt="modelfile profile"
|
|
||||||
class=" rounded-full w-full h-auto object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a
|
await tick();
|
||||||
class=" flex flex-1 cursor-pointer w-full"
|
setTags();
|
||||||
href={`/?models=${encodeURIComponent(model.id)}`}
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if (tags ?? []).length > 0}
|
||||||
|
<TagSelector
|
||||||
|
bind:value={selectedTag}
|
||||||
|
items={tags.map((tag) => {
|
||||||
|
return { value: tag, label: tag };
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if (filteredModels ?? []).length !== 0}
|
||||||
|
<div class=" px-3 my-2 gap-1 lg:gap-2 grid lg:grid-cols-2" id="model-list">
|
||||||
|
{#each filteredModels as model (model.id)}
|
||||||
|
<button
|
||||||
|
class=" flex cursor-pointer dark:hover:bg-gray-850/50 hover:bg-gray-50 transition rounded-3xl w-full p-2.5"
|
||||||
|
id="model-item-{model.id}"
|
||||||
|
on:click={() => {
|
||||||
|
if (
|
||||||
|
$user?.role === 'admin' ||
|
||||||
|
model.user_id === $user?.id ||
|
||||||
|
model.access_control.write.group_ids.some((wg) => group_ids.includes(wg))
|
||||||
|
) {
|
||||||
|
goto(`/workspace/models/edit?id=${encodeURIComponent(model.id)}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div class=" flex-1 self-center {model.is_active ? '' : 'text-gray-500'}">
|
<div class="flex group/item gap-3.5 w-full">
|
||||||
<Tooltip
|
<div class="self-center pl-0.5">
|
||||||
content={marked.parse(model?.meta?.description ?? model.id)}
|
<div class="flex bg-white rounded-2xl">
|
||||||
className=" w-fit"
|
<div
|
||||||
placement="top-start"
|
class="{model.is_active ? '' : 'opacity-50 dark:opacity-50'} {model.meta
|
||||||
>
|
.profile_image_url !== `${WEBUI_BASE_URL}/static/favicon.png`
|
||||||
<div class=" font-semibold line-clamp-1">{model.name}</div>
|
? 'bg-transparent'
|
||||||
</Tooltip>
|
: 'bg-white'} rounded-2xl"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={model?.meta?.profile_image_url ?? `${WEBUI_BASE_URL}/static/favicon.png`}
|
||||||
|
alt="modelfile profile"
|
||||||
|
class=" rounded-2xl size-12 object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-1 text-xs overflow-hidden">
|
<div class=" shrink-0 flex w-full min-w-0 flex-1 pr-1 self-center">
|
||||||
<div class="line-clamp-1">
|
<div class="flex h-full w-full flex-1 flex-col justify-start self-center group">
|
||||||
{#if (model?.meta?.description ?? '').trim()}
|
<div class="flex-1 w-full">
|
||||||
{model?.meta?.description}
|
<div class="flex items-center justify-between w-full">
|
||||||
{:else}
|
<Tooltip content={model.name} className=" w-fit" placement="top-start">
|
||||||
{model.id}
|
<a
|
||||||
{/if}
|
class=" font-semibold line-clamp-1 hover:underline capitalize"
|
||||||
|
href={`/?models=${encodeURIComponent(model.id)}`}
|
||||||
|
>
|
||||||
|
{model.name}
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<div class=" flex items-center gap-1">
|
||||||
|
<div
|
||||||
|
class="flex justify-end w-full {model.is_active ? '' : 'text-gray-500'}"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between items-center w-full">
|
||||||
|
<div class=""></div>
|
||||||
|
<div class="flex flex-row gap-0.5 items-center">
|
||||||
|
{#if shiftKey}
|
||||||
|
<Tooltip
|
||||||
|
content={model?.meta?.hidden ? $i18n.t('Show') : $i18n.t('Hide')}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="self-center w-fit text-sm p-1.5 dark:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||||
|
type="button"
|
||||||
|
on:click={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
hideModelHandler(model);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if model?.meta?.hidden}
|
||||||
|
<EyeSlash />
|
||||||
|
{:else}
|
||||||
|
<Eye />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip content={$i18n.t('Delete')}>
|
||||||
|
<button
|
||||||
|
class="self-center w-fit text-sm p-1.5 dark:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||||
|
type="button"
|
||||||
|
on:click={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
deleteModelHandler(model);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GarbageBin />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{:else}
|
||||||
|
<ModelMenu
|
||||||
|
user={$user}
|
||||||
|
{model}
|
||||||
|
shareHandler={() => {
|
||||||
|
shareModelHandler(model);
|
||||||
|
}}
|
||||||
|
cloneHandler={() => {
|
||||||
|
cloneModelHandler(model);
|
||||||
|
}}
|
||||||
|
exportHandler={() => {
|
||||||
|
exportModelHandler(model);
|
||||||
|
}}
|
||||||
|
hideHandler={() => {
|
||||||
|
hideModelHandler(model);
|
||||||
|
}}
|
||||||
|
copyLinkHandler={() => {
|
||||||
|
copyLinkHandler(model);
|
||||||
|
}}
|
||||||
|
deleteHandler={() => {
|
||||||
|
selectedModel = model;
|
||||||
|
showModelDeleteConfirm = true;
|
||||||
|
}}
|
||||||
|
onClose={() => {}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="self-center w-fit p-1 text-sm dark:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||||
|
>
|
||||||
|
<EllipsisHorizontal className="size-5" />
|
||||||
|
</div>
|
||||||
|
</ModelMenu>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
on:click={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
content={model.is_active ? $i18n.t('Enabled') : $i18n.t('Disabled')}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
bind:state={model.is_active}
|
||||||
|
on:change={async () => {
|
||||||
|
toggleModelById(localStorage.token, model.id);
|
||||||
|
_models.set(
|
||||||
|
await getModels(
|
||||||
|
localStorage.token,
|
||||||
|
$config?.features?.enable_direct_connections &&
|
||||||
|
($settings?.directConnections ?? null)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=" flex gap-1 pr-2 -mt-1 items-center">
|
||||||
|
<Tooltip
|
||||||
|
content={model?.user?.email ?? $i18n.t('Deleted User')}
|
||||||
|
className="flex shrink-0"
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
<div class="shrink-0 text-gray-500 text-xs">
|
||||||
|
{$i18n.t('By {{name}}', {
|
||||||
|
name: capitalizeFirstLetter(
|
||||||
|
model?.user?.name ?? model?.user?.email ?? $i18n.t('Deleted User')
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<div>·</div>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
content={marked.parse(model?.meta?.description ?? model.id)}
|
||||||
|
className=" w-fit text-left"
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
<div class="flex gap-1 text-xs overflow-hidden">
|
||||||
|
<div class="line-clamp-1">
|
||||||
|
{#if (model?.meta?.description ?? '').trim()}
|
||||||
|
{model?.meta?.description}
|
||||||
|
{:else}
|
||||||
|
{model.id}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</button>
|
||||||
</div>
|
{/each}
|
||||||
|
</div>
|
||||||
<div class="flex justify-between items-center -mb-0.5 px-0.5 mt-1.5">
|
{:else}
|
||||||
<div class=" text-xs mt-0.5">
|
<div class=" w-full h-full flex flex-col justify-center items-center my-16 mb-24">
|
||||||
<Tooltip
|
<div class="max-w-md text-center">
|
||||||
content={model?.user?.email ?? $i18n.t('Deleted User')}
|
<div class=" text-3xl mb-3">😕</div>
|
||||||
className="flex shrink-0"
|
<div class=" text-lg font-medium mb-1">{$i18n.t('No models found')}</div>
|
||||||
placement="top-start"
|
<div class=" text-gray-500 text-center text-xs">
|
||||||
>
|
{$i18n.t('Try adjusting your search or filter to find what you are looking for.')}
|
||||||
<div class="shrink-0 text-gray-500">
|
|
||||||
{$i18n.t('By {{name}}', {
|
|
||||||
name: capitalizeFirstLetter(
|
|
||||||
model?.user?.name ?? model?.user?.email ?? $i18n.t('Deleted User')
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-0.5 items-center">
|
|
||||||
{#if shiftKey}
|
|
||||||
<Tooltip content={model?.meta?.hidden ? $i18n.t('Show') : $i18n.t('Hide')}>
|
|
||||||
<button
|
|
||||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
|
||||||
type="button"
|
|
||||||
on:click={() => {
|
|
||||||
hideModelHandler(model);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{#if model?.meta?.hidden}
|
|
||||||
<EyeSlash />
|
|
||||||
{:else}
|
|
||||||
<Eye />
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip content={$i18n.t('Delete')}>
|
|
||||||
<button
|
|
||||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
|
||||||
type="button"
|
|
||||||
on:click={() => {
|
|
||||||
deleteModelHandler(model);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GarbageBin />
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
{:else}
|
|
||||||
{#if $user?.role === 'admin' || model.user_id === $user?.id || model.access_control.write.group_ids.some( (wg) => group_ids.includes(wg) )}
|
|
||||||
<a
|
|
||||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
|
||||||
type="button"
|
|
||||||
href={`/workspace/models/edit?id=${encodeURIComponent(model.id)}`}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<ModelMenu
|
|
||||||
user={$user}
|
|
||||||
{model}
|
|
||||||
shareHandler={() => {
|
|
||||||
shareModelHandler(model);
|
|
||||||
}}
|
|
||||||
cloneHandler={() => {
|
|
||||||
cloneModelHandler(model);
|
|
||||||
}}
|
|
||||||
exportHandler={() => {
|
|
||||||
exportModelHandler(model);
|
|
||||||
}}
|
|
||||||
hideHandler={() => {
|
|
||||||
hideModelHandler(model);
|
|
||||||
}}
|
|
||||||
copyLinkHandler={() => {
|
|
||||||
copyLinkHandler(model);
|
|
||||||
}}
|
|
||||||
deleteHandler={() => {
|
|
||||||
selectedModel = model;
|
|
||||||
showModelDeleteConfirm = true;
|
|
||||||
}}
|
|
||||||
onClose={() => {}}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="self-center w-fit text-sm p-1.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<EllipsisHorizontal className="size-5" />
|
|
||||||
</button>
|
|
||||||
</ModelMenu>
|
|
||||||
|
|
||||||
<div class="ml-1">
|
|
||||||
<Tooltip content={model.is_active ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
|
|
||||||
<Switch
|
|
||||||
bind:state={model.is_active}
|
|
||||||
on:change={async (e) => {
|
|
||||||
toggleModelById(localStorage.token, model.id);
|
|
||||||
_models.set(
|
|
||||||
await getModels(
|
|
||||||
localStorage.token,
|
|
||||||
$config?.features?.enable_direct_connections &&
|
|
||||||
($settings?.directConnections ?? null)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $config?.features.enable_community_sharing}
|
{#if $config?.features.enable_community_sharing}
|
||||||
|
|||||||
@@ -43,7 +43,14 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip content={$i18n.t('More')}>
|
<Tooltip content={$i18n.t('More')}>
|
||||||
<slot />
|
<button
|
||||||
|
on:click={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
show = !show;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<div slot="content">
|
<div slot="content">
|
||||||
|
|||||||
@@ -128,7 +128,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class=" pb-1 px-[18px] flex-1 max-h-full overflow-y-auto" id="workspace-container">
|
<div
|
||||||
|
class=" pb-1 px-3 md:px-[18px] flex-1 max-h-full overflow-y-auto"
|
||||||
|
id="workspace-container"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -71,13 +71,17 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = JSON.parse(event.data);
|
try {
|
||||||
|
let data = JSON.parse(event.data);
|
||||||
|
|
||||||
if (data?.info) {
|
if (data?.info) {
|
||||||
data = data.info;
|
data = data.info;
|
||||||
|
}
|
||||||
|
|
||||||
|
model = data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse message data:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
model = data;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (window.opener ?? false) {
|
if (window.opener ?? false) {
|
||||||
|
|||||||
Reference in New Issue
Block a user