diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index e4ab4e9a..a6638d11 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -16,6 +16,8 @@ import type { FSWatcher } from 'chokidar'; import fs from 'node:fs/promises'; import z from 'zod'; import { RunEvent } from 'packages/shared/dist/runs.js'; +import container from '@x/core/dist/di/container.js'; +import { IGranolaConfigRepo } from '@x/core/dist/knowledge/granola/repo.js'; type InvokeChannels = ipc.InvokeChannels; type IPCChannels = ipc.IPCChannels; @@ -300,5 +302,15 @@ export function setupIpcHandlers() { 'oauth:get-connected-providers': async () => { return await getConnectedProviders(); }, + 'granola:getConfig': async () => { + const repo = container.resolve('granolaConfigRepo'); + const config = await repo.getConfig(); + return { enabled: config.enabled }; + }, + 'granola:setConfig': async (_event, args) => { + const repo = container.resolve('granolaConfigRepo'); + await repo.setConfig({ enabled: args.enabled }); + return { success: true }; + }, }); } \ No newline at end of file diff --git a/apps/x/apps/renderer/src/components/connected-accounts-sidebar.tsx b/apps/x/apps/renderer/src/components/connected-accounts-sidebar.tsx index e3069fe4..6522e5b1 100644 --- a/apps/x/apps/renderer/src/components/connected-accounts-sidebar.tsx +++ b/apps/x/apps/renderer/src/components/connected-accounts-sidebar.tsx @@ -1,7 +1,8 @@ "use client" import * as React from "react" -import { Loader2, Plug } from "lucide-react" +import { useState, useEffect, useCallback } from "react" +import { Loader2, Plug, Database } from "lucide-react" import { Sidebar, SidebarContent, @@ -14,12 +15,59 @@ import { } from "@/components/ui/sidebar" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" +import { Switch } from "@/components/ui/switch" import { useOAuth, useAvailableProviders } from "@/hooks/useOAuth" +import { toast } from "@/lib/toast" type ConnectedAccountsSidebarProps = React.ComponentProps +/** + * Hook for managing Granola sync config + */ +function useGranolaConfig() { + const [enabled, setEnabled] = useState(false); + const [isLoading, setIsLoading] = useState(true); + + const loadConfig = useCallback(async () => { + try { + setIsLoading(true); + const result = await window.ipc.invoke('granola:getConfig', null); + setEnabled(result.enabled); + } catch (error) { + console.error('Failed to load Granola config:', error); + setEnabled(false); + } finally { + setIsLoading(false); + } + }, []); + + useEffect(() => { + loadConfig(); + }, [loadConfig]); + + const updateConfig = useCallback(async (newEnabled: boolean) => { + try { + setIsLoading(true); + await window.ipc.invoke('granola:setConfig', { enabled: newEnabled }); + setEnabled(newEnabled); + toast( + newEnabled ? 'Granola sync enabled' : 'Granola sync disabled', + 'success' + ); + } catch (error) { + console.error('Failed to update Granola config:', error); + toast('Failed to update Granola sync settings', 'error'); + } finally { + setIsLoading(false); + } + }, []); + + return { enabled, isLoading, updateConfig }; +} + export function ConnectedAccountsSidebar({ ...props }: ConnectedAccountsSidebarProps) { const { providers, isLoading: providersLoading } = useAvailableProviders() + const { enabled: granolaEnabled, isLoading: granolaLoading, updateConfig: updateGranolaConfig } = useGranolaConfig() return ( @@ -49,6 +97,33 @@ export function ConnectedAccountsSidebar({ ...props }: ConnectedAccountsSidebarP + + Data Sources + + + +
+ + +
+ Granola Sync + + Sync notes from Granola + +
+ {granolaLoading && ( + + )} +
+
+
+
+
) diff --git a/apps/x/packages/shared/src/ipc.ts b/apps/x/packages/shared/src/ipc.ts index 5554d3b2..6dae75d3 100644 --- a/apps/x/packages/shared/src/ipc.ts +++ b/apps/x/packages/shared/src/ipc.ts @@ -197,6 +197,20 @@ const ipcSchemas = { providers: z.array(z.string()), }), }, + 'granola:getConfig': { + req: z.null(), + res: z.object({ + enabled: z.boolean(), + }), + }, + 'granola:setConfig': { + req: z.object({ + enabled: z.boolean(), + }), + res: z.object({ + success: z.literal(true), + }), + }, } as const; // ============================================================================