diff --git a/apps/x/apps/renderer/package.json b/apps/x/apps/renderer/package.json index 5d882f88..9e31cfd3 100644 --- a/apps/x/apps/renderer/package.json +++ b/apps/x/apps/renderer/package.json @@ -43,6 +43,7 @@ "nanoid": "^5.1.6", "react": "^19.2.0", "react-dom": "^19.2.0", + "sonner": "^2.0.7", "streamdown": "^1.6.10", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.18", diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index b5569277..fe0089e7 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -46,6 +46,7 @@ import { } from "@/components/ui/sidebar" import { TooltipProvider } from "@/components/ui/tooltip" import { Separator } from "@/components/ui/separator" +import { Toaster } from "@/components/ui/sonner" import { stripKnowledgePrefix, toKnowledgePath, wikiLabel } from '@/lib/wiki-links' type DirEntry = z.infer @@ -1647,6 +1648,7 @@ function App() { )} + ) } diff --git a/apps/x/apps/renderer/src/components/chat-sidebar.tsx b/apps/x/apps/renderer/src/components/chat-sidebar.tsx index bbb2151f..5c5f7713 100644 --- a/apps/x/apps/renderer/src/components/chat-sidebar.tsx +++ b/apps/x/apps/renderer/src/components/chat-sidebar.tsx @@ -145,7 +145,6 @@ export function ChatSidebar({ recentFiles = [], visibleFiles = [], selectedPath, - pendingPermissionRequests = new Map(), pendingAskHumanRequests = new Map(), allPermissionRequests = new Map(), permissionResponses = new Map(), diff --git a/apps/x/apps/renderer/src/components/connectors-popover.tsx b/apps/x/apps/renderer/src/components/connectors-popover.tsx index 145997dd..fc814302 100644 --- a/apps/x/apps/renderer/src/components/connectors-popover.tsx +++ b/apps/x/apps/renderer/src/components/connectors-popover.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { useState, useEffect, useCallback } from "react" -import { Database, Loader2, Plug } from "lucide-react" +import { Calendar, Loader2, Mic, Mail } from "lucide-react" import { Popover, @@ -18,7 +18,7 @@ import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Switch } from "@/components/ui/switch" import { Separator } from "@/components/ui/separator" -import { toast } from "@/lib/toast" +import { toast } from "sonner" interface ProviderState { isConnected: boolean @@ -78,10 +78,10 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) setGranolaLoading(true) await window.ipc.invoke('granola:setConfig', { enabled }) setGranolaEnabled(enabled) - toast(enabled ? 'Granola sync enabled' : 'Granola sync disabled', 'success') + toast.success(enabled ? 'Granola sync enabled' : 'Granola sync disabled') } catch (error) { console.error('Failed to update Granola config:', error) - toast('Failed to update Granola sync settings', 'error') + toast.error('Failed to update Granola sync settings') } finally { setGranolaLoading(false) } @@ -142,14 +142,23 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) })) if (success) { - toast(`Successfully connected to ${provider}`, 'success') + const displayName = provider === 'fireflies-ai' ? 'Fireflies' : provider.charAt(0).toUpperCase() + provider.slice(1) + // Show detailed message for Google and Fireflies (includes sync info) + if (provider === 'google' || provider === 'fireflies-ai') { + toast.success(`Connected to ${displayName}`, { + description: 'Syncing your data in the background. This may take a few minutes before changes appear.', + duration: 8000, + }) + } else { + toast.success(`Connected to ${displayName}`) + } // Refresh status to ensure consistency refreshAllStatuses() } else { - toast(error || `Failed to connect to ${provider}`, 'error') + toast.error(error || `Failed to connect to ${provider}`) } }) - + return cleanup }, [refreshAllStatuses]) @@ -168,7 +177,7 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) // Event listener will handle the actual completion } else { // Immediate failure (e.g., couldn't start flow) - toast(result.error || `Failed to connect to ${provider}`, 'error') + toast.error(result.error || `Failed to connect to ${provider}`) setProviderStates(prev => ({ ...prev, [provider]: { ...prev[provider], isConnecting: false } @@ -176,7 +185,7 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) } } catch (error) { console.error('Failed to connect:', error) - toast(`Failed to connect to ${provider}`, 'error') + toast.error(`Failed to connect to ${provider}`) setProviderStates(prev => ({ ...prev, [provider]: { ...prev[provider], isConnecting: false } @@ -195,7 +204,8 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) const result = await window.ipc.invoke('oauth:disconnect', { provider }) if (result.success) { - toast(`Disconnected from ${provider}`, 'success') + const displayName = provider === 'fireflies-ai' ? 'Fireflies' : provider.charAt(0).toUpperCase() + provider.slice(1) + toast.success(`Disconnected from ${displayName}`) setProviderStates(prev => ({ ...prev, [provider]: { @@ -205,7 +215,7 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) } })) } else { - toast(`Failed to disconnect from ${provider}`, 'error') + toast.error(`Failed to disconnect from ${provider}`) setProviderStates(prev => ({ ...prev, [provider]: { ...prev[provider], isLoading: false } @@ -213,7 +223,7 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) } } catch (error) { console.error('Failed to disconnect:', error) - toast(`Failed to disconnect from ${provider}`, 'error') + toast.error(`Failed to disconnect from ${provider}`) setProviderStates(prev => ({ ...prev, [provider]: { ...prev[provider], isLoading: false } @@ -221,6 +231,64 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) } }, []) + // Helper to render an OAuth provider row + const renderOAuthProvider = (provider: string, displayName: string, icon: React.ReactNode, description: string) => { + const state = providerStates[provider] || { + isConnected: false, + isLoading: true, + isConnecting: false, + } + + return ( +
+
+
+ {icon} +
+
+ {displayName} + {state.isLoading ? ( + Checking... + ) : ( + {description} + )} +
+
+
+ {state.isLoading ? ( + + ) : state.isConnected ? ( + + ) : ( + + )} +
+
+ ) + } + return ( {tooltip ? ( @@ -252,123 +320,56 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps)

- {/* Data Sources Section */} -
- Data Sources -
- - {/* Granola */} -
-
-
- -
-
- Granola - - Sync meeting notes - -
-
-
- {granolaLoading && ( - - )} - -
-
- - - - {/* OAuth Connectors Section */} -
- Accounts -
- {providersLoading ? (
- ) : providers.length === 0 ? ( -
- No account connectors available -
) : ( -
- {providers.map((provider) => { - const state = providerStates[provider] || { - isConnected: false, - isLoading: true, - isConnecting: false, - } - const displayName = provider.charAt(0).toUpperCase() + provider.slice(1) - - return ( -
-
-
- -
-
- - {displayName} - - {state.isLoading ? ( - - Checking... - - ) : ( - - {state.isConnected ? "Connected" : "Not Connected"} - - )} -
-
-
- {state.isConnected ? ( - - ) : ( - - )} -
+ <> + {/* Email & Calendar Section - Google */} + {providers.includes('google') && ( + <> +
+ Email & Calendar
- ) - })} -
+ {renderOAuthProvider('google', 'Google', , 'Sync emails and calendar')} + + + )} + + {/* Meeting Notes Section - Granola & Fireflies */} +
+ Meeting Notes +
+ + {/* Granola */} +
+
+
+ +
+
+ Granola + + Local meeting notes + +
+
+
+ {granolaLoading && ( + + )} + +
+
+ + {/* Fireflies */} + {providers.includes('fireflies-ai') && renderOAuthProvider('fireflies-ai', 'Fireflies', , 'AI meeting transcripts')} + )}
diff --git a/apps/x/apps/renderer/src/components/ui/sonner.tsx b/apps/x/apps/renderer/src/components/ui/sonner.tsx new file mode 100644 index 00000000..490ba36c --- /dev/null +++ b/apps/x/apps/renderer/src/components/ui/sonner.tsx @@ -0,0 +1,34 @@ +import { + CircleCheckIcon, + InfoIcon, + Loader2Icon, + OctagonXIcon, + TriangleAlertIcon, +} from "lucide-react" +import { Toaster as Sonner, type ToasterProps } from "sonner" + +const Toaster = ({ ...props }: ToasterProps) => { + return ( + , + info: , + warning: , + error: , + loading: , + }} + style={ + { + "--normal-bg": "var(--popover)", + "--normal-text": "var(--popover-foreground)", + "--normal-border": "var(--border)", + "--border-radius": "var(--radius)", + } as React.CSSProperties + } + {...props} + /> + ) +} + +export { Toaster } diff --git a/apps/x/pnpm-lock.yaml b/apps/x/pnpm-lock.yaml index 490581f0..35be13a7 100644 --- a/apps/x/pnpm-lock.yaml +++ b/apps/x/pnpm-lock.yaml @@ -202,6 +202,9 @@ importers: react-dom: specifier: ^19.2.0 version: 19.2.3(react@19.2.3) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) streamdown: specifier: ^1.6.10 version: 1.6.10(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.3) @@ -5773,6 +5776,12 @@ packages: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -13176,6 +13185,11 @@ snapshots: ip-address: 10.1.0 smart-buffer: 4.2.0 + sonner@2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + source-map-js@1.2.1: {} source-map-support@0.5.21: