diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionModels.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionModels.svelte
index a4baa40bf9..434db0cf4f 100644
--- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionModels.svelte
+++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionModels.svelte
@@ -1,6 +1,14 @@
-{#if hasError}
+{#if hasError || isLoadingModel}
{#if isLoadingModel}
diff --git a/tools/ui/src/lib/constants/icons.constants.ts b/tools/ui/src/lib/constants/icons.constants.ts
index 71741c6957..38de4e0cdc 100644
--- a/tools/ui/src/lib/constants/icons.constants.ts
+++ b/tools/ui/src/lib/constants/icons.constants.ts
@@ -31,9 +31,6 @@ export const DEFAULT_FILE_ICON = FileIcon;
/** The mark for a model, wherever one is listed. */
export const MODEL_ICON = ModelIcon;
-/** Alias kept for the selector until it adopts MODEL_ICON. */
-export const MODEL_SELECTOR_ICON = ModelIcon;
-
export const MODALITY_ICONS = {
[ModelModality.AUDIO]: AudioIcon,
[ModelModality.VIDEO]: VideoIcon,
diff --git a/tools/ui/src/lib/constants/settings-keys.constants.ts b/tools/ui/src/lib/constants/settings-keys.constants.ts
index 7727370bde..574c906de2 100644
--- a/tools/ui/src/lib/constants/settings-keys.constants.ts
+++ b/tools/ui/src/lib/constants/settings-keys.constants.ts
@@ -41,6 +41,7 @@ export const SETTINGS_KEYS = {
MCP_SERVERS: 'mcpServers',
MENTION_SEARCH_MAX_DEPTH: 'mentionSearchMaxDepth',
MIN_P: 'min_p',
+ // Display
PASTE_LONG_TEXT_TO_FILE_LEN: 'pasteLongTextToFileLen',
PDF_AS_IMAGE: 'pdfAsImage',
// Performance
@@ -58,7 +59,8 @@ export const SETTINGS_KEYS = {
SHOW_FULL_PATH_IN_MENTIONS: 'showFullPathInMentions',
// Display
SHOW_MESSAGE_STATS: 'showMessageStats',
- SHOW_MODEL_ORG_NAME_IN_TRIGGER: 'showModelOrgNameInTrigger',
+ SHOW_MODEL_CAPABILITIES_IN_SELECTOR: 'showModelCapabilitiesInSelector',
+ SHOW_MODEL_ORG_NAME: 'showModelOrgName',
SHOW_MODEL_QUANTIZATION: 'showModelQuantization',
SHOW_MODEL_TAGS: 'showModelTags',
SHOW_RAW_MODEL_NAMES: 'showRawModelNames',
diff --git a/tools/ui/src/lib/constants/settings.constants.ts b/tools/ui/src/lib/constants/settings.constants.ts
index 27d20c9243..2d5cb4afec 100644
--- a/tools/ui/src/lib/constants/settings.constants.ts
+++ b/tools/ui/src/lib/constants/settings.constants.ts
@@ -28,6 +28,7 @@ import type {
/** Settings sections — slug is the routing identity, title is the display label. */
export const SETTINGS_SECTIONS = {
AGENTIC: { slug: 'agentic', title: 'Agentic' },
+ BACKENDS: { slug: 'backends', title: 'Providers' },
DEVELOPER: { slug: 'developer', title: 'Developer' },
DISPLAY: { slug: 'display', title: 'Display' },
GENERAL: { slug: 'general', title: 'General' },
@@ -38,6 +39,7 @@ export const SETTINGS_SECTIONS = {
export const SETTINGS_SECTION_SLUGS = {
AGENTIC: SETTINGS_SECTIONS.AGENTIC.slug,
+ BACKENDS: SETTINGS_SECTIONS.BACKENDS.slug,
DEVELOPER: SETTINGS_SECTIONS.DEVELOPER.slug,
DISPLAY: SETTINGS_SECTIONS.DISPLAY.slug,
GENERAL: SETTINGS_SECTIONS.GENERAL.slug,
@@ -48,6 +50,7 @@ export const SETTINGS_SECTION_SLUGS = {
export const SETTINGS_SECTION_TITLES = {
AGENTIC: SETTINGS_SECTIONS.AGENTIC.title,
+ BACKENDS: SETTINGS_SECTIONS.BACKENDS.title,
DEVELOPER: SETTINGS_SECTIONS.DEVELOPER.title,
DISPLAY: SETTINGS_SECTIONS.DISPLAY.title,
GENERAL: SETTINGS_SECTIONS.GENERAL.title,
@@ -291,9 +294,16 @@ export const SETTINGS_REGISTRY: SettingsSectionEntry[] = [
},
{
defaultValue: false,
- help: 'Display the organization name in the model selector trigger button.',
- key: SETTINGS_KEYS.SHOW_MODEL_ORG_NAME_IN_TRIGGER,
- label: 'Show organization name in model selector trigger',
+ help: 'Show the modality and capability icons (vision, audio, reasoning, tools) on the models selector rows.',
+ key: SETTINGS_KEYS.SHOW_MODEL_CAPABILITIES_IN_SELECTOR,
+ label: 'Show model modalities & capabilities in models selector',
+ type: SettingsFieldType.CHECKBOX
+ },
+ {
+ defaultValue: true,
+ help: 'Display the organization name alongside model names in the model selector, in the trigger and in the model rows.',
+ key: SETTINGS_KEYS.SHOW_MODEL_ORG_NAME,
+ label: 'Show organization name in model selector',
type: SettingsFieldType.CHECKBOX
},
{
@@ -303,19 +313,19 @@ export const SETTINGS_REGISTRY: SettingsSectionEntry[] = [
label: 'Show build version information',
type: SettingsFieldType.CHECKBOX
},
- {
- defaultValue: true,
- help: 'Group models into families (e.g. "Qwen") in the model picker and the models manager. Turn it off to list every model on its own.',
- key: SETTINGS_KEYS.GROUP_MODELS_BY_FAMILY,
- label: 'Group models by family',
- type: SettingsFieldType.CHECKBOX
- },
{
defaultValue: false,
help: 'Display the full file system path inside file and folder @-mention badges instead of just the file or folder name.',
key: SETTINGS_KEYS.SHOW_FULL_PATH_IN_MENTIONS,
label: 'Show full path in mentions',
type: SettingsFieldType.CHECKBOX
+ },
+ {
+ defaultValue: true,
+ help: 'Group models into families (e.g. "Qwen") in the model picker and the models manager. Turn it off to list every model on its own.',
+ key: SETTINGS_KEYS.GROUP_MODELS_BY_FAMILY,
+ label: 'Group models by family',
+ type: SettingsFieldType.CHECKBOX
}
],
slug: SETTINGS_SECTION_SLUGS.DISPLAY,
diff --git a/tools/ui/src/lib/hooks/use-models-selector.svelte.ts b/tools/ui/src/lib/hooks/use-models-selector.svelte.ts
index 7d2770a261..38c5f3b42b 100644
--- a/tools/ui/src/lib/hooks/use-models-selector.svelte.ts
+++ b/tools/ui/src/lib/hooks/use-models-selector.svelte.ts
@@ -1,14 +1,35 @@
-import { filterModelOptions, groupModelOptions } from '$lib/components/app/models/utils';
-import { CHAT_INPUT_FOCUS_SELECTOR } from '$lib/constants';
-import { modelsStore, serverStore } from '$lib/stores';
+import type { ModelItem } from '$lib/components/app/navigation/utils';
+import {
+ filterModelOptions,
+ groupFavoriteOptions,
+ groupModelOptions,
+ groupProviderOptions
+} from '$lib/components/app/navigation/utils';
+import {
+ CHAT_INPUT_FOCUS_SELECTOR,
+ LOCAL_BACKEND_ID,
+ REMOTE_PROVIDER_MODEL_LIMIT
+} from '$lib/constants';
+import { backendsModelsStore, backendsStore, modelsStore, serverStore, uiStore } from '$lib/stores';
import type { ModelOption } from '$lib/types/models';
+import { getBackend } from '$lib/utils/api-base';
+import { getBackendCapabilities } from '$lib/utils/backend';
+import { rawModelId } from '$lib/utils/model-option-id';
import { onMount } from 'svelte';
+import { SvelteSet } from 'svelte/reactivity';
+
+/** Groups of the favorites tab, which lists favorites only. */
+const EMPTY_GROUPS = { available: [], loaded: [], providers: [] };
export interface UseModelsSelectorOptions {
currentModel: () => string | null;
useGlobalSelection?: () => boolean;
onModelChange?: () =>
- | ((modelId: string, modelName: string) => Promise | boolean | void)
+ | ((
+ modelId: string,
+ modelName: string,
+ backendId?: string
+ ) => Promise | boolean | void)
| undefined;
onOpenChange?: (open: boolean) => void;
}
@@ -18,19 +39,24 @@ export interface UseModelsSelectorReturn {
readonly loading: boolean;
readonly updating: boolean;
readonly activeId: string | null;
+ readonly emptyMessage: string;
+ readonly isMultiModel: boolean;
readonly isRouter: boolean;
readonly serverModel: string | null;
readonly isHighlightedCurrentModelActive: boolean;
readonly isCurrentModelInCache: boolean;
+ readonly favoriteItems: ModelItem[];
+ readonly loadedItems: ModelItem[];
readonly filteredOptions: ModelOption[];
+ readonly isEmpty: boolean;
+ readonly isProviderView: boolean;
readonly groupedFilteredOptions: ReturnType;
readonly isLoadingModel: boolean;
readonly searchTerm: string;
- readonly showModelDialog: boolean;
- readonly infoModelId: string | null;
+ closeProvider(): void;
+ openProvider(backendId: string): void;
setSearchTerm(value: string): void;
- setShowModelDialog(value: boolean): void;
- handleInfoClick(modelName: string): void;
+ showBackendModels(backendId: string): Promise;
handleSelect(modelId: string): Promise;
handleOpenChange(open: boolean): void;
isFavorite(model: string): boolean;
@@ -45,16 +71,36 @@ export interface UseModelsSelectorReturn {
* duplicating store derivations, selection handling, and model loading.
*/
export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSelectorReturn {
- const options = $derived(
+ /**
+ * Current view: the favorites of every backend, the local server's models, or
+ * the remote backends'. Favorites are the default while there is at least one.
+ */
+ /** Remote backend drilled into from its section; null while browsing. */
+ let providerViewId = $state(null);
+
+ const isProviderView = $derived(providerViewId !== null);
+ const isLocalOption = (option: ModelOption) => option.backendId === LOCAL_BACKEND_ID;
+ // every enabled backend's models are one list: favorites, then the local
+ // server, then one section per remote provider
+ const allOptions = $derived(
modelsStore.models.filter((option) => {
const modelProps = modelsStore.props.getModelProps(option.model);
return modelProps?.ui !== false;
})
);
+ const options = $derived(
+ providerViewId ? allOptions.filter((option) => option.backendId === providerViewId) : allOptions
+ );
const loading = $derived(modelsStore.loading);
const updating = $derived(modelsStore.updating);
const activeId = $derived(modelsStore.selectedModelId);
+ // Router mode and external backends both expose a selectable model list; only
+ // a lone llama.cpp server without a router has nothing to choose from.
+ const isMultiModel = $derived(
+ serverStore.isRouterMode ||
+ backendsStore.enabled.some((backend) => backend.id !== LOCAL_BACKEND_ID)
+ );
const isRouter = $derived(serverStore.isRouterMode);
const serverModel = $derived(modelsStore.singleModelName);
const currentModel = $derived(opts.currentModel());
@@ -62,32 +108,95 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
const isHighlightedCurrentModelActive = $derived.by(() => {
if (!isRouter || !currentModel) return false;
- const currentOption = options.find((option) => option.model === currentModel);
+ const currentOption = allOptions.find((option) => option.model === currentModel);
return currentOption ? currentOption.id === activeId : false;
});
const isCurrentModelInCache = $derived.by(() => {
if (!isRouter || !currentModel) return true;
- return options.some((option) => option.model === currentModel);
+ return allOptions.some((option) => option.model === currentModel);
});
let isLoadingModel = $state(false);
let searchTerm = $state('');
- let showModelDialog = $state(false);
- let infoModelId = $state(null);
+ const visibleOptions = $derived(allOptions.filter((option) => !modelsStore.isHidden(option.id)));
const filteredOptions = $derived(filterModelOptions(options, searchTerm));
- const groupedFilteredOptions = $derived(
- groupModelOptions(filteredOptions, modelsStore.favoriteModelIds, (m) =>
- modelsStore.isModelLoaded(m)
+ // favorites span every backend, so they come from the full option list
+ const favoriteItems = $derived(
+ groupFavoriteOptions(
+ filterModelOptions(visibleOptions, searchTerm),
+ modelsStore.favoriteModelIds
)
);
+ const remoteProviders = $derived(
+ backendsStore.enabled
+ .filter((backend) => backend.id !== LOCAL_BACKEND_ID)
+ .map((backend) => {
+ const state = backendsModelsStore.get(backend.id);
- function handleInfoClick(modelName: string) {
- infoModelId = modelName;
- showModelDialog = true;
- }
+ return {
+ backendId: backend.id,
+ catalog: state.models.length,
+ error: state.error,
+ loading: state.loading,
+ name: backend.name
+ };
+ })
+ );
+ // loaded models lead the list, from any llama-compat backend
+ const isLoadedLlamaCompat = (option: ModelOption) =>
+ modelsStore.isModelLoaded(option.model) &&
+ getBackendCapabilities(getBackend(option.backendId)).loadUnload;
+ const loadedItems = $derived.by(() => {
+ if (isProviderView) return [];
+
+ return filterModelOptions(visibleOptions, searchTerm)
+ .map((option, flatIndex) => ({ flatIndex, option }))
+ .filter(({ option }) => isLoadedLlamaCompat(option));
+ });
+ const loadedIds = $derived(new SvelteSet(loadedItems.map((item) => item.option.id)));
+ // loaded models and favorites are listed once, at the top: the sections skip both
+ const sectionOptions = $derived(
+ filteredOptions.filter(
+ (option) => !modelsStore.favoriteModelIds.has(option.model) && !loadedIds.has(option.id)
+ )
+ );
+ const providerSections = $derived(
+ groupProviderOptions(
+ sectionOptions,
+ remoteProviders,
+ // a drill-in or a search reaches every model, the sections stay short
+ providerViewId || searchTerm ? Infinity : REMOTE_PROVIDER_MODEL_LIMIT,
+ modelsStore.recentModelIds
+ )
+ );
+ const groupedFilteredOptions = $derived.by(() => {
+ if (isProviderView) {
+ const sections = providerSections.filter((section) => section.backendId === providerViewId);
+
+ return { ...EMPTY_GROUPS, providers: sections };
+ }
+
+ const local = groupModelOptions(sectionOptions.filter(isLocalOption), (m) =>
+ modelsStore.isModelLoaded(m)
+ );
+
+ return { ...local, providers: providerSections };
+ });
+ const isEmpty = $derived(
+ filteredOptions.length === 0 && favoriteItems.length === 0 && loadedItems.length === 0
+ );
+ const emptyMessage = $derived(searchTerm ? 'No models found.' : 'No models yet.');
+
+ // a row action can hand over to the models manager, which takes the focus;
+ // the selector closes so it does not sit open behind the dialog
+ $effect(() => {
+ if (!uiStore.manageModelsOpen) return;
+
+ opts.onOpenChange?.(false);
+ });
onMount(() => {
modelsStore.fetch().catch((error) => {
@@ -98,36 +207,61 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
function handleOpenChange(open: boolean) {
if (loading || updating) return;
- if (isRouter) {
- searchTerm = '';
+ // a single-model llama.cpp server with no other backend has no list to show,
+ // so the trigger opens the manager, which holds the one model's configuration
+ if (!isMultiModel) {
+ if (open) uiStore.openModelsManager();
- if (open) {
- modelsStore.fetchRouterModels().then(() => {
- modelsStore.props.fetchModalitiesForLoadedModels();
- });
- }
-
- opts.onOpenChange?.(open);
- } else {
- showModelDialog = open;
+ return;
}
+
+ searchTerm = '';
+ providerViewId = null;
+
+ if (open && isRouter) {
+ modelsStore.props.fetchModalitiesForLoadedModels();
+ }
+
+ opts.onOpenChange?.(open);
+ }
+
+ /**
+ * Switch the rendered view. Views are display only: the backend that serves
+ * requests follows the selected model, not the view.
+ */
+ /** Drill into one remote backend's full model list. */
+ function openProvider(backendId: string) {
+ providerViewId = backendId;
+ searchTerm = '';
+ }
+
+ function closeProvider() {
+ providerViewId = null;
+ searchTerm = '';
+ }
+
+ /** Refresh a backend's models, e.g. right after it was added. */
+ async function showBackendModels(backendId: string): Promise {
+ await backendsModelsStore.ensureLoaded(backendId);
}
async function handleSelect(modelId: string) {
- const option = options.find((opt) => opt.id === modelId);
+ // favorites live above the tabs and may belong to another backend, so the
+ // lookup spans every enabled backend
+ const option = allOptions.find((opt) => opt.id === modelId);
if (!option) return;
let shouldCloseMenu = true;
if (onModelChange) {
- const result = await onModelChange(option.id, option.model);
+ const result = await onModelChange(rawModelId(option.id), option.model, option.backendId);
if (result === false) {
shouldCloseMenu = false;
}
} else {
- await modelsStore.selectModelById(option.id);
+ await modelsStore.selectModelById(option.id, { recordRecent: true });
}
if (shouldCloseMenu) {
@@ -140,7 +274,10 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
});
}
- if (!onModelChange && isRouter && !modelsStore.isModelLoaded(option.model)) {
+ // only the built-in server loads on request, and only in router mode
+ const canLoadHere = option.backendId === LOCAL_BACKEND_ID && isRouter;
+
+ if (!onModelChange && canLoadHere && !modelsStore.isModelLoaded(option.model)) {
isLoadingModel = true;
modelsStore.status
@@ -152,6 +289,18 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
function getDisplayOption(): ModelOption | undefined {
if (!isRouter) {
+ // External backend: the selection is backend-scoped, so it wins over
+ // the conversation's model, which may belong to another backend.
+ if (!serverStore.capabilities.props) {
+ const selected = activeId ? allOptions.find((option) => option.id === activeId) : undefined;
+
+ if (selected) return selected;
+
+ return currentModel
+ ? allOptions.find((option) => option.model === currentModel)
+ : undefined;
+ }
+
const displayModel = serverModel || currentModel;
if (displayModel) {
@@ -176,11 +325,11 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
};
}
- return options.find((option) => option.model === currentModel);
+ return allOptions.find((option) => option.model === currentModel);
}
if (activeId) {
- return options.find((option) => option.id === activeId);
+ return allOptions.find((option) => option.id === activeId);
}
return undefined;
@@ -191,6 +340,15 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
return activeId;
},
+ closeProvider,
+
+ get emptyMessage() {
+ return emptyMessage;
+ },
+
+ get favoriteItems() {
+ return favoriteItems;
+ },
get filteredOptions() {
return filteredOptions;
},
@@ -201,20 +359,18 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
return groupedFilteredOptions;
},
- handleInfoClick,
-
handleOpenChange,
handleSelect,
- get infoModelId() {
- return infoModelId;
- },
-
get isCurrentModelInCache() {
return isCurrentModelInCache;
},
+ get isEmpty() {
+ return isEmpty;
+ },
+
isFavorite(model: string) {
return modelsStore.favoriteModelIds.has(model);
},
@@ -227,14 +383,28 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
return isLoadingModel;
},
+ get isMultiModel() {
+ return isMultiModel;
+ },
+
+ get isProviderView() {
+ return isProviderView;
+ },
+
get isRouter() {
return isRouter;
},
+ get loadedItems() {
+ return loadedItems;
+ },
+
get loading() {
return loading;
},
+ openProvider,
+
get options() {
return options;
},
@@ -251,13 +421,7 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
searchTerm = value;
},
- setShowModelDialog(value: boolean) {
- showModelDialog = value;
- },
-
- get showModelDialog() {
- return showModelDialog;
- },
+ showBackendModels,
get updating() {
return updating;
diff --git a/tools/ui/src/lib/hooks/use-reasoning-menu.svelte.ts b/tools/ui/src/lib/hooks/use-reasoning-menu.svelte.ts
index 3c52d7ab27..ab3012d9c6 100644
--- a/tools/ui/src/lib/hooks/use-reasoning-menu.svelte.ts
+++ b/tools/ui/src/lib/hooks/use-reasoning-menu.svelte.ts
@@ -20,8 +20,8 @@ export interface UseReasoningMenuReturn {
/**
* Shared reactive state and helpers for the reasoning effort menu.
*
- * Used by both the desktop dropdown (`ChatFormActionAddReasoningSubmenu`)
- * and the mobile sheet (`ChatFormActionAddSheet`) to avoid duplicating the
+ * Used by the models dropdown footer (`ModelsSelectorReasoningPanel`) and the
+ * mobile sheet (`ChatFormActionAddSheet`) to avoid duplicating the
* thinking-support derivation and the effort selection logic.
*/
export function useReasoningMenu(): UseReasoningMenuReturn {
diff --git a/tools/ui/src/lib/stores/settings/index.svelte.ts b/tools/ui/src/lib/stores/settings/index.svelte.ts
index a583a1423f..fb98b03dcc 100644
--- a/tools/ui/src/lib/stores/settings/index.svelte.ts
+++ b/tools/ui/src/lib/stores/settings/index.svelte.ts
@@ -212,7 +212,7 @@ class SettingsStore {
/**
* Initialize the settings store by loading from localStorage.
- * Called by initStores() after migrations have run.
+ * Called by hydrateStores() after migrations have run.
*/
initialize() {
if (!browser) return;
@@ -220,6 +220,7 @@ class SettingsStore {
try {
this.loadConfig();
this.migrateLegacyTheme();
+ this.migrateModelOrgNameKey();
// Apply the persisted theme from config on initial load
setMode(this.config[SETTINGS_KEYS.THEME] as ColorMode);
this.isInitialized = true;
@@ -475,6 +476,30 @@ class SettingsStore {
}
}
+ /**
+ * Move the trigger-only org name key onto the selector wide one. The setting
+ * grew from the trigger to every model id, so an explicit choice has to
+ * survive the rename.
+ */
+ private migrateModelOrgNameKey() {
+ if (!browser) return;
+
+ const legacyKey = 'showModelOrgNameInTrigger';
+ const { config: saved } = SettingsService.loadConfig();
+
+ if (!(legacyKey in saved)) return;
+
+ this.config[SETTINGS_KEYS.SHOW_MODEL_ORG_NAME] = Boolean(saved[legacyKey]);
+
+ if (this.userOverrides.delete(legacyKey)) {
+ this.userOverrides.add(SETTINGS_KEYS.SHOW_MODEL_ORG_NAME);
+ }
+
+ delete (this.config as Record)[legacyKey];
+
+ this.saveConfig();
+ }
+
/**
* Save the current configuration to localStorage via the persistence service.
*/
diff --git a/tools/ui/tests/stories/a11y/ChatScreenForm.a11y.stories.svelte b/tools/ui/tests/stories/a11y/ChatScreenForm.a11y.stories.svelte
index 826e0d4aa1..c56bfd7678 100644
--- a/tools/ui/tests/stories/a11y/ChatScreenForm.a11y.stories.svelte
+++ b/tools/ui/tests/stories/a11y/ChatScreenForm.a11y.stories.svelte
@@ -2,25 +2,8 @@
import { defineMeta } from '@storybook/addon-svelte-csf';
import ChatScreenForm from '$lib/components/app/chat/ChatScreen/ChatScreenForm.svelte';
import { ATTACHMENT_TOOLTIP_TEXT } from '$lib/constants';
- import { ServerRole } from '$lib/enums';
- import { serverStore } from '$lib/stores';
- import type { ApiLlamaCppServerProps } from '$lib/types';
import { expect, screen, waitFor } from 'storybook/test';
- /**
- * The add menu mounts the reasoning submenu only outside router mode, and the
- * dev server proxies /props to whichever server happens to be running, so pin
- * the mode this story asserts instead of inheriting it from the environment.
- */
- function pinSingleModelMode(): void {
- serverStore.props = {
- ...(serverStore.props ?? {}),
- role: ServerRole.MODEL
- } as ApiLlamaCppServerProps;
-
- serverStore.role = ServerRole.MODEL;
- }
-
const { Story } = defineMeta({
component: ChatScreenForm,
parameters: {
@@ -55,8 +38,6 @@
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
name="AddDropdownFocusesFirstEnabled"
play={async ({ canvas, userEvent }) => {
- pinSingleModelMode();
-
const trigger = await canvas.findByRole('button', { name: ATTACHMENT_TOOLTIP_TEXT });
trigger.focus();
@@ -64,7 +45,7 @@
await screen.findByRole('menu');
await waitFor(() => {
- expect(document.activeElement).toHaveTextContent('Reasoning');
+ expect(document.activeElement).toHaveTextContent('Add files');
});
}}
/>