Merge main

This commit is contained in:
Bohdan Triapitsyn
2026-08-27 23:17:06 +03:00
601 changed files with 35826 additions and 13927 deletions
@@ -12,6 +12,7 @@ import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy }
import { CSS as DndCSS } from '@dnd-kit/utilities';
import { Icon } from '@/components/icon/Icon';
import { Input } from '@/components/ui/input';
import { matchesRankQuery } from '@/lib/search/fuzzySearch';
import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
@@ -455,18 +456,18 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
return hiddenModels.some((hidden) => hidden.providerID === providerID && hidden.modelID === modelID);
}, [hiddenModels]);
const matchesQuery = React.useCallback((modelName: string, providerName: string) => {
const query = searchQuery.trim().toLowerCase();
if (!query) return true;
return modelName.toLowerCase().includes(query) || providerName.toLowerCase().includes(query);
}, [searchQuery]);
const matchesQuery = React.useCallback(
(modelName: string, providerName: string, modelID?: string) =>
matchesRankQuery([modelName, modelID, providerName], searchQuery),
[searchQuery],
);
const filteredFavorites = React.useMemo(() => favoriteModels.filter(({ model, providerID, modelID }) => {
if (allowedProviderSet && !allowedProviderSet.has(providerID)) return false;
if (isModelAllowed && !isModelAllowed(providerID, modelID)) return false;
if (isHidden(providerID, modelID)) return false;
const providerName = providerById.get(providerID)?.name || providerID;
return matchesQuery(getModelDisplayName(model), providerName);
return matchesQuery(getModelDisplayName(model), providerName, modelID);
}), [allowedProviderSet, favoriteModels, isHidden, isModelAllowed, matchesQuery, providerById]);
const filteredRecents = React.useMemo(() => recentModels.filter(({ model, providerID, modelID }) => {
@@ -474,7 +475,7 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
if (isModelAllowed && !isModelAllowed(providerID, modelID)) return false;
if (isHidden(providerID, modelID)) return false;
const providerName = providerById.get(providerID)?.name || providerID;
return matchesQuery(getModelDisplayName(model), providerName);
return matchesQuery(getModelDisplayName(model), providerName, modelID);
}), [allowedProviderSet, isHidden, isModelAllowed, matchesQuery, providerById, recentModels]);
const orderedProviders = React.useMemo(() => {
@@ -495,7 +496,7 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
const modelID = typeof model.id === 'string' ? model.id : '';
if (!modelID || isHidden(provider.id, modelID)) return false;
if (isModelAllowed && !isModelAllowed(provider.id, modelID)) return false;
return matchesQuery(getModelDisplayName(model), provider.name || provider.id);
return matchesQuery(getModelDisplayName(model), provider.name || provider.id, modelID);
});
return { ...provider, models: filteredModels };
})
@@ -544,8 +545,9 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
? Math.min(STICKY_FADE_MIN_SIZE + scroller.scrollTop, STICKY_FADE_MAX_SIZE)
: 0;
stickyFadeSizeRef.current = fadeSize;
scroller.style.setProperty('--scroll-shadow-top-size', `${fadeSize}px`);
scroller.style.setProperty(
const fadeRoot = scroller.closest<HTMLElement>('.oc-sticky-fade-root');
fadeRoot?.style.setProperty('--scroll-shadow-top-size', `${fadeSize}px`);
fadeRoot?.style.setProperty(
'--scroll-shadow-top-clear-size',
`${Math.min(Math.max(fadeSize - 8, 0), STICKY_FADE_CLEAR_MAX_SIZE)}px`,
);
@@ -877,24 +879,23 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
<div
className="oc-sticky-fade-root relative flex min-h-0 flex-1"
// SAFETY: these custom properties configure the viewport-owned edge fade.
style={stickyHeaders ? { '--scroll-shadow-top-size': '0px' } as React.CSSProperties : undefined}
onPointerDownCapture={stickyHeaders ? blockStickyFadeInteraction : undefined}
onClickCapture={stickyHeaders ? blockStickyFadeInteraction : undefined}
onContextMenuCapture={stickyHeaders ? blockStickyFadeInteraction : undefined}
>
<ScrollableOverlay
ref={scrollRef}
useScrollShadow={stickyHeaders}
hideBottomScrollShadow
scrollShadowSize={12}
outerClassName={maxHeightClassName}
className="oc-sticky-fade-scroller overlay-scrollbar-target--no-gutter"
style={{
...(stickyHeaders ? { '--scroll-shadow-top-size': '0px' } as React.CSSProperties : {}),
...maxHeightStyle,
}}
onScroll={stickyHeaders ? (event) => syncStickyFade(event.currentTarget) : undefined}
>
<div className="px-1">
<ScrollableOverlay
ref={scrollRef}
useScrollShadow={stickyHeaders}
hideBottomScrollShadow
scrollShadowSize={12}
outerClassName={maxHeightClassName}
className="overlay-scrollbar-target--no-gutter"
style={maxHeightStyle}
onScroll={stickyHeaders ? (event) => syncStickyFade(event.currentTarget) : undefined}
>
<div className="px-1">
{includeNotSelected ? (
<>
<button
@@ -965,16 +966,16 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
</div>
))
)}
</div>
</ScrollableOverlay>
{stickyHeaders && leadingSectionKey ? (
<div
className="oc-sticky-fade-overlay pointer-events-none absolute inset-x-0 top-0 z-30 flex items-center gap-2 px-3 py-1.5 typography-micro font-semibold uppercase tracking-wider text-muted-foreground"
aria-hidden="true"
>
{renderSectionIdentity(leadingSectionKey)}
</div>
) : null}
</div>
</ScrollableOverlay>
{stickyHeaders && leadingSectionKey ? (
<div
className="oc-sticky-fade-overlay pointer-events-none absolute inset-x-0 top-0 z-30 flex items-center gap-2 px-3 py-1.5 typography-micro font-semibold uppercase tracking-wider text-muted-foreground"
aria-hidden="true"
>
{renderSectionIdentity(leadingSectionKey)}
</div>
) : null}
</div>
<div className="px-3 pt-1 pb-1.5 border-t border-border/40 typography-micro text-muted-foreground">