From 26afe03765d9dc485f2b7ead47c11aece284c2ed Mon Sep 17 00:00:00 2001 From: Sreeram Sreedhar Date: Tue, 23 Jun 2026 14:33:03 -0700 Subject: [PATCH] Add desktop tool onboarding panel --- apps/desktop/app/(app)/page.tsx | 215 +++++++++++++++++++++++++++++++- 1 file changed, 214 insertions(+), 1 deletion(-) diff --git a/apps/desktop/app/(app)/page.tsx b/apps/desktop/app/(app)/page.tsx index 000c2889..bc893b6c 100644 --- a/apps/desktop/app/(app)/page.tsx +++ b/apps/desktop/app/(app)/page.tsx @@ -3,7 +3,10 @@ import { useQuery } from "@tanstack/react-query" import { ArrowRight, + Bot, + CheckCircle2, Clock, + Code2, FileText, Link2, Lightbulb, @@ -13,11 +16,20 @@ import { Search, SendHorizontal, Sparkles, + Terminal, } from "lucide-react" -import { useEffect, useMemo, useState } from "react" +import { useCallback, useEffect, useMemo, useState } from "react" import { listDocuments, type DocumentWithMemories } from "@/lib/api" import type { SearchResult } from "@/lib/search" import { OPEN_MEMORY_EVENT, type SpotlightMemory } from "@/lib/spotlight" +import { + connectDesktopTool, + detectDesktopTools, + type DesktopToolId, + type DesktopToolPreview, + type DesktopToolStatus, + previewConnectDesktopTool, +} from "@/lib/tools" import { SearchCommand, useCommandK } from "@/components/search-command" type MemoryPreview = { @@ -149,6 +161,8 @@ export default function DashboardPage() { + +

@@ -207,6 +221,180 @@ export default function DashboardPage() { ) } +function ToolOnboardingPanel() { + const [tools, setTools] = useState([]) + const [toolsError, setToolsError] = useState(null) + const [toolsBusy, setToolsBusy] = useState(null) + const [toolPreview, setToolPreview] = useState( + null, + ) + const [loading, setLoading] = useState(true) + + const connectedCount = tools.filter((tool) => tool.connected).length + const detectedCount = tools.filter((tool) => tool.detected).length + + const refreshTools = useCallback(async () => { + setToolsError(null) + setLoading(true) + try { + setTools(await detectDesktopTools()) + } catch (err) { + setToolsError(formatUnknownError(err, "Could not detect local tools")) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { + refreshTools() + }, [refreshTools]) + + async function previewConnect(toolId: DesktopToolId) { + setToolsError(null) + setToolsBusy(toolId) + try { + setToolPreview(await previewConnectDesktopTool(toolId)) + } catch (err) { + setToolsError(formatUnknownError(err, "Could not prepare setup")) + } finally { + setToolsBusy(null) + } + } + + async function applyPreview() { + if (!toolPreview) return + + setToolsError(null) + setToolsBusy(toolPreview.tool.id) + try { + await connectDesktopTool(toolPreview.tool.id) + setToolPreview(null) + await refreshTools() + } catch (err) { + setToolsError(formatUnknownError(err, "Could not connect tool")) + } finally { + setToolsBusy(null) + } + } + + return ( +

+
+
+

+ + Connect AI tools +

+

+ Detected {detectedCount} local tools. {connectedCount} connected. +

+
+ +
+ +
+ {tools.map((tool) => { + const Icon = toolIcon(tool.id) + return ( +
+
+
+
+ + + +
+

+ {tool.name} +

+

+ {toolStatusLabel(tool)} +

+
+
+ {tool.connected ? ( + + ) : null} +
+

+ {tool.detail} +

+
+ +
+ ) + })} +
+ + {toolsError ? ( +

{toolsError}

+ ) : null} + + {toolPreview ? ( +
+
+
+

+ Connect {toolPreview.tool.name} +

+

+ {toolPreview.configPath} +

+
+
+ + +
+
+
+						{toolPreview.diff}
+					
+
+ ) : null} +
+ ) +} + function ComposerAction({ icon: Icon, label, @@ -228,6 +416,31 @@ function ComposerAction({ ) } +function toolIcon(toolId: DesktopToolId) { + switch (toolId) { + case "claude-code": + return Bot + case "codex": + return Terminal + case "cursor": + return Code2 + } +} + +function toolStatusLabel(tool: DesktopToolStatus) { + if (tool.connected) return "Connected" + if (tool.detected) return "Detected" + return "Not detected" +} + +function formatUnknownError(error: unknown, fallback: string) { + return error instanceof Error + ? error.message + : typeof error === "string" + ? error + : fallback +} + function RecentMemoryRow({ document, active,