diff --git a/app/src/components/settings/panels/AIPanel.tsx b/app/src/components/settings/panels/AIPanel.tsx index 9a1e491ca..d531f408d 100644 --- a/app/src/components/settings/panels/AIPanel.tsx +++ b/app/src/components/settings/panels/AIPanel.tsx @@ -612,6 +612,7 @@ const ProviderToggleChip = ({ enabled, busy, locked = false, + alwaysOn = false, onToggle, }: { slug: string; @@ -619,7 +620,11 @@ const ProviderToggleChip = ({ enabled: boolean; busy?: boolean; locked?: boolean; - onToggle: () => void; + // When true the provider is permanently available (e.g. Managed) and renders + // a static "Always on" indicator instead of a toggle. A locked toggle reads + // as switchable-but-broken (#3760); a badge has no affordance to fight. + alwaysOn?: boolean; + onToggle?: () => void; }) => { const { t } = useT(); const tone = slugTone(slug); @@ -627,13 +632,20 @@ const ProviderToggleChip = ({
{label} - + {alwaysOn ? ( + + + {t('settings.ai.routing.managedAlwaysOn')} + + ) : ( + onToggle?.()} + disabled={busy || locked} + aria-label={providerToggleAriaLabel(t, enabled, label)} + /> + )}
); }; @@ -3162,8 +3174,7 @@ const AIPanel = ({ embedded = false }: AIPanelProps = {}) => { slug="openhuman" label={t('settings.ai.routing.managed')} enabled - locked - onToggle={() => {}} + alwaysOn /> {/* Built-in cloud providers */} @@ -3283,6 +3294,13 @@ const AIPanel = ({ embedded = false }: AIPanelProps = {}) => { })} + {/* #3760: Managed is always-on and can't be turned off; point users + who want a local model at the Routing card below instead of + letting them fight the (now badge, formerly locked) Managed chip. */} +

+ {t('settings.ai.routing.managedHint')} +

