rowboat/apps/x/apps/renderer/src/components/tool-connections-card.tsx
Gagan 565a5032ec style(x): side-pane chat sizing, bg-tasks stop chip, Brain contrast
- Chat empty state: smaller type scale in the side-pane copilot,
  compact prop on connections card
- Background tasks: stop button sized to match the Updating pill,
  wraps under it in narrow panes, aligned with the state toggle
- Sidebar: Code above Meetings
- Brain: page + graph background matches other tabs, darker light-mode
  borders for Base rows and Files containers, bolder section labels
2026-07-10 00:11:40 +05:30

138 lines
4.3 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react'
import { ArrowRight } 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 (
<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 ToolConnectionsCard({ className, compact = false }: { className?: string; compact?: boolean }) {
const [toolkitPreviews, setToolkitPreviews] = useState<ToolkitPreview[]>(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 (
<>
<div className={cn('rounded-xl border border-border bg-card p-4', className)}>
<div className="flex items-start gap-3">
<div className="min-w-0 flex-1">
<div className={cn('leading-snug', compact ? 'text-[12.5px]' : 'text-[13.5px]')}>
<span className="text-muted-foreground">Bring context from and take action in 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={cn(
'ml-1 flex h-5 shrink-0 items-center gap-1 rounded-md px-1 font-medium text-primary hover:underline',
compact ? 'text-[11.5px]' : 'text-[12px]',
)}
>
Connections
<ArrowRight className="size-3" />
</button>
</div>
</div>
</div>
</div>
<SettingsDialog
defaultTab="connections"
open={connectionsSettingsOpen}
onOpenChange={setConnectionsSettingsOpen}
/>
</>
)
}