chore: unify connection copy (#5654)

This commit is contained in:
Lee Jackson 2026-05-21 08:39:53 +01:00 committed by GitHub
commit 469dbd6278
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 42 deletions

View file

@ -255,7 +255,7 @@ function ModelSelectorContent({
<Tabs defaultValue={chatOnlyTabsDefault} className="w-full">
<TabsList className="mb-2 w-full">
<TabsTrigger value="hub">Hub models</TabsTrigger>
<TabsTrigger value="external">External</TabsTrigger>
<TabsTrigger value="external">Connected</TabsTrigger>
</TabsList>
<TabsContent value="hub" className="m-0">
<HubModelPicker models={models} value={value} onSelect={onSelect} onFoldersChange={onFoldersChange} />
@ -276,7 +276,7 @@ function ModelSelectorContent({
<TabsList className="mb-2 w-full">
<TabsTrigger value="hub">Hub models</TabsTrigger>
<TabsTrigger value="lora">Fine-tuned</TabsTrigger>
{hasExternal ? <TabsTrigger value="external">External</TabsTrigger> : null}
{hasExternal ? <TabsTrigger value="external">Connected</TabsTrigger> : null}
</TabsList>
<TabsContent value="hub" className="m-0">
@ -530,15 +530,22 @@ function ExternalModelPicker({
<Input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search external models"
placeholder="Search models"
className="h-9 pl-8"
/>
</div>
<div className="max-h-64 overflow-y-auto">
<div className="space-y-2 p-1">
{grouped.length === 0 ? (
<div className="px-2.5 py-2 text-xs text-muted-foreground">
No external models configured.
<div className="px-2.5 py-2 text-xs leading-relaxed text-muted-foreground">
{externalModels.length === 0 ? (
<>
No models from your connections. Set up in Settings
Connections.
</>
) : (
"No models match your search."
)}
</div>
) : (
grouped.map((group) => (

View file

@ -775,7 +775,7 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter {
) {
toast.error("Connections are disabled.", {
description:
"Turn on Enable connections in Settings > Connections to use hosted models.",
"Turn on Enable connections in Settings Connections to use hosted models.",
});
throw new Error("Connections disabled.");
}
@ -789,20 +789,20 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter {
: "";
if (isExternalRequest && !externalProvider) {
toast.error("External provider not found.", {
description: "Open Connections and re-add this provider.",
toast.error("Connection not found.", {
description: "Open Settings → Connections and add it again.",
});
throw new Error("External provider not found.");
throw new Error("Connection not found.");
}
// Local providers (llama.cpp / vLLM / Ollama) allow an empty key — only block hosted providers.
const externalProviderIsCustom = externalProvider
? isCustomProviderType(externalProvider.providerType)
: false;
if (isExternalRequest && !externalApiKey && !externalProviderIsCustom) {
toast.error("Missing API key for selected external provider.", {
description: "Open Connections and set the API key again.",
toast.error("Missing API key for selected connection.", {
description: "Open Settings → Connections and set the API key again.",
});
throw new Error("Missing external provider API key.");
throw new Error("Missing connection API key.");
}
const outboundMessages = messages

View file

@ -74,7 +74,7 @@ const PROVIDER_FORM_EASE: [number, number, number, number] = [
];
const PROVIDER_FORM_DURATION = 0.2;
const CUSTOM_PROVIDER_MISSING_KEY_MESSAGE =
"No API key found, please make sure API key is added and valid for this provider.";
"No API key found. Add a valid API key for this connection.";
const ANTHROPIC_DATED_SNAPSHOT_SUFFIX = /-\d{8}$/;
const OPENAI_DEPRECATED_MODELS = new Set(["gpt-5.3"]);
const HIDDEN_PROVIDER_TYPES = new Set(["qwen"]);
@ -166,7 +166,7 @@ function emptyCatalogHint(providerType: string): {
} {
return (
EMPTY_CATALOG_HINTS[providerType] ?? {
title: "No models returned by this provider.",
title: "No models returned by this connection.",
description: "Enter model IDs manually below, or check the server.",
}
);
@ -290,7 +290,7 @@ export function ChatProvidersSettings({
isManualModelList && isCustomProvider
? "This connection uses manual model IDs"
: isCuratedModelList
? "Full catalog is not fetched for this provider"
? "Full catalog is not fetched for this connection"
: missingModelCatalogBaseUrl
? "Enter a Base URL before loading models"
: missingModelCatalogApiKey
@ -414,7 +414,7 @@ export function ChatProvidersSettings({
} catch (error) {
const message =
error instanceof Error ? error.message : "Unknown error";
toast.error(`Failed to load providers: ${message}`);
toast.error(`Failed to load connections: ${message}`);
} finally {
if (isMounted) {
setRegistryLoading(false);
@ -514,7 +514,7 @@ export function ChatProvidersSettings({
async function loadModels() {
if (!providerType) {
toast.error("Choose a provider first.");
toast.error("Choose a connection first.");
return;
}
if (isCustomProvider && !supportsRemoteModelCatalog(providerType)) {
@ -523,7 +523,7 @@ export function ChatProvidersSettings({
}
if (isCuratedModelList) {
toast.info(
"This provider has a very large model catalog. Use the suggestions and add model IDs manually — full list is not fetched.",
"This connection has a very large model catalog. Use the suggestions and add model IDs manually — full list is not fetched.",
);
return;
}
@ -591,7 +591,7 @@ export function ChatProvidersSettings({
async function addProvider() {
if (!providerType) {
toast.error("Choose a provider first.");
toast.error("Choose a connection first.");
return;
}
const backendProviderType = toExternalBackendProviderType(providerType);
@ -688,10 +688,10 @@ export function ChatProvidersSettings({
]);
resetForm();
setPage("list");
toast.success("Provider added.");
toast.success("Connection added.");
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
toast.error(`Failed to add provider: ${message}`);
toast.error(`Failed to add connection: ${message}`);
} finally {
setMutatingProvider(false);
}
@ -703,7 +703,7 @@ export function ChatProvidersSettings({
(provider) => provider.id === editingProviderId,
);
if (!existing) {
toast.error("Provider not found.");
toast.error("Connection not found.");
return;
}
const isEditingCustomProvider =
@ -799,12 +799,12 @@ export function ChatProvidersSettings({
: provider,
),
);
toast.success("Provider updated.");
toast.success("Connection updated.");
resetForm();
setPage("list");
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
toast.error(`Failed to update provider: ${message}`);
toast.error(`Failed to update connection: ${message}`);
} finally {
setMutatingProvider(false);
}
@ -890,7 +890,7 @@ export function ChatProvidersSettings({
);
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
toast.error(`Failed to delete provider: ${message}`);
toast.error(`Failed to delete connection: ${message}`);
} finally {
setMutatingProvider(false);
}
@ -906,7 +906,7 @@ export function ChatProvidersSettings({
return;
}
await editProvider(provider);
toast.info(`No API key found for ${provider.name}. Add one and save.`);
toast.info(`No API key for ${provider.name}. Add one in Connections and save.`);
return;
}
try {
@ -952,8 +952,8 @@ export function ChatProvidersSettings({
size="icon-sm"
className="size-8 rounded-[8px]"
onClick={closeForm}
aria-label="Back to providers"
title="Back to providers"
aria-label="Back to connections"
title="Back to connections"
>
<HugeiconsIcon icon={ArrowLeft02Icon} className="size-4" />
</Button>
@ -977,10 +977,10 @@ export function ChatProvidersSettings({
htmlFor="provider-preset"
className="text-sm font-medium"
>
Provider
Connection
</Label>
<p className="text-xs leading-snug text-muted-foreground">
Supported registry or local OpenAI-compatible connection.
OpenAI, Anthropic, or a compatible local endpoint.
</p>
</div>
<Select
@ -1002,7 +1002,7 @@ export function ChatProvidersSettings({
className="h-9 w-full text-sm"
disabled={editingProviderId != null}
>
<SelectValue placeholder="Choose a provider" />
<SelectValue placeholder="Choose a connection" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
@ -1094,7 +1094,7 @@ export function ChatProvidersSettings({
htmlFor="provider-custom-name"
className="text-sm font-medium"
>
Provider name
Connection name
</Label>
<Input
id="provider-custom-name"
@ -1446,7 +1446,7 @@ export function ChatProvidersSettings({
: void addProvider()
}
>
{editingProviderId ? "Save provider" : "Add provider"}
{editingProviderId ? "Save connection" : "Add connection"}
</Button>
<Button
type="button"
@ -1470,7 +1470,7 @@ export function ChatProvidersSettings({
<div className="flex min-w-0 flex-col gap-1">
<h1 className="font-heading text-lg font-semibold">Connections</h1>
<p className="text-xs leading-relaxed text-muted-foreground">
Manage model provider connections for chat through the Studio proxy.
Manage model connections for chat through the Studio proxy.
</p>
</div>
</header>
@ -1495,7 +1495,7 @@ export function ChatProvidersSettings({
id="chat-connections-description"
className="max-w-md text-[11px] leading-snug text-muted-foreground/65 sm:text-right"
>
When off, all provider connections are disabled.
When off, all connections are disabled.
</p>
</div>
@ -1508,20 +1508,20 @@ export function ChatProvidersSettings({
>
<span className="flex min-w-0 items-center gap-2 rounded-full border border-border bg-background/50 px-3 py-1.5 transition-colors group-hover/add:border-emerald-500/25 group-hover/add:text-emerald-700 dark:group-hover/add:text-emerald-300">
<HugeiconsIcon icon={PlusSignIcon} className="size-4 shrink-0" />
<span>Add Provider</span>
<span>Add connection</span>
</span>
<span className="shrink-0 text-xs tabular-nums text-muted-foreground/90">
{providers.length} providers · {totalModels} models
{providers.length} connections · {totalModels} models
</span>
</button>
{providers.length === 0 ? (
<div className="px-3 py-4">
<div className="flex min-w-0 flex-col gap-0.5">
<span className="text-sm font-medium text-foreground">
No providers yet
No connections yet
</span>
<span className="text-xs leading-snug text-muted-foreground">
Add an external provider to use hosted models from chat.
Add a connection to use hosted models from chat.
</span>
</div>
</div>
@ -1583,7 +1583,7 @@ export function ChatProvidersSettings({
className="size-7 rounded-[8px] hover:text-foreground"
disabled={mutatingProvider}
onClick={() => editProvider(provider)}
title="Edit provider"
title="Edit connection"
aria-label={`Edit ${provider.name}`}
>
<HugeiconsIcon icon={Edit03Icon} className="size-4" />
@ -1607,7 +1607,7 @@ export function ChatProvidersSettings({
className="size-7 rounded-[8px] hover:text-destructive"
disabled={mutatingProvider}
onClick={() => void deleteProvider(provider.id)}
title="Delete provider"
title="Delete connection"
aria-label={`Delete ${provider.name}`}
>
<HugeiconsIcon icon={Delete02Icon} className="size-4" />
@ -1644,7 +1644,7 @@ export function ChatProvidersDialog({
<DialogHeader className="sr-only">
<DialogTitle>Connections</DialogTitle>
<DialogDescription>
Manage external model connections for chat.
Manage model connections for chat.
</DialogDescription>
</DialogHeader>
<ChatProvidersSettings