feat(webui): simplify model preset settings (#5061)

This commit is contained in:
chengyongru
2026-07-24 00:55:06 +08:00
committed by GitHub
parent d993c81f08
commit aae259c790
29 changed files with 4548 additions and 1182 deletions
File diff suppressed because it is too large Load Diff
+57 -4
View File
@@ -115,6 +115,39 @@
"configurationName": "Configuration name",
"configurationNameHelp": "Rename this saved model configuration.",
"configurationNamePlaceholder": "Fast writing",
"callOrder": "Model call order",
"primary": "Primary",
"fallbackNumber": "Fallback {{number}}",
"addToOrder": "Enable preset",
"newPreset": "New model preset",
"newPresetHelp": "Save a reusable model and its generation settings.",
"presets": "Model presets",
"editPreset": "Edit preset",
"presetName": "Preset name",
"presetNameHelp": "A short name used throughout model settings.",
"presetNamePlaceholder": "Fast writing",
"advancedOptions": "Advanced options",
"advancedSummary": "Context {{context}} · Max {{max}} tokens",
"maxTokens": "Max output tokens",
"temperature": "Temperature",
"reasoningEffort": "Reasoning effort",
"convertTitle": "Convert the current model setup",
"convertHelp": "Turn the existing primary and fallback models into presets so their order can be managed here.",
"converting": "Converting...",
"convertAction": "Convert to presets",
"dragToReorder": "Drag to reorder",
"moveUp": "Move up",
"moveDown": "Move down",
"removeFromOrder": "Disable preset",
"inCallOrder": "In call order",
"disabled": "Disabled",
"noPresets": "No model presets yet",
"noPresetsHelp": "Create a preset, then add it to the call order.",
"removeBeforeDelete": "Remove this preset from the call order before deleting it.",
"providerSetupRequired": "Provider setup required",
"configureProviderBeforeSaving": "Configure this provider before saving the preset.",
"deletePresetTitle": "Delete model preset?",
"deletePresetHelp": "This removes the preset “{{name}}”. Provider credentials are not affected.",
"searchModels": "Search or type model ID",
"useCustomModel": "Use",
"loadingModels": "Loading models...",
@@ -180,7 +213,7 @@
"theme": "Switch between light and dark appearance.",
"language": "Choose the language used by the WebUI.",
"provider": "Select the provider that should serve new model requests.",
"model": "Set the default model name used by nanobot.",
"model": "Choose the model used by this preset.",
"configPath": "The gateway configuration file currently in use.",
"currentModel": "Used for new replies.",
"selectedModelProvider": "Set by the selected model.",
@@ -331,6 +364,7 @@
"comfortable": "Comfortable",
"compact": "Compact",
"auto": "Auto",
"default": "Default",
"expanded": "Expanded",
"summary": "Summary",
"diff": "Diff",
@@ -364,7 +398,11 @@
"actions": {
"save": "Save",
"saving": "Saving",
"saveOrder": "Save order",
"savePreset": "Save preset",
"edit": "Edit",
"delete": "Delete",
"deleting": "Deleting...",
"cancel": "Cancel",
"open": "Open",
"export": "Export",
@@ -372,7 +410,7 @@
"exporting": "Exporting..."
},
"byok": {
"description": "Bring your own provider keys. Nanobot reads these values from the current config and only configured providers can be selected in General.",
"description": "Bring your own provider keys. Nanobot reads these values from the current config and only configured providers can be used in model presets.",
"configured": "Configured",
"notConfigured": "Not configured",
"configuredSection": "Configured",
@@ -449,9 +487,24 @@
}
},
"providers": {
"title": "Model providers",
"searchPlaceholder": "Search providers",
"noMatches": "No providers match this search.",
"saveProvider": "Save provider"
"addOwnProvider": "Add your own model provider",
"customProvider": "Custom provider",
"customProviderName": "Provider name",
"customProviderNamePlaceholder": "My model provider",
"saveProvider": "Save provider",
"advancedOptions": "Advanced options",
"saveAdvancedBeforeSignIn": "Save advanced changes before signing in.",
"apiType": "API type",
"proxy": "Network proxy",
"thinkingStyle": "Thinking style",
"region": "Region",
"profile": "Profile",
"extraHeaders": "Extra headers",
"extraBody": "Extra body",
"extraQuery": "Extra query"
},
"legal": {
"thirdPartyBrands": "Product names, logos, and brands are property of their respective owners. Use is for identification only and does not imply endorsement."
@@ -721,7 +774,7 @@
"signInHelp": "Sign in from this device; no API key is stored in config.",
"remoteSignInHelp": "Select Sign in to open xAI on your computer, then paste the authorization code shown after login.",
"signInRequired": "Sign in required",
"signInBeforeSaving": "Sign in before saving this OAuth provider as the active model provider.",
"signInBeforeSaving": "Sign in before saving this provider in the preset.",
"signedIn": "Signed in",
"notSignedIn": "Not signed in",
"proxyLabel": "Network proxy",
+57 -4
View File
@@ -161,7 +161,7 @@
"theme": "Cambia entre apariencia clara y oscura.",
"language": "Elige el idioma usado por WebUI.",
"provider": "Selecciona el proveedor para nuevas solicitudes de modelo.",
"model": "Define el nombre de modelo predeterminado de nanobot.",
"model": "Elige el modelo que usa este preajuste.",
"configPath": "Archivo de configuración que usa actualmente el gateway.",
"selectedPreset": "Los preajustes con nombre son de solo lectura aquí; edítalos en config.json.",
"presetModel": "Cambia a Default para editar modelo y proveedor desde WebUI.",
@@ -217,6 +217,7 @@
"compact": "Compacto",
"auto": "Automático",
"expanded": "Expandido",
"default": "Predeterminado",
"summary": "Resumen",
"diff": "Diff",
"collapsedDiff": "Diff contraído",
@@ -249,6 +250,10 @@
"actions": {
"save": "Guardar",
"saving": "Guardando",
"saveOrder": "Guardar orden",
"savePreset": "Guardar preajuste",
"delete": "Eliminar",
"deleting": "Eliminando...",
"edit": "Editar",
"cancel": "Cancelar",
"open": "Abrir",
@@ -257,7 +262,7 @@
"exporting": "Exportando..."
},
"byok": {
"description": "Usa tus propias claves de proveedor. Nanobot lee estos valores desde la configuración actual, y solo los proveedores configurados se pueden elegir en General.",
"description": "Usa tus propias claves de proveedor. Nanobot lee estos valores desde la configuración actual y solo los proveedores configurados pueden usarse en los preajustes de modelo.",
"configured": "Configurado",
"notConfigured": "Sin configurar",
"configuredSection": "Configurados",
@@ -334,9 +339,24 @@
}
},
"providers": {
"title": "Proveedores de modelos",
"searchPlaceholder": "Buscar proveedores",
"noMatches": "Ningún proveedor coincide con esta búsqueda.",
"saveProvider": "Guardar proveedor"
"addOwnProvider": "Añadir tu propio proveedor de modelos",
"customProvider": "Proveedor personalizado",
"customProviderName": "Nombre del proveedor",
"customProviderNamePlaceholder": "Mi proveedor de modelos",
"saveProvider": "Guardar proveedor",
"advancedOptions": "Opciones avanzadas",
"saveAdvancedBeforeSignIn": "Guarda los cambios avanzados antes de iniciar sesión.",
"apiType": "Tipo de API",
"proxy": "Proxy de red",
"thinkingStyle": "Estilo de razonamiento",
"region": "Región",
"profile": "Perfil",
"extraHeaders": "Cabeceras adicionales",
"extraBody": "Cuerpo adicional",
"extraQuery": "Parámetros de consulta adicionales"
},
"image": {
"selectProvider": "Seleccionar proveedor",
@@ -353,6 +373,39 @@
"configurationName": "Nombre de configuración",
"configurationNameHelp": "Renombra esta configuración de modelo guardada.",
"configurationNamePlaceholder": "Escritura rápida",
"callOrder": "Orden de llamada de modelos",
"primary": "Principal",
"fallbackNumber": "Alternativa {{number}}",
"addToOrder": "Activar preajuste",
"newPreset": "Nuevo preajuste de modelo",
"newPresetHelp": "Guarda un modelo reutilizable y sus ajustes de generación.",
"presets": "Preajustes de modelo",
"editPreset": "Editar preajuste",
"presetName": "Nombre del preajuste",
"presetNameHelp": "Un nombre corto para usar en los ajustes de modelos.",
"presetNamePlaceholder": "Escritura rápida",
"advancedOptions": "Opciones avanzadas",
"advancedSummary": "Contexto {{context}} · Máx. {{max}} tokens",
"maxTokens": "Máx. tokens de salida",
"temperature": "Temperatura",
"reasoningEffort": "Esfuerzo de razonamiento",
"convertTitle": "Convertir la configuración de modelos actual",
"convertHelp": "Convierte los modelos principal y alternativos existentes en preajustes para administrar aquí su orden.",
"converting": "Convirtiendo...",
"convertAction": "Convertir en preajustes",
"dragToReorder": "Arrastrar para reordenar",
"moveUp": "Subir",
"moveDown": "Bajar",
"removeFromOrder": "Desactivar preajuste",
"inCallOrder": "En el orden de llamada",
"disabled": "Desactivado",
"noPresets": "Aún no hay preajustes de modelo",
"noPresetsHelp": "Crea un preajuste y añádelo al orden de llamada.",
"removeBeforeDelete": "Quita este preajuste del orden de llamada antes de eliminarlo.",
"providerSetupRequired": "El proveedor requiere configuración",
"configureProviderBeforeSaving": "Configura este proveedor antes de guardar el preajuste.",
"deletePresetTitle": "¿Eliminar el preajuste de modelo?",
"deletePresetHelp": "Se eliminará el preajuste «{{name}}». Las credenciales del proveedor no cambiarán.",
"searchModels": "Buscar o escribir ID de modelo",
"useCustomModel": "Usar",
"loadingModels": "Cargando modelos...",
@@ -708,7 +761,7 @@
"signInHelp": "Inicia sesión desde este dispositivo; no se guarda API key en config.",
"remoteSignInHelp": "Selecciona Iniciar sesión para abrir xAI en tu computadora y luego pega el código de autorización que se muestra tras iniciar sesión.",
"signInRequired": "Inicio de sesión requerido",
"signInBeforeSaving": "Inicia sesión antes de guardar este proveedor OAuth como proveedor activo.",
"signInBeforeSaving": "Inicia sesión en este proveedor antes de guardar el preajuste.",
"signedIn": "Sesión iniciada",
"notSignedIn": "Sin sesión",
"proxyLabel": "Proxy de red",
+57 -4
View File
@@ -161,7 +161,7 @@
"theme": "Basculer entre lapparence claire et sombre.",
"language": "Choisissez la langue utilisée par WebUI.",
"provider": "Sélectionnez le fournisseur à utiliser pour les nouvelles requêtes de modèle.",
"model": "Définissez le nom du modèle utilisé par défaut par nanobot.",
"model": "Choisissez le modèle utilisé par ce préréglage.",
"configPath": "Le fichier de configuration actuellement utilisé par la passerelle.",
"selectedPreset": "Les préréglages nommés sont en lecture seule ici ; modifiez-les dans config.json.",
"presetModel": "Passez à Default pour modifier le modèle et le fournisseur depuis WebUI.",
@@ -217,6 +217,7 @@
"compact": "Compacte",
"auto": "Automatique",
"expanded": "Développé",
"default": "Par défaut",
"summary": "Résumé",
"diff": "Diff",
"collapsedDiff": "Diff replié",
@@ -249,6 +250,10 @@
"actions": {
"save": "Enregistrer",
"saving": "Enregistrement",
"saveOrder": "Enregistrer lordre",
"savePreset": "Enregistrer le préréglage",
"delete": "Supprimer",
"deleting": "Suppression...",
"edit": "Modifier",
"cancel": "Annuler",
"open": "Ouvrir",
@@ -257,7 +262,7 @@
"exporting": "Exportation..."
},
"byok": {
"description": "Utilisez vos propres clés de fournisseur. Nanobot lit ces valeurs depuis la configuration actuelle, et seuls les fournisseurs configurés peuvent être sélectionnés dans Général.",
"description": "Utilisez vos propres clés de fournisseur. Nanobot lit ces valeurs depuis la configuration actuelle et seuls les fournisseurs configurés peuvent être utilisés dans les préréglages de modèle.",
"configured": "Configuré",
"notConfigured": "Non configuré",
"configuredSection": "Configurés",
@@ -334,9 +339,24 @@
}
},
"providers": {
"title": "Fournisseurs de modèles",
"searchPlaceholder": "Rechercher des fournisseurs",
"noMatches": "Aucun fournisseur ne correspond.",
"saveProvider": "Enregistrer le fournisseur"
"addOwnProvider": "Ajouter votre propre fournisseur de modèles",
"customProvider": "Fournisseur personnalisé",
"customProviderName": "Nom du fournisseur",
"customProviderNamePlaceholder": "Mon fournisseur de modèles",
"saveProvider": "Enregistrer le fournisseur",
"advancedOptions": "Options avancées",
"saveAdvancedBeforeSignIn": "Enregistrez les options avancées avant de vous connecter.",
"apiType": "Type dAPI",
"proxy": "Proxy réseau",
"thinkingStyle": "Style de raisonnement",
"region": "Région",
"profile": "Profil",
"extraHeaders": "En-têtes supplémentaires",
"extraBody": "Corps supplémentaire",
"extraQuery": "Paramètres de requête supplémentaires"
},
"image": {
"selectProvider": "Choisir un fournisseur",
@@ -353,6 +373,39 @@
"configurationName": "Nom de la configuration",
"configurationNameHelp": "Renomme cette configuration de modèle enregistrée.",
"configurationNamePlaceholder": "Rédaction rapide",
"callOrder": "Ordre dappel des modèles",
"primary": "Principal",
"fallbackNumber": "Secours {{number}}",
"addToOrder": "Activer le préréglage",
"newPreset": "Nouveau préréglage de modèle",
"newPresetHelp": "Enregistrez un modèle réutilisable et ses paramètres de génération.",
"presets": "Préréglages de modèle",
"editPreset": "Modifier le préréglage",
"presetName": "Nom du préréglage",
"presetNameHelp": "Un nom court utilisé dans les paramètres des modèles.",
"presetNamePlaceholder": "Rédaction rapide",
"advancedOptions": "Options avancées",
"advancedSummary": "Contexte {{context}} · Max. {{max}} tokens",
"maxTokens": "Tokens de sortie max.",
"temperature": "Température",
"reasoningEffort": "Effort de raisonnement",
"convertTitle": "Convertir la configuration de modèles actuelle",
"convertHelp": "Convertissez les modèles principal et de secours existants en préréglages afin de gérer leur ordre ici.",
"converting": "Conversion...",
"convertAction": "Convertir en préréglages",
"dragToReorder": "Faire glisser pour réorganiser",
"moveUp": "Monter",
"moveDown": "Descendre",
"removeFromOrder": "Désactiver le préréglage",
"inCallOrder": "Dans lordre dappel",
"disabled": "Désactivé",
"noPresets": "Aucun préréglage de modèle",
"noPresetsHelp": "Créez un préréglage, puis ajoutez-le à lordre dappel.",
"removeBeforeDelete": "Retirez ce préréglage de lordre dappel avant de le supprimer.",
"providerSetupRequired": "Configuration du fournisseur requise",
"configureProviderBeforeSaving": "Configurez ce fournisseur avant denregistrer le préréglage.",
"deletePresetTitle": "Supprimer le préréglage de modèle ?",
"deletePresetHelp": "Le préréglage « {{name}} » sera supprimé. Les identifiants du fournisseur ne seront pas modifiés.",
"searchModels": "Rechercher ou saisir un ID de modèle",
"useCustomModel": "Utiliser",
"loadingModels": "Chargement des modèles...",
@@ -707,7 +760,7 @@
"signInHelp": "Inicia sesión desde este dispositivo; no se guarda API key en config.",
"remoteSignInHelp": "Sélectionnez Se connecter pour ouvrir xAI sur votre ordinateur, puis collez le code dautorisation affiché après la connexion.",
"signInRequired": "Connexion requise",
"signInBeforeSaving": "Inicia sesión antes de guardar este proveedor OAuth como proveedor activo.",
"signInBeforeSaving": "Connectez-vous à ce fournisseur avant denregistrer le préréglage.",
"signedIn": "Connecté",
"notSignedIn": "Non connecté",
"proxyLabel": "Proxy réseau",
+57 -4
View File
@@ -161,7 +161,7 @@
"theme": "Beralih antara tampilan terang dan gelap.",
"language": "Pilih bahasa yang digunakan WebUI.",
"provider": "Selecciona el proveedor para nuevas solicitudes de modelo.",
"model": "Define el nombre de modelo predeterminado de nanobot.",
"model": "Pilih model yang digunakan oleh preset ini.",
"configPath": "Archivo de configuración que usa actualmente el gateway.",
"selectedPreset": "Los preajustes con nombre son de solo lectura aquí; edítalos en config.json.",
"presetModel": "Beralih ke Default untuk mengedit model dan penyedia dari WebUI.",
@@ -217,6 +217,7 @@
"compact": "Ringkas",
"auto": "Otomatis",
"expanded": "Diperluas",
"default": "Default",
"summary": "Ringkasan",
"diff": "Diff",
"collapsedDiff": "Diff diciutkan",
@@ -249,6 +250,10 @@
"actions": {
"save": "Simpan",
"saving": "Menyimpan",
"saveOrder": "Simpan urutan",
"savePreset": "Simpan preset",
"delete": "Hapus",
"deleting": "Menghapus...",
"edit": "Edit",
"cancel": "Batal",
"open": "Buka",
@@ -257,7 +262,7 @@
"exporting": "Mengekspor..."
},
"byok": {
"description": "Gunakan kunci provider Anda sendiri. Nanobot membaca nilai ini dari config saat ini, dan hanya provider yang sudah dikonfigurasi yang bisa dipilih di Umum.",
"description": "Gunakan kunci provider Anda sendiri. Nanobot membaca nilai ini dari config saat ini dan hanya provider yang sudah dikonfigurasi yang dapat digunakan dalam preset model.",
"configured": "Terkonfigurasi",
"notConfigured": "Belum dikonfigurasi",
"configuredSection": "Terkonfigurasi",
@@ -334,9 +339,24 @@
}
},
"providers": {
"title": "Penyedia model",
"searchPlaceholder": "Cari penyedia",
"noMatches": "Tidak ada penyedia yang cocok.",
"saveProvider": "Simpan penyedia"
"addOwnProvider": "Tambahkan penyedia model Anda sendiri",
"customProvider": "Penyedia khusus",
"customProviderName": "Nama penyedia",
"customProviderNamePlaceholder": "Penyedia model saya",
"saveProvider": "Simpan penyedia",
"advancedOptions": "Opsi lanjutan",
"saveAdvancedBeforeSignIn": "Simpan perubahan lanjutan sebelum masuk.",
"apiType": "Jenis API",
"proxy": "Proksi jaringan",
"thinkingStyle": "Gaya penalaran",
"region": "Wilayah",
"profile": "Profil",
"extraHeaders": "Header tambahan",
"extraBody": "Body tambahan",
"extraQuery": "Parameter kueri tambahan"
},
"image": {
"selectProvider": "Pilih penyedia",
@@ -353,6 +373,39 @@
"configurationName": "Nama konfigurasi",
"configurationNameHelp": "Ganti nama konfigurasi model tersimpan ini.",
"configurationNamePlaceholder": "Menulis cepat",
"callOrder": "Urutan pemanggilan model",
"primary": "Utama",
"fallbackNumber": "Cadangan {{number}}",
"addToOrder": "Aktifkan preset",
"newPreset": "Preset model baru",
"newPresetHelp": "Simpan model yang dapat digunakan kembali beserta pengaturan generasinya.",
"presets": "Preset model",
"editPreset": "Edit preset",
"presetName": "Nama preset",
"presetNameHelp": "Nama singkat yang digunakan di pengaturan model.",
"presetNamePlaceholder": "Menulis cepat",
"advancedOptions": "Opsi lanjutan",
"advancedSummary": "Konteks {{context}} · Maks. {{max}} token",
"maxTokens": "Maks. token keluaran",
"temperature": "Temperatur",
"reasoningEffort": "Upaya penalaran",
"convertTitle": "Konversi pengaturan model saat ini",
"convertHelp": "Ubah model utama dan cadangan yang ada menjadi preset agar urutannya dapat dikelola di sini.",
"converting": "Mengonversi...",
"convertAction": "Konversi ke preset",
"dragToReorder": "Seret untuk mengurutkan ulang",
"moveUp": "Naikkan",
"moveDown": "Turunkan",
"removeFromOrder": "Nonaktifkan preset",
"inCallOrder": "Dalam urutan pemanggilan",
"disabled": "Nonaktif",
"noPresets": "Belum ada preset model",
"noPresetsHelp": "Buat preset, lalu tambahkan ke urutan pemanggilan.",
"removeBeforeDelete": "Hapus preset ini dari urutan pemanggilan sebelum menghapusnya.",
"providerSetupRequired": "Penyedia perlu dikonfigurasi",
"configureProviderBeforeSaving": "Konfigurasikan penyedia ini sebelum menyimpan preset.",
"deletePresetTitle": "Hapus preset model?",
"deletePresetHelp": "Preset “{{name}}” akan dihapus. Kredensial penyedia tidak terpengaruh.",
"searchModels": "Cari atau ketik ID model",
"useCustomModel": "Gunakan",
"loadingModels": "Memuat model...",
@@ -707,7 +760,7 @@
"signInHelp": "Inicia sesión desde este dispositivo; no se guarda API key en config.",
"remoteSignInHelp": "Pilih Masuk untuk membuka xAI di komputer Anda, lalu tempel kode otorisasi yang ditampilkan setelah masuk.",
"signInRequired": "Perlu masuk",
"signInBeforeSaving": "Inicia sesión antes de guardar este proveedor OAuth como proveedor activo.",
"signInBeforeSaving": "Masuk ke penyedia ini sebelum menyimpan preset.",
"signedIn": "Sudah masuk",
"notSignedIn": "Belum masuk",
"proxyLabel": "Proksi jaringan",
+57 -4
View File
@@ -161,7 +161,7 @@
"theme": "ライト表示とダーク表示を切り替えます。",
"language": "WebUI で使用する言語を選択します。",
"provider": "新しいモデルリクエストに使うプロバイダーを選択します。",
"model": "nanobot が既定で使用するモデル名を設定します。",
"model": "このプリセットで使用するモデルを選択します。",
"configPath": "現在ゲートウェイが使用している設定ファイルです。",
"selectedPreset": "名前付きプリセットはここでは読み取り専用です。編集するには config.json を変更してください。",
"presetModel": "Default に切り替えると、WebUI からモデルとプロバイダーを編集できます。",
@@ -217,6 +217,7 @@
"compact": "コンパクト",
"auto": "自動",
"expanded": "展開",
"default": "デフォルト",
"summary": "概要",
"diff": "差分",
"collapsedDiff": "折りたたみ差分",
@@ -249,6 +250,10 @@
"actions": {
"save": "保存",
"saving": "保存中",
"saveOrder": "順序を保存",
"savePreset": "プリセットを保存",
"delete": "削除",
"deleting": "削除中...",
"edit": "編集",
"cancel": "キャンセル",
"open": "開く",
@@ -257,7 +262,7 @@
"exporting": "書き出しています..."
},
"byok": {
"description": "自分の provider キーを使います。Nanobot は現在の config から値を読み込み、設定済みの provider だけを一般設定で選択できます。",
"description": "自分の provider キーを使います。Nanobot は現在の config から値を読み込み、設定済みの provider だけをモデルプリセットで使用できます。",
"configured": "設定済み",
"notConfigured": "未設定",
"configuredSection": "設定済み",
@@ -334,9 +339,24 @@
}
},
"providers": {
"title": "モデルプロバイダー",
"searchPlaceholder": "プロバイダーを検索",
"noMatches": "一致するプロバイダーはありません。",
"saveProvider": "プロバイダーを保存"
"addOwnProvider": "独自のモデルプロバイダーを追加",
"customProvider": "カスタムプロバイダー",
"customProviderName": "プロバイダー名",
"customProviderNamePlaceholder": "マイモデルプロバイダー",
"saveProvider": "プロバイダーを保存",
"advancedOptions": "詳細オプション",
"saveAdvancedBeforeSignIn": "サインインする前に詳細オプションを保存してください。",
"apiType": "API タイプ",
"proxy": "ネットワークプロキシ",
"thinkingStyle": "思考スタイル",
"region": "リージョン",
"profile": "プロファイル",
"extraHeaders": "追加ヘッダー",
"extraBody": "追加ボディ",
"extraQuery": "追加クエリ"
},
"image": {
"selectProvider": "プロバイダーを選択",
@@ -353,6 +373,39 @@
"configurationName": "設定名",
"configurationNameHelp": "保存済みのモデル設定の名前を変更します。",
"configurationNamePlaceholder": "高速執筆",
"callOrder": "モデル呼び出し順序",
"primary": "プライマリ",
"fallbackNumber": "フォールバック {{number}}",
"addToOrder": "プリセットを有効化",
"newPreset": "新しいモデルプリセット",
"newPresetHelp": "再利用できるモデルと生成設定を保存します。",
"presets": "モデルプリセット",
"editPreset": "プリセットを編集",
"presetName": "プリセット名",
"presetNameHelp": "モデル設定で使用する短い名前です。",
"presetNamePlaceholder": "高速執筆",
"advancedOptions": "詳細オプション",
"advancedSummary": "コンテキスト {{context}} · 最大 {{max}} トークン",
"maxTokens": "最大出力トークン",
"temperature": "Temperature",
"reasoningEffort": "推論の強度",
"convertTitle": "現在のモデル設定を変換",
"convertHelp": "既存のプライマリモデルとフォールバックモデルをプリセットに変換し、ここで順序を管理できるようにします。",
"converting": "変換中...",
"convertAction": "プリセットに変換",
"dragToReorder": "ドラッグして並べ替え",
"moveUp": "上へ移動",
"moveDown": "下へ移動",
"removeFromOrder": "プリセットを無効化",
"inCallOrder": "呼び出し順序に追加済み",
"disabled": "無効",
"noPresets": "モデルプリセットはまだありません",
"noPresetsHelp": "プリセットを作成してから呼び出し順序に追加してください。",
"removeBeforeDelete": "削除する前に、このプリセットを呼び出し順序から外してください。",
"providerSetupRequired": "プロバイダーの設定が必要です",
"configureProviderBeforeSaving": "プリセットを保存する前に、このプロバイダーを設定してください。",
"deletePresetTitle": "モデルプリセットを削除しますか?",
"deletePresetHelp": "プリセット「{{name}}」を削除します。プロバイダーの認証情報には影響しません。",
"searchModels": "モデル ID を検索または入力",
"useCustomModel": "使用",
"loadingModels": "モデルを読み込んでいます...",
@@ -707,7 +760,7 @@
"signInHelp": "このデバイスからサインインします。API key は config に保存されません。",
"remoteSignInHelp": "「サインイン」を選択して自分のコンピューターで xAI を開き、サインイン後に表示される認証コードを貼り付けてください。",
"signInRequired": "サインインが必要です",
"signInBeforeSaving": "この OAuth プロバイダーをアクティブなモデルプロバイダーとして保存する前にサインインしてください。",
"signInBeforeSaving": "プリセットを保存する前に、このプロバイダーへサインインしてください。",
"signedIn": "サインイン済み",
"notSignedIn": "未サインイン",
"proxyLabel": "ネットワークプロキシ",
+57 -4
View File
@@ -161,7 +161,7 @@
"theme": "밝은 모드와 어두운 모드를 전환합니다.",
"language": "WebUI에서 사용할 언어를 선택합니다.",
"provider": "새 모델 요청을 처리할 제공자를 선택합니다.",
"model": "nanobot이 기본으로 사용할 모델 이름을 설정합니다.",
"model": "이 프리셋에서 사용할 모델을 선택하세요.",
"configPath": "현재 게이트웨이가 사용하는 설정 파일입니다.",
"selectedPreset": "이름 있는 프리셋은 여기서 읽기 전용입니다. config.json에서 편집하세요.",
"presetModel": "Default로 전환하면 WebUI에서 모델과 제공자를 편집할 수 있습니다.",
@@ -217,6 +217,7 @@
"compact": "컴팩트",
"auto": "자동",
"expanded": "펼침",
"default": "기본값",
"summary": "요약",
"diff": "Diff",
"collapsedDiff": "접힌 diff",
@@ -249,6 +250,10 @@
"actions": {
"save": "저장",
"saving": "저장 중",
"saveOrder": "순서 저장",
"savePreset": "프리셋 저장",
"delete": "삭제",
"deleting": "삭제 중...",
"edit": "편집",
"cancel": "취소",
"open": "열기",
@@ -257,7 +262,7 @@
"exporting": "내보내는 중..."
},
"byok": {
"description": "직접 provider 키를 가져옵니다. Nanobot은 현재 config에서 값을 읽고, 설정된 provider만 일반 설정에서 선택할 수 있습니다.",
"description": "직접 provider 키를 가져옵니다. Nanobot은 현재 config에서 값을 읽고, 설정된 provider만 모델 프리셋에서 사용할 수 있습니다.",
"configured": "설정됨",
"notConfigured": "설정 안 됨",
"configuredSection": "설정됨",
@@ -334,9 +339,24 @@
}
},
"providers": {
"title": "모델 제공업체",
"searchPlaceholder": "제공자 검색",
"noMatches": "일치하는 제공자가 없습니다.",
"saveProvider": "제공자 저장"
"addOwnProvider": "내 모델 제공업체 추가",
"customProvider": "사용자 지정 제공업체",
"customProviderName": "제공업체 이름",
"customProviderNamePlaceholder": "내 모델 제공업체",
"saveProvider": "제공업체 저장",
"advancedOptions": "고급 옵션",
"saveAdvancedBeforeSignIn": "로그인하기 전에 고급 옵션을 저장하세요.",
"apiType": "API 유형",
"proxy": "네트워크 프록시",
"thinkingStyle": "추론 방식",
"region": "리전",
"profile": "프로필",
"extraHeaders": "추가 헤더",
"extraBody": "추가 본문",
"extraQuery": "추가 쿼리"
},
"image": {
"selectProvider": "제공자 선택",
@@ -353,6 +373,39 @@
"configurationName": "구성 이름",
"configurationNameHelp": "저장된 모델 구성의 이름을 변경합니다.",
"configurationNamePlaceholder": "빠른 작성",
"callOrder": "모델 호출 순서",
"primary": "기본",
"fallbackNumber": "대체 {{number}}",
"addToOrder": "프리셋 활성화",
"newPreset": "새 모델 프리셋",
"newPresetHelp": "재사용할 모델과 생성 설정을 저장합니다.",
"presets": "모델 프리셋",
"editPreset": "프리셋 편집",
"presetName": "프리셋 이름",
"presetNameHelp": "모델 설정에서 사용할 짧은 이름입니다.",
"presetNamePlaceholder": "빠른 작성",
"advancedOptions": "고급 옵션",
"advancedSummary": "컨텍스트 {{context}} · 최대 {{max}} 토큰",
"maxTokens": "최대 출력 토큰",
"temperature": "Temperature",
"reasoningEffort": "추론 강도",
"convertTitle": "현재 모델 설정 변환",
"convertHelp": "기존 기본 및 대체 모델을 프리셋으로 변환하여 여기서 순서를 관리합니다.",
"converting": "변환 중...",
"convertAction": "프리셋으로 변환",
"dragToReorder": "드래그하여 순서 변경",
"moveUp": "위로 이동",
"moveDown": "아래로 이동",
"removeFromOrder": "프리셋 비활성화",
"inCallOrder": "호출 순서에 포함됨",
"disabled": "비활성화됨",
"noPresets": "아직 모델 프리셋이 없습니다",
"noPresetsHelp": "프리셋을 만든 다음 호출 순서에 추가하세요.",
"removeBeforeDelete": "삭제하기 전에 이 프리셋을 호출 순서에서 제거하세요.",
"providerSetupRequired": "제공자 설정 필요",
"configureProviderBeforeSaving": "프리셋을 저장하기 전에 이 제공자를 설정하세요.",
"deletePresetTitle": "모델 프리셋을 삭제할까요?",
"deletePresetHelp": "프리셋 “{{name}}”을 삭제합니다. 제공자 인증 정보에는 영향을 주지 않습니다.",
"searchModels": "모델 ID 검색 또는 입력",
"useCustomModel": "사용",
"loadingModels": "모델 불러오는 중...",
@@ -707,7 +760,7 @@
"signInHelp": "이 기기에서 로그인합니다. API key는 config에 저장되지 않습니다.",
"remoteSignInHelp": "로그인을 선택하여 사용자 컴퓨터에서 xAI를 연 다음, 로그인 후 표시되는 인증 코드를 붙여 넣으세요.",
"signInRequired": "로그인이 필요합니다",
"signInBeforeSaving": "이 OAuth 제공자를 활성 모델 제공자로 저장하기 전에 로그인하세요.",
"signInBeforeSaving": "프리셋을 저장하기 전에 이 제공자에 로그인하세요.",
"signedIn": "로그인됨",
"notSignedIn": "로그인 안 됨",
"proxyLabel": "네트워크 프록시",
+57 -4
View File
@@ -115,6 +115,39 @@
"configurationName": "Nome da configuração",
"configurationNameHelp": "Renomeie esta configuração de modelo.",
"configurationNamePlaceholder": "Escrita rápida",
"callOrder": "Ordem de chamada dos modelos",
"primary": "Principal",
"fallbackNumber": "Alternativo {{number}}",
"addToOrder": "Ativar predefinição",
"newPreset": "Nova predefinição de modelo",
"newPresetHelp": "Salve um modelo reutilizável e suas configurações de geração.",
"presets": "Predefinições de modelo",
"editPreset": "Editar predefinição",
"presetName": "Nome da predefinição",
"presetNameHelp": "Um nome curto usado nas configurações de modelo.",
"presetNamePlaceholder": "Escrita rápida",
"advancedOptions": "Opções avançadas",
"advancedSummary": "Contexto {{context}} · Máx. {{max}} tokens",
"maxTokens": "Máx. de tokens de saída",
"temperature": "Temperatura",
"reasoningEffort": "Esforço de raciocínio",
"convertTitle": "Converter a configuração de modelos atual",
"convertHelp": "Converta os modelos principal e alternativos existentes em predefinições para gerenciar a ordem aqui.",
"converting": "Convertendo...",
"convertAction": "Converter em predefinições",
"dragToReorder": "Arraste para reordenar",
"moveUp": "Mover para cima",
"moveDown": "Mover para baixo",
"removeFromOrder": "Desativar predefinição",
"inCallOrder": "Na ordem de chamada",
"disabled": "Desabilitado",
"noPresets": "Ainda não há predefinições de modelo",
"noPresetsHelp": "Crie uma predefinição e adicione-a à ordem de chamada.",
"removeBeforeDelete": "Remova esta predefinição da ordem de chamada antes de excluí-la.",
"providerSetupRequired": "Configuração do provedor necessária",
"configureProviderBeforeSaving": "Configure este provedor antes de salvar a predefinição.",
"deletePresetTitle": "Excluir predefinição de modelo?",
"deletePresetHelp": "A predefinição “{{name}}” será removida. As credenciais do provedor não serão afetadas.",
"searchModels": "Buscar ou digitar ID do modelo",
"useCustomModel": "Usar",
"loadingModels": "Carregando modelos...",
@@ -180,7 +213,7 @@
"theme": "Alterna entre aparência clara e escura.",
"language": "Escolha o idioma usado pela WebUI.",
"provider": "Selecione o provedor que atenderá as novas requisições de modelo.",
"model": "Defina o nome do modelo padrão usado pelo nanobot.",
"model": "Escolha o modelo usado por esta predefinição.",
"configPath": "O arquivo de configuração do gateway em uso.",
"currentModel": "Usado para novas respostas.",
"selectedModelProvider": "Definido pelo modelo selecionado.",
@@ -332,6 +365,7 @@
"compact": "Compacto",
"auto": "Automático",
"expanded": "Expandido",
"default": "Padrão",
"summary": "Resumo",
"diff": "Diff",
"collapsedDiff": "Diff recolhido",
@@ -364,6 +398,10 @@
"actions": {
"save": "Salvar",
"saving": "Salvando",
"saveOrder": "Salvar ordem",
"savePreset": "Salvar predefinição",
"delete": "Excluir",
"deleting": "Excluindo...",
"edit": "Editar",
"cancel": "Cancelar",
"open": "Abrir",
@@ -372,7 +410,7 @@
"exporting": "Exportando..."
},
"byok": {
"description": "Traga suas próprias chaves de provedor. O nanobot lê esses valores da configuração atual e apenas provedores configurados podem ser selecionados em Geral.",
"description": "Traga suas próprias chaves de provedor. O nanobot lê esses valores da configuração atual e apenas provedores configurados podem ser usados nas predefinições de modelo.",
"configured": "Configurado",
"notConfigured": "Não configurado",
"configuredSection": "Configurados",
@@ -449,9 +487,24 @@
}
},
"providers": {
"title": "Provedores de modelos",
"searchPlaceholder": "Buscar provedores",
"noMatches": "Nenhum provedor corresponde a esta busca.",
"saveProvider": "Salvar provedor"
"addOwnProvider": "Adicionar seu próprio provedor de modelos",
"customProvider": "Provedor personalizado",
"customProviderName": "Nome do provedor",
"customProviderNamePlaceholder": "Meu provedor de modelos",
"saveProvider": "Salvar provedor",
"advancedOptions": "Opções avançadas",
"saveAdvancedBeforeSignIn": "Salve as alterações avançadas antes de entrar.",
"apiType": "Tipo de API",
"proxy": "Proxy de rede",
"thinkingStyle": "Estilo de raciocínio",
"region": "Região",
"profile": "Perfil",
"extraHeaders": "Cabeçalhos adicionais",
"extraBody": "Corpo adicional",
"extraQuery": "Parâmetros de consulta adicionais"
},
"legal": {
"thirdPartyBrands": "Nomes de produtos, logotipos e marcas são propriedades de seus respectivos donos. O uso é apenas para identificação e não implica endosso."
@@ -721,7 +774,7 @@
"signInHelp": "Entre por este dispositivo; nenhuma chave de API é armazenada em config.",
"remoteSignInHelp": "Selecione Entrar para abrir a xAI no seu computador e depois cole o código de autorização exibido após o login.",
"signInRequired": "Login necessário",
"signInBeforeSaving": "Entre antes de salvar este provedor OAuth como provedor de modelo ativo.",
"signInBeforeSaving": "Entre neste provedor antes de salvar a predefinição.",
"signedIn": "Conectado",
"notSignedIn": "Desconectado",
"proxyLabel": "Proxy de rede",
+57 -4
View File
@@ -161,7 +161,7 @@
"theme": "Chuyển giữa giao diện sáng và tối.",
"language": "Chọn ngôn ngữ dùng trong WebUI.",
"provider": "Selecciona el proveedor para nuevas solicitudes de modelo.",
"model": "Define el nombre de modelo predeterminado de nanobot.",
"model": "Chọn mô hình mà cấu hình sẵn này sử dụng.",
"configPath": "Archivo de configuración que usa actualmente el gateway.",
"selectedPreset": "Los preajustes con nombre son de solo lectura aquí; edítalos en config.json.",
"presetModel": "Chuyển sang Default để chỉnh sửa mô hình và nhà cung cấp từ WebUI.",
@@ -217,6 +217,7 @@
"compact": "Gọn",
"auto": "Tự động",
"expanded": "Mở rộng",
"default": "Mặc định",
"summary": "Tóm tắt",
"diff": "Diff",
"collapsedDiff": "Diff thu gọn",
@@ -249,6 +250,10 @@
"actions": {
"save": "Lưu",
"saving": "Đang lưu",
"saveOrder": "Lưu thứ tự",
"savePreset": "Lưu cấu hình đặt trước",
"delete": "Xóa",
"deleting": "Đang xóa...",
"edit": "Sửa",
"cancel": "Hủy",
"open": "Mở",
@@ -257,7 +262,7 @@
"exporting": "Đang xuất..."
},
"byok": {
"description": "Dùng key provider của riêng bạn. Nanobot đọc các giá trị này từ config hiện tại, và chỉ provider đã cấu hình mới có thể chọn trong Chung.",
"description": "Dùng key provider của riêng bạn. Nanobot đọc các giá trị này từ config hiện tại và chỉ provider đã cấu hình mới có thể dùng trong cấu hình mô hình đặt trước.",
"configured": "Đã cấu hình",
"notConfigured": "Chưa cấu hình",
"configuredSection": "Đã cấu hình",
@@ -334,9 +339,24 @@
}
},
"providers": {
"title": "Nhà cung cấp mô hình",
"searchPlaceholder": "Tìm nhà cung cấp",
"noMatches": "Không có nhà cung cấp phù hợp.",
"saveProvider": "Lưu nhà cung cấp"
"addOwnProvider": "Thêm nhà cung cấp mô hình của bạn",
"customProvider": "Nhà cung cấp tùy chỉnh",
"customProviderName": "Tên nhà cung cấp",
"customProviderNamePlaceholder": "Nhà cung cấp mô hình của tôi",
"saveProvider": "Lưu nhà cung cấp",
"advancedOptions": "Tùy chọn nâng cao",
"saveAdvancedBeforeSignIn": "Lưu các thay đổi nâng cao trước khi đăng nhập.",
"apiType": "Loại API",
"proxy": "Proxy mạng",
"thinkingStyle": "Kiểu suy luận",
"region": "Khu vực",
"profile": "Hồ sơ",
"extraHeaders": "Header bổ sung",
"extraBody": "Body bổ sung",
"extraQuery": "Tham số truy vấn bổ sung"
},
"image": {
"selectProvider": "Chọn nhà cung cấp",
@@ -353,6 +373,39 @@
"configurationName": "Tên cấu hình",
"configurationNameHelp": "Đổi tên cấu hình mô hình đã lưu này.",
"configurationNamePlaceholder": "Viết nhanh",
"callOrder": "Thứ tự gọi mô hình",
"primary": "Chính",
"fallbackNumber": "Dự phòng {{number}}",
"addToOrder": "Bật cấu hình đặt trước",
"newPreset": "Cấu hình mô hình đặt trước mới",
"newPresetHelp": "Lưu mô hình có thể tái sử dụng cùng các thiết lập sinh nội dung.",
"presets": "Cấu hình mô hình đặt trước",
"editPreset": "Sửa cấu hình đặt trước",
"presetName": "Tên cấu hình đặt trước",
"presetNameHelp": "Tên ngắn dùng trong phần thiết lập mô hình.",
"presetNamePlaceholder": "Viết nhanh",
"advancedOptions": "Tùy chọn nâng cao",
"advancedSummary": "Ngữ cảnh {{context}} · Tối đa {{max}} token",
"maxTokens": "Token đầu ra tối đa",
"temperature": "Temperature",
"reasoningEffort": "Mức suy luận",
"convertTitle": "Chuyển đổi thiết lập mô hình hiện tại",
"convertHelp": "Chuyển mô hình chính và các mô hình dự phòng hiện có thành cấu hình đặt trước để quản lý thứ tự tại đây.",
"converting": "Đang chuyển đổi...",
"convertAction": "Chuyển thành cấu hình đặt trước",
"dragToReorder": "Kéo để sắp xếp lại",
"moveUp": "Di chuyển lên",
"moveDown": "Di chuyển xuống",
"removeFromOrder": "Tắt cấu hình đặt trước",
"inCallOrder": "Trong thứ tự gọi",
"disabled": "Đã tắt",
"noPresets": "Chưa có cấu hình mô hình đặt trước",
"noPresetsHelp": "Tạo cấu hình đặt trước rồi thêm vào thứ tự gọi.",
"removeBeforeDelete": "Hãy xóa cấu hình này khỏi thứ tự gọi trước khi xóa.",
"providerSetupRequired": "Cần cấu hình nhà cung cấp",
"configureProviderBeforeSaving": "Hãy cấu hình nhà cung cấp này trước khi lưu cấu hình đặt trước.",
"deletePresetTitle": "Xóa cấu hình mô hình đặt trước?",
"deletePresetHelp": "Thao tác này xóa cấu hình “{{name}}”. Thông tin xác thực của nhà cung cấp không bị ảnh hưởng.",
"searchModels": "Tìm kiếm hoặc nhập ID mô hình",
"useCustomModel": "Dùng",
"loadingModels": "Đang tải mô hình...",
@@ -707,7 +760,7 @@
"signInHelp": "Inicia sesión desde este dispositivo; no se guarda API key en config.",
"remoteSignInHelp": "Chọn Đăng nhập để mở xAI trên máy tính của bạn, sau đó dán mã ủy quyền được hiển thị sau khi đăng nhập.",
"signInRequired": "Cần đăng nhập",
"signInBeforeSaving": "Inicia sesión antes de guardar este proveedor OAuth como proveedor activo.",
"signInBeforeSaving": "Hãy đăng nhập nhà cung cấp này trước khi lưu cấu hình đặt trước.",
"signedIn": "Đã đăng nhập",
"notSignedIn": "Chưa đăng nhập",
"proxyLabel": "Proxy mạng",
+57 -4
View File
@@ -115,6 +115,39 @@
"configurationName": "配置名称",
"configurationNameHelp": "重命名这个已保存的模型配置。",
"configurationNamePlaceholder": "快速写作",
"callOrder": "模型调用顺序",
"primary": "主模型",
"fallbackNumber": "备用模型 {{number}}",
"addToOrder": "启用预设",
"newPreset": "新建模型预设",
"newPresetHelp": "保存可复用的模型及其生成参数。",
"presets": "模型预设",
"editPreset": "编辑预设",
"presetName": "预设名称",
"presetNameHelp": "在模型设置中使用的简短名称。",
"presetNamePlaceholder": "快速写作",
"advancedOptions": "高级选项",
"advancedSummary": "上下文 {{context}} · 最大输出 {{max}} tokens",
"maxTokens": "最大输出 tokens",
"temperature": "Temperature",
"reasoningEffort": "推理强度",
"convertTitle": "转换现有模型设置",
"convertHelp": "把现有主模型和备用模型转换成预设,之后即可在这里管理调用顺序。",
"converting": "正在转换...",
"convertAction": "转换为预设",
"dragToReorder": "拖动调整顺序",
"moveUp": "上移",
"moveDown": "下移",
"removeFromOrder": "停用预设",
"inCallOrder": "已加入调用顺序",
"disabled": "未启用",
"noPresets": "还没有模型预设",
"noPresetsHelp": "先创建预设,再将它加入调用顺序。",
"removeBeforeDelete": "删除前请先将此预设从调用顺序中移除。",
"providerSetupRequired": "提供商需要配置",
"configureProviderBeforeSaving": "请先配置此提供商,再保存预设。",
"deletePresetTitle": "删除模型预设?",
"deletePresetHelp": "将删除预设“{{name}}”,不会影响提供商凭据。",
"searchModels": "搜索或输入模型 ID",
"useCustomModel": "使用",
"loadingModels": "正在加载模型...",
@@ -180,7 +213,7 @@
"theme": "在浅色和深色外观之间切换。",
"language": "选择 WebUI 使用的语言。",
"provider": "选择处理新模型请求的提供商。",
"model": "设置 nanobot 默认使用的模型名称。",
"model": "选择此预设使用的模型。",
"configPath": "当前网关正在使用的配置文件。",
"currentModel": "用于新的回复。",
"selectedModelProvider": "由选中的模型决定。",
@@ -331,6 +364,7 @@
"comfortable": "舒适",
"compact": "紧凑",
"auto": "自动",
"default": "默认",
"expanded": "展开",
"summary": "摘要",
"diff": "差异",
@@ -364,7 +398,11 @@
"actions": {
"save": "保存",
"saving": "正在保存",
"saveOrder": "保存顺序",
"savePreset": "保存预设",
"edit": "编辑",
"delete": "删除",
"deleting": "正在删除...",
"cancel": "取消",
"open": "打开",
"export": "导出",
@@ -372,7 +410,7 @@
"exporting": "正在导出..."
},
"byok": {
"description": "自带服务商密钥。Nanobot 会从当前 config 读取这些值,只有已配置的服务商才能在通用设置里选择。",
"description": "自带服务商密钥。Nanobot 会从当前 config 读取这些值,只有已配置的服务商才能用于模型预设。",
"configured": "已配置",
"notConfigured": "未配置",
"configuredSection": "已配置",
@@ -449,9 +487,24 @@
}
},
"providers": {
"title": "模型供应商",
"searchPlaceholder": "搜索提供商",
"noMatches": "没有匹配的提供商。",
"saveProvider": "保存提供商"
"addOwnProvider": "添加自己的模型供应商",
"customProvider": "自定义供应商",
"customProviderName": "供应商名称",
"customProviderNamePlaceholder": "例如:公司模型网关",
"saveProvider": "保存供应商",
"advancedOptions": "高级选项",
"saveAdvancedBeforeSignIn": "请先保存高级选项再登录。",
"apiType": "API 类型",
"proxy": "网络代理",
"thinkingStyle": "思考模式",
"region": "区域",
"profile": "配置档案",
"extraHeaders": "额外请求头",
"extraBody": "额外请求体",
"extraQuery": "额外查询参数"
},
"legal": {
"thirdPartyBrands": "产品名称、Logo 和品牌归各自所有者所有;此处仅用于识别,不代表背书或合作。"
@@ -721,7 +774,7 @@
"signInHelp": "从这台设备登录;不会在配置中保存 API key。",
"remoteSignInHelp": "点击“登录”在你的电脑上打开 xAI,完成登录后粘贴页面显示的授权码。",
"signInRequired": "需要登录",
"signInBeforeSaving": "将此 OAuth 提供商设为当前模型提供商前,请先登录。",
"signInBeforeSaving": "请先登录此提供商,再保存模型预设。",
"signedIn": "已登录",
"notSignedIn": "未登录",
"proxyLabel": "网络代理",
+57 -4
View File
@@ -161,7 +161,7 @@
"theme": "在淺色與深色外觀之間切換。",
"language": "選擇 WebUI 使用的語言。",
"provider": "選擇處理新模型請求的供應商。",
"model": "設定 nanobot 預設使用的模型名稱。",
"model": "選擇此預設使用的模型。",
"configPath": "目前閘道使用中的設定檔。",
"selectedPreset": "命名預設在此為唯讀;請在 config.json 中編輯。",
"presetModel": "切回 Default 後可在 WebUI 中編輯模型與供應商。",
@@ -217,6 +217,7 @@
"compact": "緊湊",
"auto": "自動",
"expanded": "展開",
"default": "預設",
"summary": "摘要",
"diff": "差異",
"collapsedDiff": "摺疊差異",
@@ -249,6 +250,10 @@
"actions": {
"save": "儲存",
"saving": "正在儲存",
"saveOrder": "儲存順序",
"savePreset": "儲存預設",
"delete": "刪除",
"deleting": "正在刪除…",
"edit": "編輯",
"cancel": "取消",
"open": "開啟",
@@ -257,7 +262,7 @@
"exporting": "正在匯出…"
},
"byok": {
"description": "使用自備的供應商金鑰。Nanobot 會從目前的設定檔讀取這些值,只有已設定的供應商可在 [一般] 設定中選擇。",
"description": "使用自備的供應商金鑰。Nanobot 會從目前的設定檔讀取這些值,只有已設定的供應商可用於模型預設。",
"configured": "已設定",
"notConfigured": "未設定",
"configuredSection": "已設定",
@@ -334,9 +339,24 @@
}
},
"providers": {
"title": "模型供應商",
"searchPlaceholder": "搜尋供應商",
"noMatches": "沒有符合的供應商。",
"saveProvider": "儲存供應商"
"addOwnProvider": "新增自己的模型供應商",
"customProvider": "自訂供應商",
"customProviderName": "供應商名稱",
"customProviderNamePlaceholder": "例如:公司模型閘道",
"saveProvider": "儲存供應商",
"advancedOptions": "進階選項",
"saveAdvancedBeforeSignIn": "請先儲存進階選項再登入。",
"apiType": "API 類型",
"proxy": "網路代理",
"thinkingStyle": "思考模式",
"region": "區域",
"profile": "設定檔",
"extraHeaders": "額外請求標頭",
"extraBody": "額外請求本文",
"extraQuery": "額外查詢參數"
},
"image": {
"selectProvider": "選擇供應商",
@@ -353,6 +373,39 @@
"configurationName": "設定名稱",
"configurationNameHelp": "重新命名這個已儲存的模型設定。",
"configurationNamePlaceholder": "快速寫作",
"callOrder": "模型呼叫順序",
"primary": "主要模型",
"fallbackNumber": "備用模型 {{number}}",
"addToOrder": "啟用預設",
"newPreset": "新增模型預設",
"newPresetHelp": "儲存可重複使用的模型與生成參數。",
"presets": "模型預設",
"editPreset": "編輯預設",
"presetName": "預設名稱",
"presetNameHelp": "模型設定中使用的簡短名稱。",
"presetNamePlaceholder": "快速寫作",
"advancedOptions": "進階選項",
"advancedSummary": "上下文 {{context}} · 最大輸出 {{max}} tokens",
"maxTokens": "最大輸出 tokens",
"temperature": "Temperature",
"reasoningEffort": "推理強度",
"convertTitle": "轉換現有模型設定",
"convertHelp": "將現有主要模型和備用模型轉換為預設,之後即可在這裡管理呼叫順序。",
"converting": "正在轉換…",
"convertAction": "轉換為預設",
"dragToReorder": "拖曳調整順序",
"moveUp": "上移",
"moveDown": "下移",
"removeFromOrder": "停用預設",
"inCallOrder": "已加入呼叫順序",
"disabled": "未啟用",
"noPresets": "尚無模型預設",
"noPresetsHelp": "先建立預設,再將它加入呼叫順序。",
"removeBeforeDelete": "刪除前請先將此預設從呼叫順序中移除。",
"providerSetupRequired": "供應商需要設定",
"configureProviderBeforeSaving": "請先設定此供應商,再儲存預設。",
"deletePresetTitle": "刪除模型預設?",
"deletePresetHelp": "將刪除預設「{{name}}」,不會影響供應商憑證。",
"searchModels": "搜尋或輸入模型 ID",
"useCustomModel": "使用",
"loadingModels": "正在載入模型…",
@@ -707,7 +760,7 @@
"signInHelp": "請從這臺裝置登入;系統不會將 API 金鑰儲存在設定中。",
"remoteSignInHelp": "點擊「登入」在你的電腦上開啟 xAI,完成登入後貼上頁面顯示的授權碼。",
"signInRequired": "需要登入",
"signInBeforeSaving": "將此 OAuth 供應商設為目前模型供應商前,請先登入。",
"signInBeforeSaving": "請先登入此供應商,再儲存模型預設。",
"signedIn": "已登入",
"notSignedIn": "未登入",
"proxyLabel": "網路代理",
+82 -9
View File
@@ -15,6 +15,7 @@ import type {
ModelConfigurationUpdate,
NetworkSafetySettingsUpdate,
PairingPayload,
ProviderCreationUpdate,
ProviderModelsPayload,
ProviderOAuthCompletionResult,
ProviderOAuthLoginResult,
@@ -54,6 +55,7 @@ function isSlashCommandLifecycle(value: unknown): value is SlashCommandLifecycle
const CHANNEL_VALUES_HEADER = "X-Nanobot-Channel-Values";
const API_SERVICE_VALUES_HEADER = "X-Nanobot-API-Service-Values";
const OAUTH_CODE_HEADER = "X-Nanobot-OAuth-Code";
const PROVIDER_VALUES_HEADER = "X-Nanobot-Provider-Values";
export class ApiError extends Error {
status: number;
@@ -767,6 +769,27 @@ export async function updateSettings(
return request<SettingsPayload>(`${base}/api/settings/update?${query}`, token);
}
function appendModelGenerationSettings(
query: URLSearchParams,
configuration: Pick<
ModelConfigurationCreate,
"maxTokens" | "contextWindowTokens" | "temperature" | "reasoningEffort"
>,
): void {
if (configuration.maxTokens !== undefined) {
query.set("max_tokens", String(configuration.maxTokens));
}
if (configuration.contextWindowTokens !== undefined) {
query.set("context_window_tokens", String(configuration.contextWindowTokens));
}
if (configuration.temperature !== undefined) {
query.set("temperature", String(configuration.temperature));
}
if (configuration.reasoningEffort !== undefined) {
query.set("reasoning_effort", configuration.reasoningEffort ?? "");
}
}
export async function createModelConfiguration(
token: string,
configuration: ModelConfigurationCreate,
@@ -777,6 +800,7 @@ export async function createModelConfiguration(
query.set("label", configuration.label);
query.set("provider", configuration.provider);
query.set("model", configuration.model);
appendModelGenerationSettings(query, configuration);
return request<SettingsPayload>(
`${base}/api/settings/model-configurations/create?${query}`,
token,
@@ -793,29 +817,78 @@ export async function updateModelConfiguration(
if (configuration.label !== undefined) query.set("label", configuration.label);
if (configuration.provider !== undefined) query.set("provider", configuration.provider);
if (configuration.model !== undefined) query.set("model", configuration.model);
if (configuration.contextWindowTokens !== undefined) {
query.set("context_window_tokens", String(configuration.contextWindowTokens));
}
appendModelGenerationSettings(query, configuration);
return request<SettingsPayload>(
`${base}/api/settings/model-configurations/update?${query}`,
token,
);
}
export async function deleteModelConfiguration(
token: string,
name: string,
base: string = "",
): Promise<SettingsPayload> {
const query = new URLSearchParams({ name });
return request<SettingsPayload>(
`${base}/api/settings/model-configurations/delete?${query}`,
token,
);
}
export async function migrateModelConfigurations(
token: string,
base: string = "",
): Promise<SettingsPayload> {
return request<SettingsPayload>(
`${base}/api/settings/model-configurations/migrate`,
token,
);
}
export async function updateModelCallOrder(
token: string,
order: string[],
base: string = "",
): Promise<SettingsPayload> {
const query = new URLSearchParams({ order: JSON.stringify(order) });
return request<SettingsPayload>(
`${base}/api/settings/model-call-order/update?${query}`,
token,
);
}
export async function updateProviderSettings(
token: string,
update: ProviderSettingsUpdate,
base: string = "",
): Promise<SettingsPayload> {
const query = new URLSearchParams();
query.set("provider", update.provider);
if (update.apiKey !== undefined) query.set("api_key", update.apiKey);
if (update.apiBase !== undefined) query.set("api_base", update.apiBase);
if (update.apiType !== undefined) query.set("api_type", update.apiType);
if (update.proxy !== undefined) query.set("proxy", update.proxy);
const { provider, ...values } = update;
const query = new URLSearchParams({ provider });
return request<SettingsPayload>(
`${base}/api/settings/provider/update?${query}`,
token,
{
headers: {
[PROVIDER_VALUES_HEADER]: encodeURIComponent(JSON.stringify(values)),
},
},
);
}
export async function createProviderSettings(
token: string,
update: ProviderCreationUpdate,
base: string = "",
): Promise<SettingsPayload> {
return request<SettingsPayload>(
`${base}/api/settings/provider/create`,
token,
{
headers: {
[PROVIDER_VALUES_HEADER]: encodeURIComponent(JSON.stringify(update)),
},
},
);
}
+46
View File
@@ -432,9 +432,14 @@ export interface SettingsPayload {
reasoning_effort: string | null;
reasoning_effort_values?: string[];
}>;
model_call_order: string[];
model_call_order_editable: boolean;
created_model_preset?: string;
created_provider?: string;
providers: Array<{
name: string;
label: string;
is_custom?: boolean;
configured: boolean;
auth_type?: "api_key" | "oauth";
api_key_required?: boolean;
@@ -448,6 +453,22 @@ export interface SettingsPayload {
oauth_expires_at?: number | null;
oauth_login_supported?: boolean;
proxy?: string | null;
advanced_fields?: Array<
| "api_type"
| "extra_headers"
| "extra_body"
| "extra_query"
| "proxy"
| "thinking_style"
| "region"
| "profile"
>;
extra_headers?: Record<string, string> | null;
extra_body?: Record<string, unknown> | null;
extra_query?: Record<string, string> | null;
thinking_style?: string | null;
region?: string | null;
profile?: string | null;
}>;
web_search: {
provider: string;
@@ -941,6 +962,10 @@ export interface ModelConfigurationCreate {
label: string;
provider: string;
model: string;
maxTokens?: number;
contextWindowTokens?: number;
temperature?: number;
reasoningEffort?: string | null;
}
export interface ModelConfigurationUpdate {
@@ -948,15 +973,36 @@ export interface ModelConfigurationUpdate {
label?: string;
provider?: string;
model?: string;
maxTokens?: number;
contextWindowTokens?: number;
temperature?: number;
reasoningEffort?: string | null;
}
export interface ProviderSettingsUpdate {
provider: string;
displayName?: string;
apiKey?: string;
apiBase?: string;
apiType?: "auto" | "chat_completions" | "responses";
proxy?: string;
extraHeaders?: string;
extraBody?: string;
extraQuery?: string;
thinkingStyle?: string;
region?: string;
profile?: string;
}
export interface ProviderCreationUpdate {
name: string;
apiKey?: string;
apiBase: string;
proxy?: string;
extraHeaders?: string;
extraBody?: string;
extraQuery?: string;
thinkingStyle?: string;
}
export interface WebSearchSettingsUpdate {
+94 -7
View File
@@ -4,6 +4,8 @@ import {
configureChannel,
completeProviderOAuth,
createModelConfiguration,
createProviderSettings,
deleteModelConfiguration,
deleteSession,
fetchFilePreview,
fetchFilePreviewAvailability,
@@ -26,6 +28,7 @@ import {
listSlashCommands,
loginProviderOAuth,
logoutProviderOAuth,
migrateModelConfigurations,
disableNanobotFeature,
enableNanobotFeature,
runAutomationAction,
@@ -40,6 +43,7 @@ import {
updateAutomation,
updateSidebarState,
updateImageGenerationSettings,
updateModelCallOrder,
updateModelConfiguration,
updateMcpServerTools,
updateNetworkSafetySettings,
@@ -340,10 +344,14 @@ describe("webui API helpers", () => {
label: "Fast writing",
provider: "openai",
model: "openai/gpt-4.1-mini",
maxTokens: 4096,
contextWindowTokens: 128000,
temperature: 0.4,
reasoningEffort: "high",
});
expect(fetch).toHaveBeenCalledWith(
"/api/settings/model-configurations/create?label=Fast+writing&provider=openai&model=openai%2Fgpt-4.1-mini",
"/api/settings/model-configurations/create?label=Fast+writing&provider=openai&model=openai%2Fgpt-4.1-mini&max_tokens=4096&context_window_tokens=128000&temperature=0.4&reasoning_effort=high",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
@@ -356,11 +364,45 @@ describe("webui API helpers", () => {
label: "Codex",
provider: "openai_codex",
model: "openai-codex/gpt-5.5",
maxTokens: 8192,
contextWindowTokens: 65536,
temperature: 0,
reasoningEffort: null,
});
expect(fetch).toHaveBeenCalledWith(
"/api/settings/model-configurations/update?name=codex&label=Codex&provider=openai_codex&model=openai-codex%2Fgpt-5.5&context_window_tokens=65536",
"/api/settings/model-configurations/update?name=codex&label=Codex&provider=openai_codex&model=openai-codex%2Fgpt-5.5&max_tokens=8192&context_window_tokens=65536&temperature=0&reasoning_effort=",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
);
});
it("serializes model preset deletion and migration", async () => {
await deleteModelConfiguration("tok", "spare");
await migrateModelConfigurations("tok");
expect(fetch).toHaveBeenNthCalledWith(
1,
"/api/settings/model-configurations/delete?name=spare",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
);
expect(fetch).toHaveBeenNthCalledWith(
2,
"/api/settings/model-configurations/migrate",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
);
});
it("serializes model call order as an ordered JSON array", async () => {
await updateModelCallOrder("tok", ["backup", "primary"]);
expect(fetch).toHaveBeenCalledWith(
"/api/settings/model-call-order/update?order=%5B%22backup%22%2C%22primary%22%5D",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
@@ -425,23 +467,68 @@ describe("webui API helpers", () => {
});
expect(fetch).toHaveBeenCalledWith(
"/api/settings/provider/update?provider=openrouter&api_key=sk-or-test&api_base=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1",
"/api/settings/provider/update?provider=openrouter",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
headers: {
Authorization: "Bearer tok",
"X-Nanobot-Provider-Values": encodeURIComponent(JSON.stringify({
apiKey: "sk-or-test",
apiBase: "https://openrouter.ai/api/v1",
})),
},
}),
);
});
it("serializes OAuth provider proxy updates", async () => {
it("serializes OAuth provider advanced settings", async () => {
await updateProviderSettings("tok", {
provider: "xai_grok",
proxy: "http://127.0.0.1:7890",
extraBody: '{"service_tier":"priority"}',
});
expect(fetch).toHaveBeenCalledWith(
"/api/settings/provider/update?provider=xai_grok&proxy=http%3A%2F%2F127.0.0.1%3A7890",
"/api/settings/provider/update?provider=xai_grok",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
headers: {
Authorization: "Bearer tok",
"X-Nanobot-Provider-Values": encodeURIComponent(JSON.stringify({
proxy: "http://127.0.0.1:7890",
extraBody: '{"service_tier":"priority"}',
})),
},
}),
);
});
it("serializes custom provider creation with advanced settings", async () => {
await createProviderSettings("tok", {
name: "Company Gateway",
apiKey: "sk-company",
apiBase: "https://gateway.example/v1",
extraHeaders: '{"X-Tenant":"engineering"}',
extraBody: '{"service_tier":"priority"}',
extraQuery: '{"api-version":"2026-01-01"}',
proxy: "http://127.0.0.1:7890",
thinkingStyle: "enable_thinking",
});
expect(fetch).toHaveBeenCalledWith(
"/api/settings/provider/create",
expect.objectContaining({
headers: {
Authorization: "Bearer tok",
"X-Nanobot-Provider-Values": encodeURIComponent(JSON.stringify({
name: "Company Gateway",
apiKey: "sk-company",
apiBase: "https://gateway.example/v1",
extraHeaders: '{"X-Tenant":"engineering"}',
extraBody: '{"service_tier":"priority"}',
extraQuery: '{"api-version":"2026-01-01"}',
proxy: "http://127.0.0.1:7890",
thinkingStyle: "enable_thinking",
})),
},
}),
);
});
+73 -37
View File
@@ -74,6 +74,8 @@ function baseSettingsPayload() {
temperature: 0.1,
reasoning_effort: null,
}],
model_call_order: [],
model_call_order_editable: false,
providers: [],
web_search: {
provider: "duckduckgo",
@@ -370,7 +372,10 @@ describe("App layout", () => {
render(<App />);
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
expect((await screen.findAllByRole("heading", { name: "Channels" })).length).toBeGreaterThan(0);
expect(
await screen.findByRole("navigation", { name: "Settings sections" }),
).toBeInTheDocument();
expect(screen.queryByRole("heading", { name: "Channels" })).not.toBeInTheDocument();
expect(window.location.hash).toBe("#/settings?section=channels");
});
@@ -1403,7 +1408,7 @@ describe("App layout", () => {
provider: "auto",
resolved_provider: "openai",
has_api_key: true,
model_preset: "default",
model_preset: "primary",
max_tokens: 8192,
context_window_tokens: 65536,
temperature: 0.1,
@@ -1415,12 +1420,13 @@ describe("App layout", () => {
},
model_presets: [
{
name: "default",
label: "Default",
name: "primary",
label: "Primary",
active: true,
is_default: true,
is_default: false,
model: "openai/gpt-4o",
provider: "auto",
resolved_provider: "openai",
max_tokens: 8192,
context_window_tokens: 65536,
temperature: 0.1,
@@ -1439,6 +1445,8 @@ describe("App layout", () => {
reasoning_effort: "high",
},
],
model_call_order: ["primary", "deep"],
model_call_order_editable: true,
providers: [
{
name: "openai",
@@ -1599,7 +1607,10 @@ describe("App layout", () => {
expect(searchButton.compareDocumentPosition(appsButton) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
fireEvent.click(within(sidebar).getByRole("button", { name: "Settings" }));
expect(await screen.findByRole("heading", { name: "Overview" })).toBeInTheDocument();
expect(
await screen.findByRole("navigation", { name: "Settings sections" }),
).toBeInTheDocument();
expect(screen.queryByRole("heading", { name: "Overview" })).not.toBeInTheDocument();
expect(document.title).toBe("Settings · nanobot");
expect(screen.getByTestId("overview-logo-openai")).toBeInTheDocument();
expect(screen.getByTestId("overview-logo-brave")).toBeInTheDocument();
@@ -1630,21 +1641,26 @@ describe("App layout", () => {
fireEvent.pointerDown(within(settingsNav).getByRole("button", { name: "Settings: Appearance" }));
fireEvent.click(await screen.findByRole("menuitem", { name: "Models" }));
expect(screen.queryByText("AI")).not.toBeInTheDocument();
expect(screen.getByText("Current configuration")).toBeInTheDocument();
expect(screen.queryByText("Presets")).not.toBeInTheDocument();
fireEvent.pointerDown(screen.getByRole("button", { name: "Current configuration" }));
fireEvent.click(screen.getByRole("menuitem", { name: "Add configuration" }));
const modelDialog = await screen.findByRole("dialog", { name: "New model configuration" });
expect(within(modelDialog).getByText("Save a provider and model as a one-click option.")).toBeInTheDocument();
fireEvent.change(within(modelDialog).getByPlaceholderText("Fast writing"), {
expect(screen.getByText("Model presets")).toBeInTheDocument();
expect(screen.queryByText("Model call order")).not.toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "New model preset" }));
expect(screen.queryByRole("dialog", { name: "New model preset" })).not.toBeInTheDocument();
fireEvent.change(screen.getByPlaceholderText("Fast writing"), {
target: { value: "Fast writing" },
});
fireEvent.change(within(modelDialog).getByPlaceholderText("openai/gpt-4.1"), {
target: { value: "openai/gpt-4.1-mini" },
});
expect(within(modelDialog).getByRole("button", { name: /OpenAI/ })).toBeInTheDocument();
expect(within(modelDialog).getByRole("button", { name: "Save" })).toBeEnabled();
fireEvent.click(within(modelDialog).getByRole("button", { name: "Cancel" }));
expect(
screen
.getAllByRole("button", { name: /OpenAI/ })
.some((button) => button.getAttribute("aria-haspopup") === "menu"),
).toBe(true);
fireEvent.pointerDown(screen.getByRole("button", { name: "Select model" }));
fireEvent.click(await screen.findByText("openai/gpt-4o-mini"));
expect(screen.getByRole("button", { name: "Save preset" })).toBeEnabled();
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
expect(screen.queryByText("Up to date.")).not.toBeInTheDocument();
fireEvent.click(
within(screen.getByTestId("model-call-order-row-primary")).getAllByRole("button")[0],
);
fireEvent.pointerDown(screen.getByRole("button", { name: /Auto/ }));
expect(screen.getAllByTestId("provider-picker-logo-openai").length).toBeGreaterThan(0);
fireEvent.click(screen.getByRole("menuitem", { name: /Auto/ }));
@@ -1655,18 +1671,20 @@ describe("App layout", () => {
openModelPicker();
await screen.findByText("openai/gpt-4o-mini");
fireEvent.click(screen.getAllByText("openai/gpt-4o-mini")[0]);
expect(screen.getByText("Unsaved changes.").parentElement?.className).toContain(
"text-blue-600",
);
const updatedModelButtons = screen.getAllByRole("button", { name: /openai\/gpt-4o-mini/ });
fireEvent.pointerDown(updatedModelButtons[updatedModelButtons.length - 1]);
await screen.findByText("openai/gpt-4o");
fireEvent.click(screen.getAllByText("openai/gpt-4o")[0]);
expect(screen.getByText("OpenRouter")).toBeInTheDocument();
expect(screen.getByText("Ant Ling")).toBeInTheDocument();
expect(screen.queryByText("Unsaved changes.")).not.toBeInTheDocument();
expect(screen.getByText("Model providers")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Add your own model provider" })).toBeInTheDocument();
expect(screen.queryByText("OpenRouter")).not.toBeInTheDocument();
expect(screen.queryByText("Ant Ling")).not.toBeInTheDocument();
expect(
screen.queryByText(
"Bring your own provider keys. Nanobot reads these values from the current config and only configured providers can be used in model presets.",
),
).not.toBeInTheDocument();
expect(screen.queryByText("azure_openai")).not.toBeInTheDocument();
expect(screen.getByTestId("provider-logo-openai")).toBeInTheDocument();
expect(screen.getByText(/Product names, logos, and brands/)).toBeInTheDocument();
expect(screen.getAllByText("Not configured").length).toBeGreaterThan(0);
expect(screen.queryByText(/Product names, logos, and brands/)).not.toBeInTheDocument();
expect(screen.queryByText("Not configured")).not.toBeInTheDocument();
const clickProviderRow = (label: string) => {
const providerLabel = screen
.getAllByText(label)
@@ -1674,23 +1692,33 @@ describe("App layout", () => {
expect(providerLabel).toBeTruthy();
fireEvent.click(providerLabel!);
};
const chooseProvider = async (label: string) => {
fireEvent.pointerDown(
screen.getByRole("button", { name: "Add your own model provider" }),
);
fireEvent.click(await screen.findByRole("menuitem", { name: label }));
};
clickProviderRow("OpenAI");
fireEvent.click(screen.getByRole("button", { name: "Edit" }));
fireEvent.change(screen.getByPlaceholderText("Leave blank to keep the current key"), {
target: { value: "unsaved-openai-key" },
});
clickProviderRow("OpenAI");
await chooseProvider("OpenRouter");
clickProviderRow("OpenRouter");
clickProviderRow("OpenAI");
expect(screen.getByText("open••••-key")).toBeInTheDocument();
expect(screen.queryByDisplayValue("unsaved-openai-key")).not.toBeInTheDocument();
clickProviderRow("Ant Ling");
clickProviderRow("OpenAI");
await chooseProvider("Ant Ling");
expect(screen.getByDisplayValue("https://api.ant-ling.com/v1")).toBeInTheDocument();
clickProviderRow("Atomic Chat");
clickProviderRow("Ant Ling");
await chooseProvider("Atomic Chat");
expect(screen.getByDisplayValue("http://localhost:1337/v1")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Save provider" })).toBeEnabled();
fireEvent.click(within(settingsNav).getByRole("button", { name: "Image" }));
expect(screen.getByRole("heading", { name: "Image" })).toBeInTheDocument();
expect(screen.queryByRole("heading", { name: "Image" })).not.toBeInTheDocument();
expect(screen.getByRole("switch", { name: "Image generation" })).toBeInTheDocument();
expect(screen.getByText("Provider status")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "openai/gpt-5.4-image-2" })).toBeInTheDocument();
@@ -1750,7 +1778,10 @@ describe("App layout", () => {
render(<App />);
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
expect(await screen.findByRole("heading", { name: "Overview" })).toBeInTheDocument();
expect(
await screen.findByRole("navigation", { name: "Settings sections" }),
).toBeInTheDocument();
expect(screen.queryByRole("heading", { name: "Overview" })).not.toBeInTheDocument();
});
it("updates the URL hash when switching settings sections", async () => {
@@ -1761,13 +1792,16 @@ describe("App layout", () => {
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
fireEvent.click(within(sidebar).getByRole("button", { name: "Settings" }));
expect(await screen.findByRole("heading", { name: "Overview" })).toBeInTheDocument();
expect(
await screen.findByRole("navigation", { name: "Settings sections" }),
).toBeInTheDocument();
expect(window.location.hash).toBe("#/settings");
const settingsNav = screen.getByRole("navigation", { name: "Settings sections" });
fireEvent.click(within(settingsNav).getByRole("button", { name: "Models" }));
expect(await screen.findByRole("heading", { name: "Models" })).toBeInTheDocument();
expect(await screen.findByText("Model presets")).toBeInTheDocument();
expect(screen.queryByRole("heading", { name: "Models" })).not.toBeInTheDocument();
expect(window.location.hash).toBe("#/settings?section=models");
fireEvent.click(within(settingsNav).getByRole("button", { name: "Voice" }));
@@ -1939,7 +1973,9 @@ describe("App layout", () => {
await waitFor(() => expect(document.title).toBe("nanobot"));
fireEvent.click(within(sidebar).getByRole("button", { name: "Settings" }));
expect(await screen.findByRole("heading", { name: "Overview" })).toBeInTheDocument();
expect(
await screen.findByRole("navigation", { name: "Settings sections" }),
).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Back to chat" }));
await waitFor(() => expect(document.title).toBe("nanobot"));
File diff suppressed because it is too large Load Diff
+2
View File
@@ -170,6 +170,8 @@ function modelSettings(model: string, provider: string): SettingsPayload {
temperature: 0.7,
reasoning_effort: null,
}],
model_call_order: [],
model_call_order_editable: false,
providers: [
{ name: "deepseek", label: "DeepSeek", configured: true },
{ name: "openai_codex", label: "OpenAI Codex", configured: true },