Files
paseo/packages/app/src/components/provider-icons.ts
T
Matteo Pietro DazziandGitHub 862154541a feat: add MiniMax quota fetcher and brand icon (#1662)
* feat(server): add MiniMax quota fetcher

Mirror the live multi-provider quota panel (#1278) for MiniMax. Resolves the
bearer token from $MINIMAX_API_KEY or the MiniMax CLI config at
~/.mmx/config.json (api_key, oauth.access_token) and credentials at
~/.mmx/credentials.json, then queries the /v1/token_plan/remains endpoint on
the configured region (global by default, cn when region='cn'). The response
is normalized into ProviderUsage windows per model_remains entry, surfacing
the interval and weekly limits with their reset times and a danger tone when
the server reports status=2.

* feat(app): add MiniMax brand icon for usage page

The quota panel resolves the icon via resolveProviderIconName, which falls
back to a generic Bot for any provider not in BUILTIN_PROVIDER_ICON_NAMES.
Register 'minimax' as a built-in icon and vendor the official 24×24 brand
mark (single Path, currentColor) as MiniMaxIcon so the usage card stops
showing the generic robot.

* refactor(server): flatten nested ternaries in MiniMax quota fetcher

Greptile review on #1662 called out two nested ternaries in toIntervalWindow
and toWeeklyWindow. Extract a toneForStatus helper that uses an explicit
if/else chain so the same status-to-tone mapping is shared between the
interval and weekly windows, and add the missing trailing newline.

* fix: format
2026-06-26 14:14:29 +08:00

74 lines
2.6 KiB
TypeScript

import { Bot, PackagePlus } from "lucide-react-native";
import { createElement, type ComponentType } from "react";
import { SvgXml } from "react-native-svg";
import { ClaudeIcon } from "@/components/icons/claude-icon";
import { CodexIcon } from "@/components/icons/codex-icon";
import { CopilotIcon } from "@/components/icons/copilot-icon";
import { MiniMaxIcon } from "@/components/icons/minimax-icon";
import { OpenCodeIcon } from "@/components/icons/opencode-icon";
import { OmpIcon } from "@/components/icons/omp-icon";
import { PiIcon } from "@/components/icons/pi-icon";
import { ACP_PROVIDER_CATALOG } from "@/data/acp-provider-catalog";
import { resolveProviderIconName } from "@/components/provider-icon-name";
export interface ProviderIconProps {
size: number;
color: string;
}
export type ProviderIconComponent = ComponentType<ProviderIconProps>;
const BUILTIN_PROVIDER_ICONS: Record<string, ProviderIconComponent> = {
claude: ClaudeIcon as unknown as ProviderIconComponent,
codex: CodexIcon as unknown as ProviderIconComponent,
copilot: CopilotIcon as unknown as ProviderIconComponent,
kiro: PackagePlus,
minimax: MiniMaxIcon as unknown as ProviderIconComponent,
omp: OmpIcon as unknown as ProviderIconComponent,
opencode: OpenCodeIcon as unknown as ProviderIconComponent,
pi: PiIcon as unknown as ProviderIconComponent,
};
const CATALOG_ICON_SVGS = new Map(
ACP_PROVIDER_CATALOG.flatMap((entry) => (entry.iconSvg ? [[entry.id, entry.iconSvg]] : [])),
);
const catalogIconComponents = new Map<string, ProviderIconComponent>();
function createCatalogIcon(provider: string, iconSvg: string): ProviderIconComponent {
const CatalogProviderIcon: ProviderIconComponent = ({ size, color }) =>
createElement(SvgXml, {
xml: iconSvg,
width: size,
height: size,
color,
});
CatalogProviderIcon.displayName = `CatalogProviderIcon(${provider})`;
return CatalogProviderIcon;
}
function getCatalogProviderIcon(provider: string): ProviderIconComponent {
const cached = catalogIconComponents.get(provider);
if (cached) {
return cached;
}
const iconSvg = CATALOG_ICON_SVGS.get(provider);
if (!iconSvg) {
return Bot;
}
const icon = createCatalogIcon(provider, iconSvg);
catalogIconComponents.set(provider, icon);
return icon;
}
export function getProviderIcon(provider: string): ProviderIconComponent {
const name = resolveProviderIconName(provider);
if (name.kind === "builtin") {
return BUILTIN_PROVIDER_ICONS[name.id];
}
if (name.kind === "catalog") {
return getCatalogProviderIcon(name.id);
}
return Bot;
}