Add Composio settings panel for external tool configuration
Settings > Tools tab lets the user enter their Composio API key and select which toolkits (GitHub, Slack, Gmail, etc.) to enable for agents. The configuration is saved per-org and loaded at agent run time.
This commit is contained in:
1 parent
77644121db
commit
59e3b6bf51
2 files changed
+124
-1
No files matched your search
@@ -4,10 +4,11 @@ import { SkillsPanel } from "./settings/SkillsPanel";
|
||||
import { WorkspacePanel } from "./settings/WorkspacePanel";
|
||||
import { BillingPanel } from "./settings/BillingPanel";
|
||||
import { MigrationPanel } from "./settings/MigrationPanel";
|
||||
import { ComposioPanel } from "./settings/ComposioPanel";
|
||||
import type { PageProps } from "./types";
|
||||
import { activeOrgId, requireActiveEmployee, requireActiveOrganization } from "./types";
|
||||
|
||||
type SettingsTab = "workspace" | "billing" | "providers" | "skills" | "migration";
|
||||
type SettingsTab = "workspace" | "billing" | "providers" | "tools" | "skills" | "migration";
|
||||
|
||||
interface NavSpec {
|
||||
id: SettingsTab;
|
||||
@@ -18,6 +19,7 @@ const TABS: NavSpec[] = [
|
||||
{ id: "workspace", label: "Workspace" },
|
||||
{ id: "billing", label: "Billing" },
|
||||
{ id: "providers", label: "Providers" },
|
||||
{ id: "tools", label: "Tools" },
|
||||
{ id: "skills", label: "Skills" },
|
||||
{ id: "migration", label: "Migration" },
|
||||
];
|
||||
@@ -74,6 +76,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
|
||||
actions={actions}
|
||||
/>
|
||||
)}
|
||||
{tab === "tools" && <ComposioPanel orgId={orgId} actions={actions} />}
|
||||
{tab === "skills" && (
|
||||
<SkillsPanel
|
||||
orgId={orgId}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { useState, type FormEvent } from "react";
|
||||
import { Button } from "../../shared/ui";
|
||||
import type { SnapshotActions } from "../../app/useSnapshot";
|
||||
|
||||
const AVAILABLE_TOOLKITS = [
|
||||
{ id: "github", label: "GitHub" },
|
||||
{ id: "gmail", label: "Gmail" },
|
||||
{ id: "slack", label: "Slack" },
|
||||
{ id: "notion", label: "Notion" },
|
||||
{ id: "linear", label: "Linear" },
|
||||
{ id: "jira", label: "Jira" },
|
||||
{ id: "google_calendar", label: "Google Calendar" },
|
||||
{ id: "google_drive", label: "Google Drive" },
|
||||
{ id: "google_docs", label: "Google Docs" },
|
||||
{ id: "google_sheets", label: "Google Sheets" },
|
||||
{ id: "trello", label: "Trello" },
|
||||
{ id: "asana", label: "Asana" },
|
||||
{ id: "discord", label: "Discord" },
|
||||
{ id: "twitter", label: "Twitter / X" },
|
||||
{ id: "youtube", label: "YouTube" },
|
||||
{ id: "hubspot", label: "HubSpot" },
|
||||
{ id: "salesforce", label: "Salesforce" },
|
||||
{ id: "zendesk", label: "Zendesk" },
|
||||
{ id: "figma", label: "Figma" },
|
||||
{ id: "airtable", label: "Airtable" },
|
||||
];
|
||||
|
||||
export function ComposioPanel({
|
||||
orgId,
|
||||
actions,
|
||||
}: {
|
||||
orgId: string;
|
||||
actions: SnapshotActions;
|
||||
}) {
|
||||
const [apiKey, setApiKey] = useState("");
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
const toggle = (id: string) => {
|
||||
setSelected((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) next.delete(id);
|
||||
else next.add(id);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSave = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!apiKey.trim()) return;
|
||||
await actions.run(() => window.agentCompany.saveComposioSettings({
|
||||
orgId,
|
||||
apiKey: apiKey.trim(),
|
||||
toolkits: [...selected],
|
||||
}));
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 640 }}>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 600, marginBottom: 4 }}>External Tools</h2>
|
||||
<p style={{ fontSize: 13, color: "var(--text-3)", marginBottom: 24 }}>
|
||||
Connect your agents to external services via Composio.
|
||||
</p>
|
||||
<form onSubmit={handleSave}>
|
||||
<label style={{ display: "block", marginBottom: 16 }}>
|
||||
<span style={{ fontSize: 13, fontWeight: 500, display: "block", marginBottom: 6 }}>API Key</span>
|
||||
<input
|
||||
type="password"
|
||||
className="input"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
autoComplete="off"
|
||||
style={{ width: "100%", maxWidth: 400 }}
|
||||
/>
|
||||
</label>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<span style={{ fontSize: 13, fontWeight: 500, display: "block", marginBottom: 8 }}>Toolkits</span>
|
||||
<div style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(140px, 1fr))",
|
||||
gap: 6,
|
||||
}}>
|
||||
{AVAILABLE_TOOLKITS.map((toolkit) => (
|
||||
<label
|
||||
key={toolkit.id}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
padding: "6px 10px",
|
||||
borderRadius: 8,
|
||||
cursor: "pointer",
|
||||
fontSize: 13,
|
||||
background: selected.has(toolkit.id) ? "var(--accent-soft)" : "transparent",
|
||||
transition: "background 0.12s",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.has(toolkit.id)}
|
||||
onChange={() => toggle(toolkit.id)}
|
||||
style={{ accentColor: "var(--accent)" }}
|
||||
/>
|
||||
{toolkit.label}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<Button tone="primary" type="submit" disabled={!apiKey.trim()}>
|
||||
Save
|
||||
</Button>
|
||||
{saved && <span style={{ fontSize: 13, color: "var(--green)" }}>Saved</span>}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user