From c8d801a123015485a78d1ba9b2a0bcf27011717b Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Sat, 20 Jun 2026 03:02:41 +0530 Subject: [PATCH] add toolkits to the new chat for ease of discovery --- .../src/components/chat-empty-state.tsx | 3 + .../renderer/src/components/home-view.tsx | 127 +--------------- .../src/components/tool-connections-card.tsx | 138 ++++++++++++++++++ 3 files changed, 145 insertions(+), 123 deletions(-) create mode 100644 apps/x/apps/renderer/src/components/tool-connections-card.tsx diff --git a/apps/x/apps/renderer/src/components/chat-empty-state.tsx b/apps/x/apps/renderer/src/components/chat-empty-state.tsx index bb9cea8d..9423d060 100644 --- a/apps/x/apps/renderer/src/components/chat-empty-state.tsx +++ b/apps/x/apps/renderer/src/components/chat-empty-state.tsx @@ -2,6 +2,7 @@ import { ArrowUpRight, Bot, Mail, MessageSquare, Sparkles, Telescope } from 'luc import { cn } from '@/lib/utils' import { formatRelativeTime } from '@/lib/relative-time' +import { ToolConnectionsCard } from '@/components/tool-connections-card' export interface ChatEmptyStateRun { id: string @@ -101,6 +102,8 @@ export function ChatEmptyState({ ))} + + ) } diff --git a/apps/x/apps/renderer/src/components/home-view.tsx b/apps/x/apps/renderer/src/components/home-view.tsx index 1f1e6e6f..43b5978b 100644 --- a/apps/x/apps/renderer/src/components/home-view.tsx +++ b/apps/x/apps/renderer/src/components/home-view.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import { ArrowRight, Bot, Calendar, Clock, ExternalLink, FileText, Mail, MessageSquare, Mic, Plug, Plus, Video } from 'lucide-react' +import { ArrowRight, Bot, Calendar, Clock, ExternalLink, FileText, Mail, MessageSquare, Mic, Plus, Video } from 'lucide-react' import { extractConferenceLink } from '@/lib/calendar-event' -import { SettingsDialog } from '@/components/settings-dialog' +import { ToolConnectionsCard } from '@/components/tool-connections-card' interface TreeNode { path: string @@ -65,7 +65,6 @@ type SlackFeedMessage = { ts: string url?: string } -type ToolkitPreview = { slug: string; logo: string; name: string; description: string } function greeting(): string { const h = new Date().getHours() @@ -187,54 +186,6 @@ function triggerMeetingCapture(event: CalEvent, openConference: boolean) { } const CARD = 'rounded-xl border border-border bg-card p-4' -const TOOLKIT_PREVIEW_LIMIT = 8 - -let cachedToolkitPreviews: ToolkitPreview[] | null = null -let cachedToolkitLogosLoaded = false - -function ToolkitPreviewIcon({ - toolkit, - onInvalid, -}: { - toolkit: ToolkitPreview - onInvalid: (slug: string) => void -}) { - const [loaded, setLoaded] = useState(false) - - if (!loaded) { - return ( - { - const img = event.currentTarget - if (img.naturalWidth > 1 && img.naturalHeight > 1) { - setLoaded(true) - } else { - onInvalid(toolkit.slug) - } - }} - onError={() => onInvalid(toolkit.slug)} - /> - ) - } - - return ( -
- onInvalid(toolkit.slug)} - /> -
- ) -} export function HomeView({ tree, @@ -255,9 +206,6 @@ export function HomeView({ const [slackMessages, setSlackMessages] = useState([]) const [slackError, setSlackError] = useState(null) const [slackErrorKind, setSlackErrorKind] = useState(null) - const [toolkitPreviews, setToolkitPreviews] = useState(cachedToolkitPreviews ?? []) - const [toolkitLogosLoaded, setToolkitLogosLoaded] = useState(cachedToolkitLogosLoaded) - const [connectionsSettingsOpen, setConnectionsSettingsOpen] = useState(false) const loadEvents = useCallback(async () => { try { @@ -313,40 +261,7 @@ export function HomeView({ } }, []) - const loadConnectorLogos = useCallback(async () => { - if (cachedToolkitLogosLoaded) return - try { - const configured = await window.ipc.invoke('composio:is-configured', null) - if (!configured.configured) return - const toolkits = await window.ipc.invoke('composio:list-toolkits', {}) - const previews = toolkits.items - .filter((toolkit) => Boolean(toolkit.meta.logo)) - .slice(0, TOOLKIT_PREVIEW_LIMIT) - .map((toolkit) => ({ - slug: toolkit.slug, - logo: toolkit.meta.logo, - name: toolkit.name, - description: toolkit.meta.description, - })) - cachedToolkitPreviews = previews - setToolkitPreviews(previews) - } catch { - cachedToolkitPreviews = [] - } finally { - cachedToolkitLogosLoaded = true - setToolkitLogosLoaded(true) - } - }, []) - - const removeToolkitPreview = useCallback((slug: string) => { - setToolkitPreviews((prev) => { - const next = prev.filter((toolkit) => toolkit.slug !== slug) - cachedToolkitPreviews = next - return next - }) - }, []) - - useEffect(() => { void loadEvents(); void loadEmails(); void loadSlackMessages(); void loadConnectorLogos() }, [loadEvents, loadEmails, loadSlackMessages, loadConnectorLogos]) + useEffect(() => { void loadEvents(); void loadEmails(); void loadSlackMessages() }, [loadEvents, loadEmails, loadSlackMessages]) // Upcoming (not-yet-ended) events, soonest first. const upcoming = useMemo(() => { @@ -624,41 +539,7 @@ export function HomeView({ )} {/* Tool connections */} -
-
-
- -
-
-
- Connect your tools. - Bring context from the apps you already use. -
-
- {toolkitLogosLoaded && toolkitPreviews.map((toolkit) => ( - - ))} - -
-
-
-
- + {/* Open chat CTA */} {onOpenChat && ( diff --git a/apps/x/apps/renderer/src/components/tool-connections-card.tsx b/apps/x/apps/renderer/src/components/tool-connections-card.tsx new file mode 100644 index 00000000..70c0d3fe --- /dev/null +++ b/apps/x/apps/renderer/src/components/tool-connections-card.tsx @@ -0,0 +1,138 @@ +import { useCallback, useEffect, useState } from 'react' +import { ArrowRight, Plug } from 'lucide-react' + +import { SettingsDialog } from '@/components/settings-dialog' +import { cn } from '@/lib/utils' + +type ToolkitPreview = { slug: string; logo: string; name: string; description: string } + +const TOOLKIT_PREVIEW_LIMIT = 8 + +let cachedToolkitPreviews: ToolkitPreview[] | null = null +let cachedToolkitLogosLoaded = false + +function ToolkitPreviewIcon({ + toolkit, + onInvalid, +}: { + toolkit: ToolkitPreview + onInvalid: (slug: string) => void +}) { + const [loaded, setLoaded] = useState(false) + + if (!loaded) { + return ( + { + const img = event.currentTarget + if (img.naturalWidth > 1 && img.naturalHeight > 1) { + setLoaded(true) + } else { + onInvalid(toolkit.slug) + } + }} + onError={() => onInvalid(toolkit.slug)} + /> + ) + } + + return ( +
+ onInvalid(toolkit.slug)} + /> +
+ ) +} + +export function ToolConnectionsCard({ className }: { className?: string }) { + const [toolkitPreviews, setToolkitPreviews] = useState(cachedToolkitPreviews ?? []) + const [toolkitLogosLoaded, setToolkitLogosLoaded] = useState(cachedToolkitLogosLoaded) + const [connectionsSettingsOpen, setConnectionsSettingsOpen] = useState(false) + + const loadConnectorLogos = useCallback(async () => { + if (cachedToolkitLogosLoaded) return + try { + const configured = await window.ipc.invoke('composio:is-configured', null) + if (!configured.configured) return + const toolkits = await window.ipc.invoke('composio:list-toolkits', {}) + const previews = toolkits.items + .filter((toolkit) => Boolean(toolkit.meta.logo)) + .slice(0, TOOLKIT_PREVIEW_LIMIT) + .map((toolkit) => ({ + slug: toolkit.slug, + logo: toolkit.meta.logo, + name: toolkit.name, + description: toolkit.meta.description, + })) + cachedToolkitPreviews = previews + setToolkitPreviews(previews) + } catch { + cachedToolkitPreviews = [] + } finally { + cachedToolkitLogosLoaded = true + setToolkitLogosLoaded(true) + } + }, []) + + const removeToolkitPreview = useCallback((slug: string) => { + setToolkitPreviews((prev) => { + const next = prev.filter((toolkit) => toolkit.slug !== slug) + cachedToolkitPreviews = next + return next + }) + }, []) + + useEffect(() => { + void loadConnectorLogos() + }, [loadConnectorLogos]) + + return ( + <> +
+
+
+ +
+
+
+ Bring context from and take action in the apps you already use. +
+
+ {toolkitLogosLoaded && toolkitPreviews.map((toolkit) => ( + + ))} + +
+
+
+
+ + + ) +}