feat(ui): add bulk actions hamburger menu to workspace Models page (#22484)

Add hamburger menu with bulk actions (Enable All, Disable All, Show All,
Hide All) to the workspace Models page filter bar, matching the
functionality from the admin Settings Models page.
This commit is contained in:
G30 2026-03-15 18:36:45 -04:00 committed by GitHub
parent b535275b17
commit bc9ee7b4d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -29,6 +29,8 @@
import { capitalizeFirstLetter, copyToClipboard } from '$lib/utils';
import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
import CheckCircle from '../icons/CheckCircle.svelte';
import Minus from '../icons/Minus.svelte';
import ModelMenu from './Models/ModelMenu.svelte';
import ModelDeleteConfirmDialog from '../common/ConfirmDialog.svelte';
import Tooltip from '../common/Tooltip.svelte';
@ -41,6 +43,9 @@
import XMark from '../icons/XMark.svelte';
import EyeSlash from '../icons/EyeSlash.svelte';
import Eye from '../icons/Eye.svelte';
import { DropdownMenu } from 'bits-ui';
import { flyAndScale } from '$lib/utils/transitions';
import Dropdown from '$lib/components/common/Dropdown.svelte';
import ViewSelector from './common/ViewSelector.svelte';
import TagSelector from './common/TagSelector.svelte';
import Pagination from '../common/Pagination.svelte';
@ -224,6 +229,50 @@
await updateUserSettings(localStorage.token, { ui: $settings });
};
const enableAllHandler = async () => {
const modelsToEnable = (models ?? []).filter((m) => !(m.is_active ?? true));
// Optimistic UI update
modelsToEnable.forEach((m) => (m.is_active = true));
models = models;
// Sync with server
await Promise.all(modelsToEnable.map((model) => toggleModelById(localStorage.token, model.id)));
};
const disableAllHandler = async () => {
const modelsToDisable = (models ?? []).filter((m) => m.is_active ?? true);
// Optimistic UI update
modelsToDisable.forEach((m) => (m.is_active = false));
models = models;
// Sync with server
await Promise.all(
modelsToDisable.map((model) => toggleModelById(localStorage.token, model.id))
);
};
const showAllHandler = async () => {
const modelsToShow = (models ?? []).filter((m) => m?.meta?.hidden === true);
// Optimistic UI update
modelsToShow.forEach((m) => {
m.meta = { ...m.meta, hidden: false };
});
models = models;
// Sync with server
await Promise.all(modelsToShow.map((model) => updateModelById(localStorage.token, model.id, model)));
toast.success($i18n.t('All models are now visible'));
};
const hideAllHandler = async () => {
const modelsToHide = (models ?? []).filter((m) => !(m?.meta?.hidden ?? false));
// Optimistic UI update
modelsToHide.forEach((m) => {
m.meta = { ...m.meta, hidden: true };
});
models = models;
// Sync with server
await Promise.all(modelsToHide.map((model) => updateModelById(localStorage.token, model.id, model)));
toast.success($i18n.t('All models are now hidden'));
};
onMount(async () => {
viewOption = localStorage.workspaceViewOption ?? '';
page = 1;
@ -420,6 +469,7 @@
</button>
</div>
{/if}
</div>
</div>
@ -453,6 +503,71 @@
/>
{/if}
</div>
<div class="flex-1"></div>
<Dropdown>
<Tooltip content={$i18n.t('Actions')}>
<button
class="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition"
type="button"
>
<EllipsisHorizontal className="size-4" />
</button>
</Tooltip>
<div slot="content">
<DropdownMenu.Content
class="w-full max-w-[170px] rounded-xl p-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-sm"
sideOffset={-2}
side="bottom"
align="end"
transition={flyAndScale}
>
<DropdownMenu.Item
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
enableAllHandler();
}}
>
<CheckCircle className="size-4" />
<div class="flex items-center">{$i18n.t('Enable All')}</div>
</DropdownMenu.Item>
<DropdownMenu.Item
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
disableAllHandler();
}}
>
<Minus className="size-4" />
<div class="flex items-center">{$i18n.t('Disable All')}</div>
</DropdownMenu.Item>
<hr class="border-gray-100 dark:border-gray-800 my-1" />
<DropdownMenu.Item
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
showAllHandler();
}}
>
<Eye className="size-4" />
<div class="flex items-center">{$i18n.t('Show All')}</div>
</DropdownMenu.Item>
<DropdownMenu.Item
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
hideAllHandler();
}}
>
<EyeSlash className="size-4" />
<div class="flex items-center">{$i18n.t('Hide All')}</div>
</DropdownMenu.Item>
</DropdownMenu.Content>
</div>
</Dropdown>
</div>
{#if models !== null}