diff --git a/crates/desktop/artcraft/src/core/commands/providers/provider_list_command.rs b/crates/desktop/artcraft/src/core/commands/providers/provider_list_command.rs index 197d1a0787..55eee65b61 100644 --- a/crates/desktop/artcraft/src/core/commands/providers/provider_list_command.rs +++ b/crates/desktop/artcraft/src/core/commands/providers/provider_list_command.rs @@ -39,6 +39,8 @@ pub struct ProviderListEntry { pub struct ProviderCredentialDetails { /// For API keys: the first few characters followed by asterisks. pub maybe_key_start: Option, + /// For API keys: the full key value. + pub maybe_full_key: Option, /// For web logins: the email address if available. pub maybe_email_address: Option, /// For web logins: the username if available. @@ -68,6 +70,7 @@ pub async fn provider_list_command( ProviderCredentialPayload::ApiKey(data) => { ProviderCredentialDetails { maybe_key_start: Some(redact_key(data.as_str())), + maybe_full_key: Some(data.as_str().to_string()), maybe_email_address: None, maybe_username: None, } @@ -75,6 +78,7 @@ pub async fn provider_list_command( ProviderCredentialPayload::WebLogin(data) => { ProviderCredentialDetails { maybe_key_start: None, + maybe_full_key: None, maybe_email_address: data.email_address, maybe_username: data.username, } diff --git a/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ProviderCredentialsGroupBlock.tsx b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ProviderCredentialsGroupBlock.tsx index c9873ed7a1..fcfca1604c 100644 --- a/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ProviderCredentialsGroupBlock.tsx +++ b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ProviderCredentialsGroupBlock.tsx @@ -13,6 +13,7 @@ type ProviderCredentialKey = interface ProviderCredentialDetails { maybe_key_start: string | null; + maybe_full_key: string | null; maybe_email_address: string | null; maybe_username: string | null; } @@ -34,36 +35,62 @@ interface ApiKeyRowProps { label: string; credentialKey: ProviderCredentialKey; initialRedactedValue: string; - hasCredentials: boolean; + initialFullKey: string; } const ApiKeyRow = ({ label, credentialKey, initialRedactedValue, - hasCredentials, + initialFullKey, }: ApiKeyRowProps) => { - const [value, setValue] = useState(""); + // The actual key value (editable). + const [fullKey, setFullKey] = useState(initialFullKey); + // The redacted display value. + const [redacted, setRedacted] = useState(initialRedactedValue); + // Whether the input is focused (show real value vs redacted). + const [isFocused, setIsFocused] = useState(false); + // Track if user changed something since last save. + const [isDirty, setIsDirty] = useState(false); const handleChange = (e: React.ChangeEvent) => { const newValue = e.target.value; - console.log(`[ProviderCredentials] onChange fired for ${credentialKey}: "${newValue}"`); - setValue(newValue); + setFullKey(newValue); + setIsDirty(true); + }; - if (newValue.trim() === "") { - console.log(`[ProviderCredentials] Clearing ${credentialKey}...`); + const handleFocus = () => { + setIsFocused(true); + }; + + const handleBlur = () => { + setIsFocused(false); + + if (!isDirty) return; + setIsDirty(false); + + const trimmed = fullKey.trim(); + + if (trimmed === "") { invoke("provider_clear_command", { request: { provider_credential: credentialKey }, }).then(() => { + setFullKey(""); + setRedacted(""); console.log(`[ProviderCredentials] Cleared ${credentialKey}`); }).catch((e) => { console.error(`[ProviderCredentials] Error clearing ${credentialKey}:`, e); }); } else { - console.log(`[ProviderCredentials] Saving ${credentialKey}...`); invoke("provider_set_api_key_command", { - request: { provider_credential: credentialKey, api_key: newValue }, + request: { provider_credential: credentialKey, api_key: trimmed }, }).then(() => { + setFullKey(trimmed); + setRedacted( + trimmed.length > 6 + ? trimmed.substring(0, 6) + "********" + : "********" + ); console.log(`[ProviderCredentials] Saved ${credentialKey}`); }).catch((e) => { console.error(`[ProviderCredentials] Error saving ${credentialKey}:`, e); @@ -71,15 +98,27 @@ const ApiKeyRow = ({ } }; + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + (e.target as HTMLInputElement).blur(); + } + }; + + const hasKey = isFocused ? fullKey.length > 0 : redacted.length > 0; + return (
); @@ -94,11 +133,9 @@ export const ProviderCredentialsGroupBlock = () => { useEffect(() => { const fetchProviders = async () => { try { - console.log("[ProviderCredentials] Fetching provider list..."); const result = (await invoke("provider_list_command")) as { payload: ProviderListResponse; }; - console.log("[ProviderCredentials] Got providers:", result.payload.providers); setProviders(result.payload.providers); } catch (e) { console.error("[ProviderCredentials] Error fetching provider list:", e); @@ -128,7 +165,9 @@ export const ProviderCredentialsGroupBlock = () => { initialRedactedValue={ falProvider?.maybe_details?.maybe_key_start ?? "" } - hasCredentials={falProvider?.has_credentials ?? false} + initialFullKey={ + falProvider?.maybe_details?.maybe_full_key ?? "" + } /> { initialRedactedValue={ replicateProvider?.maybe_details?.maybe_key_start ?? "" } - hasCredentials={replicateProvider?.has_credentials ?? false} + initialFullKey={ + replicateProvider?.maybe_details?.maybe_full_key ?? "" + } /> );