AppAvatar.tsx raw

   1  import claudeLogo from "src/assets/suggested-apps/claude.png";
   2  import clineLogo from "src/assets/suggested-apps/cline.png";
   3  import codexLogo from "src/assets/suggested-apps/codex.png";
   4  import cursorLogo from "src/assets/suggested-apps/cursor.png";
   5  import geminiLogo from "src/assets/suggested-apps/gemini.png";
   6  import gooseLogo from "src/assets/suggested-apps/goose.png";
   7  import hermesLogo from "src/assets/suggested-apps/hermes.png";
   8  import openclawLogo from "src/assets/suggested-apps/openclaw.png";
   9  import opencodeLogo from "src/assets/suggested-apps/opencode.png";
  10  import { appStoreApps } from "src/components/connections/SuggestedAppData";
  11  import UserAvatar from "src/components/UserAvatar";
  12  import { ALBY_ACCOUNT_APP_NAME } from "src/constants";
  13  import { cn } from "src/lib/utils";
  14  import { App } from "src/types";
  15  
  16  // Lightweight logo map for agents that don't have full app store entries.
  17  // Matches on app_store_app_id metadata set when the connection was created.
  18  const agentLogos: Record<string, string> = {
  19    claude: claudeLogo,
  20    goose: gooseLogo,
  21    hermes: hermesLogo,
  22    openclaw: openclawLogo,
  23    cursor: cursorLogo,
  24    codex: codexLogo,
  25    cline: clineLogo,
  26    gemini: geminiLogo,
  27    opencode: opencodeLogo,
  28  };
  29  
  30  type Props = {
  31    app: App;
  32    className?: string;
  33  };
  34  
  35  export default function AppAvatar({ app, className }: Props) {
  36    if (app.name === ALBY_ACCOUNT_APP_NAME) {
  37      return <UserAvatar className={className} />;
  38    }
  39    const agentLogo =
  40      (app?.metadata?.app_store_app_id
  41        ? agentLogos[app.metadata.app_store_app_id]
  42        : undefined) ||
  43      Object.entries(agentLogos).find(([key]) =>
  44        app.name.toLowerCase().includes(key)
  45      )?.[1];
  46    const appStoreApp = appStoreApps.find(
  47      (suggestedApp) =>
  48        (app?.metadata?.app_store_app_id &&
  49          suggestedApp.id === app.metadata?.app_store_app_id) ||
  50        app.name.includes(suggestedApp.title)
  51    );
  52    const image = agentLogo || appStoreApp?.logo;
  53  
  54    const gradient =
  55      app.name
  56        .split("")
  57        .map((c) => c.charCodeAt(0))
  58        .reduce((a, b) => a + b, 0) % 10;
  59    return (
  60      <div
  61        className={cn(
  62          "rounded-lg relative overflow-hidden",
  63          !image && `avatar-gradient-${gradient}`,
  64          className
  65        )}
  66      >
  67        {image && (
  68          <img
  69            src={image}
  70            className={cn("absolute w-full h-full rounded-lg", className)}
  71          />
  72        )}
  73        {!image && (
  74          <span className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white text-sm font-medium capitalize pointer-events-none">
  75            {app.name.charAt(0)}
  76          </span>
  77        )}
  78      </div>
  79    );
  80  }
  81