diff --git a/app/src/components/settings/panels/AIPanel.tsx b/app/src/components/settings/panels/AIPanel.tsx index ac4c0fce07..e60f5326f5 100644 --- a/app/src/components/settings/panels/AIPanel.tsx +++ b/app/src/components/settings/panels/AIPanel.tsx @@ -760,7 +760,6 @@ const ProviderKeyDialog = ({ ); }; -// ───────────────────────────────────────────────────────────────────────────── // Background loop controls + usage diagnostics // ───────────────────────────────────────────────────────────────────────────── diff --git a/app/src/components/settings/panels/__tests__/AIPanel.test.tsx b/app/src/components/settings/panels/__tests__/AIPanel.test.tsx index 9065c079a0..1116bd7f33 100644 --- a/app/src/components/settings/panels/__tests__/AIPanel.test.tsx +++ b/app/src/components/settings/panels/__tests__/AIPanel.test.tsx @@ -38,7 +38,7 @@ vi.mock('../../../../services/api/aiSettingsApi', () => ({ saveAISettings: vi.fn(), loadLocalProviderSnapshot: vi.fn(), testProviderModel: vi.fn(), - setCloudProviderKey: vi.fn(), + setCloudProviderKey: vi.fn().mockResolvedValue(undefined), clearCloudProviderKey: vi.fn().mockResolvedValue(undefined), serializeProviderRef: vi.fn((r: { kind: string; providerSlug?: string; model?: string }) => r.kind === 'openhuman' @@ -356,6 +356,25 @@ describe('AIPanel', () => { expect(screen.getByLabelText(/API key/i)).toBeInTheDocument(); }); + it('surfaces provider setup errors in an alert with technical details collapsed', async () => { + vi.mocked(loadAISettings).mockResolvedValue({ ...baseSettings, cloudProviders: [] }); + vi.mocked(listProviderModels).mockRejectedValueOnce( + new Error('Could not reach OpenAI: provider returned 401 Unauthorized') + ); + + renderWithProviders(); + + fireEvent.click(await screen.findByRole('switch', { name: /Connect OpenAI/i })); + const dialog = await screen.findByRole('dialog', { name: /Connect OpenAI/i }); + fireEvent.change(within(dialog).getByLabelText(/API key/i), { + target: { value: 'sk-bad-key' }, + }); + fireEvent.click(within(dialog).getByRole('button', { name: /^Save$/i })); + + const alert = await within(dialog).findByRole('alert'); + expect(alert).toHaveTextContent(/rejected the credentials/i); + }); + it('clicking the OpenRouter chip shows both API key entry and the OAuth button', async () => { vi.mocked(loadAISettings).mockResolvedValue({ ...baseSettings, cloudProviders: [] }); diff --git a/app/src/lib/i18n/chunks/de-5.ts b/app/src/lib/i18n/chunks/de-5.ts index b88de30cf6..d0f425708e 100644 --- a/app/src/lib/i18n/chunks/de-5.ts +++ b/app/src/lib/i18n/chunks/de-5.ts @@ -241,9 +241,9 @@ const de5: TranslationMap = { 'settings.mascot.characterDesc': 'Charakterbeschreibung', 'settings.mascot.characterHeading': 'Zeichenüberschrift', 'settings.mascot.customGifError': - 'Gib eine HTTPS-.gif-URL, eine Loopback-HTTP-.gif-URL, eine file://-.gif-URL oder einen lokalen .gif-Pfad ein.', - 'settings.mascot.customGifHeading': 'Eigener GIF-Avatar', - 'settings.mascot.customGifLabel': 'URL des eigenen GIF-Avatars', + 'GIF konnte nicht geladen werden. Bitte überprüfe die URL und versuche es erneut.', + 'settings.mascot.customGifHeading': 'Benutzerdefinierter GIF-Avatar', + 'settings.mascot.customGifLabel': 'URL für benutzerdefinierten GIF-Avatar', 'settings.mascot.colorDesc': 'Farbbeschreibung', 'settings.mascot.colorHeading': 'Farbüberschrift', 'settings.mascot.loadingLibrary': 'OpenHuman-Bibliothek wird geladen…',