Mirror the Composio project's auth configs as the connector catalog with logo.dev icons
This commit is contained in:
1 parent
dde449d226
commit
00e05054ca
18 files changed
+269
-175
No files matched your search
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in new issue
Block a user