Mirror the Composio project's auth configs as the connector catalog with logo.dev icons

This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 11:06:07 -04:00
1 parent dde449d226
commit 00e05054ca
18 files changed
+269 -175

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' http://localhost:*; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self' http://localhost:* ws://localhost:*; object-src 'none'; base-uri 'none'; frame-src 'none';"
content="default-src 'self'; script-src 'self' http://localhost:*; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://img.logo.dev; font-src 'self' data:; connect-src 'self' http://localhost:* ws://localhost:*; object-src 'none'; base-uri 'none'; frame-src 'none';"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AgentCompany</title>
+27 -15
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { Button } from "../shared/ui";
import type { ComposioToolkitSummaryDto } from "../../../shared/commands";
import type { ConnectorCatalogEntry } from "../../../shared/commands";
import type { PageProps } from "./types";
import { activeOrgId, requireActiveEmployee } from "./types";
import { ConnectedList } from "./connectors/ConnectedList";
@@ -8,7 +8,7 @@ import { ToolkitCatalog } from "./connectors/ToolkitCatalog";
type CatalogState =
| { phase: "loading" }
| { phase: "ready"; toolkits: ComposioToolkitSummaryDto[] }
| { phase: "ready"; entries: ConnectorCatalogEntry[] }
| { phase: "error"; message: string };
type CatalogSlot = { orgId: string; state: CatalogState } | null;
@@ -47,14 +47,22 @@ export function ConnectorsPage({ snapshot, actions }: PageProps) {
[accounts],
);
const logoBySlug = useMemo(() => {
const map = new Map<string, string | null>();
if (catalog.phase === "ready") {
for (const entry of catalog.entries) map.set(entry.slug, entry.logoUrl);
}
return map;
}, [catalog]);
useEffect(() => {
let cancelled = false;
void window.agentCompany.listComposioToolkits({ orgId }).then((result) => {
void window.agentCompany.listConnectorCatalog().then((result) => {
if (cancelled) return;
setCatalogSlot({
orgId,
state: result.ok
? { phase: "ready", toolkits: result.data }
? { phase: "ready", entries: result.data }
: { phase: "error", message: result.error.message },
});
});
@@ -89,17 +97,17 @@ export function ConnectorsPage({ snapshot, actions }: PageProps) {
}, CONNECT_POLL_INTERVAL_MS);
};
const connect = (toolkit: ComposioToolkitSummaryDto) => {
setBusySlug(toolkit.slug);
const connect = (entry: ConnectorCatalogEntry) => {
setBusySlug(entry.slug);
setConnectError(null);
void window.agentCompany
.connectComposioToolkit({ orgId, toolkitSlug: toolkit.slug, toolkitName: toolkit.name })
.connectComposioToolkit({ orgId, toolkitSlug: entry.slug })
.then(async (result) => {
if (!result.ok) {
setConnectError(result.error.message);
return;
}
pollUntilActive(toolkit.slug, 0);
pollUntilActive(entry.slug, 0);
if (result.data.redirectUrl) {
const opened = await window.agentCompany.openExternalUrl(result.data.redirectUrl);
if (!opened.ok) setConnectError(opened.error.message);
@@ -133,11 +141,15 @@ export function ConnectorsPage({ snapshot, actions }: PageProps) {
</div>
)}
<ConnectedList accounts={accounts} grants={grants} agents={agents} onGrant={grant} onRevoke={revoke} />
<ConnectedList
accounts={accounts}
grants={grants}
agents={agents}
logoBySlug={logoBySlug}
onGrant={grant}
onRevoke={revoke}
/>
{catalog.phase === "loading" && (
<div style={{ color: "var(--text-3)", fontSize: 13, padding: "24px 0" }}>Loading catalog…</div>
)}
{catalog.phase === "error" && (
<div className="row" style={{ gap: 10, padding: "24px 0" }}>
<span
@@ -155,11 +167,11 @@ export function ConnectorsPage({ snapshot, actions }: PageProps) {
</span>
<Button size="small" onClick={() => {
setCatalogSlot(null);
void window.agentCompany.listComposioToolkits({ orgId }).then((result) => {
void window.agentCompany.listConnectorCatalog().then((result) => {
setCatalogSlot({
orgId,
state: result.ok
? { phase: "ready", toolkits: result.data }
? { phase: "ready", entries: result.data }
: { phase: "error", message: result.error.message },
});
});
@@ -168,7 +180,7 @@ export function ConnectorsPage({ snapshot, actions }: PageProps) {
)}
{catalog.phase === "ready" && (
<ToolkitCatalog
toolkits={catalog.toolkits}
entries={catalog.entries}
connectedSlugs={connectedSlugs}
busySlug={busySlug}
onConnect={connect}
@@ -20,12 +20,14 @@ export function ConnectedList({
accounts,
grants,
agents,
logoBySlug,
onGrant,
onRevoke,
}: {
accounts: ConnectorAccountRecord[];
grants: ConnectorGrantRecord[];
agents: AgentRecord[];
logoBySlug: Map<string, string | null>;
onGrant: (agentId: string, toolkitSlug: string) => void;
onRevoke: (agentId: string, toolkitSlug: string) => void;
}) {
@@ -40,6 +42,7 @@ export function ConnectedList({
account={account}
grantedAgentIds={new Set(grants.filter((grant) => grant.toolkitSlug === account.toolkitSlug).map((grant) => grant.agentId))}
agents={agents}
logoUrl={logoBySlug.get(account.toolkitSlug) ?? null}
onGrant={onGrant}
onRevoke={onRevoke}
/>
@@ -53,12 +56,14 @@ function ConnectedRow({
account,
grantedAgentIds,
agents,
logoUrl,
onGrant,
onRevoke,
}: {
account: ConnectorAccountRecord;
grantedAgentIds: Set<string>;
agents: AgentRecord[];
logoUrl: string | null;
onGrant: (agentId: string, toolkitSlug: string) => void;
onRevoke: (agentId: string, toolkitSlug: string) => void;
}) {
@@ -66,7 +71,7 @@ function ConnectedRow({
return (
<div style={{ border: "1px solid var(--border)", borderRadius: 12, padding: 14, background: "var(--panel)" }}>
<div className="row" style={{ gap: 10 }}>
<ToolkitTile name={account.toolkitName} slug={account.toolkitSlug} size={30} />
<ToolkitTile name={account.toolkitName} slug={account.toolkitSlug} logoUrl={logoUrl} size={30} />
<span style={{ fontWeight: 600, fontSize: 13.5 }}>{account.toolkitName}</span>
<Pill tone={STATUS_TONE[account.status]} dot>{account.status.charAt(0) + account.status.slice(1).toLowerCase()}</Pill>
</div>
@@ -1,6 +1,6 @@
import { useMemo, useState } from "react";
import { Button, Icon, Pill } from "../../shared/ui";
import type { ComposioToolkitSummaryDto } from "../../../../shared/commands";
import type { ConnectorCatalogEntry } from "../../../../shared/commands";
/** Deterministic tile color for a toolkit's letter glyph. */
const TILE_COLORS = ["#5e6ad2", "#3b82a0", "#7c5cb0", "#3f8f5f", "#b0684f", "#a05c86", "#6f7d3d", "#4f6fb0"];
@@ -11,7 +11,18 @@ function tileColor(slug: string): string {
return TILE_COLORS[Math.abs(hash) % TILE_COLORS.length];
}
export function ToolkitTile({ name, slug, size = 34 }: { name: string; slug: string; size?: number }) {
export function ToolkitTile({ name, slug, logoUrl, size = 34 }: { name: string; slug: string; logoUrl: string | null; size?: number }) {
if (logoUrl) {
return (
<img
src={logoUrl}
alt=""
width={size}
height={size}
style={{ width: size, height: size, borderRadius: 9, objectFit: "cover", flexShrink: 0 }}
/>
);
}
return (
<span
style={{
@@ -34,26 +45,22 @@ export function ToolkitTile({ name, slug, size = 34 }: { name: string; slug: str
}
export function ToolkitCatalog({
toolkits,
entries,
connectedSlugs,
busySlug,
onConnect,
}: {
toolkits: ComposioToolkitSummaryDto[];
entries: ConnectorCatalogEntry[];
connectedSlugs: Set<string>;
busySlug: string | null;
onConnect: (toolkit: ComposioToolkitSummaryDto) => void;
onConnect: (entry: ConnectorCatalogEntry) => void;
}) {
const [query, setQuery] = useState("");
const filtered = useMemo(() => {
const trimmed = query.trim().toLowerCase();
if (!trimmed) return toolkits;
return toolkits.filter((toolkit) =>
toolkit.name.toLowerCase().includes(trimmed) ||
toolkit.slug.includes(trimmed) ||
toolkit.categories.some((category) => category.toLowerCase().includes(trimmed)),
);
}, [toolkits, query]);
if (!trimmed) return entries;
return entries.filter((entry) => entry.name.toLowerCase().includes(trimmed) || entry.slug.includes(trimmed));
}, [entries, query]);
return (
<section>
@@ -70,12 +77,12 @@ export function ToolkitCatalog({
/>
</div>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))", gap: 10 }}>
{filtered.map((toolkit) => {
const connected = connectedSlugs.has(toolkit.slug);
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))", gap: 10 }}>
{filtered.map((entry) => {
const connected = connectedSlugs.has(entry.slug);
return (
<div
key={toolkit.slug}
key={entry.slug}
style={{
border: "1px solid var(--border)",
borderRadius: 12,
@@ -83,46 +90,20 @@ export function ToolkitCatalog({
background: "var(--panel)",
display: "flex",
gap: 10,
alignItems: "flex-start",
alignItems: "center",
}}
>
<ToolkitTile name={toolkit.name} slug={toolkit.slug} />
<div style={{ flex: 1, minWidth: 0 }}>
<div className="row" style={{ gap: 6 }}>
<span style={{ fontWeight: 600, fontSize: 13.5, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{toolkit.name}
</span>
{toolkit.categories[0] && <Pill>{toolkit.categories[0]}</Pill>}
</div>
<div
style={{
fontSize: 12,
color: "var(--text-3)",
lineHeight: 1.45,
marginTop: 2,
display: "-webkit-box",
WebkitLineClamp: 2,
WebkitBoxOrient: "vertical",
overflow: "hidden",
minHeight: 34,
}}
>
{toolkit.description}
</div>
<div className="row" style={{ marginTop: 8 }}>
{connected ? (
<Pill tone="green" dot>Connected</Pill>
) : (
<Button
size="small"
disabled={busySlug !== null}
onClick={() => onConnect(toolkit)}
>
{busySlug === toolkit.slug ? "Opening…" : "Connect"}
</Button>
)}
</div>
</div>
<ToolkitTile name={entry.name} slug={entry.slug} logoUrl={entry.logoUrl} />
<span style={{ fontWeight: 600, fontSize: 13.5, flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{entry.name}
</span>
{connected ? (
<Pill tone="green" dot>Connected</Pill>
) : (
<Button size="small" disabled={busySlug !== null} onClick={() => onConnect(entry)}>
{busySlug === entry.slug ? "Opening…" : "Connect"}
</Button>
)}
</div>
);
})}