Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions src/browser/components/Settings/sections/ProvidersSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import { useAPI } from "@/browser/contexts/API";
import { useSettings } from "@/browser/contexts/SettingsContext";
import { usePersistedState } from "@/browser/hooks/usePersistedState";
import { useProvidersConfig } from "@/browser/hooks/useProvidersConfig";
import {
formatMuxGatewayBalance,
useMuxGatewayAccountStatus,
} from "@/browser/hooks/useMuxGatewayAccountStatus";
import { useGateway } from "@/browser/hooks/useGatewayModels";
import { getEligibleGatewayModels } from "@/browser/utils/gatewayModels";
import { Button } from "@/browser/components/ui/button";
Expand Down Expand Up @@ -144,6 +148,12 @@ export function ProvidersSection() {

const { api } = useAPI();
const { config, updateOptimistically } = useProvidersConfig();
const {
data: muxGatewayAccountStatus,
error: muxGatewayAccountError,
isLoading: muxGatewayAccountLoading,
refresh: refreshMuxGatewayAccountStatus,
} = useMuxGatewayAccountStatus();

const gateway = useGateway();

Expand Down Expand Up @@ -318,6 +328,7 @@ export function ProvidersSection() {
}

setMuxGatewayLoginStatus("success");
void refreshMuxGatewayAccountStatus();
return;
}

Expand Down Expand Up @@ -426,6 +437,7 @@ export function ProvidersSection() {
}

setMuxGatewayLoginStatus("success");
void refreshMuxGatewayAccountStatus();
return;
}

Expand All @@ -444,6 +456,7 @@ export function ProvidersSection() {
api,
config,
applyGatewayModels,
refreshMuxGatewayAccountStatus,
]);
const muxGatewayCouponCodeSet = config?.["mux-gateway"]?.couponCodeSet ?? false;
const muxGatewayLoginInProgress =
Expand Down Expand Up @@ -471,6 +484,30 @@ export function ProvidersSection() {
setExpandedProvider(providersExpandedProvider);
setProvidersExpandedProvider(null);
}, [providersExpandedProvider, setProvidersExpandedProvider]);

useEffect(() => {
if (expandedProvider !== "mux-gateway" || !muxGatewayIsLoggedIn) {
return;
}

// Fetch lazily when the user expands the Mux Gateway provider.
//
// Important: avoid auto-retrying after a failure. If the request fails,
// `muxGatewayAccountStatus` remains null and we'd otherwise trigger a refresh
// on every render while the provider stays expanded.
if (muxGatewayAccountStatus || muxGatewayAccountLoading || muxGatewayAccountError) {
return;
}

void refreshMuxGatewayAccountStatus();
}, [
expandedProvider,
muxGatewayAccountError,
muxGatewayAccountLoading,
muxGatewayAccountStatus,
muxGatewayIsLoggedIn,
refreshMuxGatewayAccountStatus,
]);
const [editingField, setEditingField] = useState<{
provider: string;
field: string;
Expand Down Expand Up @@ -706,6 +743,46 @@ export function ProvidersSection() {
</div>
)}

