1- import React , { useState } from 'react' ;
1+ import React , { useState , useEffect , useCallback } from 'react' ;
22import { IconButton } from '~/components/ui/IconButton' ;
33import type { ProviderInfo } from '~/types/model' ;
44import Cookies from 'js-cookie' ;
5+ import { providerBaseUrlEnvKeys } from '~/utils/constants' ;
56
67interface APIKeyManagerProps {
78 provider : ProviderInfo ;
@@ -11,11 +12,14 @@ interface APIKeyManagerProps {
1112 labelForGetApiKey ?: string ;
1213}
1314
15+ // cache which stores whether the provider's API key is set via environment variable
16+ const providerEnvKeyStatusCache : Record < string , boolean > = { } ;
17+
1418const apiKeyMemoizeCache : { [ k : string ] : Record < string , string > } = { } ;
1519
1620export function getApiKeysFromCookies ( ) {
1721 const storedApiKeys = Cookies . get ( 'apiKeys' ) ;
18- let parsedKeys = { } ;
22+ let parsedKeys : Record < string , string > = { } ;
1923
2024 if ( storedApiKeys ) {
2125 parsedKeys = apiKeyMemoizeCache [ storedApiKeys ] ;
@@ -32,54 +36,137 @@ export function getApiKeysFromCookies() {
3236export const APIKeyManager : React . FC < APIKeyManagerProps > = ( { provider, apiKey, setApiKey } ) => {
3337 const [ isEditing , setIsEditing ] = useState ( false ) ;
3438 const [ tempKey , setTempKey ] = useState ( apiKey ) ;
39+ const [ isEnvKeySet , setIsEnvKeySet ] = useState ( false ) ;
40+
41+ // Reset states and load saved key when provider changes
42+ useEffect ( ( ) => {
43+ // Load saved API key from cookies for this provider
44+ const savedKeys = getApiKeysFromCookies ( ) ;
45+ const savedKey = savedKeys [ provider . name ] || '' ;
46+
47+ setTempKey ( savedKey ) ;
48+ setApiKey ( savedKey ) ;
49+ setIsEditing ( false ) ;
50+ } , [ provider . name ] ) ;
51+
52+ const checkEnvApiKey = useCallback ( async ( ) => {
53+ // Check cache first
54+ if ( providerEnvKeyStatusCache [ provider . name ] !== undefined ) {
55+ setIsEnvKeySet ( providerEnvKeyStatusCache [ provider . name ] ) ;
56+ return ;
57+ }
58+
59+ try {
60+ const response = await fetch ( `/api/check-env-key?provider=${ encodeURIComponent ( provider . name ) } ` ) ;
61+ const data = await response . json ( ) ;
62+ const isSet = ( data as { isSet : boolean } ) . isSet ;
63+
64+ // Cache the result
65+ providerEnvKeyStatusCache [ provider . name ] = isSet ;
66+ setIsEnvKeySet ( isSet ) ;
67+ } catch ( error ) {
68+ console . error ( 'Failed to check environment API key:' , error ) ;
69+ setIsEnvKeySet ( false ) ;
70+ }
71+ } , [ provider . name ] ) ;
72+
73+ useEffect ( ( ) => {
74+ checkEnvApiKey ( ) ;
75+ } , [ checkEnvApiKey ] ) ;
3576
3677 const handleSave = ( ) => {
78+ // Save to parent state
3779 setApiKey ( tempKey ) ;
80+
81+ // Save to cookies
82+ const currentKeys = getApiKeysFromCookies ( ) ;
83+ const newKeys = { ...currentKeys , [ provider . name ] : tempKey } ;
84+ Cookies . set ( 'apiKeys' , JSON . stringify ( newKeys ) ) ;
85+
3886 setIsEditing ( false ) ;
3987 } ;
4088
4189 return (
42- < div className = "flex items-start sm:items-center mt-2 mb-2 flex-col sm:flex-row" >
43- < div >
44- < span className = "text-sm text-bolt-elements-textSecondary" > { provider ?. name } API Key:</ span >
45- { ! isEditing && (
46- < div className = "flex items-center mb-4" >
47- < span className = "flex-1 text-xs text-bolt-elements-textPrimary mr-2" >
48- { apiKey ? '••••••••' : 'Not set (will still work if set in .env file)' }
49- </ span >
50- < IconButton onClick = { ( ) => setIsEditing ( true ) } title = "Edit API Key" >
51- < div className = "i-ph:pencil-simple" />
90+ < div className = "flex items-center justify-between py-3 px-1" >
91+ < div className = "flex items-center gap-2 flex-1" >
92+ < div className = "flex items-center gap-2" >
93+ < span className = "text-sm font-medium text-bolt-elements-textSecondary" > { provider ?. name } API Key:</ span >
94+ { ! isEditing && (
95+ < div className = "flex items-center gap-2" >
96+ { isEnvKeySet ? (
97+ < >
98+ < div className = "i-ph:check-circle-fill text-green-500 w-4 h-4" />
99+ < span className = "text-xs text-green-500" >
100+ Set via { providerBaseUrlEnvKeys [ provider . name ] . apiTokenKey } environment variable
101+ </ span >
102+ </ >
103+ ) : apiKey ? (
104+ < >
105+ < div className = "i-ph:check-circle-fill text-green-500 w-4 h-4" />
106+ < span className = "text-xs text-green-500" > Set via UI</ span >
107+ </ >
108+ ) : (
109+ < >
110+ < div className = "i-ph:x-circle-fill text-red-500 w-4 h-4" />
111+ < span className = "text-xs text-red-500" > Not Set (Please set via UI or ENV_VAR)</ span >
112+ </ >
113+ ) }
114+ </ div >
115+ ) }
116+ </ div >
117+ </ div >
118+
119+ < div className = "flex items-center gap-2 shrink-0" >
120+ { isEditing && ! isEnvKeySet ? (
121+ < div className = "flex items-center gap-2" >
122+ < input
123+ type = "password"
124+ value = { tempKey }
125+ placeholder = "Enter API Key"
126+ onChange = { ( e ) => setTempKey ( e . target . value ) }
127+ className = "w-[300px] px-3 py-1.5 text-sm rounded border border-bolt-elements-borderColor
128+ bg-bolt-elements-prompt-background text-bolt-elements-textPrimary
129+ focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus"
130+ />
131+ < IconButton
132+ onClick = { handleSave }
133+ title = "Save API Key"
134+ className = "bg-green-500/10 hover:bg-green-500/20 text-green-500"
135+ >
136+ < div className = "i-ph:check w-4 h-4" />
137+ </ IconButton >
138+ < IconButton
139+ onClick = { ( ) => setIsEditing ( false ) }
140+ title = "Cancel"
141+ className = "bg-red-500/10 hover:bg-red-500/20 text-red-500"
142+ >
143+ < div className = "i-ph:x w-4 h-4" />
52144 </ IconButton >
53145 </ div >
146+ ) : (
147+ < >
148+ { ! isEnvKeySet && (
149+ < IconButton
150+ onClick = { ( ) => setIsEditing ( true ) }
151+ title = "Edit API Key"
152+ className = "bg-blue-500/10 hover:bg-blue-500/20 text-blue-500"
153+ >
154+ < div className = "i-ph:pencil-simple w-4 h-4" />
155+ </ IconButton >
156+ ) }
157+ { provider ?. getApiKeyLink && ! isEnvKeySet && (
158+ < IconButton
159+ onClick = { ( ) => window . open ( provider ?. getApiKeyLink ) }
160+ title = "Get API Key"
161+ className = "bg-purple-500/10 hover:bg-purple-500/20 text-purple-500 flex items-center gap-2"
162+ >
163+ < span className = "text-xs whitespace-nowrap" > { provider ?. labelForGetApiKey || 'Get API Key' } </ span >
164+ < div className = { `${ provider ?. icon || 'i-ph:key' } w-4 h-4` } />
165+ </ IconButton >
166+ ) }
167+ </ >
54168 ) }
55169 </ div >
56-
57- { isEditing ? (
58- < div className = "flex items-center gap-3 mt-2" >
59- < input
60- type = "password"
61- value = { tempKey }
62- placeholder = "Your API Key"
63- onChange = { ( e ) => setTempKey ( e . target . value ) }
64- className = "flex-1 px-2 py-1 text-xs lg:text-sm rounded border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus"
65- />
66- < IconButton onClick = { handleSave } title = "Save API Key" >
67- < div className = "i-ph:check" />
68- </ IconButton >
69- < IconButton onClick = { ( ) => setIsEditing ( false ) } title = "Cancel" >
70- < div className = "i-ph:x" />
71- </ IconButton >
72- </ div >
73- ) : (
74- < >
75- { provider ?. getApiKeyLink && (
76- < IconButton className = "ml-auto" onClick = { ( ) => window . open ( provider ?. getApiKeyLink ) } title = "Edit API Key" >
77- < span className = "mr-2 text-xs lg:text-sm" > { provider ?. labelForGetApiKey || 'Get API Key' } </ span >
78- < div className = { provider ?. icon || 'i-ph:key' } />
79- </ IconButton >
80- ) }
81- </ >
82- ) }
83170 </ div >
84171 ) ;
85172} ;
0 commit comments