feat(model-connections): add hint support for API Base URL field and improve dialog accessibility

This commit is contained in:
Anish Sarkar 2026-06-13 09:29:38 +05:30
parent 7493ba9324
commit 02070201fb
3 changed files with 37 additions and 11 deletions

View file

@ -1,4 +1,5 @@
import { Eye, EyeOff } from "lucide-react";
import type { ReactNode } from "react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { DialogFooter } from "@/components/ui/dialog";
@ -9,25 +10,27 @@ import { Spinner } from "@/components/ui/spinner";
interface ApiBaseUrlFieldProps {
value: string;
onChange: (value: string) => void;
optional?: boolean;
/** Placeholder, typically the provider's prefilled default base URL. */
placeholder?: string;
hint?: ReactNode;
}
/** Shared API Base URL input. The prefilled default is passed in via `value`. */
export function ApiBaseUrlField({ value, onChange, optional, placeholder }: ApiBaseUrlFieldProps) {
export function ApiBaseUrlField({
value,
onChange,
placeholder,
hint,
}: ApiBaseUrlFieldProps) {
return (
<div className="flex flex-col gap-2">
<Label>API Base URL{optional ? " (optional)" : ""}</Label>
<Label>API Base URL</Label>
<Input
value={value}
onChange={(event) => onChange(event.target.value)}
placeholder={placeholder || "https://api.example.com/v1"}
/>
<p className="text-xs text-muted-foreground">
Local URLs are tested from the backend container, so use host.docker.internal instead of
localhost.
</p>
{hint ? <p className="text-xs text-muted-foreground">{hint}</p> : null}
</div>
);
}

View file

@ -2,6 +2,19 @@ import { useEffect, useState } from "react";
import { ApiBaseUrlField, ApiKeyField } from "./connect-fields";
import type { ProviderConnectFormProps } from "./provider-metadata";
function baseUrlHint(provider: string) {
if (provider === "ollama_chat" || provider === "lm_studio") {
return "For local servers, use host.docker.internal instead of localhost.";
}
if (provider === "openai_compatible") {
return "Enter the full endpoint URL.";
}
if (provider === "openai" || provider === "anthropic" || provider === "openrouter") {
return "Override only if you route through a proxy or gateway.";
}
return undefined;
}
/**
* Connect form for OpenAI-compatible / native key providers (OpenAI, Anthropic,
* OpenRouter, OpenAI-Compatible, LM Studio, Ollama, ). The base URL is
@ -16,6 +29,7 @@ export function DefaultConnectForm({
const [baseUrl, setBaseUrl] = useState(defaultBaseUrl);
const [apiKey, setApiKey] = useState("");
const isOllama = provider === "ollama_chat";
const hint = baseUrlHint(provider);
const canSubmit = !(baseUrlRequired && !baseUrl.trim());
useEffect(() => {
@ -27,8 +41,8 @@ export function DefaultConnectForm({
<ApiBaseUrlField
value={baseUrl}
onChange={setBaseUrl}
optional={!baseUrlRequired}
placeholder={defaultBaseUrl}
hint={hint}
/>
<ApiKeyField
value={apiKey}

View file

@ -1,4 +1,4 @@
import { useCallback, useState } from "react";
import { useCallback, useRef, useState } from "react";
import {
Dialog,
DialogContent,
@ -61,6 +61,7 @@ export function ProviderConnectDialog({
const isAzure = provider === "azure";
const isBedrock = provider === "bedrock";
const isVertex = provider === "vertex_ai";
const titleRef = useRef<HTMLHeadingElement>(null);
const [currentDraft, setCurrentDraft] = useState<ConnectionDraft>({
base_url: null,
api_key: null,
@ -99,12 +100,20 @@ export function ProviderConnectDialog({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="flex h-[85vh] max-h-[760px] min-h-[640px] max-w-2xl flex-col overflow-hidden bg-popover p-0 text-popover-foreground">
<DialogContent
className="flex h-[85vh] max-h-[760px] min-h-[640px] max-w-2xl flex-col overflow-hidden bg-popover p-0 text-popover-foreground"
onOpenAutoFocus={(event) => {
event.preventDefault();
titleRef.current?.focus();
}}
>
<DialogHeader className="shrink-0 border-b px-6 py-5">
<div className="flex items-center gap-3">
{providerIcon(provider, "size-5")}
<div>
<DialogTitle>Connect {meta.name}</DialogTitle>
<DialogTitle ref={titleRef} tabIndex={-1}>
Connect {meta.name}
</DialogTitle>
<DialogDescription>{meta.subtitle}</DialogDescription>
</div>
</div>