oxios 1.5.2

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { Check, ChevronDown, Key } from 'lucide-react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils'
import type { ProviderCategory, ProviderInfo } from '@/types/engine'

// ─── Category definitions ────────────────────────────────────

const CATEGORY_ORDER: ProviderCategory[] = ['major', 'open', 'regional', 'local']

const CATEGORY_LABELS: Record<ProviderCategory, string> = {
  major: 'engine.majorProviders',
  open: 'engine.openSpecialty',
  regional: 'engine.regional',
  local: 'engine.localSelfHosted',
}

// ─── Component ───────────────────────────────────────────────

interface ProviderSelectProps {
  providers: ProviderInfo[]
  value: string | null
  onValueChange: (providerId: string) => void
  className?: string
}

export function ProviderSelect({
  providers,
  value,
  onValueChange,
  className,
}: ProviderSelectProps) {
  const { t } = useTranslation()
  const [open, setOpen] = useState(false)
  const ref = useRef<HTMLDivElement>(null)

  // Close on outside click
  useEffect(() => {
    function handleClickOutside(e: MouseEvent) {
      if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
    }
    if (open) document.addEventListener('mousedown', handleClickOutside)
    return () => document.removeEventListener('mousedown', handleClickOutside)
  }, [open])

  // Group providers by category
  const grouped = useMemo(() => {
    const map = new Map<ProviderCategory, ProviderInfo[]>()
    for (const p of providers) {
      const list = Array.isArray(map.get(p.category)) ? map.get(p.category)! : []
      list.push(p)
      map.set(p.category, list)
    }
    return map
  }, [providers])

  const selected = providers.find((p) => p.id === value)

  return (
    <div className={cn('relative', className)} ref={ref}>
      <button
        type="button"
        onClick={() => setOpen(!open)}
        className="flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background hover:bg-accent/50 focus:outline-none focus:ring-1 focus:ring-ring"
      >
        <span className={selected ? '' : 'text-muted-foreground'}>
          {selected ? selected.name : t('engine.selectProvider')}
        </span>
        <ChevronDown className="h-4 w-4 opacity-50" />
      </button>

      {open && (
        <div className="absolute z-50 mt-1 w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md max-h-80 overflow-y-auto">
          {CATEGORY_ORDER.map((cat) => {
            const items = grouped.get(cat)
            if (!items || items.length === 0) return null
            return (
              <div key={cat}>
                <div className="px-3 py-1.5 text-xs font-semibold text-muted-foreground bg-muted/50">
                  {t(CATEGORY_LABELS[cat])}
                </div>
                {items.map((p) => (
                  <button
                    key={p.id}
                    type="button"
                    className={cn(
                      'relative flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground',
                      value === p.id && 'bg-accent text-accent-foreground',
                    )}
                    onClick={() => {
                      onValueChange(p.id)
                      setOpen(false)
                    }}
                  >
                    {/* Selection indicator */}
                    <span className="w-4 shrink-0">
                      {value === p.id && <Check className="h-4 w-4" />}
                    </span>

                    {/* Provider name */}
                    <span className="flex-1 text-left">{p.name}</span>

                    {/* Model count badge */}
                    {p.modelCount > 0 && (
                      <span className="text-xs text-muted-foreground">{p.modelCount}</span>
                    )}

                    {/* API key indicator */}
                    {p.hasKey ? (
                      <Key className="h-3.5 w-3.5 text-success shrink-0" />
                    ) : (
                      <Key className="h-3.5 w-3.5 text-muted-foreground/40 shrink-0" />
                    )}
                  </button>
                ))}
              </div>
            )
          })}
        </div>
      )}
    </div>
  )
}