oxios 1.10.1

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { ChevronDown, ChevronRight, Wrench } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ToolDetail } from '@/components/mcp/tool-detail'
import { EmptyState } from '@/components/shared/empty-state'
import { ErrorState } from '@/components/shared/error-state'
import { LoadingCards } from '@/components/shared/loading'
import { Input } from '@/components/ui/input'
import { useMcpTools } from '@/hooks/use-mcp'
import type { McpTool } from '@/types/mcp'

export function ToolList() {
  const { t } = useTranslation()
  const { data: tools, isLoading, isError, refetch } = useMcpTools()
  const [search, setSearch] = useState('')
  const [expanded, setExpanded] = useState<string | null>(null)

  if (isLoading) return <LoadingCards count={4} />
  if (isError) return <ErrorState onRetry={() => refetch()} />

  const filtered = (tools ?? []).filter(
    (tool) =>
      tool.name.toLowerCase().includes(search.toLowerCase()) ||
      tool.description.toLowerCase().includes(search.toLowerCase()) ||
      tool.server.toLowerCase().includes(search.toLowerCase()),
  )

  // Group by server
  const grouped = filtered.reduce<Record<string, McpTool[]>>((acc, tool) => {
    const key = tool.server || 'unknown'
    if (!acc[key]) acc[key] = []
    acc[key].push(tool)
    return acc
  }, {})

  if (!tools || tools.length === 0) {
    return (
      <EmptyState
        icon={<Wrench className="h-8 w-8" />}
        title={t('mcp.noTools', 'No MCP tools available')}
        description={t(
          'mcp.noToolsDescription',
          'Register and enable an MCP server to see its tools.',
        )}
        className="py-6"
      />
    )
  }

  return (
    <div className="space-y-4">
      <Input
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        placeholder={`${t('common.search', 'Search')}...`}
        className="max-w-sm"
      />
      {Object.entries(grouped).length === 0 ? (
        <p className="text-sm text-muted-foreground py-4">{t('common.noData', 'No data')}</p>
      ) : (
        Object.entries(grouped).map(([server, serverTools]) => (
          <div key={server} className="space-y-1">
            <h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wider px-2">
              {server} ({serverTools.length})
            </h3>
            <div className="space-y-1">
              {serverTools.map((tool) => {
                const key = `${server}::${tool.name}`
                const isExpanded = expanded === key
                return (
                  <div key={key} className="rounded-lg border">
                    <button
                      type="button"
                      className="flex items-center gap-2 w-full p-3 text-left hover:bg-muted/50 transition-colors"
                      onClick={() => setExpanded(isExpanded ? null : key)}
                    >
                      {isExpanded ? (
                        <ChevronDown className="h-4 w-4 shrink-0" />
                      ) : (
                        <ChevronRight className="h-4 w-4 shrink-0" />
                      )}
                      <span className="font-mono text-sm font-medium">{tool.name}</span>
                      <span className="text-xs text-muted-foreground truncate flex-1">
                        {tool.description}
                      </span>
                    </button>
                    {isExpanded && (
                      <div className="border-t px-3 pt-3 pb-3">
                        <ToolDetail tool={tool} />
                      </div>
                    )}
                  </div>
                )
              })}
            </div>
          </div>
        ))
      )}
    </div>
  )
}