/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
import { useEffect, useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { ChevronLeft, ChevronRight, Loader2, Plus, Search } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { useIsMobile } from '@/hooks/use-mobile'
import { Button } from '@/components/ui/button'
import {
Empty,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from '@/components/ui/empty'
import { Input } from '@/components/ui/input'
import { Dialog } from '@/components/dialog'
import { StatusBadge } from '@/components/status-badge'
import { getMissingModels } from '../../api'
import { DEFAULT_PAGE_SIZE } from '../../constants'
import { modelsQueryKeys } from '../../lib'
import type { Model } from '../../types'
import { useModels } from '../models-provider'
type MissingModelsDialogProps = {
open: boolean
onOpenChange: (open: boolean) => void
}
export function MissingModelsDialog({
open,
onOpenChange,
}: MissingModelsDialogProps) {
const { t } = useTranslation()
const { setOpen, setCurrentRow } = useModels()
const isMobile = useIsMobile()
const [searchTerm, setSearchTerm] = useState('')
const [currentPage, setCurrentPage] = useState(1)
const { data, isLoading } = useQuery({
queryKey: modelsQueryKeys.missing(),
queryFn: getMissingModels,
enabled: open,
})
const missingModels = useMemo(() => data?.data || [], [data?.data])
const pageSize = DEFAULT_PAGE_SIZE
const handleConfigureModel = (modelName: string) => {
setCurrentRow({ model_name: modelName } as unknown as Model)
setOpen('create-model')
}
useEffect(() => {
if (open) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setSearchTerm('')
setCurrentPage(1)
}
}, [open])
const filteredModels = useMemo(() => {
if (!searchTerm.trim()) {
return missingModels
}
const keyword = searchTerm.toLowerCase().trim()
return missingModels.filter((modelName) =>
modelName.toLowerCase().includes(keyword)
)
}, [missingModels, searchTerm])
const totalItems = filteredModels.length
const totalPages =
totalItems === 0 ? 1 : Math.ceil(totalItems / Math.max(1, pageSize))
useEffect(() => {
if (currentPage > totalPages) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setCurrentPage(Math.max(1, totalPages))
}
}, [currentPage, totalPages])
const paginatedModels = useMemo(() => {
const startIndex = (currentPage - 1) * pageSize
const endIndex = startIndex + pageSize
return filteredModels.slice(startIndex, endIndex)
}, [filteredModels, currentPage, pageSize])
const displayStart = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1
const displayEnd =
totalItems === 0 ? 0 : Math.min(currentPage * pageSize, totalItems)
const showPagination = totalItems > pageSize
return (
)
}