diff --git a/backend/services/settingsService.js b/backend/services/settingsService.js index 1d3bafe..10ad35a 100644 --- a/backend/services/settingsService.js +++ b/backend/services/settingsService.js @@ -428,14 +428,16 @@ class SettingsService extends BaseService{ } } - const hasKey = Boolean(keyRow && keyRow.value && String(keyRow.value).trim()); + const hasStoredKey = Boolean(keyRow && keyRow.getDataValue('value') && String(keyRow.getDataValue('value')).trim()); + const hasReadableKey = Boolean(keyRow && keyRow.value && String(keyRow.value).trim()); return { enabled: parsed.enabled !== false, baseUrl: parsed.baseUrl || '', model: parsed.model || 'gpt-4o-mini', - hasKey, - keyLast4: parsed.keyLast4 || null + hasKey: hasStoredKey, + keyLast4: parsed.keyLast4 || null, + keyStatus: hasStoredKey ? (hasReadableKey ? 'stored' : 'invalid') : 'missing' }; } diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index cd46739..8dc01c2 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -12,7 +12,6 @@ import UserParam from '../models/community/user_param.js'; import { sequelize } from '../utils/sequelize.js'; import { notifyUser } from '../utils/socket.js'; import { Op } from 'sequelize'; -import { decrypt } from '../utils/encryption.js'; export default class VocabService { async _getUserByHashedId(hashedUserId) { @@ -56,7 +55,7 @@ export default class VocabService { } } - const decryptedKey = keyRow?.value ? decrypt(keyRow.value) : null; + const decryptedKey = keyRow?.value ? String(keyRow.value).trim() : null; const hasKey = Boolean(decryptedKey && String(decryptedKey).trim()); const enabled = parsed.enabled !== false; const baseUrl = String(parsed.baseUrl || '').trim(); diff --git a/frontend/src/i18n/locales/de/settings.json b/frontend/src/i18n/locales/de/settings.json index 1f9b868..01d5439 100644 --- a/frontend/src/i18n/locales/de/settings.json +++ b/frontend/src/i18n/locales/de/settings.json @@ -169,7 +169,10 @@ "save": "Speichern", "saved": "Einstellungen gespeichert.", "saveError": "Speichern fehlgeschlagen.", - "confirmClear": "API-Schlüssel wirklich löschen?" + "confirmClear": "API-Schlüssel wirklich löschen?", + "keyStatusStored": "API-Schlüssel gespeichert.", + "keyStatusInvalid": "Ein gespeicherter API-Schlüssel ist vorhanden, kann aber nicht gelesen werden. Bitte neu speichern.", + "keyStatusMissing": "Derzeit ist kein API-Schlüssel gespeichert." }, "interests": { "title": "Interessen", diff --git a/frontend/src/i18n/locales/en/settings.json b/frontend/src/i18n/locales/en/settings.json index 053e7f0..d3a9f00 100644 --- a/frontend/src/i18n/locales/en/settings.json +++ b/frontend/src/i18n/locales/en/settings.json @@ -169,7 +169,10 @@ "save": "Save", "saved": "Settings saved.", "saveError": "Could not save.", - "confirmClear": "Really delete the API key?" + "confirmClear": "Really delete the API key?", + "keyStatusStored": "API key stored.", + "keyStatusInvalid": "A stored API key exists, but it cannot be read. Please save it again.", + "keyStatusMissing": "No API key is currently stored." }, "interests": { "title": "Interests", @@ -198,4 +201,4 @@ } } } -} \ No newline at end of file +} diff --git a/frontend/src/i18n/locales/es/settings.json b/frontend/src/i18n/locales/es/settings.json index 09ea7f3..9055e60 100644 --- a/frontend/src/i18n/locales/es/settings.json +++ b/frontend/src/i18n/locales/es/settings.json @@ -169,7 +169,10 @@ "save": "Guardar", "saved": "Ajustes guardados.", "saveError": "No se pudo guardar.", - "confirmClear": "¿Eliminar realmente la clave API?" + "confirmClear": "¿Eliminar realmente la clave API?", + "keyStatusStored": "Clave API guardada.", + "keyStatusInvalid": "Existe una clave API guardada, pero no se puede leer. Guárdala de nuevo.", + "keyStatusMissing": "Actualmente no hay ninguna clave API guardada." }, "interests": { "title": "Intereses", diff --git a/frontend/src/views/settings/LanguageAssistantView.vue b/frontend/src/views/settings/LanguageAssistantView.vue index 4845cf4..71e7d06 100644 --- a/frontend/src/views/settings/LanguageAssistantView.vue +++ b/frontend/src/views/settings/LanguageAssistantView.vue @@ -51,6 +51,9 @@ :placeholder="apiKeyPlaceholder" /> {{ $t('settings.languageAssistant.apiKeyHint') }} + + {{ keyStatusLabel }} + @@ -84,6 +87,7 @@ export default { }, hasKey: false, keyLast4: null, + keyStatus: 'missing', saving: false, loadError: null }; @@ -100,6 +104,14 @@ export default { }); } return this.$t('settings.languageAssistant.apiKeyPlaceholderNew'); + }, + keyStatusLabel() { + const statusKey = { + stored: 'settings.languageAssistant.keyStatusStored', + invalid: 'settings.languageAssistant.keyStatusInvalid', + missing: 'settings.languageAssistant.keyStatusMissing' + }[this.keyStatus] || 'settings.languageAssistant.keyStatusMissing'; + return this.$t(statusKey); } }, async mounted() { @@ -115,6 +127,7 @@ export default { this.form.model = data.model || 'gpt-4o-mini'; this.hasKey = data.hasKey; this.keyLast4 = data.keyLast4; + this.keyStatus = data.keyStatus || (data.hasKey ? 'stored' : 'missing'); this.form.apiKey = ''; this.form.clearKey = false; } catch (e) { @@ -208,6 +221,18 @@ export default { font-size: 0.8rem; opacity: 0.8; } +.language-assistant-settings__key-status { + font-weight: 600; +} +.language-assistant-settings__key-status--stored { + color: #2e7d32; +} +.language-assistant-settings__key-status--invalid { + color: #c62828; +} +.language-assistant-settings__key-status--missing { + color: #8a6d3b; +} .language-assistant-settings__toggle { display: flex; align-items: center;