@@ -33,6 +33,7 @@ import {
3333 connectionDisplayName ,
3434 parseHeaderOverrides ,
3535} from "@/lib/credentials-source-helpers" ;
36+ import { sourceForCredentialKey } from "@/lib/tools-source-helpers" ;
3637import { ConnectionAuthFields } from "./connection-auth-fields" ;
3738import { type ConnectionMode } from "./connection-form-dialog-state" ;
3839import { useConnectionFormDialogForm } from "./connection-form-dialog-form" ;
@@ -50,6 +51,7 @@ export function ConnectionFormDialog({
5051 sources,
5152 credentials,
5253 sourceAuthProfiles,
54+ loadingSourceNames = [ ] ,
5355} : {
5456 open : boolean ;
5557 onOpenChange : ( open : boolean ) => void ;
@@ -58,6 +60,7 @@ export function ConnectionFormDialog({
5860 sources : ToolSourceRecord [ ] ;
5961 credentials : CredentialRecord [ ] ;
6062 sourceAuthProfiles : Record < string , SourceAuthProfile > ;
63+ loadingSourceNames ?: string [ ] ;
6164} ) {
6265 const { context, clientConfig } = useSession ( ) ;
6366 const upsertCredential = useAction ( convexApi . credentialsNode . upsertCredential ) ;
@@ -101,17 +104,27 @@ export function ConnectionFormDialog({
101104 const storageCopy = clientConfig ?. authProviderMode === "workos"
102105 ? "Stored encrypted"
103106 : "Stored locally on this machine" ;
107+ const selectedSource = sourceForCredentialKey ( sources , sourceKey ) ;
108+ const selectedSourceName = selectedSource ?. name ;
109+ const authDetectionPending =
110+ ! editing
111+ && selectedAuth . type === "none"
112+ && Boolean ( selectedSource )
113+ && Boolean ( selectedSource ?. type === "openapi" || selectedSource ?. type === "graphql" )
114+ && selectedSource ?. config . auth === undefined
115+ && ( selectedSourceName ? loadingSourceNames . includes ( selectedSourceName ) : false ) ;
116+ const detectedAuthLabel = authDetectionPending ? "Detecting..." : authBadge ;
104117
105118 const handleSave = async ( ) => {
106119 if ( ! context ) {
107120 return ;
108121 }
109122 if ( ! sourceKey . trim ( ) ) {
110- toast . error ( "Source key is required " ) ;
123+ toast . error ( "Choose an API source " ) ;
111124 return ;
112125 }
113126 if ( scope === "actor" && ! actorId . trim ( ) ) {
114- toast . error ( "Actor ID is required for actor-scoped credentials" ) ;
127+ toast . error ( "User ID is required for personal credentials" ) ;
115128 return ;
116129 }
117130
@@ -123,21 +136,25 @@ export function ConnectionFormDialog({
123136
124137 const linkExisting = ! editing && connectionMode === "existing" ;
125138 if ( linkExisting && ! existingConnectionId ) {
126- toast . error ( "Select an existing connection to link " ) ;
139+ toast . error ( "Select saved credentials " ) ;
127140 return ;
128141 }
129142 if ( linkExisting && ! compatibleConnectionOptions . some ( ( connection ) => connection . id === existingConnectionId ) ) {
130- toast . error ( "Selected connection does not match this scope" ) ;
143+ toast . error ( "Selected credentials do not match this scope" ) ;
131144 return ;
132145 }
133146
134147 if ( selectedAuth . type === "none" ) {
135- toast . error ( "This source does not require auth" ) ;
148+ if ( authDetectionPending ) {
149+ toast . error ( "Still detecting auth from the API spec. Try again in a few seconds." ) ;
150+ return ;
151+ }
152+ toast . error ( "This source is currently set to no auth. Configure auth before adding a connection." ) ;
136153 return ;
137154 }
138155
139156 if ( selectedAuth . type === "mixed" && ! linkExisting && ! editing ) {
140- toast . error ( "Mixed-auth sources must link to an existing connection " ) ;
157+ toast . error ( "This API uses mixed auth and must reuse saved credentials " ) ;
141158 return ;
142159 }
143160
@@ -183,16 +200,16 @@ export function ConnectionFormDialog({
183200 < DialogContent className = "bg-card border-border sm:max-w-lg" >
184201 < DialogHeader >
185202 < DialogTitle className = "text-sm font-medium" >
186- { editing ? "Edit Connection" : "Add Connection " }
203+ { editing ? "Update Connection" : "Connect API " }
187204 </ DialogTitle >
188205 </ DialogHeader >
189206 < div className = "space-y-3" >
190207 < div className = "space-y-1.5" >
191- < Label className = "text-xs text-muted-foreground" > Source</ Label >
208+ < Label className = "text-xs text-muted-foreground" > API Source</ Label >
192209 { sourceOptions . length > 0 ? (
193210 < Select value = { sourceKey } onValueChange = { handleSourceKeyChange } >
194211 < SelectTrigger className = "h-8 text-xs bg-background" >
195- < SelectValue />
212+ < SelectValue placeholder = "Select a source" />
196213 </ SelectTrigger >
197214 < SelectContent >
198215 { sourceOptions . map ( ( entry ) => (
@@ -206,42 +223,46 @@ export function ConnectionFormDialog({
206223 < Input
207224 value = { sourceKey }
208225 onChange = { ( e ) => handleSourceKeyChange ( e . target . value ) }
209- placeholder = "source:<source-id> "
226+ placeholder = "Enter source id "
210227 className = "h-8 text-xs font-mono bg-background"
211228 />
212229 ) }
213- { sourceKey && (
214- < p className = "text-[10px] text-muted-foreground font-mono" > key: { sourceKey } </ p >
215- ) }
216230 </ div >
217231
218- < div className = "flex items-center gap-2 flex-wrap" >
219- < span className = "text-[11px] text-muted-foreground" > Detected auth</ span >
220- < Badge variant = "outline" className = "text-[9px] font-mono uppercase tracking-wider" >
221- { authBadge }
222- </ Badge >
223- { selectedAuth . inferred && (
232+ < div className = "rounded-md border border-border/70 bg-muted/30 px-2.5 py-2" >
233+ < div className = "flex items-center gap-2 flex-wrap" >
234+ < span className = "text-[11px] text-muted-foreground" > Detected auth</ span >
224235 < Badge variant = "outline" className = "text-[9px] font-mono uppercase tracking-wider" >
225- inferred
236+ { detectedAuthLabel }
226237 </ Badge >
238+ { selectedAuth . inferred && ! authDetectionPending && (
239+ < Badge variant = "outline" className = "text-[9px] uppercase tracking-wider" >
240+ inferred from spec
241+ </ Badge >
242+ ) }
243+ </ div >
244+ { authDetectionPending && (
245+ < p className = "text-[10px] text-muted-foreground mt-1" >
246+ This spec is still loading. Auth settings will auto-fill once parsing completes.
247+ </ p >
227248 ) }
228249 { selectedAuth . type === "apiKey" && selectedAuth . header && (
229- < span className = "text-[10px] font-mono text-muted-foreground bg-muted px-1.5 py-0.5 rounded " >
230- header: { selectedAuth . header }
231- </ span >
250+ < p className = "text-[10px] text-muted-foreground mt-1 " >
251+ API key header: < span className = "font-mono" > { selectedAuth . header } </ span >
252+ </ p >
232253 ) }
233254 </ div >
234255
235256 < div className = "grid grid-cols-2 gap-3" >
236257 < div className = "space-y-1.5" >
237- < Label className = "text-xs text-muted-foreground" > Scope </ Label >
258+ < Label className = "text-xs text-muted-foreground" > Who this is for </ Label >
238259 < Select value = { scope } onValueChange = { ( value ) => setScope ( value as CredentialScope ) } >
239260 < SelectTrigger className = "h-8 text-xs bg-background" >
240261 < SelectValue />
241262 </ SelectTrigger >
242263 < SelectContent >
243- < SelectItem value = "workspace" className = "text-xs" > Workspace</ SelectItem >
244- < SelectItem value = "actor" className = "text-xs" > Per-user (actor )</ SelectItem >
264+ < SelectItem value = "workspace" className = "text-xs" > Workspace-wide </ SelectItem >
265+ < SelectItem value = "actor" className = "text-xs" > Only me (current user )</ SelectItem >
245266 </ SelectContent >
246267 </ Select >
247268 </ div >
@@ -253,7 +274,7 @@ export function ConnectionFormDialog({
253274
254275 { scope === "actor" && (
255276 < div className = "space-y-1.5" >
256- < Label className = "text-xs text-muted-foreground" > Actor ID</ Label >
277+ < Label className = "text-xs text-muted-foreground" > User ID</ Label >
257278 < Input
258279 value = { actorId }
259280 onChange = { ( e ) => setActorId ( e . target . value ) }
@@ -265,36 +286,36 @@ export function ConnectionFormDialog({
265286
266287 { ! editing && connectionOptions . length > 0 && (
267288 < div className = "space-y-1.5" >
268- < Label className = "text-xs text-muted-foreground" > Connection Mode </ Label >
289+ < Label className = "text-xs text-muted-foreground" > How to connect </ Label >
269290 < Select value = { connectionMode } onValueChange = { ( value ) => setConnectionMode ( value as ConnectionMode ) } >
270291 < SelectTrigger className = "h-8 text-xs bg-background" >
271292 < SelectValue />
272293 </ SelectTrigger >
273294 < SelectContent >
274- < SelectItem value = "new" className = "text-xs" > Create new connection </ SelectItem >
275- < SelectItem value = "existing" className = "text-xs" > Use existing connection </ SelectItem >
295+ < SelectItem value = "new" className = "text-xs" > Enter new credentials </ SelectItem >
296+ < SelectItem value = "existing" className = "text-xs" > Reuse saved credentials </ SelectItem >
276297 </ SelectContent >
277298 </ Select >
278299 </ div >
279300 ) }
280301
281302 { ! editing && connectionMode === "existing" && (
282303 < div className = "space-y-1.5" >
283- < Label className = "text-xs text-muted-foreground" > Existing Connection </ Label >
304+ < Label className = "text-xs text-muted-foreground" > Saved Credentials </ Label >
284305 < Select value = { existingConnectionId } onValueChange = { setExistingConnectionId } >
285306 < SelectTrigger className = "h-8 text-xs bg-background" >
286- < SelectValue placeholder = "Select a connection " />
307+ < SelectValue placeholder = "Select saved credentials " />
287308 </ SelectTrigger >
288309 < SelectContent >
289310 { compatibleConnectionOptions . map ( ( connection ) => (
290311 < SelectItem key = { connection . id } value = { connection . id } className = "text-xs" >
291- { connectionDisplayName ( sources , connection ) } ({ connection . sourceKeys . size } source { connection . sourceKeys . size === 1 ? "" : "s" } )
312+ { connectionDisplayName ( sources , connection ) } ({ connection . sourceKeys . size } API { connection . sourceKeys . size === 1 ? "" : "s" } )
292313 </ SelectItem >
293314 ) ) }
294315 </ SelectContent >
295316 </ Select >
296317 { compatibleConnectionOptions . length === 0 && (
297- < p className = "text-[10px] text-muted-foreground" > No compatible existing connections for this scope.</ p >
318+ < p className = "text-[10px] text-muted-foreground" > No compatible saved credentials for this scope.</ p >
298319 ) }
299320 </ div >
300321 ) }
@@ -314,7 +335,7 @@ export function ConnectionFormDialog({
314335 />
315336
316337 < div className = "space-y-1.5" >
317- < Label className = "text-xs text-muted-foreground" > Custom Headers (optional)</ Label >
338+ < Label className = "text-xs text-muted-foreground" > Extra Headers (optional)</ Label >
318339 < Textarea
319340 value = { customHeadersText }
320341 onChange = { ( e ) => setCustomHeadersText ( e . target . value ) }
@@ -324,7 +345,7 @@ export function ConnectionFormDialog({
324345 />
325346 </ div >
326347
327- < Button onClick = { handleSave } disabled = { saving } className = "w-full h-9" size = "sm" >
348+ < Button onClick = { handleSave } disabled = { saving || authDetectionPending } className = "w-full h-9" size = "sm" >
328349 { connectionSubmitCopy ( Boolean ( editing ) , saving , connectionMode ) }
329350 </ Button >
330351 </ div >
0 commit comments