diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 7a7d5059eb0c..4acfbcd7f55d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -13,6 +13,8 @@ export * from "./mcp.js" export * from "./message.js" export * from "./mode.js" export * from "./model.js" +export * from "./provider-metadata.js" +export * from "./provider-metadata-definitions.js" export * from "./provider-settings.js" export * from "./single-file-read-models.js" export * from "./task.js" diff --git a/packages/types/src/provider-metadata-definitions.ts b/packages/types/src/provider-metadata-definitions.ts new file mode 100644 index 000000000000..7aa9cc2a5d4c --- /dev/null +++ b/packages/types/src/provider-metadata-definitions.ts @@ -0,0 +1,821 @@ +import { ProviderMetadataMap } from "./provider-metadata.js" + +/** + * Comprehensive provider metadata with categorization + */ +export const PROVIDER_METADATA: ProviderMetadataMap = { + // Premium Cloud Providers (Paid, High Quality) + "openai-native": { + id: "openai-native", + label: "OpenAI", + category: "cloud", + pricing: "pay-per-use", + quality: "premium", + authentication: "api-key", + recommended: true, + deprecated: false, + description: "Official OpenAI API with GPT-4, GPT-4 Turbo, and GPT-5 models", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: true, + supportsPromptCaching: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + costInfo: { + pricingUrl: "https://openai.com/pricing", + }, + tags: ["official", "gpt", "premium"], + }, + anthropic: { + id: "anthropic", + label: "Anthropic", + category: "cloud", + pricing: "pay-per-use", + quality: "premium", + authentication: "api-key", + recommended: true, + deprecated: false, + description: "Claude models with strong reasoning and coding capabilities", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: true, + supportsPromptCaching: true, + }, + performance: { + speed: "fast", + reliability: "high", + contextWindow: 200000, + }, + costInfo: { + pricingUrl: "https://www.anthropic.com/pricing", + }, + tags: ["official", "claude", "premium"], + }, + gemini: { + id: "gemini", + label: "Google Gemini", + category: "cloud", + pricing: "freemium", + quality: "premium", + authentication: "api-key", + recommended: true, + deprecated: false, + description: "Google's Gemini models with large context windows", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: true, + }, + performance: { + speed: "fast", + reliability: "high", + contextWindow: 1000000, + }, + costInfo: { + pricingUrl: "https://ai.google.dev/pricing", + freeTokensPerMonth: 60, + }, + tags: ["official", "google", "large-context"], + }, + + // Enterprise Cloud Providers + bedrock: { + id: "bedrock", + label: "Amazon Bedrock", + category: "cloud", + pricing: "pay-per-use", + quality: "premium", + authentication: "cloud-account", + recommended: true, + deprecated: false, + description: "AWS managed service for various AI models", + setupDifficulty: "advanced", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: true, + supportsPromptCaching: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + tags: ["enterprise", "aws", "multi-model"], + }, + vertex: { + id: "vertex", + label: "GCP Vertex AI", + category: "cloud", + pricing: "pay-per-use", + quality: "premium", + authentication: "cloud-account", + recommended: true, + deprecated: false, + description: "Google Cloud's enterprise AI platform", + setupDifficulty: "advanced", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + tags: ["enterprise", "gcp", "multi-model"], + }, + + // Local Providers (Free, Self-hosted) + ollama: { + id: "ollama", + label: "Ollama", + category: "local", + pricing: "free", + quality: "standard", + authentication: "none", + recommended: true, + deprecated: false, + description: "Run AI models locally on your machine", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "high", + }, + tags: ["local", "open-source", "privacy"], + }, + lmstudio: { + id: "lmstudio", + label: "LM Studio", + category: "local", + pricing: "free", + quality: "standard", + authentication: "none", + recommended: true, + deprecated: false, + description: "User-friendly local model runner with GUI", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "high", + }, + tags: ["local", "user-friendly", "privacy"], + }, + + // Router/Aggregator Services + openrouter: { + id: "openrouter", + label: "OpenRouter", + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: true, + deprecated: false, + description: "Access multiple AI providers through one API", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: true, + supportsPromptCaching: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + costInfo: { + pricingUrl: "https://openrouter.ai/pricing", + }, + tags: ["router", "multi-provider", "flexible"], + }, + litellm: { + id: "litellm", + label: "LiteLLM", + category: "hybrid", + pricing: "freemium", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Unified interface for multiple LLM providers", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: true, + }, + performance: { + speed: "medium", + reliability: "medium", + }, + tags: ["router", "proxy", "multi-provider"], + }, + "vercel-ai-gateway": { + id: "vercel-ai-gateway", + label: "Vercel AI Gateway", + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Vercel's unified AI gateway with caching", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + tags: ["router", "vercel", "caching"], + }, + + // Alternative/Specialized Providers + deepseek: { + id: "deepseek", + label: "DeepSeek", + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: true, + deprecated: false, + description: "Cost-effective Chinese AI provider with strong coding models", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + tags: ["affordable", "coding", "chinese"], + }, + groq: { + id: "groq", + label: "Groq", + category: "cloud", + pricing: "freemium", + quality: "standard", + authentication: "api-key", + recommended: true, + deprecated: false, + description: "Ultra-fast inference with specialized hardware", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + tags: ["fast", "free-tier", "specialized"], + }, + cerebras: { + id: "cerebras", + label: "Cerebras", + category: "cloud", + pricing: "freemium", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Fast inference with Cerebras hardware", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "fast", + reliability: "medium", + }, + tags: ["fast", "specialized-hardware"], + }, + mistral: { + id: "mistral", + label: "Mistral", + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "European AI provider with efficient models", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + tags: ["european", "efficient"], + }, + xai: { + id: "xai", + label: "xAI (Grok)", + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Grok models from xAI", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: true, + }, + performance: { + speed: "fast", + reliability: "medium", + }, + tags: ["grok", "new"], + }, + + // Specialized/Niche Providers + "claude-code": { + id: "claude-code", + label: "Claude Code", + category: "cloud", + pricing: "subscription", + quality: "premium", + authentication: "oauth", + recommended: true, + deprecated: false, + description: "Claude optimized for coding tasks", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + tags: ["claude", "coding", "specialized"], + }, + "qwen-code": { + id: "qwen-code", + label: "Qwen Code", + category: "cloud", + pricing: "free", + quality: "standard", + authentication: "oauth", + recommended: false, + deprecated: false, + description: "Alibaba's Qwen model for coding", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: true, + }, + performance: { + speed: "medium", + reliability: "medium", + }, + tags: ["alibaba", "coding", "chinese"], + }, + moonshot: { + id: "moonshot", + label: "Moonshot", + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Chinese AI provider with large context models", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "medium", + }, + tags: ["chinese", "large-context"], + }, + doubao: { + id: "doubao", + label: "Doubao", + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "ByteDance's AI models", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "medium", + }, + tags: ["bytedance", "chinese"], + }, + + // Infrastructure/Development Providers + deepinfra: { + id: "deepinfra", + label: "DeepInfra", + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Serverless inference for open-source models", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "medium", + }, + tags: ["open-source", "serverless"], + }, + huggingface: { + id: "huggingface", + label: "Hugging Face", + category: "cloud", + pricing: "freemium", + quality: "experimental", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Access thousands of open-source models", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "medium", + }, + tags: ["open-source", "community", "experimental"], + }, + sambanova: { + id: "sambanova", + label: "SambaNova", + category: "cloud", + pricing: "freemium", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Fast inference with SambaNova hardware", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "fast", + reliability: "medium", + }, + tags: ["specialized-hardware", "free-tier"], + }, + fireworks: { + id: "fireworks", + label: "Fireworks AI", + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Fast and affordable AI inference", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + tags: ["fast", "affordable"], + }, + + // Custom/Compatible Providers + openai: { + id: "openai", + label: "OpenAI Compatible", + category: "hybrid", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Connect to any OpenAI-compatible API endpoint", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "medium", + }, + warning: "Compatibility varies by provider", + tags: ["flexible", "custom", "compatible"], + }, + + // VSCode Integration + "vscode-lm": { + id: "vscode-lm", + label: "VS Code LM API", + category: "local", + pricing: "free", + quality: "standard", + authentication: "none", + recommended: false, + deprecated: false, + description: "Use models provided by VS Code extensions", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "high", + }, + tags: ["vscode", "integrated", "copilot"], + }, + + // Lesser-known/Experimental Providers + glama: { + id: "glama", + label: "Glama", + category: "cloud", + pricing: "pay-per-use", + quality: "experimental", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Multi-model AI gateway", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "medium", + }, + tags: ["gateway", "experimental"], + }, + unbound: { + id: "unbound", + label: "Unbound", + category: "cloud", + pricing: "pay-per-use", + quality: "experimental", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Experimental AI provider", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "low", + }, + tags: ["experimental"], + }, + requesty: { + id: "requesty", + label: "Requesty", + category: "cloud", + pricing: "pay-per-use", + quality: "experimental", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "API gateway service", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "low", + }, + tags: ["gateway", "experimental"], + }, + chutes: { + id: "chutes", + label: "Chutes AI", + category: "cloud", + pricing: "pay-per-use", + quality: "experimental", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Experimental AI provider", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "low", + }, + tags: ["experimental"], + }, + featherless: { + id: "featherless", + label: "Featherless AI", + category: "cloud", + pricing: "pay-per-use", + quality: "experimental", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Serverless AI inference", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "low", + }, + tags: ["serverless", "experimental"], + }, + "io-intelligence": { + id: "io-intelligence", + label: "IO Intelligence", + category: "cloud", + pricing: "pay-per-use", + quality: "experimental", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Experimental AI provider", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "low", + }, + tags: ["experimental"], + }, + zai: { + id: "zai", + label: "Z AI", + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + description: "Zhipu AI models", + setupDifficulty: "moderate", + features: { + supportsStreaming: true, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "medium", + reliability: "medium", + }, + tags: ["chinese", "zhipu"], + }, + + // Special/Internal Providers + roo: { + id: "roo", + label: "Roo Code Cloud", + category: "cloud", + pricing: "subscription", + quality: "premium", + authentication: "cloud-account", + recommended: true, + deprecated: false, + description: "Roo Code's managed cloud service", + setupDifficulty: "easy", + features: { + supportsStreaming: true, + supportsImages: true, + supportsTools: true, + supportsPromptCaching: true, + }, + performance: { + speed: "fast", + reliability: "high", + }, + tags: ["official", "managed", "integrated"], + }, + "human-relay": { + id: "human-relay", + label: "Human Relay", + category: "local", + pricing: "free", + quality: "experimental", + authentication: "none", + recommended: false, + deprecated: false, + description: "Manual responses for testing", + setupDifficulty: "easy", + features: { + supportsStreaming: false, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "slow", + reliability: "low", + }, + warning: "For testing only - requires manual intervention", + tags: ["testing", "manual", "development"], + }, + "fake-ai": { + id: "fake-ai", + label: "Fake AI", + category: "local", + pricing: "free", + quality: "experimental", + authentication: "none", + recommended: false, + deprecated: false, + description: "Mock AI for testing", + setupDifficulty: "easy", + features: { + supportsStreaming: false, + supportsImages: false, + supportsTools: false, + }, + performance: { + speed: "fast", + reliability: "high", + }, + warning: "For testing only - returns mock responses", + tags: ["testing", "mock", "development"], + }, +} diff --git a/packages/types/src/provider-metadata.ts b/packages/types/src/provider-metadata.ts new file mode 100644 index 000000000000..a83c23dcc3be --- /dev/null +++ b/packages/types/src/provider-metadata.ts @@ -0,0 +1,77 @@ +/** + * Provider metadata for enhanced categorization and user guidance + */ + +export type ProviderCategory = "cloud" | "local" | "hybrid" + +export type PricingModel = "free" | "paid" | "freemium" | "pay-per-use" | "subscription" + +export type QualityTier = "premium" | "standard" | "experimental" | "deprecated" + +export type AuthenticationMethod = "api-key" | "oauth" | "none" | "cloud-account" + +export interface ProviderMetadata { + /** Unique identifier for the provider */ + id: string + + /** Display name for the provider */ + label: string + + /** Category of the provider */ + category: ProviderCategory + + /** Pricing model */ + pricing: PricingModel + + /** Quality tier indicator */ + quality: QualityTier + + /** Authentication method required */ + authentication: AuthenticationMethod + + /** Whether this provider is recommended */ + recommended: boolean + + /** Whether this provider should be avoided */ + deprecated: boolean + + /** Short description of the provider */ + description?: string + + /** Setup difficulty level */ + setupDifficulty?: "easy" | "moderate" | "advanced" + + /** Link to documentation */ + documentationUrl?: string + + /** Specific features or capabilities */ + features?: { + supportsStreaming?: boolean + supportsImages?: boolean + supportsTools?: boolean + supportsPromptCaching?: boolean + supportsFinetuning?: boolean + } + + /** Performance characteristics */ + performance?: { + speed: "fast" | "medium" | "slow" + reliability: "high" | "medium" | "low" + contextWindow?: number + } + + /** Cost information for paid providers */ + costInfo?: { + pricingUrl?: string + estimatedCostPer1kTokens?: number + freeTokensPerMonth?: number + } + + /** Warning message if any */ + warning?: string + + /** Tags for additional categorization */ + tags?: string[] +} + +export type ProviderMetadataMap = Record diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 4142796b668a..0c451d206440 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -56,11 +56,11 @@ import { SelectValue, SelectContent, SelectItem, - SearchableSelect, Collapsible, CollapsibleTrigger, CollapsibleContent, } from "@src/components/ui" +import { CategorizedProviderSelect } from "./CategorizedProviderSelect" import { Anthropic, @@ -443,15 +443,11 @@ const ApiOptions = ({ )} - onProviderChange(value as ProviderName)} - options={providerOptions} + availableProviders={providerOptions} placeholder={t("settings:common.select")} - searchPlaceholder={t("settings:providers.searchProviderPlaceholder")} - emptyMessage={t("settings:providers.noProviderMatchFound")} - className="w-full" - data-testid="provider-select" /> diff --git a/webview-ui/src/components/settings/CategorizedProviderSelect.tsx b/webview-ui/src/components/settings/CategorizedProviderSelect.tsx new file mode 100644 index 000000000000..64688a0aa783 --- /dev/null +++ b/webview-ui/src/components/settings/CategorizedProviderSelect.tsx @@ -0,0 +1,353 @@ +import React, { useMemo, useState } from "react" +import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons" +import { ProviderMetadata, PROVIDER_METADATA, ProviderCategory, PricingModel } from "@roo-code/types" +import { cn } from "@src/lib/utils" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui" + +interface CategorizedProviderSelectProps { + value: string + onValueChange: (value: string) => void + availableProviders: Array<{ value: string; label: string }> + placeholder?: string +} + +// Helper function to get badge styles based on pricing +const getPricingBadgeStyles = (pricing: PricingModel) => { + switch (pricing) { + case "free": + return "bg-green-500/20 text-green-700 dark:text-green-400" + case "freemium": + return "bg-blue-500/20 text-blue-700 dark:text-blue-400" + case "pay-per-use": + return "bg-orange-500/20 text-orange-700 dark:text-orange-400" + case "subscription": + return "bg-purple-500/20 text-purple-700 dark:text-purple-400" + default: + return "bg-gray-500/20 text-gray-700 dark:text-gray-400" + } +} + +// Helper function to get badge styles based on quality +const getQualityBadgeStyles = (quality: string) => { + switch (quality) { + case "premium": + return "bg-yellow-500/20 text-yellow-700 dark:text-yellow-400" + case "standard": + return "bg-gray-500/20 text-gray-700 dark:text-gray-400" + case "experimental": + return "bg-red-500/20 text-red-700 dark:text-red-400" + case "deprecated": + return "bg-red-800/20 text-red-900 dark:text-red-300" + default: + return "bg-gray-500/20 text-gray-700 dark:text-gray-400" + } +} + +// Helper function to get category icon +const getCategoryIcon = (category: ProviderCategory) => { + switch (category) { + case "cloud": + return "โ˜๏ธ" + case "local": + return "๐Ÿ’ป" + case "hybrid": + return "๐Ÿ”„" + default: + return "๐Ÿ“ฆ" + } +} + +export const CategorizedProviderSelect: React.FC = ({ + value, + onValueChange, + availableProviders, + placeholder = "Select a provider", +}) => { + const [expandedCategories, setExpandedCategories] = useState>( + new Set(["recommended", "cloud", "local"]), + ) + + // Group providers by category + const categorizedProviders = useMemo(() => { + const groups: Record> = { + recommended: [], + cloud: [], + local: [], + hybrid: [], + experimental: [], + deprecated: [], + } + + availableProviders.forEach(({ value: providerId }) => { + const metadata = PROVIDER_METADATA[providerId] + if (metadata) { + const provider = { provider: metadata, value: providerId } + + // Add to recommended section if applicable + if (metadata.recommended) { + groups.recommended.push(provider) + } + + // Add to quality-based sections + if (metadata.deprecated || metadata.quality === "deprecated") { + groups.deprecated.push(provider) + } else if (metadata.quality === "experimental") { + groups.experimental.push(provider) + } else { + // Add to category-based section + groups[metadata.category]?.push(provider) + } + } else { + // Fallback for providers without metadata + groups.cloud.push({ + provider: { + id: providerId, + label: availableProviders.find((p) => p.value === providerId)?.label || providerId, + category: "cloud", + pricing: "pay-per-use", + quality: "standard", + authentication: "api-key", + recommended: false, + deprecated: false, + }, + value: providerId, + }) + } + }) + + // Sort providers within each group + Object.keys(groups).forEach((key) => { + groups[key].sort((a, b) => a.provider.label.localeCompare(b.provider.label)) + }) + + // Remove empty groups + return Object.entries(groups).filter(([_, providers]) => providers.length > 0) + }, [availableProviders]) + + const toggleCategory = (category: string) => { + setExpandedCategories((prev) => { + const newSet = new Set(prev) + if (newSet.has(category)) { + newSet.delete(category) + } else { + newSet.add(category) + } + return newSet + }) + } + + const getCategoryLabel = (category: string) => { + switch (category) { + case "recommended": + return "โญ Recommended" + case "cloud": + return "โ˜๏ธ Cloud Providers" + case "local": + return "๐Ÿ’ป Local Providers" + case "hybrid": + return "๐Ÿ”„ Hybrid Providers" + case "experimental": + return "๐Ÿงช Experimental" + case "deprecated": + return "โš ๏ธ Deprecated" + default: + return category + } + } + + const selectedMetadata = PROVIDER_METADATA[value] + const selectedLabel = selectedMetadata?.label || availableProviders.find((p) => p.value === value)?.label || value + + return ( +
+ + + {/* Show metadata info for selected provider */} + {selectedMetadata && ( +
+
+
+ {selectedMetadata.description && ( +

+ {selectedMetadata.description} +

+ )} + {selectedMetadata.warning && ( +

+ โš ๏ธ {selectedMetadata.warning} +

+ )} +
+ + {selectedMetadata.pricing === "free" + ? "Free" + : selectedMetadata.pricing === "freemium" + ? "Free tier available" + : selectedMetadata.pricing === "pay-per-use" + ? "Pay per use" + : selectedMetadata.pricing === "subscription" + ? "Subscription" + : selectedMetadata.pricing} + + {selectedMetadata.quality !== "standard" && ( + + {selectedMetadata.quality === "premium" + ? "Premium Quality" + : selectedMetadata.quality === "experimental" + ? "Experimental" + : selectedMetadata.quality === "deprecated" + ? "Deprecated" + : selectedMetadata.quality} + + )} + {selectedMetadata.setupDifficulty && ( + + Setup: {selectedMetadata.setupDifficulty} + + )} + {selectedMetadata.performance && ( + <> + + Speed: {selectedMetadata.performance.speed} + + + Reliability: {selectedMetadata.performance.reliability} + + + )} +
+ {selectedMetadata.features && ( +
+ {selectedMetadata.features.supportsStreaming && ( + + โœ“ Streaming + + )} + {selectedMetadata.features.supportsImages && ( + + โœ“ Images + + )} + {selectedMetadata.features.supportsTools && ( + + โœ“ Tools + + )} + {selectedMetadata.features.supportsPromptCaching && ( + + โœ“ Caching + + )} +
+ )} +
+
+
+ )} +
+ ) +}