11import React from "react" ;
2- import { List , Typography } from "@mui/material" ;
2+ import { Box , CircularProgress , List , Typography } from "@mui/material" ;
33import ModelListItem from "./ModelListItem" ;
44import { UnifiedModel } from "../../../stores/ApiTypes" ;
55import { useModelManagerStore } from "../../../stores/ModelManagerStore" ;
@@ -19,6 +19,44 @@ const ModelDisplay: React.FC<ModelDisplayProps> = ({
1919 const { handleShowInExplorer, ollamaBasePath } = useModels ( ) ;
2020 const downloadStore = useModelDownloadStore ( ) ;
2121
22+ const PAGE_SIZE = 50 ;
23+ const [ visibleCount , setVisibleCount ] = React . useState < number > ( PAGE_SIZE ) ;
24+ const sentinelRef = React . useRef < HTMLDivElement | null > ( null ) ;
25+
26+ const visibleModels = React . useMemo (
27+ ( ) => models . slice ( 0 , Math . min ( visibleCount , models . length ) ) ,
28+ [ models , visibleCount ]
29+ ) ;
30+
31+ const hasMore = visibleModels . length < models . length ;
32+
33+ const loadMore = React . useCallback ( ( ) => {
34+ if ( ! hasMore ) return ;
35+ setVisibleCount ( ( prev ) => Math . min ( prev + PAGE_SIZE , models . length ) ) ;
36+ } , [ hasMore , models . length ] ) ;
37+
38+ // Reset pagination when the models set changes
39+ React . useEffect ( ( ) => {
40+ setVisibleCount ( PAGE_SIZE ) ;
41+ } , [ models ] ) ;
42+
43+ // IntersectionObserver to trigger loading more when sentinel enters view
44+ React . useEffect ( ( ) => {
45+ const node = sentinelRef . current ;
46+ if ( ! node ) return ;
47+ const observer = new IntersectionObserver (
48+ ( entries ) => {
49+ const entry = entries [ 0 ] ;
50+ if ( entry . isIntersecting ) {
51+ loadMore ( ) ;
52+ }
53+ } ,
54+ { root : null , rootMargin : "200px 0px" , threshold : 0 }
55+ ) ;
56+ observer . observe ( node ) ;
57+ return ( ) => observer . unobserve ( node ) ;
58+ } , [ loadMore , visibleModels . length ] ) ;
59+
2260 const startDownload = React . useCallback (
2361 ( model : UnifiedModel ) => {
2462 const repoId = model . repo_id || model . id ;
@@ -45,22 +83,34 @@ const ModelDisplay: React.FC<ModelDisplayProps> = ({
4583 const allowShowInExplorer = modelSource === "downloaded" ;
4684
4785 return (
48- < List >
49- { models . map ( ( model : UnifiedModel , idx : number ) => (
50- < ModelListItem
51- key = { `${ model . id } -${ idx } ` }
52- model = { model }
53- handleModelDelete = { allowDelete ? handleDeleteClick : undefined }
54- onDownload = { allowDownload ? ( ) => startDownload ( model ) : undefined }
55- handleShowInExplorer = {
56- allowShowInExplorer ? handleShowInExplorer : undefined
57- }
58- // hideMissingInfo={modelSource === "recommended"}
59- showModelStats = { modelSource === "recommended" }
60- ollamaBasePath = { ollamaBasePath }
61- />
62- ) ) }
63- </ List >
86+ < >
87+ < List >
88+ { visibleModels . map ( ( model : UnifiedModel , idx : number ) => (
89+ < ModelListItem
90+ key = { `${ model . id } -${ idx } ` }
91+ model = { model }
92+ handleModelDelete = { allowDelete ? handleDeleteClick : undefined }
93+ onDownload = { allowDownload ? ( ) => startDownload ( model ) : undefined }
94+ handleShowInExplorer = {
95+ allowShowInExplorer ? handleShowInExplorer : undefined
96+ }
97+ // hideMissingInfo={modelSource === "recommended"}
98+ showModelStats = { modelSource === "recommended" }
99+ ollamaBasePath = { ollamaBasePath }
100+ />
101+ ) ) }
102+ </ List >
103+ < Box
104+ ref = { sentinelRef }
105+ sx = { {
106+ display : hasMore ? "flex" : "none" ,
107+ justifyContent : "center" ,
108+ py : 2
109+ } }
110+ >
111+ < CircularProgress size = { 20 } />
112+ </ Box >
113+ </ >
64114 ) ;
65115} ;
66116
0 commit comments