@@ -13,7 +13,7 @@ import { useSettings, useUpdateSettings } from "../../hooks/useSettings";
1313import { cn } from "../../lib/utils" ;
1414import { normalizeProviderAccentColor } from "../../providerInstances" ;
1515import { Button } from "../ui/button" ;
16- import { ACPRegistryIcon , Gemini , GithubCopilotIcon , PiAgentIcon } from "../Icons" ;
16+ import { ACPRegistryIcon , Gemini , GithubCopilotIcon , PiAgentIcon , type Icon } from "../Icons" ;
1717import {
1818 Dialog ,
1919 DialogDescription ,
@@ -26,7 +26,8 @@ import { Badge } from "../ui/badge";
2626import { Input } from "../ui/input" ;
2727import { RadioGroup } from "../ui/radio-group" ;
2828import { toastManager } from "../ui/toast" ;
29- import { DRIVER_OPTION_BY_VALUE , DRIVER_OPTIONS , type DriverOption } from "./providerDriverMeta" ;
29+ import { DRIVER_OPTION_BY_VALUE , DRIVER_OPTIONS } from "./providerDriverMeta" ;
30+ import { ProviderSettingsForm , deriveProviderSettingsFields } from "./ProviderSettingsForm" ;
3031
3132const PROVIDER_ACCENT_SWATCHES = [
3233 "#2563eb" ,
@@ -61,30 +62,33 @@ function deriveInstanceId(driver: ProviderDriverKind, label: string): string {
6162const INSTANCE_ID_PATTERN = / ^ [ a - z A - Z ] [ a - z A - Z 0 - 9 _ - ] * $ / ;
6263const DEFAULT_DRIVER_KIND = ProviderDriverKind . make ( "codex" ) ;
6364const DEFAULT_DRIVER_OPTION = DRIVER_OPTIONS [ 0 ] ! ;
64- const COMING_SOON_DRIVER_OPTIONS : readonly DriverOption [ ] = [
65+ const EMPTY_CONFIG_DRAFT : Record < string , unknown > = { } ;
66+ interface ComingSoonDriverOption {
67+ readonly value : ProviderDriverKind ;
68+ readonly label : string ;
69+ readonly icon : Icon ;
70+ }
71+
72+ const COMING_SOON_DRIVER_OPTIONS : readonly ComingSoonDriverOption [ ] = [
6573 {
6674 value : ProviderDriverKind . make ( "githubCopilot" ) ,
6775 label : "Github Copilot" ,
6876 icon : GithubCopilotIcon ,
69- fields : [ ] ,
7077 } ,
7178 {
7279 value : ProviderDriverKind . make ( "gemini" ) ,
7380 label : "Gemini" ,
7481 icon : Gemini ,
75- fields : [ ] ,
7682 } ,
7783 {
7884 value : ProviderDriverKind . make ( "acpRegistry" ) ,
7985 label : "ACP Registry" ,
8086 icon : ACPRegistryIcon ,
81- fields : [ ] ,
8287 } ,
8388 {
8489 value : ProviderDriverKind . make ( "piAgent" ) ,
8590 label : "Pi Agent" ,
8691 icon : PiAgentIcon ,
87- fields : [ ] ,
8892 } ,
8993] ;
9094
@@ -118,10 +122,9 @@ export function AddProviderInstanceDialog({ open, onOpenChange }: AddProviderIns
118122 const [ accentColor , setAccentColor ] = useState < string > ( "" ) ;
119123 const [ instanceId , setInstanceId ] = useState ( "" ) ;
120124 const [ instanceIdDirty , setInstanceIdDirty ] = useState ( false ) ;
121- // Driver-specific field values keyed by `${driver}:${fieldKey}` so toggling
122- // between drivers during the same dialog session doesn't lose in-progress
123- // input. Only the active driver's values are persisted on save.
124- const [ fieldValues , setFieldValues ] = useState < Record < string , string > > ( { } ) ;
125+ // Driver-specific config drafts keyed by driver so toggling between drivers
126+ // during the same dialog session does not lose in-progress input.
127+ const [ configByDriver , setConfigByDriver ] = useState < Record < string , Record < string , unknown > > > ( { } ) ;
125128 // Errors are suppressed until the user has tried to submit once. After that
126129 // they update live so fixing the problem clears the message in place.
127130 const [ hasAttemptedSubmit , setHasAttemptedSubmit ] = useState ( false ) ;
@@ -141,7 +144,7 @@ export function AddProviderInstanceDialog({ open, onOpenChange }: AddProviderIns
141144 setInstanceId ( "" ) ;
142145 setWizardStep ( 0 ) ;
143146 setInstanceIdDirty ( false ) ;
144- setFieldValues ( { } ) ;
147+ setConfigByDriver ( { } ) ;
145148 setHasAttemptedSubmit ( false ) ;
146149 } , [ open ] ) ;
147150
@@ -153,23 +156,28 @@ export function AddProviderInstanceDialog({ open, onOpenChange }: AddProviderIns
153156 } , [ driver , label , instanceIdDirty ] ) ;
154157
155158 const driverOption = DRIVER_OPTION_BY_VALUE [ driver ] ?? DEFAULT_DRIVER_OPTION ;
159+ const driverSettingsFields = useMemo (
160+ ( ) => deriveProviderSettingsFields ( driverOption ) ,
161+ [ driverOption ] ,
162+ ) ;
156163 const instanceIdError = validateInstanceId ( instanceId , existingIds ) ;
157164 const showInstanceIdError = hasAttemptedSubmit && instanceIdError !== null ;
158165 const previewLabel = label . trim ( ) || `${ driverOption . label } Workspace` ;
159166 const wizardSteps = [ "Driver" , "Identity" , "Config" ] as const ;
160167 const wizardStepSummaries = [ driverOption . label , previewLabel , null ] as const ;
161168
162- const getFieldValue = useCallback (
163- ( fieldKey : string ) => fieldValues [ `${ driver } :${ fieldKey } ` ] ?? "" ,
164- [ driver , fieldValues ] ,
165- ) ;
166-
167- const setFieldValue = useCallback (
168- ( fieldKey : string , value : string ) => {
169- setFieldValues ( ( existing ) => ( {
170- ...existing ,
171- [ `${ driver } :${ fieldKey } ` ] : value ,
172- } ) ) ;
169+ const configDraft = configByDriver [ driver ] ?? EMPTY_CONFIG_DRAFT ;
170+ const setConfigDraft = useCallback (
171+ ( config : Record < string , unknown > | undefined ) => {
172+ setConfigByDriver ( ( existing ) => {
173+ const next = { ...existing } ;
174+ if ( config === undefined || Object . keys ( config ) . length === 0 ) {
175+ delete next [ driver ] ;
176+ } else {
177+ next [ driver ] = config ;
178+ }
179+ return next ;
180+ } ) ;
173181 } ,
174182 [ driver ] ,
175183 ) ;
@@ -178,13 +186,7 @@ export function AddProviderInstanceDialog({ open, onOpenChange }: AddProviderIns
178186 setHasAttemptedSubmit ( true ) ;
179187 if ( instanceIdError !== null ) return ;
180188
181- // Build the config blob from non-empty driver-specific field values.
182- // Empty strings are dropped so defaults remain in effect on the server.
183- const config : Record < string , string > = { } ;
184- for ( const field of driverOption . fields ) {
185- const value = ( fieldValues [ `${ driver } :${ field . key } ` ] ?? "" ) . trim ( ) ;
186- if ( value . length > 0 ) config [ field . key ] = value ;
187- }
189+ const config = configByDriver [ driver ] ?? { } ;
188190 const hasConfig = Object . keys ( config ) . length > 0 ;
189191 const normalizedAccentColor = normalizeProviderAccentColor ( accentColor ) ;
190192
@@ -222,7 +224,7 @@ export function AddProviderInstanceDialog({ open, onOpenChange }: AddProviderIns
222224 } , [
223225 driver ,
224226 driverOption ,
225- fieldValues ,
227+ configByDriver ,
226228 instanceId ,
227229 instanceIdError ,
228230 label ,
@@ -433,25 +435,15 @@ export function AddProviderInstanceDialog({ open, onOpenChange }: AddProviderIns
433435 </ span >
434436 </ div >
435437
436- { driverOption . fields . length > 0 ? (
438+ { driverSettingsFields . length > 0 ? (
437439 < div className = { cn ( "grid gap-4" , wizardStep !== 2 && "hidden" ) } >
438- { driverOption . fields . map ( ( field ) => (
439- < label key = { field . key } className = "grid gap-1.5" >
440- < span className = "text-xs font-medium text-foreground" > { field . label } </ span >
441- < Input
442- className = "bg-background"
443- type = { field . type === "password" ? "password" : undefined }
444- autoComplete = { field . type === "password" ? "off" : undefined }
445- placeholder = { field . placeholder }
446- value = { getFieldValue ( field . key ) }
447- onChange = { ( event ) => setFieldValue ( field . key , event . target . value ) }
448- spellCheck = { false }
449- />
450- { field . description ? (
451- < span className = "text-[11px] text-muted-foreground" > { field . description } </ span >
452- ) : null }
453- </ label >
454- ) ) }
440+ < ProviderSettingsForm
441+ definition = { driverOption }
442+ value = { configDraft }
443+ idPrefix = { `add-provider-${ driver } ` }
444+ variant = "dialog"
445+ onChange = { setConfigDraft }
446+ />
455447 </ div >
456448 ) : wizardStep === 2 ? (
457449 < div className = "grid gap-2" >
0 commit comments