diff --git a/apps/x/apps/renderer/src/components/home-view.tsx b/apps/x/apps/renderer/src/components/home-view.tsx
index 3cc0899d..bfcc0a33 100644
--- a/apps/x/apps/renderer/src/components/home-view.tsx
+++ b/apps/x/apps/renderer/src/components/home-view.tsx
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
-import { ArrowRight, Bot, Calendar, Clock, FileText, Mail, MessageSquare, Mic, Plus, Video } from 'lucide-react'
+import { ArrowRight, Bot, Calendar, Clock, FileText, Mail, MessageSquare, Mic, Plug, Plus, Video } from 'lucide-react'
import { extractConferenceLink } from '@/lib/calendar-event'
+import { SettingsDialog } from '@/components/settings-dialog'
interface TreeNode {
path: string
@@ -53,6 +54,7 @@ type RawCalEvent = {
}
type EmailThread = { threadId: string; subject: string; from: string }
+type ToolkitPreview = { slug: string; logo: string; name: string; description: string }
function greeting(): string {
const h = new Date().getHours()
@@ -152,6 +154,54 @@ 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,
@@ -168,6 +218,9 @@ export function HomeView({
}: HomeViewProps) {
const [events, setEvents] = useState([])
const [emails, setEmails] = useState([])
+ const [toolkitPreviews, setToolkitPreviews] = useState(cachedToolkitPreviews ?? [])
+ const [toolkitLogosLoaded, setToolkitLogosLoaded] = useState(cachedToolkitLogosLoaded)
+ const [connectionsSettingsOpen, setConnectionsSettingsOpen] = useState(false)
const loadEvents = useCallback(async () => {
try {
@@ -207,7 +260,40 @@ export function HomeView({
}
}, [])
- useEffect(() => { void loadEvents(); void loadEmails() }, [loadEvents, loadEmails])
+ 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 loadConnectorLogos() }, [loadEvents, loadEmails, loadConnectorLogos])
// Upcoming (not-yet-ended) events, soonest first.
const upcoming = useMemo(() => {
@@ -437,6 +523,43 @@ export function HomeView({
)}
+ {/* Tool connections */}
+
+
+
+
+
+ Connect your tools.
+ Bring context from the apps you already use.
+
+
+ {toolkitLogosLoaded && toolkitPreviews.map((toolkit) => (
+
+ ))}
+
+
+
+
+
+
+
{/* Open chat CTA */}
{onOpenChat && (