tools row in home

This commit is contained in:
Arjun 2026-05-25 13:19:34 +05:30
parent 84aa980894
commit 8aa0c1763a

View file

@ -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 (
<img
src={toolkit.logo}
alt=""
className="hidden"
onLoad={(event) => {
const img = event.currentTarget
if (img.naturalWidth > 1 && img.naturalHeight > 1) {
setLoaded(true)
} else {
onInvalid(toolkit.slug)
}
}}
onError={() => onInvalid(toolkit.slug)}
/>
)
}
return (
<div
title={`${toolkit.name}: ${toolkit.description}`}
aria-label={toolkit.name}
className="flex size-6 shrink-0 items-center justify-center rounded-md border border-border bg-muted/60"
>
<img
src={toolkit.logo}
alt=""
className="size-5 shrink-0 object-contain"
onError={() => onInvalid(toolkit.slug)}
/>
</div>
)
}
export function HomeView({
tree,
@ -168,6 +218,9 @@ export function HomeView({
}: HomeViewProps) {
const [events, setEvents] = useState<CalEvent[]>([])
const [emails, setEmails] = useState<EmailThread[]>([])
const [toolkitPreviews, setToolkitPreviews] = useState<ToolkitPreview[]>(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({
</div>
)}
{/* Tool connections */}
<div className={CARD}>
<div className="flex items-start gap-3">
<div className="flex size-8 shrink-0 items-center justify-center rounded-lg border border-border bg-muted text-muted-foreground">
<Plug className="size-[14px]" />
</div>
<div className="min-w-0 flex-1">
<div className="text-[13.5px] leading-snug">
<span className="font-medium">Connect your tools.</span>
<span className="text-muted-foreground"> Bring context from the apps you already use.</span>
</div>
<div className="mt-3 flex min-h-5 flex-wrap items-center gap-1.5">
{toolkitLogosLoaded && toolkitPreviews.map((toolkit) => (
<ToolkitPreviewIcon
key={toolkit.slug}
toolkit={toolkit}
onInvalid={removeToolkitPreview}
/>
))}
<button
type="button"
onClick={() => setConnectionsSettingsOpen(true)}
className="ml-1 flex h-5 shrink-0 items-center gap-1 rounded-md px-1 text-[12px] font-medium text-primary hover:underline"
>
Connections
<ArrowRight className="size-3" />
</button>
</div>
</div>
</div>
</div>
<SettingsDialog
defaultTab="connections"
open={connectionsSettingsOpen}
onOpenChange={setConnectionsSettingsOpen}
/>
{/* Open chat CTA */}
{onOpenChat && (
<button