diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index e1ac4abb..a4dd886b 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -1205,6 +1205,7 @@ "Transcribe as you speak, cutting on pauses": "Transcribe as you speak, cutting on pauses", "Voice dictation is not available yet.": "Voice dictation is not available yet.", "Test endpoint": "Test endpoint", + "Save and test": "Save and test", "Save endpoints": "Save endpoints", "Configured and enabled": "Configured and enabled", "Configured but disabled": "Configured but disabled", diff --git a/apps/client/src/features/workspace/components/settings/components/ai-provider-settings.tsx b/apps/client/src/features/workspace/components/settings/components/ai-provider-settings.tsx index f57348a1..713d9b65 100644 --- a/apps/client/src/features/workspace/components/settings/components/ai-provider-settings.tsx +++ b/apps/client/src/features/workspace/components/settings/components/ai-provider-settings.tsx @@ -35,6 +35,7 @@ import { useUpdateAiSettingsMutation, } from "@/features/workspace/queries/ai-settings-query.ts"; import { + AiTestCapability, IAiSettingsUpdate, SttApiStyle, } from "@/features/workspace/services/ai-settings-service.ts"; @@ -252,6 +253,12 @@ export default function AiProviderSettings() { const embedTest = useTestAiConnectionMutation(); const sttTest = useTestAiConnectionMutation(); + // Which card's "Save and test" is currently mid-save. The save mutation is + // shared, so without this every save-and-test button would spin at once; + // this lets only the clicked card's button show the spinner during the save. + const [savingTestCapability, setSavingTestCapability] = + useState(null); + // Agent roles drive the public-share assistant identity picker. Admin-gated // (the component returns early for non-admins), same as the AI settings query. const { data: roles } = useAiRolesQuery(isAdmin); @@ -408,6 +415,28 @@ export default function AiProviderSettings() { form.resetDirty(); } + // "Save and test" for a single card: the connection test probes the + // SERVER-STORED settings, so the whole form must be persisted before testing. + // Save first (handleSubmit rethrows on failure and the mutation already shows + // its own error notification); only run the probe on a successful save. + async function handleSaveAndTest( + capability: AiTestCapability, + test: ReturnType, + ) { + setSavingTestCapability(capability); + // Clear any previous probe result so the stale "successful/failed" text does + // not linger next to the spinner while the (now preceding) save runs. + test.reset(); + try { + await handleSubmit(form.values); + } catch { + return; // save failed — error already surfaced; do not test stale settings + } finally { + setSavingTestCapability(null); + } + test.mutate(capability); + } + function handleClearKey() { setKeyCleared(true); setHasApiKey(false); @@ -780,10 +809,13 @@ export default function AiProviderSettings() { {chatTest.data && (chatTest.data.ok ? ( @@ -905,10 +937,13 @@ export default function AiProviderSettings() { {embedTest.data && (embedTest.data.ok ? ( @@ -1099,10 +1134,13 @@ export default function AiProviderSettings() { {sttTest.data && (sttTest.data.ok ? (