fix(settings): render Managed provider as always-on badge, not a locked toggle (#3984)

This commit is contained in:
Shabnam
2026-06-23 11:49:22 -07:00
committed by GitHub
parent c96bd3db3f
commit 6b75c49232
16 changed files with 77 additions and 14 deletions
+28 -10
View File
@@ -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 = ({
<div
className={`inline-flex items-center gap-2 rounded-full px-2.5 py-1 text-xs font-medium ring-1 transition-colors dark:ring-neutral-700 ${tone}`}>
<span>{label}</span>
<SettingsSwitch
id={`provider-toggle-${slug}`}
checked={enabled}
onCheckedChange={onToggle}
disabled={busy || locked}
aria-label={providerToggleAriaLabel(t, enabled, label)}
/>
{alwaysOn ? (
<span className="inline-flex items-center gap-1 opacity-80">
<LuCheck className="h-3 w-3" />
{t('settings.ai.routing.managedAlwaysOn')}
</span>
) : (
<SettingsSwitch
id={`provider-toggle-${slug}`}
checked={enabled}
onCheckedChange={() => onToggle?.()}
disabled={busy || locked}
aria-label={providerToggleAriaLabel(t, enabled, label)}
/>
)}
</div>
);
};
@@ -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 = {}) => {
})}
</div>
{/* #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. */}
<p className="text-xs text-neutral-500 dark:text-neutral-400">
{t('settings.ai.routing.managedHint')}
</p>
<div className="flex flex-col gap-2 pt-1">
{/* Codex — imports the existing Codex CLI login as an OpenAI credential. */}
<div className="flex flex-wrap items-center gap-2">
@@ -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(<AIPanel />);
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 () => {
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':
+3
View File
@@ -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':