Compare commits

...

2 commits

2 changed files with 75 additions and 63 deletions

View file

@ -79,7 +79,7 @@ const tabs: TabConfig[] = [
id: "tools", id: "tools",
label: "Tools Library", label: "Tools Library",
icon: Wrench, icon: Wrench,
description: "Browse and enable Composio toolkits", description: "Browse and enable toolkits",
}, },
{ {
id: "note-tagging", id: "note-tagging",
@ -724,7 +724,7 @@ interface ToolkitInfo {
composio_managed_auth_schemes?: string[] composio_managed_auth_schemes?: string[]
} }
function ToolsLibrarySettings({ dialogOpen }: { dialogOpen: boolean }) { function ToolsLibrarySettings({ dialogOpen, rowboatConnected }: { dialogOpen: boolean; rowboatConnected: boolean }) {
// API key state // API key state
const [apiKeyConfigured, setApiKeyConfigured] = useState(false) const [apiKeyConfigured, setApiKeyConfigured] = useState(false)
const [apiKeyInput, setApiKeyInput] = useState("") const [apiKeyInput, setApiKeyInput] = useState("")
@ -869,65 +869,67 @@ function ToolsLibrarySettings({ dialogOpen }: { dialogOpen: boolean }) {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{/* Section A: API Key */} {/* Section A: API Key (only in BYOK mode) */}
<div className="space-y-2"> {!rowboatConnected && (
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Composio API Key</span> <div className="space-y-2">
{apiKeyConfigured && !showApiKeyInput ? ( <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Composio API Key</span>
<div className="flex items-center gap-2"> {apiKeyConfigured && !showApiKeyInput ? (
<div className="flex items-center gap-1.5 text-sm text-green-600"> <div className="flex items-center gap-2">
<CheckCircle2 className="size-4" /> <div className="flex items-center gap-1.5 text-sm text-green-600">
API key configured <CheckCircle2 className="size-4" />
API key configured
</div>
<button
onClick={() => setShowApiKeyInput(true)}
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
>
Change
</button>
</div> </div>
<button ) : (
onClick={() => setShowApiKeyInput(true)} <div className="space-y-2">
className="text-xs text-muted-foreground hover:text-foreground transition-colors" <p className="text-xs text-muted-foreground">
> Enter your Composio API key to browse and enable tool integrations.
Change Get your key from{" "}
</button> <a
</div> href="https://app.composio.dev/settings"
) : ( target="_blank"
<div className="space-y-2"> rel="noopener noreferrer"
<p className="text-xs text-muted-foreground"> className="text-primary hover:underline"
Enter your Composio API key to browse and enable tool integrations.
Get your key from{" "}
<a
href="https://app.composio.dev/settings"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
app.composio.dev/settings
</a>
</p>
<div className="flex gap-2">
<Input
type="password"
value={apiKeyInput}
onChange={(e) => setApiKeyInput(e.target.value)}
placeholder="Paste your Composio API key"
onKeyDown={(e) => e.key === "Enter" && handleSaveApiKey()}
className="flex-1"
/>
<Button
onClick={handleSaveApiKey}
disabled={!apiKeyInput.trim() || apiKeySaving}
size="sm"
>
{apiKeySaving ? <Loader2 className="size-4 animate-spin" /> : "Save"}
</Button>
{apiKeyConfigured && (
<Button
variant="outline"
size="sm"
onClick={() => { setShowApiKeyInput(false); setApiKeyInput("") }}
> >
Cancel app.composio.dev/settings
</a>
</p>
<div className="flex gap-2">
<Input
type="password"
value={apiKeyInput}
onChange={(e) => setApiKeyInput(e.target.value)}
placeholder="Paste your Composio API key"
onKeyDown={(e) => e.key === "Enter" && handleSaveApiKey()}
className="flex-1"
/>
<Button
onClick={handleSaveApiKey}
disabled={!apiKeyInput.trim() || apiKeySaving}
size="sm"
>
{apiKeySaving ? <Loader2 className="size-4 animate-spin" /> : "Save"}
</Button> </Button>
)} {apiKeyConfigured && (
<Button
variant="outline"
size="sm"
onClick={() => { setShowApiKeyInput(false); setApiKeyInput("") }}
>
Cancel
</Button>
)}
</div>
</div> </div>
</div> )}
)} </div>
</div> )}
{/* Section B: Toolkit Browser (only when API key configured) */} {/* Section B: Toolkit Browser (only when API key configured) */}
{apiKeyConfigured && ( {apiKeyConfigured && (
@ -964,11 +966,11 @@ function ToolsLibrarySettings({ dialogOpen }: { dialogOpen: boolean }) {
<img <img
src={toolkit.meta.logo} src={toolkit.meta.logo}
alt="" alt=""
className="size-7 rounded object-contain flex-shrink-0" className="size-7 rounded object-contain shrink-0"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }} onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
/> />
) : ( ) : (
<div className="size-7 rounded bg-muted flex items-center justify-center flex-shrink-0"> <div className="size-7 rounded bg-muted flex items-center justify-center shrink-0">
<Wrench className="size-3.5 text-muted-foreground" /> <Wrench className="size-3.5 text-muted-foreground" />
</div> </div>
)} )}
@ -994,7 +996,7 @@ function ToolsLibrarySettings({ dialogOpen }: { dialogOpen: boolean }) {
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => handleDisconnect(toolkit.slug)} onClick={() => handleDisconnect(toolkit.slug)}
className="text-xs h-7 flex-shrink-0" className="text-xs h-7 shrink-0"
> >
Disconnect Disconnect
</Button> </Button>
@ -1003,7 +1005,7 @@ function ToolsLibrarySettings({ dialogOpen }: { dialogOpen: boolean }) {
size="sm" size="sm"
onClick={() => handleConnect(toolkit.slug)} onClick={() => handleConnect(toolkit.slug)}
disabled={isConnecting} disabled={isConnecting}
className="text-xs h-7 flex-shrink-0" className="text-xs h-7 shrink-0"
> >
{isConnecting ? ( {isConnecting ? (
<><Loader2 className="size-3 animate-spin mr-1" />Connecting...</> <><Loader2 className="size-3 animate-spin mr-1" />Connecting...</>
@ -1649,7 +1651,7 @@ export function SettingsDialog({ children }: SettingsDialogProps) {
) : activeTab === "appearance" ? ( ) : activeTab === "appearance" ? (
<AppearanceSettings /> <AppearanceSettings />
) : activeTab === "tools" ? ( ) : activeTab === "tools" ? (
<ToolsLibrarySettings dialogOpen={open} /> <ToolsLibrarySettings dialogOpen={open} rowboatConnected={rowboatConnected} />
) : loading ? ( ) : loading ? (
<div className="h-full flex items-center justify-center text-muted-foreground text-sm"> <div className="h-full flex items-center justify-center text-muted-foreground text-sm">
Loading... Loading...

View file

@ -11,7 +11,17 @@ const runtimeContextPrompt = getRuntimeContextPrompt(getRuntimeContext());
* Lists connected toolkits and explains the meta-tool discovery flow. * Lists connected toolkits and explains the meta-tool discovery flow.
*/ */
async function getComposioToolsPrompt(): Promise<string> { async function getComposioToolsPrompt(): Promise<string> {
if (!(await isComposioConfigured())) return ''; if (!(await isComposioConfigured())) {
return `
## Composio Integrations
**Composio is not configured.** Composio enables integrations with third-party services like Google Sheets, GitHub, Slack, Jira, Notion, LinkedIn, and 20+ others.
When the user asks to interact with any third-party service (e.g., "connect to Google Sheets", "create a GitHub issue"), do NOT attempt to write code, use shell commands, or load the composio-integration skill. Instead, let the user know that these integrations are available through Composio, and they can enable them by adding their Composio API key in **Settings > Tools Library**. They can get their key from https://app.composio.dev/settings.
**Exception Email and Calendar:** For email-related requests (reading emails, sending emails, drafting replies) or calendar-related requests (checking schedule, listing events), do NOT direct the user to Composio. Instead, tell them to connect their email and calendar in **Settings > Connected Accounts**.
`;
}
const connectedToolkits = composioAccountsRepo.getConnectedToolkits(); const connectedToolkits = composioAccountsRepo.getConnectedToolkits();
const connectedSection = connectedToolkits.length > 0 const connectedSection = connectedToolkits.length > 0