feat(site): infer model family icon from model name in AI Bridge logs (#22022)

## Problem

Previously, the AI Bridge model column icon was derived from the provider field. This worked because each provider only served its own models: OpenAI interceptions always used OpenAI models, and Anthropic interceptions always used Anthropic models.

With the introduction of the Copilot provider, this assumption no longer holds. Copilot can forward requests to both OpenAI and Anthropic models, so the provider field alone is not enough to determine the correct model icon. This caused Copilot interceptions to display a fallback question mark icon for the model.

<img width="1337" height="365" alt="Screenshot 2026-02-10 at 09 10 34" src="https://github.com/user-attachments/assets/1efd613d-16c9-4738-8337-6ccf92e610fc" />

## Changes

* Added `AIBridgeModelIcon` component that infers the model family (Claude, OpenAI) from the model name string and renders the appropriate icon.
* Updated `RequestLogsRow` to use `AIBridgeModelIcon` instead of `AIBridgeProviderIcon` in both the table row and the expanded detail view.
This commit is contained in:
Susana Ferreira
2026-02-11 14:32:13 +00:00
committed by GitHub
parent fcf431c1d7
commit 8e9638c750
2 changed files with 70 additions and 5 deletions
@@ -0,0 +1,65 @@
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import { CircleQuestionMarkIcon } from "lucide-react";
import { cn } from "utils/cn";
// Infers the model family from a model name string.
// See official model naming docs:
// - Anthropic: https://docs.anthropic.com/en/docs/about-claude/models/all-models
// - OpenAI: https://platform.openai.com/docs/models
function inferModelFamily(model: string): string {
const modelFamily = model.toLowerCase();
// Anthropic model families
if (
modelFamily.includes("claude") ||
modelFamily.includes("opus") ||
modelFamily.includes("sonnet") ||
modelFamily.includes("haiku")
) {
return "claude";
}
// OpenAI model families (gpt-*, o1/o3/o4-*, codex, whisper)
if (
modelFamily.includes("gpt") ||
modelFamily.startsWith("o1") ||
modelFamily.startsWith("o3") ||
modelFamily.startsWith("o4") ||
modelFamily.includes("codex") ||
modelFamily.includes("whisper")
) {
return "openai";
}
return "unknown";
}
export const AIBridgeModelIcon = ({
model,
...props
}: {
model: string;
} & React.ComponentProps<"svg">) => {
const iconClassName = "flex-shrink-0";
const family = inferModelFamily(model);
switch (family) {
case "claude":
return (
<ExternalImage
src="/icon/claude.svg"
className={cn(iconClassName, props.className)}
/>
);
case "openai":
return (
<ExternalImage
src="/icon/openai.svg"
className={cn(iconClassName, props.className)}
/>
);
default:
return (
<CircleQuestionMarkIcon
className={cn(iconClassName, props.className)}
{...props}
/>
);
}
};
@@ -17,7 +17,7 @@ import {
import { type FC, Fragment, useState } from "react";
import { cn } from "utils/cn";
import { formatDate, humanDuration } from "utils/time";
import { AIBridgeProviderIcon } from "../AIBridgeProviderIcon";
import { AIBridgeModelIcon } from "../AIBridgeModelIcon";
type RequestLogsRowProps = {
interception: AIBridgeInterception;
@@ -204,8 +204,8 @@ export const RequestLogsRow: FC<RequestLogsRowProps> = ({ interception }) => {
<div className="w-full min-w-0 overflow-hidden">
<Badge className="gap-1.5 w-full">
<div className="flex-shrink-0 flex items-center">
<AIBridgeProviderIcon
provider={interception.provider}
<AIBridgeModelIcon
model={interception.model}
className="size-icon-xs"
/>
</div>
@@ -279,8 +279,8 @@ export const RequestLogsRow: FC<RequestLogsRowProps> = ({ interception }) => {
<dd data-chromatic="ignore">
<Badge className="gap-2">
<div className="flex-shrink-0 flex items-center">
<AIBridgeProviderIcon
provider={interception.provider}
<AIBridgeModelIcon
model={interception.model}
className="size-icon-xs"
/>
</div>