diff --git a/lib/core/auth/auth_state_manager.dart b/lib/core/auth/auth_state_manager.dart index a5f58fc..6883d20 100644 --- a/lib/core/auth/auth_state_manager.dart +++ b/lib/core/auth/auth_state_manager.dart @@ -187,6 +187,23 @@ class AuthStateManager extends _$AuthStateManager { if (token != null && token.isNotEmpty) { DebugLogger.auth('Found stored token during initialization'); + + // Check if stored token is an API key - force logout if so + if (TokenValidator.isApiKey(token)) { + DebugLogger.auth('Detected API key token, forcing logout'); + await storage.deleteAuthToken(); + await storage.deleteSavedCredentials(); + _update( + (current) => current.copyWith( + status: AuthStatus.credentialError, + error: 'apiKeyNoLongerSupported', + isLoading: false, + clearToken: true, + ), + ); + return; + } + // Fast path: trust token format to avoid blocking startup on network final formatOk = _isValidTokenFormat(token); if (formatOk) { @@ -270,7 +287,8 @@ class AuthStateManager extends _$AuthStateManager { } } - /// Perform login with API key + /// Perform login with JWT token + /// Note: API keys (sk-...) are not supported for streaming. Future loginWithApiKey( String apiKey, { bool rememberCredentials = false, @@ -284,9 +302,16 @@ class AuthStateManager extends _$AuthStateManager { ); try { - // Validate API key format + // Validate token is not empty if (apiKey.trim().isEmpty) { - throw Exception('API key cannot be empty'); + throw Exception('Token cannot be empty'); + } + + final tokenStr = apiKey.trim(); + + // Reject API keys - they don't support streaming + if (TokenValidator.isApiKey(tokenStr)) { + throw Exception('apiKeyNotSupported'); } // Ensure API service is available @@ -296,12 +321,9 @@ class AuthStateManager extends _$AuthStateManager { throw Exception('No server connection available'); } - // Use API key directly as Bearer token - final tokenStr = apiKey.trim(); - - // Validate token format (consistent with credentials method) + // Validate token format if (!_isValidTokenFormat(tokenStr)) { - throw Exception('Invalid API key format'); + throw Exception('Invalid token format'); } // Update API service with the API key @@ -315,16 +337,15 @@ class AuthStateManager extends _$AuthStateManager { final storage = ref.read(optimizedStorageServiceProvider); await storage.saveAuthToken(tokenStr); - // Save API key if requested (for convenience, though less secure than credentials) + // Save JWT token if requested if (rememberCredentials) { final activeServer = await ref.read(activeServerProvider.future); if (activeServer != null) { - // Store API key as a special credential type + // Store JWT as a special credential type await storage.saveCredentials( serverId: activeServer.id, - username: - 'api_key_user', // Special username to indicate API key auth - password: tokenStr, // Store API key in password field + username: 'jwt_user', // Special username to indicate JWT auth + password: tokenStr, // Store JWT in password field ); } } @@ -348,11 +369,11 @@ class AuthStateManager extends _$AuthStateManager { _loadUserData(); _prefetchConversations(); - DebugLogger.auth('API key login successful'); + DebugLogger.auth('JWT token login successful'); return true; } catch (e) { - // If user fetch fails, the API key might be invalid - throw Exception('Invalid API key or insufficient permissions'); + // If user fetch fails, the token might be invalid + throw Exception('Invalid token or insufficient permissions'); } } catch (e, stack) { DebugLogger.error( @@ -561,8 +582,8 @@ class AuthStateManager extends _$AuthStateManager { } // Attempt login (detect API key vs normal credentials) - if (username == 'api_key_user') { - // This is a saved API key + if (username == 'api_key_user' || username == 'jwt_user') { + // This is a saved JWT token (or legacy API key) return await loginWithApiKey(password, rememberCredentials: false); } else { // Normal username/password credentials diff --git a/lib/core/auth/token_validator.dart b/lib/core/auth/token_validator.dart index 84e4355..08093bc 100644 --- a/lib/core/auth/token_validator.dart +++ b/lib/core/auth/token_validator.dart @@ -6,7 +6,15 @@ import '../utils/debug_logger.dart'; class TokenValidator { static const Duration _validationTimeout = Duration(seconds: 5); - /// Validate token format (supports both JWT and API key formats) + /// Check if token is an API key format (sk-, api-, key-) + /// API keys are not supported for streaming. + static bool isApiKey(String token) { + return token.startsWith('sk-') || + token.startsWith('api-') || + token.startsWith('key-'); + } + + /// Validate token format (JWT tokens only - API keys not supported) static TokenValidationResult validateTokenFormat(String token) { try { // Basic format check @@ -14,15 +22,11 @@ class TokenValidator { return TokenValidationResult.invalid('Token too short'); } - // Check if it's an API key format (starts with sk- or similar) - if (token.startsWith('sk-') || - token.startsWith('api-') || - token.startsWith('key-')) { - // API key format - validate differently - if (token.length < 20) { - return TokenValidationResult.invalid('API key too short'); - } - return TokenValidationResult.valid('API key format valid'); + // Reject API keys - they don't support streaming + if (isApiKey(token)) { + return TokenValidationResult.apiKeyNotSupported( + 'API keys are not supported. Please use a JWT token.', + ); } // Check if it looks like a JWT (has at least 2 dots) @@ -209,6 +213,9 @@ class TokenValidationResult { const TokenValidationResult.networkError(String message) : this._(false, TokenValidationStatus.networkError, message); + const TokenValidationResult.apiKeyNotSupported(String message) + : this._(false, TokenValidationStatus.apiKeyNotSupported, message); + final bool isValid; final TokenValidationStatus status; final String message; @@ -218,6 +225,8 @@ class TokenValidationResult { bool get isExpired => status == TokenValidationStatus.expired; bool get isExpiringSoon => status == TokenValidationStatus.expiringSoon; bool get hasNetworkError => status == TokenValidationStatus.networkError; + bool get isApiKeyNotSupported => + status == TokenValidationStatus.apiKeyNotSupported; @override String toString() => @@ -230,6 +239,7 @@ enum TokenValidationStatus { expired, expiringSoon, networkError, + apiKeyNotSupported, } /// Cache for token validation results diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 5b51590..77dd499 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -62,6 +62,14 @@ class RouterNotifier extends ChangeNotifier { final reviewerMode = ref.read(reviewerModeProvider); final activeServerAsync = ref.read(activeServerProvider); + // Check for API key forced logout first - redirect to authentication + final authSnapshot = ref + .read(authStateManagerProvider) + .maybeWhen(data: (s) => s, orElse: () => null); + if (authSnapshot?.error?.contains('apiKey') == true) { + return location == Routes.authentication ? null : Routes.authentication; + } + if (reviewerMode) { // Stay on whatever route if already in chat; otherwise go to chat if (location == Routes.chat) return null; diff --git a/lib/features/auth/views/authentication_page.dart b/lib/features/auth/views/authentication_page.dart index 00752e4..9150c77 100644 --- a/lib/features/auth/views/authentication_page.dart +++ b/lib/features/auth/views/authentication_page.dart @@ -43,6 +43,23 @@ class _AuthenticationPageState extends ConsumerState { void initState() { super.initState(); _loadSavedCredentials(); + // Check for auth errors (e.g., forced logout due to API key) + WidgetsBinding.instance.addPostFrameCallback((_) { + _checkAuthStateError(); + }); + } + + void _checkAuthStateError() { + final authState = ref.read(authStateManagerProvider).asData?.value; + if (authState?.error != null && authState!.error!.isNotEmpty) { + setState(() { + _loginError = _formatLoginError(authState.error!); + // Switch to token tab if the error is about API keys + if (authState.error!.contains('apiKey')) { + _useApiKey = true; + } + }); + } } Future _loadSavedCredentials() async { @@ -127,16 +144,21 @@ class _AuthenticationPageState extends ConsumerState { } String _formatLoginError(String error) { - if (error.contains('401') || error.contains('Unauthorized')) { - return AppLocalizations.of(context)!.invalidCredentials; + final l10n = AppLocalizations.of(context)!; + if (error.contains('apiKeyNotSupported')) { + return l10n.apiKeyNotSupported; + } else if (error.contains('apiKeyNoLongerSupported')) { + return l10n.apiKeyNoLongerSupported; + } else if (error.contains('401') || error.contains('Unauthorized')) { + return l10n.invalidCredentials; } else if (error.contains('redirect')) { - return AppLocalizations.of(context)!.serverRedirectingHttps; + return l10n.serverRedirectingHttps; } else if (error.contains('SocketException')) { - return AppLocalizations.of(context)!.unableToConnectServer; + return l10n.unableToConnectServer; } else if (error.contains('timeout')) { - return AppLocalizations.of(context)!.requestTimedOut; + return l10n.requestTimedOut; } - return AppLocalizations.of(context)!.genericSignInFailed; + return l10n.genericSignInFailed; } @override @@ -399,7 +421,7 @@ class _AuthenticationPageState extends ConsumerState { icon: Platform.isIOS ? CupertinoIcons.lock_shield : Icons.vpn_key_outlined, - label: AppLocalizations.of(context)!.apiKey, + label: AppLocalizations.of(context)!.token, isSelected: _useApiKey, onTap: () => setState(() => _useApiKey = true), ), @@ -480,24 +502,43 @@ class _AuthenticationPageState extends ConsumerState { ); } + /// Validates that a token is a JWT and not an API key. + /// API keys (sk-, api-, key-) don't work with WebSocket authentication. + String? _validateJwtToken(String? value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of(context)!.validationMissingRequired; + } + + final trimmed = value.trim(); + final lowerTrimmed = trimmed.toLowerCase(); + + // Reject API keys - they don't work with socket authentication + // Case-insensitive check to catch SK-, API-, KEY- variants + if (lowerTrimmed.startsWith('sk-') || + lowerTrimmed.startsWith('api-') || + lowerTrimmed.startsWith('key-')) { + return AppLocalizations.of(context)!.apiKeyNotSupported; + } + + // Check minimum length + if (trimmed.length < 10) { + return AppLocalizations.of(context)!.tokenTooShort; + } + + return null; + } + Widget _buildApiKeyForm() { return Column( key: const ValueKey('api_key_form'), children: [ AccessibleFormField( - label: AppLocalizations.of(context)!.apiKey, - hint: 'sk-...', + label: AppLocalizations.of(context)!.token, + hint: 'eyJ...', controller: _apiKeyController, - validator: InputValidationService.combine([ - InputValidationService.validateRequired, - (value) => InputValidationService.validateMinLength( - value, - 10, - fieldName: AppLocalizations.of(context)!.apiKey, - ), - ]), + validator: _validateJwtToken, obscureText: _obscurePassword, - semanticLabel: AppLocalizations.of(context)!.enterApiKey, + semanticLabel: AppLocalizations.of(context)!.enterToken, prefixIcon: Icon( Platform.isIOS ? CupertinoIcons.lock_shield @@ -520,6 +561,13 @@ class _AuthenticationPageState extends ConsumerState { isRequired: true, autofillHints: const [AutofillHints.password], ), + const SizedBox(height: Spacing.sm), + Text( + AppLocalizations.of(context)!.tokenHint, + style: context.conduitTheme.bodySmall?.copyWith( + color: context.conduitTheme.textSecondary, + ), + ), ], ); } @@ -591,7 +639,7 @@ class _AuthenticationPageState extends ConsumerState { text: _isSigningIn ? AppLocalizations.of(context)!.signingIn : _useApiKey - ? AppLocalizations.of(context)!.signInWithApiKey + ? AppLocalizations.of(context)!.signInWithToken : AppLocalizations.of(context)!.signIn, icon: _isSigningIn ? null diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b74147c..5455e87 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -46,9 +46,11 @@ "enterCredentials": "Gib deine Anmeldedaten ein, um auf deine KI-Unterhaltungen zuzugreifen", "credentials": "Zugangsdaten", "apiKey": "API-Schlüssel", + "token": "Token", "usernameOrEmail": "Benutzername oder E‑Mail", "password": "Passwort", "signInWithApiKey": "Mit API-Schlüssel anmelden", + "signInWithToken": "Mit Token anmelden", "connectToServer": "Mit Server verbinden", "enterServerAddress": "Gib die Adresse deines Open-WebUI-Servers ein, um zu beginnen", "serverUrl": "Server-URL", @@ -225,6 +227,11 @@ "usernameOrEmailHint": "Gib deinen Benutzernamen oder deine E‑Mail ein", "passwordHint": "Gib dein Passwort ein", "enterApiKey": "Gib deinen API-Schlüssel ein", + "enterToken": "Gib dein JWT-Token ein", + "tokenHint": "Hole das JWT-Token aus den OpenWebUI-Einstellungen. API-Schlüssel (sk-...) werden für Streaming nicht unterstützt.", + "apiKeyNotSupported": "API-Schlüssel (sk-...) werden nicht unterstützt. Bitte verwende stattdessen ein JWT-Token.", + "apiKeyNoLongerSupported": "Du wurdest abgemeldet, da API-Schlüssel nicht mehr unterstützt werden. Bitte melde dich mit einem JWT-Token aus den OpenWebUI-Einstellungen an.", + "tokenTooShort": "Token ist zu kurz", "signingIn": "Anmeldung läuft...", "advancedSettings": "Erweiterte Einstellungen", "customHeaders": "Benutzerdefinierte Header", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0de9784..1a9dfbd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -216,6 +216,10 @@ "@apiKey": { "description": "Label for API key input field." }, + "token": "Token", + "@token": { + "description": "Label for JWT token input field." + }, "usernameOrEmail": "Username or Email", "@usernameOrEmail": { "description": "Label for username/email input field." @@ -228,6 +232,10 @@ "@signInWithApiKey": { "description": "Alternative sign-in method using an API key." }, + "signInWithToken": "Sign in with Token", + "@signInWithToken": { + "description": "Alternative sign-in method using a JWT token." + }, "connectToServer": "Connect to Server", "@connectToServer": { "description": "Call-to-action button for server connection." @@ -1004,6 +1012,26 @@ "@enterApiKey": { "description": "Hint text for API key input." }, + "enterToken": "Enter your JWT token", + "@enterToken": { + "description": "Hint text for JWT token input." + }, + "tokenHint": "Get the JWT token from OpenWebUI settings. API keys (sk-...) are not supported for streaming.", + "@tokenHint": { + "description": "Help text explaining what type of token to use." + }, + "apiKeyNotSupported": "API keys (sk-...) are not supported. Please use a JWT token instead.", + "@apiKeyNotSupported": { + "description": "Error message when user tries to use an API key instead of JWT token." + }, + "apiKeyNoLongerSupported": "You were logged out because API keys are no longer supported. Please sign in with a JWT token from OpenWebUI settings.", + "@apiKeyNoLongerSupported": { + "description": "Error message shown when user is forced logged out due to using an API key." + }, + "tokenTooShort": "Token is too short", + "@tokenTooShort": { + "description": "Error message when token is too short." + }, "signingIn": "Signing in...", "@signingIn": { "description": "Status message shown while signing in." diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index d7f2474..b8128d7 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -46,9 +46,11 @@ "enterCredentials": "Ingresa tus credenciales para acceder a tus conversaciones de IA", "credentials": "Credenciales", "apiKey": "Clave API", + "token": "Token", "usernameOrEmail": "Usuario o correo electrónico", "password": "Contraseña", "signInWithApiKey": "Iniciar sesión con clave API", + "signInWithToken": "Iniciar sesión con token", "connectToServer": "Conectar al servidor", "enterServerAddress": "Ingresa la dirección de tu servidor Open-WebUI para comenzar", "serverUrl": "URL del servidor", @@ -225,6 +227,11 @@ "usernameOrEmailHint": "Ingresa tu usuario o correo electrónico", "passwordHint": "Ingresa tu contraseña", "enterApiKey": "Ingresa tu clave API", + "enterToken": "Ingresa tu token JWT", + "tokenHint": "Obtén el token JWT desde la configuración de OpenWebUI. Las claves API (sk-...) no son compatibles con streaming.", + "apiKeyNotSupported": "Las claves API (sk-...) no son compatibles. Por favor usa un token JWT en su lugar.", + "apiKeyNoLongerSupported": "Se cerró tu sesión porque las claves API ya no son compatibles. Por favor inicia sesión con un token JWT desde la configuración de OpenWebUI.", + "tokenTooShort": "El token es demasiado corto", "signingIn": "Iniciando sesión...", "advancedSettings": "Configuración avanzada", "customHeaders": "Encabezados personalizados", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d3f8cee..2063bed 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -46,9 +46,11 @@ "enterCredentials": "Entrez vos identifiants pour accéder à vos conversations IA", "credentials": "Identifiants", "apiKey": "Clé API", + "token": "Jeton", "usernameOrEmail": "Nom d'utilisateur ou e‑mail", "password": "Mot de passe", "signInWithApiKey": "Se connecter avec une clé API", + "signInWithToken": "Se connecter avec un jeton", "connectToServer": "Se connecter au serveur", "enterServerAddress": "Saisissez l'adresse de votre serveur Open-WebUI pour commencer", "serverUrl": "URL du serveur", @@ -225,6 +227,11 @@ "usernameOrEmailHint": "Entrez votre nom d'utilisateur ou e‑mail", "passwordHint": "Entrez votre mot de passe", "enterApiKey": "Entrez votre clé API", + "enterToken": "Entrez votre jeton JWT", + "tokenHint": "Obtenez le jeton JWT dans les paramètres d'OpenWebUI. Les clés API (sk-...) ne sont pas prises en charge pour le streaming.", + "apiKeyNotSupported": "Les clés API (sk-...) ne sont pas prises en charge. Veuillez utiliser un jeton JWT à la place.", + "apiKeyNoLongerSupported": "Vous avez été déconnecté car les clés API ne sont plus prises en charge. Veuillez vous connecter avec un jeton JWT depuis les paramètres d'OpenWebUI.", + "tokenTooShort": "Le jeton est trop court", "signingIn": "Connexion en cours...", "advancedSettings": "Paramètres avancés", "customHeaders": "En-têtes personnalisés", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index f8bde04..6d754fe 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -46,9 +46,11 @@ "enterCredentials": "Inserisci le credenziali per accedere alle conversazioni IA", "credentials": "Credenziali", "apiKey": "Chiave API", + "token": "Token", "usernameOrEmail": "Username o e‑mail", "password": "Password", "signInWithApiKey": "Accedi con chiave API", + "signInWithToken": "Accedi con token", "connectToServer": "Connetti al server", "enterServerAddress": "Inserisci l'indirizzo del server Open-WebUI per iniziare", "serverUrl": "URL del server", @@ -225,6 +227,11 @@ "usernameOrEmailHint": "Inserisci il tuo username o e‑mail", "passwordHint": "Inserisci la password", "enterApiKey": "Inserisci la tua chiave API", + "enterToken": "Inserisci il tuo token JWT", + "tokenHint": "Ottieni il token JWT dalle impostazioni di OpenWebUI. Le chiavi API (sk-...) non sono supportate per lo streaming.", + "apiKeyNotSupported": "Le chiavi API (sk-...) non sono supportate. Per favore usa un token JWT.", + "apiKeyNoLongerSupported": "Sei stato disconnesso perché le chiavi API non sono più supportate. Per favore accedi con un token JWT dalle impostazioni di OpenWebUI.", + "tokenTooShort": "Il token è troppo corto", "signingIn": "Accesso in corso...", "advancedSettings": "Impostazioni avanzate", "customHeaders": "Header personalizzati", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 93f7199..c0a5a53 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -87,9 +87,11 @@ "enterCredentials": "AI 대화에 액세스하려면 자격 증명을 입력하세요", "credentials": "자격 증명", "apiKey": "API 키", + "token": "토큰", "usernameOrEmail": "사용자 이름 또는 이메일", "password": "비밀번호", "signInWithApiKey": "API 키로 로그인", + "signInWithToken": "토큰으로 로그인", "connectToServer": "서버 연결", "enterServerAddress": "시작하려면 Open-WebUI 서버 주소를 입력하세요", "serverUrl": "서버 URL", @@ -335,6 +337,11 @@ "usernameOrEmailHint": "사용자 이름 또는 이메일을 입력하세요", "passwordHint": "비밀번호를 입력하세요", "enterApiKey": "API 키를 입력하세요", + "enterToken": "JWT 토큰을 입력하세요", + "tokenHint": "OpenWebUI 설정에서 JWT 토큰을 가져오세요. API 키(sk-...)는 스트리밍에 지원되지 않습니다.", + "apiKeyNotSupported": "API 키(sk-...)는 지원되지 않습니다. JWT 토큰을 사용하세요.", + "apiKeyNoLongerSupported": "API 키가 더 이상 지원되지 않아 로그아웃되었습니다. OpenWebUI 설정에서 JWT 토큰으로 로그인하세요.", + "tokenTooShort": "토큰이 너무 짧습니다", "signingIn": "로그인 중...", "advancedSettings": "고급 설정", "customHeaders": "사용자 정의 헤더", diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index b706083..048151d 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -46,9 +46,11 @@ "enterCredentials": "Voer je inloggegevens in om toegang te krijgen tot je AI-gesprekken", "credentials": "Inloggegevens", "apiKey": "API-sleutel", + "token": "Token", "usernameOrEmail": "Gebruikersnaam of e-mail", "password": "Wachtwoord", "signInWithApiKey": "Inloggen met API-sleutel", + "signInWithToken": "Inloggen met token", "connectToServer": "Verbinden met server", "enterServerAddress": "Voer je Open-WebUI serveradres in om te beginnen", "serverUrl": "Server-URL", @@ -225,6 +227,11 @@ "usernameOrEmailHint": "Voer je gebruikersnaam of e-mail in", "passwordHint": "Voer je wachtwoord in", "enterApiKey": "Voer je API-sleutel in", + "enterToken": "Voer je JWT-token in", + "tokenHint": "Haal het JWT-token uit de OpenWebUI-instellingen. API-sleutels (sk-...) worden niet ondersteund voor streaming.", + "apiKeyNotSupported": "API-sleutels (sk-...) worden niet ondersteund. Gebruik in plaats daarvan een JWT-token.", + "apiKeyNoLongerSupported": "Je bent uitgelogd omdat API-sleutels niet meer worden ondersteund. Log in met een JWT-token uit de OpenWebUI-instellingen.", + "tokenTooShort": "Token is te kort", "signingIn": "Inloggen...", "advancedSettings": "Geavanceerde instellingen", "customHeaders": "Aangepaste headers", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 41cd196..66a963c 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -46,9 +46,11 @@ "enterCredentials": "Введите свои учетные данные для доступа к вашим разговорам с ИИ", "credentials": "Учетные данные", "apiKey": "API-ключ", + "token": "Токен", "usernameOrEmail": "Имя пользователя или email", "password": "Пароль", "signInWithApiKey": "Войти с помощью API-ключа", + "signInWithToken": "Войти с помощью токена", "connectToServer": "Подключиться к серверу", "enterServerAddress": "Введите адрес вашего сервера Open-WebUI для начала", "serverUrl": "URL сервера", @@ -225,6 +227,11 @@ "usernameOrEmailHint": "Введите ваше имя пользователя или email", "passwordHint": "Введите ваш пароль", "enterApiKey": "Введите ваш API-ключ", + "enterToken": "Введите ваш JWT-токен", + "tokenHint": "Получите JWT-токен в настройках OpenWebUI. API-ключи (sk-...) не поддерживаются для потоковой передачи.", + "apiKeyNotSupported": "API-ключи (sk-...) не поддерживаются. Пожалуйста, используйте JWT-токен.", + "apiKeyNoLongerSupported": "Вы были выведены из системы, так как API-ключи больше не поддерживаются. Пожалуйста, войдите с JWT-токеном из настроек OpenWebUI.", + "tokenTooShort": "Токен слишком короткий", "signingIn": "Вход...", "advancedSettings": "Расширенные настройки", "customHeaders": "Пользовательские заголовки", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 4148d33..827d626 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -46,9 +46,11 @@ "enterCredentials": "输入您的凭据以访问您的 AI 对话", "credentials": "凭据", "apiKey": "API 密钥", + "token": "令牌", "usernameOrEmail": "用户名或电子邮件", "password": "密码", "signInWithApiKey": "使用 API 密钥登录", + "signInWithToken": "使用令牌登录", "connectToServer": "连接到服务器", "enterServerAddress": "输入您的 Open-WebUI 服务器地址以开始", "serverUrl": "服务器 URL", @@ -225,6 +227,11 @@ "usernameOrEmailHint": "输入您的用户名或电子邮件", "passwordHint": "输入您的密码", "enterApiKey": "输入您的 API 密钥", + "enterToken": "输入您的 JWT 令牌", + "tokenHint": "从 OpenWebUI 设置获取 JWT 令牌。API 密钥 (sk-...) 不支持流式传输。", + "apiKeyNotSupported": "不支持 API 密钥 (sk-...)。请改用 JWT 令牌。", + "apiKeyNoLongerSupported": "由于不再支持 API 密钥,您已被登出。请使用 OpenWebUI 设置中的 JWT 令牌登录。", + "tokenTooShort": "令牌太短", "signingIn": "正在登录...", "advancedSettings": "高级设置", "customHeaders": "自定义标头", diff --git a/lib/l10n/app_zh_Hant.arb b/lib/l10n/app_zh_Hant.arb index 9b8433c..eae014e 100644 --- a/lib/l10n/app_zh_Hant.arb +++ b/lib/l10n/app_zh_Hant.arb @@ -46,9 +46,11 @@ "enterCredentials": "輸入您的憑據以訪問您的 AI 對話", "credentials": "憑據", "apiKey": "API 密鑰", + "token": "令牌", "usernameOrEmail": "用戶名或電子郵件", "password": "密碼", "signInWithApiKey": "使用 API 密鑰登錄", + "signInWithToken": "使用令牌登錄", "connectToServer": "連接到服務器", "enterServerAddress": "輸入您的 Open-WebUI 服務器地址以開始", "serverUrl": "服務器 URL", @@ -225,6 +227,11 @@ "usernameOrEmailHint": "輸入您的用戶名或電子郵件", "passwordHint": "輸入您的密碼", "enterApiKey": "輸入您的 API 密鑰", + "enterToken": "輸入您的 JWT 令牌", + "tokenHint": "從 OpenWebUI 設定取得 JWT 令牌。API 密鑰 (sk-...) 不支援串流。", + "apiKeyNotSupported": "不支援 API 密鑰 (sk-...)。請改用 JWT 令牌。", + "apiKeyNoLongerSupported": "由於不再支援 API 密鑰,您已被登出。請使用 OpenWebUI 設定中的 JWT 令牌登入。", + "tokenTooShort": "令牌太短", "signingIn": "正在登錄...", "advancedSettings": "高級設置", "customHeaders": "自定義標頭",