{provider === "mux-gateway" && muxGatewayIsLoggedIn && (
<div className="border-border-light space-y-2 border-t pt-3">
<div className="flex items-center justify-between gap-2">
<div>
<label className="text-foreground block text-xs font-medium">Account</label>
<span className="text-muted text-xs">
Balance and limits from Mux Gateway
</span>
</div>
<Button
variant="outline"
size="sm"
onClick={() => {
void refreshMuxGatewayAccountStatus();
}}
disabled={muxGatewayAccountLoading}
>
{muxGatewayAccountLoading ? "Refreshing..." : "Refresh"}
</Button>
</div>

<div className="flex items-center justify-between gap-4">
<span className="text-muted text-xs">Balance</span>
<span className="text-foreground font-mono text-xs">
{formatMuxGatewayBalance(muxGatewayAccountStatus?.remaining_microdollars)}
</span>
</div>

<div className="flex items-center justify-between gap-4">
<span className="text-muted text-xs">Concurrent requests per user</span>
<span className="text-foreground font-mono text-xs">
{muxGatewayAccountStatus?.ai_gateway_concurrent_requests_per_user ?? "—"}
</span>
</div>

{muxGatewayAccountError && (
<p className="text-destructive text-xs">{muxGatewayAccountError}</p>
)}
</div>
)}
{fields.map((fieldConfig) => {
const isEditing =
editingField?.provider === provider && editingField?.field === fieldConfig.key;
Expand Down
78 changes: 78 additions & 0 deletions src/browser/components/TitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ import React, { useState, useEffect, useRef } from "react";
import { cn } from "@/common/lib/utils";
import { VERSION } from "@/version";
import { SettingsButton } from "./SettingsButton";
import { GatewayIcon } from "./icons/GatewayIcon";
import { useProvidersConfig } from "@/browser/hooks/useProvidersConfig";
import {
formatMuxGatewayBalance,
useMuxGatewayAccountStatus,
} from "@/browser/hooks/useMuxGatewayAccountStatus";
import { Button } from "./ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { Tooltip, TooltipTrigger, TooltipContent } from "./ui/tooltip";
import type { UpdateStatus } from "@/common/orpc/types";
import { Download, Loader2, RefreshCw, ShieldCheck } from "lucide-react";
Expand Down Expand Up @@ -66,6 +74,17 @@ export function TitleBar() {
const { api } = useAPI();
const policyState = usePolicy();
const policyEnforced = policyState.status.state === "enforced";

const { config: providersConfig } = useProvidersConfig();
const muxGatewayIsLoggedIn = providersConfig?.["mux-gateway"]?.couponCodeSet ?? false;
const {
data: muxGatewayAccountStatus,
error: muxGatewayAccountError,
isLoading: muxGatewayAccountLoading,
refresh: refreshMuxGatewayAccountStatus,
} = useMuxGatewayAccountStatus();
const [muxGatewayPopoverOpen, setMuxGatewayPopoverOpen] = useState(false);

const { extendedTimestamp, gitDescribe } = parseBuildInfo(VERSION satisfies unknown);
const [updateStatus, setUpdateStatus] = useState<UpdateStatus>({ type: "idle" });
const [isCheckingOnHover, setIsCheckingOnHover] = useState(false);
Expand Down Expand Up @@ -275,6 +294,65 @@ export function TitleBar() {
</Tooltip>
</div>
<div className={cn("flex items-center gap-1.5", isDesktop && "titlebar-no-drag")}>
{muxGatewayIsLoggedIn && (
<Popover
open={muxGatewayPopoverOpen}
onOpenChange={(open) => {
setMuxGatewayPopoverOpen(open);
if (open) {
void refreshMuxGatewayAccountStatus();
}
}}
>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="icon"
className="border-border-light text-muted-foreground hover:border-border-medium/80 hover:bg-toggle-bg/70 h-5 w-5 border"
aria-label="Show Mux Gateway balance"
>
<GatewayIcon className="h-3.5 w-3.5" aria-hidden />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-72 p-3">
<div className="text-foreground text-sm font-medium">Mux Gateway</div>

<div className="mt-2 space-y-1 text-xs">
<div className="flex items-center justify-between gap-4">
<span className="text-muted">Balance</span>
<span className="text-foreground font-mono">
{formatMuxGatewayBalance(muxGatewayAccountStatus?.remaining_microdollars)}
</span>
</div>

<div className="flex items-center justify-between gap-4">
<span className="text-muted">Concurrent requests per user</span>
<span className="text-foreground font-mono">
{muxGatewayAccountStatus?.ai_gateway_concurrent_requests_per_user ?? "—"}
</span>
</div>
</div>

{muxGatewayAccountError && (
<div className="text-destructive mt-2 text-xs">{muxGatewayAccountError}</div>
)}

<div className="mt-3 flex justify-end">
<Button
variant="outline"
size="sm"
onClick={() => {
void refreshMuxGatewayAccountStatus();
}}
disabled={muxGatewayAccountLoading}
>
{muxGatewayAccountLoading ? "Refreshing..." : "Refresh"}
</Button>
</div>
</PopoverContent>
</Popover>
)}

{policyEnforced && (
<Tooltip>
<TooltipTrigger asChild>
Expand Down
Loading
Loading