diff --git a/packages/ui/src/components/chat/ModelControls.tsx b/packages/ui/src/components/chat/ModelControls.tsx index aaaca165..3e254195 100644 --- a/packages/ui/src/components/chat/ModelControls.tsx +++ b/packages/ui/src/components/chat/ModelControls.tsx @@ -366,6 +366,8 @@ export const ModelControls: React.FC = ({ const toggleFavoriteModel = useUIStore((state) => state.toggleFavoriteModel); const reorderFavoriteModel = useUIStore((state) => state.reorderFavoriteModel); + const providerOrder = useUIStore((state) => state.providerOrder); + const setProviderOrder = useUIStore((state) => state.setProviderOrder); const isFavoriteModel = useUIStore((state) => state.isFavoriteModel); const addRecentModel = useUIStore((state) => state.addRecentModel); const addRecentAgent = useUIStore((state) => state.addRecentAgent); @@ -2368,6 +2370,9 @@ export const ModelControls: React.FC = ({ )} reorderFavoriteAriaLabel={t('chat.modelControls.reorderFavoriteAria')} reorderFavoriteTitle={t('chat.modelControls.reorderFavoriteTitle')} + providerOrder={providerOrder} + onReorderProvider={setProviderOrder} + reorderProviderTitle={t('chat.modelControls.reorderProviderTitle')} footerContent={(activeEntry) => { const activeHasThinkingVariants = activeEntry ? getModelVariantOptions(activeEntry.providerID, activeEntry.modelID).length > 0 diff --git a/packages/ui/src/components/model-picker/ModelPickerList.tsx b/packages/ui/src/components/model-picker/ModelPickerList.tsx index 88fb1c2a..7d9314b2 100644 --- a/packages/ui/src/components/model-picker/ModelPickerList.tsx +++ b/packages/ui/src/components/model-picker/ModelPickerList.tsx @@ -1,13 +1,14 @@ import React from 'react'; import { DndContext, + MouseSensor, PointerSensor, closestCenter, useSensor, useSensors, type DragEndEvent, } from '@dnd-kit/core'; -import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; +import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS as DndCSS } from '@dnd-kit/utilities'; import { Icon } from '@/components/icon/Icon'; import { Input } from '@/components/ui/input'; @@ -18,6 +19,7 @@ import { getCurrentIntlLocale } from '@/lib/i18n'; import { mergeModelMetadataWithLiveModel } from '@/lib/modelMetadata'; import { getModelDisplayName as getSharedModelDisplayName } from '@/lib/modelDisplay'; import { cn } from '@/lib/utils'; +import { useModelPickerSectionsStore } from '@/stores/useModelPickerSectionsStore'; import type { ModelMetadata } from '@/types'; type ProviderModel = Record & { id?: string; name?: string }; @@ -256,6 +258,35 @@ const SortableFavoriteModelRow: React.FC<{ ); }; +const SortableProviderSection: React.FC<{ + id: string; + disabled?: boolean; + children: (dragHandleProps: SortableFavoriteHandleProps) => React.ReactNode; +}> = ({ id, disabled = false, children }) => { + const { + attributes, + listeners, + setNodeRef, + setActivatorNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id, disabled }); + + return ( +
+ {children({ attributes, listeners, setActivatorNodeRef, isDragging })} +
+ ); +}; + const STICKY_HEADER_OFFSET = 32; const scrollIntoView = (container: HTMLElement | null, node: HTMLElement | null) => { @@ -328,6 +359,9 @@ interface ModelPickerListProps { onReorderFavorite?: (active: ModelPickerEntry, over: ModelPickerEntry) => void; reorderFavoriteAriaLabel?: string; reorderFavoriteTitle?: string; + providerOrder?: string[]; + onReorderProvider?: (orderedProviderIDs: string[]) => void; + reorderProviderTitle?: string; footerContent?: React.ReactNode | ((activeEntry: ModelPickerEntry | undefined) => React.ReactNode); renderVersion?: number; tooltipsEnabled?: boolean; @@ -365,6 +399,9 @@ export const ModelPickerList: React.FC = ({ onReorderFavorite, reorderFavoriteAriaLabel, reorderFavoriteTitle, + providerOrder, + onReorderProvider, + reorderProviderTitle, footerContent, renderVersion, tooltipsEnabled = true, @@ -376,10 +413,21 @@ export const ModelPickerList: React.FC = ({ const scrollRef = React.useRef(null); const keyboardOwnsSelectionRef = React.useRef(false); const lastMousePositionRef = React.useRef<{ x: number; y: number } | null>(null); - const [collapsedSections, setCollapsedSections] = React.useState>(() => new Set()); + const collapsedRecord = useModelPickerSectionsStore((state) => state.collapsedSections); + const toggleSection = useModelPickerSectionsStore((state) => state.toggleSection); + const collapsedSections = React.useMemo( + () => new Set(Object.keys(collapsedRecord).filter((key) => collapsedRecord[key])), + [collapsedRecord], + ); const favoriteRowSensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), ); + // Desktop-only provider reordering: a MouseSensor (no TouchSensor) keeps the + // section headers tappable/scrollable on touch devices while enabling + // click-and-drag with a mouse. + const providerSectionSensors = useSensors( + useSensor(MouseSensor, { activationConstraint: { distance: 8 } }), + ); const allowedProviderSet = React.useMemo(() => { if (!allowedProviderIds || allowedProviderIds.length === 0) return null; @@ -412,7 +460,17 @@ export const ModelPickerList: React.FC = ({ return matchesQuery(getModelDisplayName(model), providerName); }), [allowedProviderSet, isHidden, matchesQuery, providerById, recentModels]); - const filteredProviders = React.useMemo(() => providers + const orderedProviders = React.useMemo(() => { + if (!providerOrder || providerOrder.length === 0) return providers; + const rank = new Map(providerOrder.map((id, index) => [id, index] as const)); + const ranked = providers + .filter((provider) => rank.has(provider.id)) + .sort((a, b) => (rank.get(a.id) ?? 0) - (rank.get(b.id) ?? 0)); + const unranked = providers.filter((provider) => !rank.has(provider.id)); + return [...ranked, ...unranked]; + }, [providerOrder, providers]); + + const filteredProviders = React.useMemo(() => orderedProviders .filter((provider) => !allowedProviderSet || allowedProviderSet.has(provider.id)) .map((provider) => { const models = Array.isArray(provider.models) ? provider.models : []; @@ -423,7 +481,7 @@ export const ModelPickerList: React.FC = ({ }); return { ...provider, models: filteredModels }; }) - .filter((provider) => provider.models.length > 0), [allowedProviderSet, isHidden, matchesQuery, providers]); + .filter((provider) => provider.models.length > 0), [allowedProviderSet, isHidden, matchesQuery, orderedProviders]); const flatModelList = React.useMemo(() => { const items: ModelPickerEntry[] = []; @@ -438,6 +496,7 @@ export const ModelPickerList: React.FC = ({ const hasResults = flatModelList.length > 0; const favoriteSortingEnabled = Boolean(onReorderFavorite) && searchQuery.trim().length === 0 && filteredFavorites.length > 1; + const providerSortingEnabled = Boolean(onReorderProvider) && searchQuery.trim().length === 0 && !allowedProviderSet && filteredProviders.length > 1; const favoriteLookup: Map = React.useMemo(() => new Map( filteredFavorites.map((entry) => [`${entry.providerID}:${entry.modelID}`, entry] as const), ), [filteredFavorites]); @@ -591,18 +650,61 @@ export const ModelPickerList: React.FC = ({ onReorderFavorite(activeFavorite, overFavorite); }; - const isSectionCollapsed = (key: string) => collapsedSections.has(key); - const toggleSectionCollapsed = (key: string) => { - setCollapsedSections((prev) => { - const next = new Set(prev); - if (next.has(key)) next.delete(key); - else next.add(key); - return next; - }); + const handleProviderDragEnd = (event: DragEndEvent) => { + if (!onReorderProvider) return; + const { active, over } = event; + if (!over || active.id === over.id) return; + + const ids = orderedProviders.map((provider) => provider.id); + const from = ids.indexOf(String(active.id)); + const to = ids.indexOf(String(over.id)); + if (from === -1 || to === -1) return; + + onReorderProvider(arrayMove(ids, from, to)); }; - const renderSectionHeader = (key: string, icon: React.ReactNode, label: React.ReactNode) => { + const isSectionCollapsed = (key: string) => collapsedSections.has(key); + const toggleSectionCollapsed = (key: string) => toggleSection(key); + + const renderSectionHeader = (key: string, icon: React.ReactNode, label: React.ReactNode, headerDragProps?: SortableFavoriteHandleProps) => { const collapsed = isSectionCollapsed(key); + const toggleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + toggleSectionCollapsed(key); + } + }; + + // When the section is reorderable, the whole header acts as the drag + // activator (desktop mouse, with an 8px threshold so a plain click still + // toggles collapse). A + + + {label} + + + ); +}); + function SessionNodeItemComponent(props: Props): React.ReactNode { const { t } = useI18n(); const { @@ -634,6 +697,13 @@ function SessionNodeItemComponent(props: Props): React.ReactNode { handleDeleteSession(session, { archivedBucket }); }; + const handleQuickDeleteClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + setOpenSidebarMenuKey(null); + handleDeleteSession(session, { archivedBucket, hardDelete: true, skipConfirm: true }); + }; + const handleOpenInEditorPointerDown = (event: React.PointerEvent) => { event.preventDefault(); event.stopPropagation(); @@ -1046,27 +1116,16 @@ function SessionNodeItemComponent(props: Props): React.ReactNode { : cn('opacity-0', revealOnHoverClass), )}> {showQuickArchiveAction ? ( - - - - - - {t('sessions.sidebar.bulkActions.archive')} - - + ) : null} {showOpenInEditorAction ? ( diff --git a/packages/ui/src/components/session/sidebar/hooks/useSessionActions.ts b/packages/ui/src/components/session/sidebar/hooks/useSessionActions.ts index 73ac5755..e7447fae 100644 --- a/packages/ui/src/components/session/sidebar/hooks/useSessionActions.ts +++ b/packages/ui/src/components/session/sidebar/hooks/useSessionActions.ts @@ -15,6 +15,8 @@ type DeleteSessionConfirmSetter = React.Dispatch { collectDescendants(session.id), shouldHardDelete, ).map((s) => s.id); - if (!args.showDeletionDialog) { + if (!args.showDeletionDialog || source?.skipConfirm === true) { void executeDeleteSession(session, source, { descendantIds: effectiveDescendantIds }); return; } diff --git a/packages/ui/src/components/ui/ProviderLogo.tsx b/packages/ui/src/components/ui/ProviderLogo.tsx index 76c358c9..ef2e3bac 100644 --- a/packages/ui/src/components/ui/ProviderLogo.tsx +++ b/packages/ui/src/components/ui/ProviderLogo.tsx @@ -34,6 +34,7 @@ export const ProviderLogo: React.FC = ({ loading="eager" decoding="async" fetchPriority="high" + draggable={false} onError={handleError} /> ); diff --git a/packages/ui/src/hooks/useShiftKeyHeld.ts b/packages/ui/src/hooks/useShiftKeyHeld.ts new file mode 100644 index 00000000..01332f42 --- /dev/null +++ b/packages/ui/src/hooks/useShiftKeyHeld.ts @@ -0,0 +1,77 @@ +import { useSyncExternalStore } from 'react'; + +/** + * Tracks whether the Shift key is currently held, shared across all consumers + * via a single set of window listeners. + * + * Using one module-level listener set (instead of per-component listeners) + * keeps this cheap even when many rows subscribe, and `useSyncExternalStore` + * lets unrelated subtrees stay isolated — only components that actually call + * this hook re-render when the Shift state flips. + */ +let shiftHeld = false; +const listeners = new Set<() => void>(); +let initialized = false; + +function emit(): void { + for (const listener of listeners) { + listener(); + } +} + +function setShiftHeld(next: boolean): void { + if (shiftHeld === next) { + return; + } + shiftHeld = next; + emit(); +} + +function handleKeyDown(event: KeyboardEvent): void { + if (event.key === 'Shift') { + setShiftHeld(true); + } +} + +function handleKeyUp(event: KeyboardEvent): void { + if (event.key === 'Shift') { + setShiftHeld(false); + } +} + +// The window can lose focus while Shift is held (e.g. alt-tab), and the +// matching keyup never arrives — reset so the UI doesn't get stuck in the +// "delete" affordance. +function handleReset(): void { + setShiftHeld(false); +} + +function ensureListeners(): void { + if (initialized || typeof window === 'undefined') { + return; + } + initialized = true; + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + window.addEventListener('blur', handleReset); +} + +function subscribe(onStoreChange: () => void): () => void { + ensureListeners(); + listeners.add(onStoreChange); + return () => { + listeners.delete(onStoreChange); + }; +} + +function getSnapshot(): boolean { + return shiftHeld; +} + +function getServerSnapshot(): boolean { + return false; +} + +export function useShiftKeyHeld(): boolean { + return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); +} diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index f69c7480..54f09542 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -1999,6 +1999,7 @@ export const dict = { 'chat.modelControls.noProvidersOrModelsFound': 'No providers or models match your search.', 'chat.modelControls.reorderFavoriteAria': 'Reorder favorite', 'chat.modelControls.reorderFavoriteTitle': 'Drag to reorder favorite', + 'chat.modelControls.reorderProviderTitle': 'Drag to reorder provider', 'chat.modelControls.permissionLabel.custom': 'Custom', 'chat.modelControls.permissionLabel.allow': 'Allow', 'chat.modelControls.permissionLabel.deny': 'Deny', diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index a35fc63d..d1c0a41d 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -1965,6 +1965,7 @@ export const dict: Record = { "chat.modelControls.noProvidersOrModelsFound": "Ningún proveedor o modelo coincide con tu búsqueda.", "chat.modelControls.reorderFavoriteAria": "Reordenar favorito", "chat.modelControls.reorderFavoriteTitle": "Arrastrar para reordenar favorito", + "chat.modelControls.reorderProviderTitle": "Arrastrar para reordenar proveedor", "chat.modelControls.permissionLabel.custom": "Personalizado", "chat.modelControls.permissionLabel.allow": "Permitir", "chat.modelControls.permissionLabel.deny": "Denegar", diff --git a/packages/ui/src/lib/i18n/messages/fr.ts b/packages/ui/src/lib/i18n/messages/fr.ts index 8b40f88f..81f823ac 100644 --- a/packages/ui/src/lib/i18n/messages/fr.ts +++ b/packages/ui/src/lib/i18n/messages/fr.ts @@ -1820,6 +1820,7 @@ export const dict = { 'chat.modelControls.noProvidersOrModelsFound': 'Aucun fournisseur ou modèle ne correspond à votre recherche.', 'chat.modelControls.reorderFavoriteAria': 'Réorganiser les favoris', 'chat.modelControls.reorderFavoriteTitle': 'Faites glisser pour réorganiser les favoris', + 'chat.modelControls.reorderProviderTitle': 'Faites glisser pour réorganiser le fournisseur', 'chat.modelControls.permissionLabel.custom': 'Personnalisé', 'chat.modelControls.permissionLabel.allow': 'Permettre', 'chat.modelControls.permissionLabel.deny': 'Refuser', diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index e1a60c79..536dae55 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -1999,6 +1999,7 @@ export const dict: Record = { 'chat.modelControls.noProvidersOrModelsFound': '검색과 일치하는 프로바이더 또는 모델이 없습니다.', 'chat.modelControls.reorderFavoriteAria': '즐겨찾기 순서 변경', 'chat.modelControls.reorderFavoriteTitle': '드래그하여 즐겨찾기 순서 변경', + 'chat.modelControls.reorderProviderTitle': '드래그하여 공급자 순서 변경', 'chat.modelControls.permissionLabel.custom': '사용자 지정', 'chat.modelControls.permissionLabel.allow': '허용', 'chat.modelControls.permissionLabel.deny': '거부', diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts index c3eef999..8429129a 100644 --- a/packages/ui/src/lib/i18n/messages/pl.ts +++ b/packages/ui/src/lib/i18n/messages/pl.ts @@ -1169,6 +1169,7 @@ export const dict: Record = { 'chat.modelControls.removeFromFavorites': 'Usuń z ulubionych', 'chat.modelControls.reorderFavoriteAria': 'Zmień kolejność ulubionych', 'chat.modelControls.reorderFavoriteTitle': 'Przeciągnij, aby zmienić kolejność ulubionych', + 'chat.modelControls.reorderProviderTitle': 'Przeciągnij, aby zmienić kolejność dostawcy', 'chat.modelControls.resetToDefault': 'Przywróć domyślne', 'chat.modelControls.searchAgents': 'Szukaj agentów', 'chat.modelControls.searchModels': 'Szukaj modeli...', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index 21f43066..c60672b8 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -1965,6 +1965,7 @@ export const dict: Record = { "chat.modelControls.noProvidersOrModelsFound": "Nenhum provedor ou modelo corresponde à sua pesquisa.", "chat.modelControls.reorderFavoriteAria": "Reordenar favorito", "chat.modelControls.reorderFavoriteTitle": "Arraste para reordenar favorito", + "chat.modelControls.reorderProviderTitle": "Arraste para reordenar provedor", "chat.modelControls.permissionLabel.custom": "Personalizado", "chat.modelControls.permissionLabel.allow": "Permitir", "chat.modelControls.permissionLabel.deny": "Negar", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index a1801209..add85902 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -1965,6 +1965,7 @@ export const dict: Record = { "chat.modelControls.noProvidersOrModelsFound": "Жоден провайдер або модель не відповідає пошуку.", "chat.modelControls.reorderFavoriteAria": "Змінити порядок вибраного", "chat.modelControls.reorderFavoriteTitle": "Перетягніть, щоб змінити порядок вибраного", + "chat.modelControls.reorderProviderTitle": "Перетягніть, щоб змінити порядок провайдера", "chat.modelControls.permissionLabel.custom": "Custom", "chat.modelControls.permissionLabel.allow": "Дозволити", "chat.modelControls.permissionLabel.deny": "Заборонити", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index a86c388f..73899a31 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -1965,6 +1965,7 @@ export const dict: Record = { 'chat.modelControls.noProvidersOrModelsFound': '没有提供商或模型匹配你的搜索。', 'chat.modelControls.reorderFavoriteAria': '重新排序收藏', 'chat.modelControls.reorderFavoriteTitle': '拖动以重新排序收藏', + 'chat.modelControls.reorderProviderTitle': '拖动以重新排序提供商', 'chat.modelControls.permissionLabel.custom': '自定义', 'chat.modelControls.permissionLabel.allow': '允许', 'chat.modelControls.permissionLabel.deny': '拒绝', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.ts b/packages/ui/src/lib/i18n/messages/zh-TW.ts index bec6f32e..ac74fd07 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.ts @@ -1969,6 +1969,7 @@ export const dict: Record = { 'chat.modelControls.noProvidersOrModelsFound': '沒有供應商或模型符合你的搜尋。', 'chat.modelControls.reorderFavoriteAria': '重新排序最愛', 'chat.modelControls.reorderFavoriteTitle': '拖曳以重新排序最愛', + 'chat.modelControls.reorderProviderTitle': '拖曳以重新排序供應商', 'chat.modelControls.permissionLabel.custom': '自訂', 'chat.modelControls.permissionLabel.allow': '允許', 'chat.modelControls.permissionLabel.deny': '拒絕', diff --git a/packages/ui/src/stores/useModelPickerSectionsStore.ts b/packages/ui/src/stores/useModelPickerSectionsStore.ts new file mode 100644 index 00000000..b2b3278b --- /dev/null +++ b/packages/ui/src/stores/useModelPickerSectionsStore.ts @@ -0,0 +1,36 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +/** + * Persisted collapsed state for the collapsible sections (accordions) in the + * model/provider picker (`ModelPickerList`): the `favorites` and `recent` + * sections plus each `provider:` group. Section keys are stable and shared + * across every picker surface, so collapsing a provider in one picker is + * remembered everywhere and survives remounts and full page reloads. + * + * Only collapsed keys are stored (presence === collapsed); the default for any + * unknown key is expanded. + */ +type ModelPickerSectionsStore = { + collapsedSections: Record; + toggleSection: (key: string) => void; +}; + +export const useModelPickerSectionsStore = create()( + persist( + (set) => ({ + collapsedSections: {}, + toggleSection: (key) => + set((state) => { + const next = { ...state.collapsedSections }; + if (next[key]) delete next[key]; + else next[key] = true; + return { collapsedSections: next }; + }), + }), + { + name: 'model-picker-collapsed-sections', + version: 1, + }, + ), +); diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index dc8addea..450ee6e4 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -577,6 +577,7 @@ interface UIStore { favoriteModels: Array<{ providerID: string; modelID: string }>; hiddenModels: Array<{ providerID: string; modelID: string }>; + providerOrder: string[]; collapsedModelProviders: string[]; recentModels: Array<{ providerID: string; modelID: string }>; recentAgents: string[]; @@ -727,6 +728,7 @@ interface UIStore { overProviderID: string, overModelID: string, ) => void; + setProviderOrder: (orderedProviderIDs: string[]) => void; toggleHiddenModel: (providerID: string, modelID: string) => void; isHiddenModel: (providerID: string, modelID: string) => boolean; hideAllModels: (providerID: string, modelIDs: string[]) => void; @@ -861,6 +863,7 @@ export const useUIStore = create()( mobileKeyboardMode: getStoredMobileKeyboardMode(), favoriteModels: [], hiddenModels: [], + providerOrder: [], collapsedModelProviders: [], recentModels: [], recentAgents: [], @@ -1735,6 +1738,17 @@ export const useUIStore = create()( }); }, + setProviderOrder: (orderedProviderIDs) => { + set((state) => { + const next = orderedProviderIDs.filter((id) => typeof id === 'string' && id.length > 0); + const current = state.providerOrder; + if (current.length === next.length && current.every((id, index) => id === next[index])) { + return state; + } + return { providerOrder: next }; + }); + }, + toggleHiddenModel: (providerID, modelID) => { set((state) => { const exists = state.hiddenModels.some( @@ -2217,6 +2231,7 @@ export const useUIStore = create()( cornerRadius: state.cornerRadius, favoriteModels: state.favoriteModels, hiddenModels: state.hiddenModels, + providerOrder: state.providerOrder, collapsedModelProviders: state.collapsedModelProviders, recentModels: state.recentModels, recentAgents: state.recentAgents,