mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-19 18:35:18 +02:00
use composio for calendar
This commit is contained in:
parent
429e7e4f03
commit
c81a04b497
6 changed files with 306 additions and 16 deletions
|
|
@ -277,6 +277,13 @@ export async function useComposioForGoogle(): Promise<{ enabled: boolean }> {
|
||||||
return { enabled: await composioClient.useComposioForGoogle() };
|
return { enabled: await composioClient.useComposioForGoogle() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Composio should be used for Google Calendar
|
||||||
|
*/
|
||||||
|
export async function useComposioForGoogleCalendar(): Promise<{ enabled: boolean }> {
|
||||||
|
return { enabled: await composioClient.useComposioForGoogleCalendar() };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a Composio action
|
* Execute a Composio action
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -547,6 +547,9 @@ export function setupIpcHandlers() {
|
||||||
'composio:use-composio-for-google': async () => {
|
'composio:use-composio-for-google': async () => {
|
||||||
return composioHandler.useComposioForGoogle();
|
return composioHandler.useComposioForGoogle();
|
||||||
},
|
},
|
||||||
|
'composio:use-composio-for-google-calendar': async () => {
|
||||||
|
return composioHandler.useComposioForGoogleCalendar();
|
||||||
|
},
|
||||||
// Agent schedule handlers
|
// Agent schedule handlers
|
||||||
'agent-schedule:getConfig': async () => {
|
'agent-schedule:getConfig': async () => {
|
||||||
const repo = container.resolve<IAgentScheduleRepo>('agentScheduleRepo');
|
const repo = container.resolve<IAgentScheduleRepo>('agentScheduleRepo');
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { useState, useEffect, useCallback } from "react"
|
import { useState, useEffect, useCallback } from "react"
|
||||||
import { AlertTriangle, Loader2, Mic, Mail, MessageSquare, User } from "lucide-react"
|
import { AlertTriangle, Loader2, Mic, Mail, Calendar, MessageSquare, User } from "lucide-react"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
|
|
@ -75,6 +75,12 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
const [gmailLoading, setGmailLoading] = useState(true)
|
const [gmailLoading, setGmailLoading] = useState(true)
|
||||||
const [gmailConnecting, setGmailConnecting] = useState(false)
|
const [gmailConnecting, setGmailConnecting] = useState(false)
|
||||||
|
|
||||||
|
// Composio/Google Calendar state
|
||||||
|
const [useComposioForGoogleCalendar, setUseComposioForGoogleCalendar] = useState(false)
|
||||||
|
const [googleCalendarConnected, setGoogleCalendarConnected] = useState(false)
|
||||||
|
const [googleCalendarLoading, setGoogleCalendarLoading] = useState(true)
|
||||||
|
const [googleCalendarConnecting, setGoogleCalendarConnecting] = useState(false)
|
||||||
|
|
||||||
// Load available providers on mount
|
// Load available providers on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function loadProviders() {
|
async function loadProviders() {
|
||||||
|
|
@ -103,7 +109,16 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
console.error('Failed to check composio-for-google flag:', error)
|
console.error('Failed to check composio-for-google flag:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function loadComposioForGoogleCalendarFlag() {
|
||||||
|
try {
|
||||||
|
const result = await window.ipc.invoke('composio:use-composio-for-google-calendar', null)
|
||||||
|
setUseComposioForGoogleCalendar(result.enabled)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to check composio-for-google-calendar flag:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
loadComposioForGoogleFlag()
|
loadComposioForGoogleFlag()
|
||||||
|
loadComposioForGoogleCalendarFlag()
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
// Load Granola config
|
// Load Granola config
|
||||||
|
|
@ -189,6 +204,20 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Load Google Calendar connection status
|
||||||
|
const refreshGoogleCalendarStatus = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setGoogleCalendarLoading(true)
|
||||||
|
const result = await window.ipc.invoke('composio:get-connection-status', { toolkitSlug: 'googlecalendar' })
|
||||||
|
setGoogleCalendarConnected(result.isConnected)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load Google Calendar status:', error)
|
||||||
|
setGoogleCalendarConnected(false)
|
||||||
|
} finally {
|
||||||
|
setGoogleCalendarLoading(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Connect to Gmail via Composio
|
// Connect to Gmail via Composio
|
||||||
const startGmailConnect = useCallback(async () => {
|
const startGmailConnect = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -217,6 +246,52 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
await startGmailConnect()
|
await startGmailConnect()
|
||||||
}, [startGmailConnect])
|
}, [startGmailConnect])
|
||||||
|
|
||||||
|
// Connect to Google Calendar via Composio
|
||||||
|
const startGoogleCalendarConnect = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setGoogleCalendarConnecting(true)
|
||||||
|
const result = await window.ipc.invoke('composio:initiate-connection', { toolkitSlug: 'googlecalendar' })
|
||||||
|
if (!result.success) {
|
||||||
|
toast.error(result.error || 'Failed to connect to Google Calendar')
|
||||||
|
setGoogleCalendarConnecting(false)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to connect to Google Calendar:', error)
|
||||||
|
toast.error('Failed to connect to Google Calendar')
|
||||||
|
setGoogleCalendarConnecting(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Handle Google Calendar connect button click
|
||||||
|
const handleConnectGoogleCalendar = useCallback(async () => {
|
||||||
|
const configResult = await window.ipc.invoke('composio:is-configured', null)
|
||||||
|
if (!configResult.configured) {
|
||||||
|
setComposioApiKeyTarget('gmail')
|
||||||
|
setComposioApiKeyOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await startGoogleCalendarConnect()
|
||||||
|
}, [startGoogleCalendarConnect])
|
||||||
|
|
||||||
|
// Disconnect from Google Calendar
|
||||||
|
const handleDisconnectGoogleCalendar = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setGoogleCalendarLoading(true)
|
||||||
|
const result = await window.ipc.invoke('composio:disconnect', { toolkitSlug: 'googlecalendar' })
|
||||||
|
if (result.success) {
|
||||||
|
setGoogleCalendarConnected(false)
|
||||||
|
toast.success('Disconnected from Google Calendar')
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to disconnect from Google Calendar')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to disconnect from Google Calendar:', error)
|
||||||
|
toast.error('Failed to disconnect from Google Calendar')
|
||||||
|
} finally {
|
||||||
|
setGoogleCalendarLoading(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Disconnect from Gmail
|
// Disconnect from Gmail
|
||||||
const handleDisconnectGmail = useCallback(async () => {
|
const handleDisconnectGmail = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -297,6 +372,11 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
refreshGmailStatus()
|
refreshGmailStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh Google Calendar Composio status if enabled
|
||||||
|
if (useComposioForGoogleCalendar) {
|
||||||
|
refreshGoogleCalendarStatus()
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh OAuth providers
|
// Refresh OAuth providers
|
||||||
if (providers.length === 0) return
|
if (providers.length === 0) return
|
||||||
|
|
||||||
|
|
@ -333,7 +413,7 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
}
|
}
|
||||||
|
|
||||||
setProviderStates(newStates)
|
setProviderStates(newStates)
|
||||||
}, [providers, refreshGranolaConfig, refreshSlackConfig, refreshGmailStatus, useComposioForGoogle])
|
}, [providers, refreshGranolaConfig, refreshSlackConfig, refreshGmailStatus, useComposioForGoogle, refreshGoogleCalendarStatus, useComposioForGoogleCalendar])
|
||||||
|
|
||||||
// Refresh statuses when popover opens or providers list changes
|
// Refresh statuses when popover opens or providers list changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -368,13 +448,17 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
toast.success(`Connected to ${displayName}`)
|
toast.success(`Connected to ${displayName}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// When Rowboat account connects, re-check composio flag so Gmail uses the right flow
|
// When Rowboat account connects, re-check composio flags so Gmail/Calendar use the right flow
|
||||||
if (provider === 'rowboat') {
|
if (provider === 'rowboat') {
|
||||||
try {
|
try {
|
||||||
const result = await window.ipc.invoke('composio:use-composio-for-google', null)
|
const [googleResult, calendarResult] = await Promise.all([
|
||||||
setUseComposioForGoogle(result.enabled)
|
window.ipc.invoke('composio:use-composio-for-google', null),
|
||||||
|
window.ipc.invoke('composio:use-composio-for-google-calendar', null),
|
||||||
|
])
|
||||||
|
setUseComposioForGoogle(googleResult.enabled)
|
||||||
|
setUseComposioForGoogleCalendar(calendarResult.enabled)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to re-check composio-for-google flag:', err)
|
console.error('Failed to re-check composio flags:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -388,7 +472,7 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
return cleanup
|
return cleanup
|
||||||
}, [refreshAllStatuses])
|
}, [refreshAllStatuses])
|
||||||
|
|
||||||
// Listen for Composio connection events (Gmail)
|
// Listen for Composio connection events (Gmail, Google Calendar)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cleanup = window.ipc.on('composio:didConnect', (event) => {
|
const cleanup = window.ipc.on('composio:didConnect', (event) => {
|
||||||
const { toolkitSlug, success, error } = event
|
const { toolkitSlug, success, error } = event
|
||||||
|
|
@ -406,6 +490,17 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
toast.error(error || 'Failed to connect to Gmail')
|
toast.error(error || 'Failed to connect to Gmail')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toolkitSlug === 'googlecalendar') {
|
||||||
|
setGoogleCalendarConnected(success)
|
||||||
|
setGoogleCalendarConnecting(false)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
toast.success('Connected to Google Calendar')
|
||||||
|
} else {
|
||||||
|
toast.error(error || 'Failed to connect to Google Calendar')
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return cleanup
|
return cleanup
|
||||||
|
|
@ -656,11 +751,11 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Email & Calendar Section */}
|
{/* Email & Calendar Section */}
|
||||||
{(useComposioForGoogle || providers.includes('google')) && (
|
{(useComposioForGoogle || useComposioForGoogleCalendar || providers.includes('google')) && (
|
||||||
<>
|
<>
|
||||||
<div className="px-2 py-1.5">
|
<div className="px-2 py-1.5">
|
||||||
<span className="text-xs font-medium text-muted-foreground">
|
<span className="text-xs font-medium text-muted-foreground">
|
||||||
{useComposioForGoogle ? 'Email' : 'Email & Calendar'}
|
Email & Calendar
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{useComposioForGoogle ? (
|
{useComposioForGoogle ? (
|
||||||
|
|
@ -712,6 +807,53 @@ export function ConnectorsPopover({ children, tooltip, open: openProp, onOpenCha
|
||||||
) : (
|
) : (
|
||||||
renderOAuthProvider('google', 'Google', <Mail className="size-4" />, 'Sync emails and calendar')
|
renderOAuthProvider('google', 'Google', <Mail className="size-4" />, 'Sync emails and calendar')
|
||||||
)}
|
)}
|
||||||
|
{useComposioForGoogleCalendar && (
|
||||||
|
<div className="flex items-center justify-between gap-3 rounded-md px-3 py-2 hover:bg-accent">
|
||||||
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
|
<div className="flex size-8 items-center justify-center rounded-md bg-muted">
|
||||||
|
<Calendar className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col min-w-0">
|
||||||
|
<span className="text-sm font-medium truncate">Google Calendar</span>
|
||||||
|
{googleCalendarLoading ? (
|
||||||
|
<span className="text-xs text-muted-foreground">Checking...</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground truncate">
|
||||||
|
Sync calendar events
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="shrink-0">
|
||||||
|
{googleCalendarLoading ? (
|
||||||
|
<Loader2 className="size-4 animate-spin text-muted-foreground" />
|
||||||
|
) : googleCalendarConnected ? (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleDisconnectGoogleCalendar}
|
||||||
|
className="h-7 px-2 text-xs"
|
||||||
|
>
|
||||||
|
Disconnect
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleConnectGoogleCalendar}
|
||||||
|
disabled={googleCalendarConnecting}
|
||||||
|
className="h-7 px-2 text-xs"
|
||||||
|
>
|
||||||
|
{googleCalendarConnecting ? (
|
||||||
|
<Loader2 className="size-3 animate-spin" />
|
||||||
|
) : (
|
||||||
|
"Connect"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Separator className="my-2" />
|
<Separator className="my-2" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { useState, useEffect, useCallback } from "react"
|
import { useState, useEffect, useCallback } from "react"
|
||||||
import { Loader2, Mic, Mail, CheckCircle2, ArrowLeft, MessageSquare } from "lucide-react"
|
import { Loader2, Mic, Mail, Calendar, CheckCircle2, ArrowLeft, MessageSquare } from "lucide-react"
|
||||||
// import { MessageSquare } from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
|
@ -103,6 +102,12 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
const [gmailLoading, setGmailLoading] = useState(true)
|
const [gmailLoading, setGmailLoading] = useState(true)
|
||||||
const [gmailConnecting, setGmailConnecting] = useState(false)
|
const [gmailConnecting, setGmailConnecting] = useState(false)
|
||||||
|
|
||||||
|
// Composio/Google Calendar state
|
||||||
|
const [useComposioForGoogleCalendar, setUseComposioForGoogleCalendar] = useState(false)
|
||||||
|
const [googleCalendarConnected, setGoogleCalendarConnected] = useState(false)
|
||||||
|
const [googleCalendarLoading, setGoogleCalendarLoading] = useState(true)
|
||||||
|
const [googleCalendarConnecting, setGoogleCalendarConnecting] = useState(false)
|
||||||
|
|
||||||
const updateProviderConfig = useCallback(
|
const updateProviderConfig = useCallback(
|
||||||
(provider: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>) => {
|
(provider: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>) => {
|
||||||
setProviderConfigs(prev => ({
|
setProviderConfigs(prev => ({
|
||||||
|
|
@ -154,8 +159,17 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
console.error('Failed to check composio-for-google flag:', error)
|
console.error('Failed to check composio-for-google flag:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function loadComposioForGoogleCalendarFlag() {
|
||||||
|
try {
|
||||||
|
const result = await window.ipc.invoke('composio:use-composio-for-google-calendar', null)
|
||||||
|
setUseComposioForGoogleCalendar(result.enabled)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to check composio-for-google-calendar flag:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
loadProviders()
|
loadProviders()
|
||||||
loadComposioForGoogleFlag()
|
loadComposioForGoogleFlag()
|
||||||
|
loadComposioForGoogleCalendarFlag()
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
// Load LLM models catalog on open
|
// Load LLM models catalog on open
|
||||||
|
|
@ -292,6 +306,20 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Load Google Calendar connection status
|
||||||
|
const refreshGoogleCalendarStatus = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setGoogleCalendarLoading(true)
|
||||||
|
const result = await window.ipc.invoke('composio:get-connection-status', { toolkitSlug: 'googlecalendar' })
|
||||||
|
setGoogleCalendarConnected(result.isConnected)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load Google Calendar status:', error)
|
||||||
|
setGoogleCalendarConnected(false)
|
||||||
|
} finally {
|
||||||
|
setGoogleCalendarLoading(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Connect to Gmail via Composio
|
// Connect to Gmail via Composio
|
||||||
const startGmailConnect = useCallback(async () => {
|
const startGmailConnect = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -319,6 +347,33 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
await startGmailConnect()
|
await startGmailConnect()
|
||||||
}, [startGmailConnect])
|
}, [startGmailConnect])
|
||||||
|
|
||||||
|
// Connect to Google Calendar via Composio
|
||||||
|
const startGoogleCalendarConnect = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setGoogleCalendarConnecting(true)
|
||||||
|
const result = await window.ipc.invoke('composio:initiate-connection', { toolkitSlug: 'googlecalendar' })
|
||||||
|
if (!result.success) {
|
||||||
|
toast.error(result.error || 'Failed to connect to Google Calendar')
|
||||||
|
setGoogleCalendarConnecting(false)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to connect to Google Calendar:', error)
|
||||||
|
toast.error('Failed to connect to Google Calendar')
|
||||||
|
setGoogleCalendarConnecting(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Handle Google Calendar connect button click
|
||||||
|
const handleConnectGoogleCalendar = useCallback(async () => {
|
||||||
|
const configResult = await window.ipc.invoke('composio:is-configured', null)
|
||||||
|
if (!configResult.configured) {
|
||||||
|
setComposioApiKeyTarget('gmail')
|
||||||
|
setComposioApiKeyOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await startGoogleCalendarConnect()
|
||||||
|
}, [startGoogleCalendarConnect])
|
||||||
|
|
||||||
// Handle Composio API key submission
|
// Handle Composio API key submission
|
||||||
const handleComposioApiKeySubmit = useCallback(async (apiKey: string) => {
|
const handleComposioApiKeySubmit = useCallback(async (apiKey: string) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -442,6 +497,11 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
refreshGmailStatus()
|
refreshGmailStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh Google Calendar Composio status if enabled
|
||||||
|
if (useComposioForGoogleCalendar) {
|
||||||
|
refreshGoogleCalendarStatus()
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh OAuth providers
|
// Refresh OAuth providers
|
||||||
if (providers.length === 0) return
|
if (providers.length === 0) return
|
||||||
|
|
||||||
|
|
@ -469,7 +529,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setProviderStates(newStates)
|
setProviderStates(newStates)
|
||||||
}, [providers, refreshGranolaConfig, refreshSlackConfig, refreshGmailStatus, useComposioForGoogle])
|
}, [providers, refreshGranolaConfig, refreshSlackConfig, refreshGmailStatus, useComposioForGoogle, refreshGoogleCalendarStatus, useComposioForGoogleCalendar])
|
||||||
|
|
||||||
// Refresh statuses when modal opens or providers list changes
|
// Refresh statuses when modal opens or providers list changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -516,7 +576,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
return cleanup
|
return cleanup
|
||||||
}, [onboardingPath, currentStep])
|
}, [onboardingPath, currentStep])
|
||||||
|
|
||||||
// Listen for Composio connection events
|
// Listen for Composio connection events (Gmail, Google Calendar)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cleanup = window.ipc.on('composio:didConnect', (event) => {
|
const cleanup = window.ipc.on('composio:didConnect', (event) => {
|
||||||
const { toolkitSlug, success, error } = event
|
const { toolkitSlug, success, error } = event
|
||||||
|
|
@ -534,6 +594,17 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
toast.error(error || 'Failed to connect to Gmail')
|
toast.error(error || 'Failed to connect to Gmail')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toolkitSlug === 'googlecalendar') {
|
||||||
|
setGoogleCalendarConnected(success)
|
||||||
|
setGoogleCalendarConnecting(false)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
toast.success('Connected to Google Calendar')
|
||||||
|
} else {
|
||||||
|
toast.error(error || 'Failed to connect to Google Calendar')
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return cleanup
|
return cleanup
|
||||||
|
|
@ -736,6 +807,50 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Render Google Calendar Composio row
|
||||||
|
const renderGoogleCalendarRow = () => (
|
||||||
|
<div className="flex items-center justify-between gap-3 rounded-md px-3 py-3 hover:bg-accent">
|
||||||
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
|
<div className="flex size-10 items-center justify-center rounded-md bg-muted">
|
||||||
|
<Calendar className="size-5" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col min-w-0">
|
||||||
|
<span className="text-sm font-medium truncate">Google Calendar</span>
|
||||||
|
{googleCalendarLoading ? (
|
||||||
|
<span className="text-xs text-muted-foreground">Checking...</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground truncate">
|
||||||
|
Sync calendar events
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="shrink-0">
|
||||||
|
{googleCalendarLoading ? (
|
||||||
|
<Loader2 className="size-4 animate-spin text-muted-foreground" />
|
||||||
|
) : googleCalendarConnected ? (
|
||||||
|
<div className="flex items-center gap-1.5 text-sm text-green-600">
|
||||||
|
<CheckCircle2 className="size-4" />
|
||||||
|
<span>Connected</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleConnectGoogleCalendar}
|
||||||
|
disabled={googleCalendarConnecting}
|
||||||
|
>
|
||||||
|
{googleCalendarConnecting ? (
|
||||||
|
<Loader2 className="size-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
"Connect"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
// Render Slack row
|
// Render Slack row
|
||||||
const renderSlackRow = () => (
|
const renderSlackRow = () => (
|
||||||
<div className="rounded-md px-3 py-3 hover:bg-accent">
|
<div className="rounded-md px-3 py-3 hover:bg-accent">
|
||||||
|
|
@ -1147,17 +1262,18 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Email / Email & Calendar Section */}
|
{/* Email / Email & Calendar Section */}
|
||||||
{(useComposioForGoogle || providers.includes('google')) && (
|
{(useComposioForGoogle || useComposioForGoogleCalendar || providers.includes('google')) && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="px-3">
|
<div className="px-3">
|
||||||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||||
{useComposioForGoogle ? 'Email' : 'Email & Calendar'}
|
{(useComposioForGoogle || useComposioForGoogleCalendar) ? 'Email & Calendar' : 'Email & Calendar'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{useComposioForGoogle
|
{useComposioForGoogle
|
||||||
? renderGmailRow()
|
? renderGmailRow()
|
||||||
: renderOAuthProvider('google', 'Google', <Mail className="size-5" />, 'Sync emails and calendar events')
|
: renderOAuthProvider('google', 'Google', <Mail className="size-5" />, 'Sync emails and calendar events')
|
||||||
}
|
}
|
||||||
|
{useComposioForGoogleCalendar && renderGoogleCalendarRow()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -1200,7 +1316,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
|
|
||||||
// Step 4: Completion
|
// Step 4: Completion
|
||||||
const renderCompletionStep = () => {
|
const renderCompletionStep = () => {
|
||||||
const hasConnections = connectedProviders.length > 0 || granolaEnabled || slackEnabled || gmailConnected
|
const hasConnections = connectedProviders.length > 0 || granolaEnabled || slackEnabled || gmailConnected || googleCalendarConnected
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center text-center">
|
<div className="flex flex-col items-center text-center">
|
||||||
|
|
@ -1229,6 +1345,12 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
<span>Gmail (Email)</span>
|
<span>Gmail (Email)</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{googleCalendarConnected && (
|
||||||
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
<CheckCircle2 className="size-4 text-green-600" />
|
||||||
|
<span>Google Calendar</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{connectedProviders.includes('google') && (
|
{connectedProviders.includes('google') && (
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<CheckCircle2 className="size-4 text-green-600" />
|
<CheckCircle2 className="size-4 text-green-600" />
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ async function getAuthHeaders(): Promise<Record<string, string>> {
|
||||||
const ZComposioConfig = z.object({
|
const ZComposioConfig = z.object({
|
||||||
apiKey: z.string().optional(),
|
apiKey: z.string().optional(),
|
||||||
use_composio_for_google: z.boolean().optional(),
|
use_composio_for_google: z.boolean().optional(),
|
||||||
|
use_composio_for_google_calendar: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type ComposioConfig = z.infer<typeof ZComposioConfig>;
|
type ComposioConfig = z.infer<typeof ZComposioConfig>;
|
||||||
|
|
@ -113,6 +114,15 @@ export async function useComposioForGoogle(): Promise<boolean> {
|
||||||
return config.use_composio_for_google === true;
|
return config.use_composio_for_google === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Composio should be used for Google Calendar
|
||||||
|
*/
|
||||||
|
export async function useComposioForGoogleCalendar(): Promise<boolean> {
|
||||||
|
if (await isSignedIn()) return true;
|
||||||
|
const config = loadConfig();
|
||||||
|
return config.use_composio_for_google_calendar === true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make an API call to Composio
|
* Make an API call to Composio
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,12 @@ const ipcSchemas = {
|
||||||
enabled: z.boolean(),
|
enabled: z.boolean(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
'composio:use-composio-for-google-calendar': {
|
||||||
|
req: z.null(),
|
||||||
|
res: z.object({
|
||||||
|
enabled: z.boolean(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
'composio:didConnect': {
|
'composio:didConnect': {
|
||||||
req: z.object({
|
req: z.object({
|
||||||
toolkitSlug: z.string(),
|
toolkitSlug: z.string(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue