mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-25 18:55:19 +02:00
tools row in home
This commit is contained in:
parent
84aa980894
commit
8aa0c1763a
1 changed files with 125 additions and 2 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
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 { extractConferenceLink } from '@/lib/calendar-event'
|
||||||
|
import { SettingsDialog } from '@/components/settings-dialog'
|
||||||
|
|
||||||
interface TreeNode {
|
interface TreeNode {
|
||||||
path: string
|
path: string
|
||||||
|
|
@ -53,6 +54,7 @@ type RawCalEvent = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailThread = { threadId: string; subject: string; from: string }
|
type EmailThread = { threadId: string; subject: string; from: string }
|
||||||
|
type ToolkitPreview = { slug: string; logo: string; name: string; description: string }
|
||||||
|
|
||||||
function greeting(): string {
|
function greeting(): string {
|
||||||
const h = new Date().getHours()
|
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 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({
|
export function HomeView({
|
||||||
tree,
|
tree,
|
||||||
|
|
@ -168,6 +218,9 @@ export function HomeView({
|
||||||
}: HomeViewProps) {
|
}: HomeViewProps) {
|
||||||
const [events, setEvents] = useState<CalEvent[]>([])
|
const [events, setEvents] = useState<CalEvent[]>([])
|
||||||
const [emails, setEmails] = useState<EmailThread[]>([])
|
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 () => {
|
const loadEvents = useCallback(async () => {
|
||||||
try {
|
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.
|
// Upcoming (not-yet-ended) events, soonest first.
|
||||||
const upcoming = useMemo(() => {
|
const upcoming = useMemo(() => {
|
||||||
|
|
@ -437,6 +523,43 @@ export function HomeView({
|
||||||
</div>
|
</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 */}
|
{/* Open chat CTA */}
|
||||||
{onOpenChat && (
|
{onOpenChat && (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue