diff --git a/components/ai-elements/model-selector.tsx b/components/ai-elements/model-selector.tsx index 9b5aff8..d5bcae2 100644 --- a/components/ai-elements/model-selector.tsx +++ b/components/ai-elements/model-selector.tsx @@ -1,3 +1,4 @@ +import { Cloud } from "lucide-react" import type { ComponentProps, ReactNode } from "react" import { Command, @@ -112,16 +113,23 @@ export const ModelSelectorLogo = ({ provider, className, ...props -}: ModelSelectorLogoProps) => ( - {`${provider} -) +}: ModelSelectorLogoProps) => { + // Use Lucide icon for bedrock since models.dev doesn't have a good AWS icon + if (provider === "amazon-bedrock") { + return + } + + return ( + {`${provider} + ) +} export type ModelSelectorLogoGroupProps = ComponentProps<"div"> diff --git a/components/chat-input.tsx b/components/chat-input.tsx index 684d94d..fd67c84 100644 --- a/components/chat-input.tsx +++ b/components/chat-input.tsx @@ -14,6 +14,7 @@ import { toast } from "sonner" import { ButtonWithTooltip } from "@/components/button-with-tooltip" import { ErrorToast } from "@/components/error-toast" import { HistoryDialog } from "@/components/history-dialog" +import { ModelSelector } from "@/components/model-selector" import { ResetWarningModal } from "@/components/reset-warning-modal" import { SaveDialog } from "@/components/save-dialog" import { Button } from "@/components/ui/button" @@ -28,6 +29,7 @@ import { useDiagram } from "@/contexts/diagram-context" import { useDictionary } from "@/hooks/use-dictionary" import { formatMessage } from "@/lib/i18n/utils" import { isPdfFile, isTextFile } from "@/lib/pdf-utils" +import type { FlattenedModel } from "@/lib/types/model-config" import { FilePreviewList } from "./file-preview-list" const MAX_IMAGE_SIZE = 2 * 1024 * 1024 // 2MB @@ -156,6 +158,11 @@ interface ChatInputProps { error?: Error | null minimalStyle?: boolean onMinimalStyleChange?: (value: boolean) => void + // Model selector props + models?: FlattenedModel[] + selectedModelId?: string + onModelSelect?: (modelId: string | undefined) => void + onConfigureModels?: () => void } export function ChatInput({ @@ -173,6 +180,10 @@ export function ChatInput({ error = null, minimalStyle = false, onMinimalStyleChange = () => {}, + models = [], + selectedModelId, + onModelSelect = () => {}, + onConfigureModels = () => {}, }: ChatInputProps) { const dict = useDictionary() const { @@ -465,6 +476,14 @@ export function ChatInput({ disabled={isDisabled} /> + +
- ))} +
+ {config.providers.length === 0 ? ( +
+
+ +
+

+ Add a provider to get started +

+
+ ) : ( +
+ {config.providers.map((provider) => ( + + ))} +
+ )}
{/* Add Provider */} - {availableProviders.length > 0 && ( +
- )} +
{/* Provider Details (Right Panel) */} - +
{selectedProvider ? ( -
- {/* Provider Header */} -
-

- { - PROVIDER_INFO[ - selectedProvider.provider - ].label - } -

-
- - {/* Provider Name */} -
- - - handleProviderUpdate( - "name", - e.target.value, - ) - } - placeholder={ - PROVIDER_INFO[ - selectedProvider.provider - ].label - } - /> -

- Custom name to identify this provider - (e.g., "OpenAI Production") -

-
- - {/* API Key */} -
- -
-
- - handleProviderUpdate( - "apiKey", - e.target.value, - ) - } - placeholder="Enter API key" - className="pr-10" - /> - -
- -
- {validationStatus === "error" && - validationError && ( -

- {validationError} -

- )} -
+
- {/* Base URL */} -
- - - handleProviderUpdate( - "baseUrl", - e.target.value, - ) - } - placeholder={ - PROVIDER_INFO[ - selectedProvider.provider - ].defaultBaseUrl || - "Custom endpoint URL" - } - /> -
+ {/* Configuration Section */} +
+
+ + Configuration +
- {/* Models Section */} -
-
- - { - setModelPopoverOpen(open) - if (!open) - setModelSearchValue("") - }} - > - - - - - - + {/* Display Name */} +
+ + + handleProviderUpdate( + "name", + e.target.value, + ) + } + placeholder={ + PROVIDER_INFO[ + selectedProvider + .provider + ].label + } + className="h-9" /> - - - - {modelSearchValue.trim() - ? "Press Enter to add custom model" - : "Type a model ID..."} - - - {/* Custom model option - appears when search doesn't match suggestions */} - {modelSearchValue.trim() && - !suggestedModels.some( - (m) => - m - .toLowerCase() - .includes( - modelSearchValue.toLowerCase(), - ), - ) && ( - - { - handleAddModel( - modelSearchValue.trim(), - ) - setModelSearchValue( - "", - ) - setModelPopoverOpen( - false, - ) - }} - className="text-xs cursor-pointer" - > - Add - " - {modelSearchValue.trim()} - " - - +
+ + {/* API Key */} +
+ +
+
+ + handleProviderUpdate( + "apiKey", + e.target + .value, + ) + } + placeholder="Enter your API key" + className="h-9 pr-10 font-mono text-xs" + /> + +
+ +
+ {validationStatus === + "error" && + validationError && ( +

+ + { + validationError + } +

+ )} +
+ + {/* Base URL */} +
+ + + handleProviderUpdate( + "baseUrl", + e.target.value, + ) + } + placeholder={ + PROVIDER_INFO[ + selectedProvider + .provider + ].defaultBaseUrl || + "Custom endpoint URL" + } + className="h-9 font-mono text-xs" + /> +
+
+
+ + {/* Models Section */} +
+
+
+ + Models +
+
+ + setCustomModelInput( + e.target.value, + ) + } + onKeyDown={(e) => { + if ( + e.key === + "Enter" && + customModelInput.trim() + ) { + handleAddModel( + customModelInput.trim(), + ) + setCustomModelInput( + "", + ) + } + }} + className="h-8 w-48 font-mono text-xs" + /> + + +
+
- {/* Model List */} -
- {selectedProvider.models.length === - 0 ? ( -

- No models configured. Add a - model to get started. -

- ) : ( - selectedProvider.models.map( - (model) => ( -
- - handleModelUpdate( - model.id, - "modelId", - e.target - .value, - ) - } - placeholder="Model ID (e.g., gpt-4o)" - className="h-8 text-xs flex-1" - /> -
- - handleModelUpdate( - model.id, - "streaming", - checked, - ) - } - /> - - Stream - - + {/* Model List */} +
+ {selectedProvider.models + .length === 0 ? ( +
+
+
+

+ No models configured +

- ), - ) - )} -
-
+ ) : ( +
+ {selectedProvider.models.map( + (model, index) => ( +
+ {/* Status icon */} +
+ {validatingModelIndex !== + null && + index === + validatingModelIndex ? ( + // Currently validating +
+ +
+ ) : validatingModelIndex !== + null && + index > + validatingModelIndex && + model.validated === + undefined ? ( + // Queued +
+ +
+ ) : model.validated === + true ? ( + // Valid +
+ +
+ ) : model.validated === + false ? ( + // Invalid +
+ +
+ ) : ( + // Not validated yet +
+ +
+ )} +
+ { + updateModel( + selectedProviderId!, + model.id, + { + modelId: + e + .target + .value, + validated: + undefined, + validationError: + undefined, + }, + ) + }} + className="flex-1 font-mono text-sm h-8 border-0 bg-transparent focus-visible:bg-background focus-visible:ring-1" + /> + +
+ ), + )} +
+ )} +
+
- {/* Delete Provider */} -
- -
-
+ {/* Danger Zone */} +
+ +
+
+ + ) : ( -
-

- Select a provider or add a new one -

-

- Configure multiple AI providers and switch - between them easily +

+
+ +
+

+ Configure AI Providers +

+

+ Select a provider from the list or add a new + one to configure API keys and models

)} - +
{/* Footer */} -
- API keys are stored locally in your browser +
+

