Skip to content

Commit 805fdca

Browse files
committed
feat: add gateway enabled toggle in Providers settings
- Add global gateway enabled state to localStorage (defaults to true) - Add toggle switch in Mux Gateway provider section when coupon is set - Respect global enabled state across all gateway UI elements - Simplify GatewayIcon to diamond relay symbol This allows users to disable gateway routing without removing their coupon code, useful for testing or debugging.
1 parent d75244f commit 805fdca

File tree

5 files changed

+88
-17
lines changed

5 files changed

+88
-17
lines changed

src/browser/components/ModelSelector.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
3333
isEnabled: isGatewayEnabled,
3434
toggle: toggleGateway,
3535
gatewayAvailable,
36+
gatewayGloballyEnabled,
3637
} = useGatewayModels();
3738
const [isEditing, setIsEditing] = useState(false);
3839
const [inputValue, setInputValue] = useState(value);
@@ -198,7 +199,10 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
198199

199200
if (!isEditing) {
200201
const gatewayEnabled =
201-
isGatewayEnabled(value) && gatewayAvailable && isGatewaySupported(value);
202+
gatewayGloballyEnabled &&
203+
isGatewayEnabled(value) &&
204+
gatewayAvailable &&
205+
isGatewaySupported(value);
202206
return (
203207
<div ref={containerRef} className="relative flex items-center gap-1">
204208
{gatewayEnabled && (
@@ -270,8 +274,8 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
270274
<div className="grid w-full grid-cols-[1fr_auto] items-center gap-2">
271275
<span className="min-w-0 truncate">{model}</span>
272276
<div className="flex items-center gap-0.5">
273-
{/* Gateway toggle - only show when gateway is configured and model's provider is supported */}
274-
{gatewayAvailable && isGatewaySupported(model) && (
277+
{/* Gateway toggle - only show when gateway is globally enabled, configured, and model's provider is supported */}
278+
{gatewayGloballyEnabled && gatewayAvailable && isGatewaySupported(model) && (
275279
<TooltipWrapper inline>
276280
<button
277281
type="button"

src/browser/components/Settings/sections/ModelsSection.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function ModelsSection() {
3333
isEnabled: isGatewayEnabled,
3434
toggle: toggleGateway,
3535
gatewayAvailable,
36+
gatewayGloballyEnabled,
3637
} = useGatewayModels();
3738

3839
// Check if a model already exists (for duplicate prevention)
@@ -234,7 +235,7 @@ export function ModelsSection() {
234235
}
235236
onRemove={() => handleRemoveModel(model.provider, model.modelId)}
236237
onToggleGateway={
237-
gatewayAvailable && isGatewaySupported(model.fullId)
238+
gatewayGloballyEnabled && gatewayAvailable && isGatewaySupported(model.fullId)
238239
? () => toggleGateway(model.fullId)
239240
: undefined
240241
}
@@ -261,7 +262,7 @@ export function ModelsSection() {
261262
isGatewayEnabled={isGatewayEnabled(model.fullId)}
262263
onSetDefault={() => setDefaultModel(model.fullId)}
263264
onToggleGateway={
264-
gatewayAvailable && isGatewaySupported(model.fullId)
265+
gatewayGloballyEnabled && gatewayAvailable && isGatewaySupported(model.fullId)
265266
? () => toggleGateway(model.fullId)
266267
: undefined
267268
}

src/browser/components/Settings/sections/ProvidersSection.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { ProviderName } from "@/common/constants/providers";
55
import { ProviderWithIcon } from "@/browser/components/ProviderIcon";
66
import { useAPI } from "@/browser/contexts/API";
77
import { useProvidersConfig } from "@/browser/hooks/useProvidersConfig";
8+
import { useGatewayModels } from "@/browser/hooks/useGatewayModels";
89

910
interface FieldConfig {
1011
key: string;
@@ -69,6 +70,7 @@ function getProviderFields(provider: ProviderName): FieldConfig[] {
6970
export function ProvidersSection() {
7071
const { api } = useAPI();
7172
const { config, updateOptimistically } = useProvidersConfig();
73+
const { gatewayAvailable, gatewayGloballyEnabled, toggleGloballyEnabled } = useGatewayModels();
7274
const [expandedProvider, setExpandedProvider] = useState<string | null>(null);
7375
const [editingField, setEditingField] = useState<{
7476
provider: string;
@@ -315,6 +317,31 @@ export function ProvidersSection() {
315317
</div>
316318
);
317319
})}
320+
321+
{/* Gateway enabled toggle - only for mux-gateway when configured */}
322+
{provider === "mux-gateway" && gatewayAvailable && (
323+
<div className="border-border-light flex items-center justify-between border-t pt-3">
324+
<div>
325+
<label className="text-foreground block text-xs font-medium">Enabled</label>
326+
<span className="text-muted text-xs">Route requests through Mux Gateway</span>
327+
</div>
328+
<button
329+
type="button"
330+
onClick={toggleGloballyEnabled}
331+
className={`relative h-5 w-9 rounded-full transition-colors ${
332+
gatewayGloballyEnabled ? "bg-accent" : "bg-border-medium"
333+
}`}
334+
role="switch"
335+
aria-checked={gatewayGloballyEnabled}
336+
>
337+
<span
338+
className={`absolute top-0.5 left-0.5 h-4 w-4 rounded-full bg-white transition-transform ${
339+
gatewayGloballyEnabled ? "translate-x-4" : "translate-x-0"
340+
}`}
341+
/>
342+
</button>
343+
</div>
344+
)}
318345
</div>
319346
)}
320347
</div>

src/browser/components/icons/GatewayIcon.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ interface GatewayIconProps extends React.SVGProps<SVGSVGElement> {
66

77
/**
88
* Gateway icon - represents routing through Mux Gateway.
9-
* A stylized "relay" symbol showing data passing through a central hub.
9+
* A simplified relay symbol: arrow passing through a central node.
1010
*/
1111
export function GatewayIcon(props: GatewayIconProps) {
1212
return (
@@ -20,14 +20,13 @@ export function GatewayIcon(props: GatewayIconProps) {
2020
strokeLinejoin="round"
2121
{...props}
2222
>
23-
{/* Central hexagon hub */}
24-
<path d="M12 6l5.196 3v6L12 18l-5.196-3V9z" />
25-
{/* Left incoming arrow */}
26-
<path d="M2 12h4" />
27-
<path d="M4 10l2 2-2 2" />
28-
{/* Right outgoing arrow */}
29-
<path d="M18 12h4" />
30-
<path d="M20 10l2 2-2 2" />
23+
{/* Central diamond/node */}
24+
<path d="M12 8l4 4-4 4-4-4z" />
25+
{/* Input line */}
26+
<path d="M4 12h4" />
27+
{/* Output arrow */}
28+
<path d="M16 12h4" />
29+
<path d="M18 10l2 2-2 2" />
3130
</svg>
3231
);
3332
}

src/browser/hooks/useGatewayModels.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useProvidersConfig } from "./useProvidersConfig";
44

55
const GATEWAY_MODELS_KEY = "gateway-models";
66
const GATEWAY_AVAILABLE_KEY = "gateway-available";
7+
const GATEWAY_ENABLED_KEY = "gateway-enabled";
78

89
/**
910
* Providers that Mux Gateway supports routing to.
@@ -88,6 +89,20 @@ export function isGatewayAvailable(): boolean {
8889
return readPersistedState<boolean>(GATEWAY_AVAILABLE_KEY, false);
8990
}
9091

92+
/**
93+
* Check if the gateway is globally enabled (user toggle, defaults to true when available)
94+
*/
95+
export function isGatewayGloballyEnabled(): boolean {
96+
return readPersistedState<boolean>(GATEWAY_ENABLED_KEY, true);
97+
}
98+
99+
/**
100+
* Set the global gateway enabled state
101+
*/
102+
export function setGatewayGloballyEnabled(enabled: boolean): void {
103+
updatePersistedState(GATEWAY_ENABLED_KEY, enabled);
104+
}
105+
91106
/**
92107
* Transform a model ID to gateway format if gateway is enabled AND available AND supported.
93108
* Falls back to direct provider if:
@@ -98,8 +113,17 @@ export function isGatewayAvailable(): boolean {
98113
* Example: "anthropic:claude-opus-4-5" → "mux-gateway:anthropic/claude-opus-4-5"
99114
*/
100115
export function toGatewayModel(modelId: string): string {
101-
// Only transform if user enabled gateway for this model, gateway is configured, and provider is supported
102-
if (!isGatewayEnabled(modelId) || !isGatewayAvailable() || !isGatewaySupported(modelId)) {
116+
// Only transform if:
117+
// 1. Gateway is globally enabled (user hasn't disabled it)
118+
// 2. User enabled gateway for this specific model
119+
// 3. Gateway is configured (coupon code set)
120+
// 4. Provider is supported by gateway
121+
if (
122+
!isGatewayGloballyEnabled() ||
123+
!isGatewayEnabled(modelId) ||
124+
!isGatewayAvailable() ||
125+
!isGatewaySupported(modelId)
126+
) {
103127
return modelId;
104128
}
105129
// Transform provider:model to mux-gateway:provider/model
@@ -144,6 +168,11 @@ export function useGatewayModels() {
144168
false,
145169
{ listener: true }
146170
);
171+
const [gatewayGloballyEnabled, setGatewayGloballyEnabled] = usePersistedState<boolean>(
172+
GATEWAY_ENABLED_KEY,
173+
true,
174+
{ listener: true }
175+
);
147176

148177
// Sync gateway availability from provider config
149178
useEffect(() => {
@@ -152,6 +181,10 @@ export function useGatewayModels() {
152181
setGatewayAvailable(available);
153182
}, [config, setGatewayAvailable]);
154183

184+
const toggleGloballyEnabled = useCallback(() => {
185+
setGatewayGloballyEnabled((prev) => !prev);
186+
}, [setGatewayGloballyEnabled]);
187+
155188
const isEnabled = useCallback(
156189
(modelId: string) => gatewayModels.includes(modelId),
157190
[gatewayModels]
@@ -169,5 +202,12 @@ export function useGatewayModels() {
169202
[setGatewayModels]
170203
);
171204

172-
return { gatewayModels, isEnabled, toggle, gatewayAvailable };
205+
return {
206+
gatewayModels,
207+
isEnabled,
208+
toggle,
209+
gatewayAvailable,
210+
gatewayGloballyEnabled,
211+
toggleGloballyEnabled,
212+
};
173213
}

0 commit comments

Comments
 (0)