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