+ + API keys are stored locally in your browser +

+ + {/* Delete Confirmation Dialog */} + + + + Delete Provider + + Are you sure you want to delete{" "} + + {selectedProvider + ? selectedProvider.name || + PROVIDER_INFO[selectedProvider.provider] + .label + : "this provider"} + + ? This will remove all configured models and cannot + be undone. + + + + Cancel + + Delete + + + + ) } diff --git a/components/model-selector.tsx b/components/model-selector.tsx index f6d46d2..10b7c64 100644 --- a/components/model-selector.tsx +++ b/components/model-selector.tsx @@ -1,6 +1,6 @@ "use client" -import { Bot, Check, Settings2 } from "lucide-react" +import { Bot, Check, Server, Settings2 } from "lucide-react" import { useMemo, useState } from "react" import { ModelSelectorContent, @@ -37,8 +37,7 @@ const PROVIDER_LOGO_MAP: Record = { openrouter: "openrouter", deepseek: "deepseek", siliconflow: "siliconflow", - ollama: "ollama", - gateway: "openai", // fallback + gateway: "vercel", } // Group models by providerLabel (handles duplicate providers) @@ -69,7 +68,15 @@ export function ModelSelector({ disabled = false, }: ModelSelectorProps) { const [open, setOpen] = useState(false) - const groupedModels = useMemo(() => groupModelsByProvider(models), [models]) + // Only show validated models in the selector + const validatedModels = useMemo( + () => models.filter((m) => m.validated === true), + [models], + ) + const groupedModels = useMemo( + () => groupModelsByProvider(validatedModels), + [validatedModels], + ) // Find selected model for display const selectedModel = useMemo( @@ -108,14 +115,21 @@ export function ModelSelector({ - No models found. + + {validatedModels.length === 0 && models.length > 0 + ? "No verified models. Test your models first." + : "No models found."} + {/* Server Default Option */} - + - + + Server Default @@ -186,6 +201,10 @@ export function ModelSelector({ + {/* Info text */} +
+ Only verified models are shown +
diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..0863e40 --- /dev/null +++ b/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/components/ui/command.tsx b/components/ui/command.tsx index 82e269a..f0940ff 100644 --- a/components/ui/command.tsx +++ b/components/ui/command.tsx @@ -145,18 +145,6 @@ function CommandItem({ "data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className )} - onMouseEnter={(e) => { - // Ensure hover updates selection for visual feedback - const item = e.currentTarget - item.setAttribute("data-selected", "true") - // Deselect siblings - const siblings = item.parentElement?.querySelectorAll("[cmdk-item]") - siblings?.forEach((sibling) => { - if (sibling !== item) { - sibling.setAttribute("data-selected", "false") - } - }) - }} {...props} /> ) diff --git a/components/ui/select.tsx b/components/ui/select.tsx index 25e5439..f58365e 100644 --- a/components/ui/select.tsx +++ b/components/ui/select.tsx @@ -37,7 +37,7 @@ function SelectTrigger({ data-slot="select-trigger" data-size={size} className={cn( - "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive hover:bg-accent dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className )} {...props} diff --git a/lib/types/model-config.ts b/lib/types/model-config.ts index 472fc5c..046e95f 100644 --- a/lib/types/model-config.ts +++ b/lib/types/model-config.ts @@ -9,14 +9,14 @@ export type ProviderName = | "openrouter" | "deepseek" | "siliconflow" - | "ollama" | "gateway" // Individual model configuration export interface ModelConfig { id: string // UUID for this model modelId: string // e.g., "gpt-4o", "claude-sonnet-4-5" - streaming?: boolean // Default true + validated?: boolean // Has this model been validated + validationError?: string // Error message if validation failed } // Provider configuration @@ -45,7 +45,7 @@ export interface FlattenedModel { providerLabel: string // Provider display name apiKey: string baseUrl?: string - streaming?: boolean + validated?: boolean // Has this model been validated } // Provider metadata @@ -67,34 +67,35 @@ export const PROVIDER_INFO: Record< label: "SiliconFlow", defaultBaseUrl: "https://api.siliconflow.com/v1", }, - ollama: { label: "Ollama", defaultBaseUrl: "http://localhost:11434" }, gateway: { label: "AI Gateway" }, } // Suggested models per provider for quick add export const SUGGESTED_MODELS: Record = { openai: [ - // GPT-4o series - "gpt-4o", - "gpt-4o-mini", - "gpt-4o-audio-preview", + // GPT-5.2 series + "gpt-5.2-pro", + "gpt-5.2-chat-latest", + "gpt-5.2", + // GPT-5.1 series + "gpt-5.1-codex-mini", + "gpt-5.1-codex", + "gpt-5.1-chat-latest", + "gpt-5.1", + // GPT-5 series + "gpt-5-pro", + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-codex", + "gpt-5-chat-latest", // GPT-4.1 series "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - // GPT-4 Turbo - "gpt-4-turbo", - "gpt-4-turbo-preview", - // Reasoning models - "o1", - "o1-mini", - "o1-preview", - "o3", - "o3-mini", - "o4-mini", - // Legacy - "gpt-4", - "gpt-3.5-turbo", + // GPT-4o series + "gpt-4o", + "gpt-4o-mini", ], anthropic: [ // Claude 4.5 series (latest) @@ -191,19 +192,6 @@ export const SUGGESTED_MODELS: Record = { "Qwen/Qwen2.5-7B-Instruct", "Qwen/Qwen2-VL-72B-Instruct", ], - ollama: [ - "llama3.3", - "llama3.2", - "llama3.1", - "qwen2.5", - "qwen2.5-coder", - "deepseek-r1", - "deepseek-coder-v2", - "gemma2", - "phi4", - "mistral", - "codellama", - ], gateway: [ "openai/gpt-4o", "openai/gpt-4o-mini", @@ -244,7 +232,6 @@ export function createModelConfig(modelId: string): ModelConfig { return { id: generateId(), modelId, - streaming: true, } } @@ -265,7 +252,7 @@ export function flattenModels(config: MultiModelConfig): FlattenedModel[] { providerLabel, apiKey: provider.apiKey, baseUrl: provider.baseUrl, - streaming: model.streaming, + validated: model.validated, }) } } diff --git a/package-lock.json b/package-lock.json index 1fa14b6..ba8fd30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,13 +26,14 @@ "@openrouter/ai-sdk-provider": "^1.2.3", "@opentelemetry/exporter-trace-otlp-http": "^0.208.0", "@opentelemetry/sdk-trace-node": "^2.2.0", + "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-scroll-area": "^1.2.3", "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-use-controllable-state": "^1.2.2", @@ -4342,6 +4343,111 @@ "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", "license": "MIT" }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", @@ -5104,21 +5210,6 @@ } } }, - "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", @@ -5142,24 +5233,6 @@ } } }, - "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-slot": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", - "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-popover": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", @@ -5615,6 +5688,24 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-scroll-area": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.3.tgz", @@ -6025,12 +6116,12 @@ "license": "MIT" }, "node_modules/@radix-ui/react-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", - "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" + "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -6042,6 +6133,21 @@ } } }, + "node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-switch": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", @@ -6215,6 +6321,24 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-controllable-state": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", diff --git a/package.json b/package.json index f1d31f6..7826186 100644 --- a/package.json +++ b/package.json @@ -41,13 +41,14 @@ "@openrouter/ai-sdk-provider": "^1.2.3", "@opentelemetry/exporter-trace-otlp-http": "^0.208.0", "@opentelemetry/sdk-trace-node": "^2.2.0", + "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-scroll-area": "^1.2.3", "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-use-controllable-state": "^1.2.2",