/*
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 type { ReactNode } from 'react'
import { ChevronDown, RotateCcw } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { getLobeIcon } from '@/lib/lobe-icon'
import { cn } from '@/lib/utils'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible'
import {
ENDPOINT_TYPES,
FILTER_ALL,
QUOTA_TYPES,
getEndpointTypeLabels,
getQuotaTypeLabels,
} from '../constants'
import { parseTags } from '../lib/filters'
import type { PricingModel, PricingVendor } from '../types'
type FilterOption = {
value: string
label: string
count?: number
suffix?: string
icon?: ReactNode
}
type FilterSectionProps = {
title: string
value: string
options: FilterOption[]
onChange: (value: string) => void
}
export interface PricingSidebarProps {
quotaTypeFilter: string
endpointTypeFilter: string
vendorFilter: string
groupFilter: string
tagFilter: string
onQuotaTypeChange: (value: string) => void
onEndpointTypeChange: (value: string) => void
onVendorChange: (value: string) => void
onGroupChange: (value: string) => void
onTagChange: (value: string) => void
vendors: PricingVendor[]
groups: string[]
groupRatios?: Record
tags: string[]
models: PricingModel[]
hasActiveFilters: boolean
onClearFilters: () => void
className?: string
}
function countBy(
models: PricingModel[],
predicate: (model: PricingModel) => boolean
): number {
return models.reduce((count, model) => count + (predicate(model) ? 1 : 0), 0)
}
function formatGroupRatio(ratio: number | undefined): string | undefined {
if (ratio == null) return undefined
const formatted = Number.isInteger(ratio)
? ratio.toString()
: ratio.toFixed(3).replace(/0+$/, '').replace(/\.$/, '')
return `x${formatted}`
}
function FilterChip(props: {
option: FilterOption
active: boolean
onClick: () => void
}) {
return (
)
}
function FilterSection(props: FilterSectionProps) {
return (
{props.title}