+
{/* Codex — imports the existing Codex CLI login as an OpenAI credential. */}
diff --git a/app/src/components/settings/panels/__tests__/AIPanel.test.tsx b/app/src/components/settings/panels/__tests__/AIPanel.test.tsx index c9bda0c46..16d15c7dd 100644 --- a/app/src/components/settings/panels/__tests__/AIPanel.test.tsx +++ b/app/src/components/settings/panels/__tests__/AIPanel.test.tsx @@ -279,11 +279,14 @@ describe('AIPanel', () => { await waitFor(() => expect(screen.getAllByText(/OpenHuman/i).length).toBeGreaterThan(0)); }); - it('renders the always-on Managed chip', async () => { + it('renders Managed as an always-on badge, not a switchable toggle (#3760)', async () => { renderWithProviders(); - const managedSwitch = await screen.findByRole('switch', { name: /Disconnect Managed/i }); - expect(managedSwitch).toBeDisabled(); - expect(managedSwitch).toHaveAttribute('aria-checked', 'true'); + // The Managed chip must show an "Always on" indicator... + expect(await screen.findByText(/Always on/i)).toBeInTheDocument(); + // ...and must NOT render a toggle switch users would try (and fail) to flip. + expect(screen.queryByRole('switch', { name: /Managed/i })).toBeNull(); + // A hint points users wanting a local model at the Routing card below. + expect(screen.getByText(/choose a routing mode below/i)).toBeInTheDocument(); }); it('renders Managed, Use Your Own Models, and Advanced routing controls', async () => { diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index 454802eb0..74b52f218 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -3623,6 +3623,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'استطلاعات عاملي الذاكرة', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'المُدارة', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': '(Xqx0xx) سيحقق كل شيء في السحابة، ويختار أفضل نموذج للمهمة، ويرفع التكاليف إلى أقصى حد، ويحتفظ بأمن التخلف عن الدفع.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index fac4f0305..ea73eaf95 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -3700,6 +3700,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'মেমরি কর্মী পোল', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'পরিচালিত', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'xq0xqx সমস্ত প্রকার মেঘে রান করা হবে, কর্মের জন্য সেরা মডেল নির্বাচন করুন, খরচের জন্য ব্যবহারযোগ্য ডিফল্ট মান নির্বাচন করুন।', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index 20e7e7862..b9f34547f 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -3789,6 +3789,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'Speicher-Worker-Umfragen', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'Verwaltet', + 'settings.ai.routing.managedAlwaysOn': 'Immer aktiv', + 'settings.ai.routing.managedHint': + 'Verwaltet ist immer als Fallback verfügbar. Um dein eigenes Modell zu verwenden, wähle unten einen Routing-Modus.', 'settings.ai.routing.managedDesc': 'OpenHuman führt alle Inferenzen in der Cloud aus, wählt das beste Modell für die Aufgabe aus, optimiert die Kosten und behält die sichersten Routing-Standards bei.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index ddc42d860..b0ff5f1b5 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -4303,6 +4303,9 @@ const en: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'Memory worker polls', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'Managed', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman will run all inference in the cloud, choose the best model for the task, optimize for cost, and keep the safest routing defaults.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index 391cb9a62..fbc4bcfbc 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -3765,6 +3765,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'Encuestas de trabajadores de la memoria', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'Gestionado', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman ejecutará toda la inferencia en la nube, elegirá el mejor modelo para la tarea, optimizará para el costo y mantendrá los valores predeterminados de enrutamiento más seguros.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index a817c0551..ee5ea1ee6 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -3779,6 +3779,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'Sondages de mémoire', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'Géré', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman exécutera toutes les inférences dans le cloud, choisira le meilleur modèle pour la tâche, optimisera les coûts et conservera les paramètres de routage les plus sûrs par défaut.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index 7180f2e02..9dd2cfe1d 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -3706,6 +3706,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'स्मृति कार्यकर्ता सर्वेक्षण', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'प्रबंधित', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman बादल में सभी अनुमान चला जाएगा, कार्य के लिए सबसे अच्छा मॉडल चुनें, लागत के लिए अनुकूलन करें और सबसे सुरक्षित रूटिंग डिफ़ॉल्ट रखें।', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index 50af404d4..4f8138d49 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -3709,6 +3709,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'Jajak pendapat pekerja memori', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'Terkelola', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman akan menjalankan semua kesimpulan di awan, memilih model terbaik untuk tugas ini, mengoptimalkan biaya, dan menjaga standar routing teraman.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index 08189487a..bf2206a23 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -3756,6 +3756,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'Sondaggi del Memory Worker', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'Gestiti', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman eseguirà tutte le inferenze nel cloud, sceglierà il miglior modello per il compito, ottimizzerà i costi e manterrà le impostazioni di routing più sicure.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index c0e1ac24e..38b7ca9ed 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -3673,6 +3673,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': '메모리 작업자 설문 조사', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': '관리됨', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman이 모든 추론을 클라우드에서 실행하고, 작업에 가장 적합한 모델을 선택하며, 비용을 최적화하고 가장 안전한 라우팅 기본값을 유지합니다.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index 419a7479e..9c9cdfe7a 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -3755,6 +3755,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'Odpytywania procesu pamięci', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'Zarządzane', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman uruchomi całą inferencję w chmurze, wybierze najlepszy model dla zadania, zoptymalizuje koszty i zachowa najbezpieczniejsze domyślne ustawienia routingu.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index 775a8b412..90eec2793 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -3761,6 +3761,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'Pesquisas de trabalho de memória', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'Gerenciadas', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman executará toda a inferência na nuvem, escolherá o melhor modelo para a tarefa, otimizará os custos e manterá os padrões de roteamento mais seguros.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index bfad46ac6..2991f33eb 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -3733,6 +3733,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': 'Опросы работников памяти', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': 'Управляемый', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman выполнит все логические выводы в облаке, выберет лучшую модель для задачи, оптимизирует затраты и сохранит самые безопасные настройки маршрутизации по умолчанию.', 'settings.ai.routing.managedMsg': diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index 6303e71b3..9f1d27298 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -3518,6 +3518,9 @@ const messages: TranslationMap = { 'settings.ai.memoryWorkerPolls': '内存工作者民意调查', 'settings.ai.defaultProviderName': 'OpenHuman', 'settings.ai.routing.managed': '托管', + 'settings.ai.routing.managedAlwaysOn': 'Always on', + 'settings.ai.routing.managedHint': + 'Managed is always available as a fallback. To use your own model, choose a routing mode below.', 'settings.ai.routing.managedDesc': 'OpenHuman 会在云端运行所有推理,为任务选择最佳模型,优化成本,并保持最安全的路由默认值。', 'settings.ai.routing.managedMsg':