Files
bifrost/ui/app/workspace/providers/fragments/networkFormFragment.tsx
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

522 lines
19 KiB
TypeScript

import { Button } from "@/components/ui/button";
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { HeadersTable } from "@/components/ui/headersTable";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { DefaultNetworkConfig } from "@/lib/constants/config";
import { getErrorMessage, setProviderFormDirtyState, useAppDispatch } from "@/lib/store";
import { useUpdateProviderMutation } from "@/lib/store/apis/providersApi";
import { ModelProvider, isKnownProvider } from "@/lib/types/config";
import { networkOnlyFormSchema, type NetworkOnlyFormSchema } from "@/lib/types/schemas";
import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm, type Resolver } from "react-hook-form";
import { toast } from "sonner";
import { buildProviderUpdatePayload } from "../views/utils";
interface NetworkFormFragmentProps {
provider: ModelProvider;
}
// seconds to human readable time
const secondsToHumanReadable = (seconds: number) => {
// Handle edge cases
if (!seconds || seconds < 0 || isNaN(seconds)) {
return "0 seconds";
}
seconds = Math.floor(seconds);
if (seconds < 60) {
return `${seconds} ${seconds === 1 ? "second" : "seconds"}`;
}
if (seconds < 3600) {
const minutes = Math.floor(seconds / 60);
return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
}
if (seconds < 86400) {
const hours = Math.floor(seconds / 3600);
return `${hours} ${hours === 1 ? "hour" : "hours"}`;
}
// For >= 1 day, only show non-zero components
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
const parts: string[] = [];
parts.push(`${days} ${days === 1 ? "day" : "days"}`);
if (hours > 0) parts.push(`${hours} ${hours === 1 ? "hour" : "hours"}`);
if (minutes > 0) parts.push(`${minutes} ${minutes === 1 ? "minute" : "minutes"}`);
if (remainingSeconds > 0) parts.push(`${remainingSeconds} ${remainingSeconds === 1 ? "second" : "seconds"}`);
return parts.join(" ");
};
export function NetworkFormFragment({ provider }: NetworkFormFragmentProps) {
const dispatch = useAppDispatch();
const hasUpdateProviderAccess = useRbac(RbacResource.ModelProvider, RbacOperation.Update);
const [updateProvider, { isLoading: isUpdatingProvider }] = useUpdateProviderMutation();
const isCustomProvider = !isKnownProvider(provider.name as string);
const form = useForm<NetworkOnlyFormSchema, any, NetworkOnlyFormSchema>({
resolver: zodResolver(networkOnlyFormSchema) as Resolver<NetworkOnlyFormSchema, any, NetworkOnlyFormSchema>,
mode: "onChange",
reValidateMode: "onChange",
defaultValues: {
network_config: {
base_url: provider.network_config?.base_url || undefined,
extra_headers: provider.network_config?.extra_headers,
default_request_timeout_in_seconds:
provider.network_config?.default_request_timeout_in_seconds ?? DefaultNetworkConfig.default_request_timeout_in_seconds,
max_retries: provider.network_config?.max_retries ?? DefaultNetworkConfig.max_retries,
retry_backoff_initial: provider.network_config?.retry_backoff_initial ?? DefaultNetworkConfig.retry_backoff_initial,
retry_backoff_max: provider.network_config?.retry_backoff_max ?? DefaultNetworkConfig.retry_backoff_max,
insecure_skip_verify: provider.network_config?.insecure_skip_verify ?? DefaultNetworkConfig.insecure_skip_verify,
ca_cert_pem: provider.network_config?.ca_cert_pem ?? DefaultNetworkConfig.ca_cert_pem,
stream_idle_timeout_in_seconds:
provider.network_config?.stream_idle_timeout_in_seconds ?? DefaultNetworkConfig.stream_idle_timeout_in_seconds,
max_conns_per_host: provider.network_config?.max_conns_per_host ?? DefaultNetworkConfig.max_conns_per_host,
enforce_http2: provider.network_config?.enforce_http2 ?? DefaultNetworkConfig.enforce_http2,
},
},
});
useEffect(() => {
dispatch(setProviderFormDirtyState(form.formState.isDirty));
}, [form.formState.isDirty, dispatch]);
const onSubmit = (data: NetworkOnlyFormSchema) => {
const requiresBaseUrl = isCustomProvider;
if (requiresBaseUrl && (data.network_config?.base_url ?? "").trim() === "") {
if ((provider.network_config?.base_url ?? "").trim() !== "") {
toast.error("You can't remove network configuration for this provider.");
} else {
toast.error("Base URL is required for this provider.");
}
return;
}
// Create updated provider configuration
const updatedProvider = buildProviderUpdatePayload(provider, {
network_config: {
...provider.network_config,
base_url: data.network_config?.base_url || undefined,
extra_headers: data.network_config?.extra_headers || undefined,
default_request_timeout_in_seconds: data.network_config?.default_request_timeout_in_seconds ?? 30,
max_retries: data.network_config?.max_retries ?? 0,
retry_backoff_initial: data.network_config?.retry_backoff_initial ?? 500,
retry_backoff_max: data.network_config?.retry_backoff_max ?? 10000,
insecure_skip_verify: data.network_config?.insecure_skip_verify ?? false,
ca_cert_pem: data.network_config?.ca_cert_pem?.trim() || undefined,
stream_idle_timeout_in_seconds:
data.network_config?.stream_idle_timeout_in_seconds ?? DefaultNetworkConfig.stream_idle_timeout_in_seconds,
max_conns_per_host: data.network_config?.max_conns_per_host ?? DefaultNetworkConfig.max_conns_per_host,
enforce_http2: data.network_config?.enforce_http2 ?? DefaultNetworkConfig.enforce_http2,
},
});
updateProvider(updatedProvider)
.unwrap()
.then(() => {
toast.success("Provider configuration updated successfully");
form.reset(data);
})
.catch((err) => {
toast.error("Failed to update provider configuration", {
description: getErrorMessage(err),
});
});
};
useEffect(() => {
// Reset form with new provider's network_config when provider.name changes
form.reset({
network_config: {
base_url: provider.network_config?.base_url || undefined,
extra_headers: provider.network_config?.extra_headers,
default_request_timeout_in_seconds:
provider.network_config?.default_request_timeout_in_seconds ?? DefaultNetworkConfig.default_request_timeout_in_seconds,
max_retries: provider.network_config?.max_retries ?? DefaultNetworkConfig.max_retries,
retry_backoff_initial: provider.network_config?.retry_backoff_initial ?? DefaultNetworkConfig.retry_backoff_initial,
retry_backoff_max: provider.network_config?.retry_backoff_max ?? DefaultNetworkConfig.retry_backoff_max,
insecure_skip_verify: provider.network_config?.insecure_skip_verify ?? DefaultNetworkConfig.insecure_skip_verify,
ca_cert_pem: provider.network_config?.ca_cert_pem ?? DefaultNetworkConfig.ca_cert_pem,
stream_idle_timeout_in_seconds:
provider.network_config?.stream_idle_timeout_in_seconds ?? DefaultNetworkConfig.stream_idle_timeout_in_seconds,
max_conns_per_host: provider.network_config?.max_conns_per_host ?? DefaultNetworkConfig.max_conns_per_host,
},
});
}, [form, provider.name, provider.network_config]);
const baseURLRequired = isCustomProvider;
const hideBaseURL = provider.name === "vllm" || provider.name === "ollama" || provider.name === "sgl";
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 px-6">
{/* Network Configuration */}
<div className="space-y-4">
<div className="grid grid-cols-1 gap-4">
{!hideBaseURL && (
<FormField
control={form.control}
name="network_config.base_url"
render={({ field }) => (
<FormItem>
<FormLabel>Base URL {baseURLRequired ? "(Required)" : "(Optional)"}</FormLabel>
<FormControl>
<Input
placeholder={isCustomProvider ? "https://api.your-provider.com" : "https://api.example.com"}
{...field}
value={field.value || ""}
disabled={!hasUpdateProviderAccess}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<div className="flex w-full flex-row items-start gap-4">
<FormField
control={form.control}
name="network_config.default_request_timeout_in_seconds"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Timeout (seconds)</FormLabel>
<FormControl>
<Input
placeholder="30"
{...field}
value={field.value === undefined || Number.isNaN(field.value) ? "" : field.value}
disabled={!hasUpdateProviderAccess}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
field.onChange(undefined);
return;
}
const parsed = Number(value);
if (!Number.isNaN(parsed)) {
field.onChange(parsed);
}
form.trigger("network_config");
}}
/>
</FormControl>
<FormDescription>{secondsToHumanReadable(field.value)}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="network_config.stream_idle_timeout_in_seconds"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Stream Idle Timeout (seconds)</FormLabel>
<FormControl>
<Input
placeholder="60"
data-testid="network-config-stream-idle-timeout-input"
{...field}
value={field.value === undefined || Number.isNaN(field.value) ? "" : field.value}
disabled={!hasUpdateProviderAccess}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
field.onChange(undefined);
return;
}
const parsed = Number(value);
if (!Number.isNaN(parsed)) {
field.onChange(parsed);
}
form.trigger("network_config");
}}
/>
</FormControl>
<FormDescription>
{field.value ? secondsToHumanReadable(field.value) : ""} Max time to wait for next chunk before closing a stalled
stream
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="network_config.max_retries"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Max Retries</FormLabel>
<FormControl>
<Input
placeholder="0"
{...field}
value={field.value === undefined || Number.isNaN(field.value) ? "" : field.value}
disabled={!hasUpdateProviderAccess}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
field.onChange(undefined);
return;
}
const parsed = Number(value);
if (!Number.isNaN(parsed)) {
field.onChange(parsed);
}
form.trigger("network_config");
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex w-full flex-row items-start gap-4">
<FormField
control={form.control}
name="network_config.retry_backoff_initial"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Initial Backoff (ms)</FormLabel>
<FormControl>
<Input
placeholder="e.g 500"
{...field}
value={field.value === undefined || Number.isNaN(field.value) ? "" : field.value}
disabled={!hasUpdateProviderAccess}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
field.onChange(undefined);
return;
}
const parsed = Number(value);
if (!Number.isNaN(parsed)) {
field.onChange(parsed);
}
form.trigger("network_config");
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="network_config.retry_backoff_max"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Max Backoff (ms)</FormLabel>
<FormControl>
<Input
placeholder="e.g 10000"
{...field}
value={field.value === undefined || Number.isNaN(field.value) ? "" : field.value}
disabled={!hasUpdateProviderAccess}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
field.onChange(undefined);
return;
}
const parsed = Number(value);
if (!Number.isNaN(parsed)) {
field.onChange(parsed);
}
form.trigger("network_config");
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex w-full flex-row items-start gap-4">
<FormField
control={form.control}
name="network_config.max_conns_per_host"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Max Connections Per Host</FormLabel>
<FormControl>
<Input
data-testid="network-config-max-conns-per-host-input"
placeholder="5000"
{...field}
value={field.value === undefined || Number.isNaN(field.value) ? "" : field.value}
disabled={!hasUpdateProviderAccess}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
field.onChange(undefined);
return;
}
const parsed = Number(value);
if (!Number.isNaN(parsed)) {
field.onChange(parsed);
}
form.trigger("network_config");
}}
/>
</FormControl>
<FormDescription>
Max TCP connections per provider host. For HTTP/2 providers (e.g. Bedrock), each connection supports ~100 concurrent
streams.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="network_config.enforce_http2"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between">
<div className="space-y-0.5">
<FormLabel>Enforce HTTP/2</FormLabel>
<FormDescription>
Force HTTP/2 on provider connections. Relevant for net/http-based providers (e.g. Bedrock) where each HTTP/2
connection supports ~100 concurrent streams.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value ?? false}
onCheckedChange={field.onChange}
disabled={!hasUpdateProviderAccess}
data-testid="network-config-enforce-http2"
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="network_config.extra_headers"
render={({ field }) => (
<FormItem>
<FormControl>
<HeadersTable
value={field.value || {}}
onChange={field.onChange}
keyPlaceholder="Header name"
valuePlaceholder="Header value"
label="Extra Headers"
disabled={!hasUpdateProviderAccess}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="space-y-4 rounded-lg border p-4">
<h4 className="text-sm font-medium">TLS / Certificate</h4>
<FormField
control={form.control}
name="network_config.insecure_skip_verify"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel>Skip TLS verification</FormLabel>
<FormDescription>
Disable TLS certificate verification for provider connections. This bypasses server certificate validation and
should be used only as a last resort when a trusted CA chain cannot be configured. Prefer ca_cert_pem for
self-signed or private CA deployments.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value ?? false}
onCheckedChange={field.onChange}
disabled={!hasUpdateProviderAccess}
data-testid="network-config-insecure-skip-verify"
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="network_config.ca_cert_pem"
render={({ field }) => (
<FormItem>
<FormLabel>CA Certificate (PEM) (Optional)</FormLabel>
<FormControl>
<Textarea
placeholder="-----BEGIN CERTIFICATE-----&#10;...&#10;-----END CERTIFICATE-----"
className="font-mono text-xs"
rows={6}
{...field}
value={field.value || ""}
disabled={!hasUpdateProviderAccess}
data-testid="network-config-ca-cert-pem"
/>
</FormControl>
<FormDescription>
PEM-encoded CA certificate to trust for provider endpoint connections (e.g. self-signed or internal CA)
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</div>
{/* Form Actions */}
<div className="flex justify-end space-x-2 py-2">
{!hideBaseURL && (
<Button
type="button"
variant="outline"
onClick={() => {
form.reset({
network_config: undefined,
});
onSubmit(form.getValues());
}}
disabled={
!hasUpdateProviderAccess ||
isUpdatingProvider ||
!provider.network_config ||
!provider.network_config.base_url ||
provider.network_config.base_url.trim() === ""
}
>
Remove configuration
</Button>
)}
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="submit"
disabled={!form.formState.isDirty || !form.formState.isValid || !hasUpdateProviderAccess}
isLoading={isUpdatingProvider}
>
Save Network Configuration
</Button>
</TooltipTrigger>
{(!form.formState.isDirty || !form.formState.isValid) && (
<TooltipContent>
<p>
{!form.formState.isDirty && !form.formState.isValid
? "No changes made and validation errors present"
: !form.formState.isDirty
? "No changes made"
: "Please fix validation errors"}
</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
</div>
</form>
</Form>
);
}