From dd56682f2739e68f5e223646a02b0e021f2af6c0 Mon Sep 17 00:00:00 2001 From: Sreeram Sreedhar Date: Mon, 22 Jun 2026 16:17:45 -0700 Subject: [PATCH] Add desktop recent memories view --- apps/desktop/app/(app)/page.tsx | 208 +++++++++++++++++++++++++------- apps/desktop/lib/api.ts | 52 ++++++++ 2 files changed, 219 insertions(+), 41 deletions(-) create mode 100644 apps/desktop/lib/api.ts diff --git a/apps/desktop/app/(app)/page.tsx b/apps/desktop/app/(app)/page.tsx index ef0ff82d..b692b583 100644 --- a/apps/desktop/app/(app)/page.tsx +++ b/apps/desktop/app/(app)/page.tsx @@ -1,68 +1,194 @@ "use client" +import { useQuery } from "@tanstack/react-query" import { Button } from "@ui/components/button" import { Card, + CardContent, CardDescription, CardHeader, CardTitle, } from "@ui/components/card" -import { Search } from "lucide-react" +import { Clock, FileText, RefreshCcw, Search } from "lucide-react" +import { useMemo, useState } from "react" +import { listDocuments, type DocumentWithMemories } from "@/lib/api" import { SearchCommand, useCommandK } from "@/components/search-command" -const MOCK_MEMORIES = [ - { id: "1", title: "Q3 planning notes", desc: "Roadmap, OKRs, hiring plan" }, - { - id: "2", - title: "Tauri vs Electron", - desc: "Why we picked Tauri for the desktop app", - }, - { - id: "3", - title: "Onboarding flow", - desc: "Detect installed AI tools, one-click connect", - }, - { - id: "4", - title: "SMFS notes", - desc: "One container, two interfaces (API + folder)", - }, -] - export default function DashboardPage() { const { open, setOpen } = useCommandK() + const [selectedDocument, setSelectedDocument] = + useState(null) + const documentsQuery = useQuery({ + queryKey: ["desktop-documents"], + queryFn: listDocuments, + staleTime: 60 * 1000, + }) + const documents = documentsQuery.data?.documents ?? [] + const activeDocument = selectedDocument ?? documents.at(0) ?? null + const totalCount = documentsQuery.data?.pagination.totalItems + const subtitle = useMemo(() => { + if (documentsQuery.isPending) return "Loading your recent memories." + if (typeof totalCount === "number") return `${totalCount} memories indexed.` + return "Your recent memories." + }, [documentsQuery.isPending, totalCount]) return ( -
+

Your memories

-

- Everything you have saved, in one place. -

+

{subtitle}

+
+
+ +
-
-
- {MOCK_MEMORIES.map((memory) => ( - - - {memory.title} - {memory.desc} - - - ))} +
+ + + Recent + + Latest documents from Supermemory. + + + + {documentsQuery.isPending ? ( +
+ Loading memories... +
+ ) : null} + {documentsQuery.isError ? ( +
+ {documentsQuery.error.message} +
+ ) : null} + {!documentsQuery.isPending && documents.length === 0 ? ( +
+ No memories found. +
+ ) : null} +
+ {documents.map((document) => ( + + ))} +
+
+
+ +
) } + +function DocumentPreview({ + document, +}: { + document: DocumentWithMemories | null +}) { + if (!document) { + return ( + + + Select a memory to preview it. + + + ) + } + + return ( + + +
+
+ + {document.title ?? "Untitled memory"} + + + + {formatDate(document.createdAt)} + +
+ {document.type ? ( + + {document.type} + + ) : null} +
+
+ + {document.url ? ( + + {document.url} + + ) : null} +
+ {document.summary ? ( +
+

Summary

+

{document.summary}

+
+ ) : null} +
+

Content

+

+ {document.content ?? document.raw ?? "No content available."} +

+
+
+
+
+ ) +} + +function formatDate(value: string | Date) { + return new Intl.DateTimeFormat(undefined, { + dateStyle: "medium", + timeStyle: "short", + }).format(new Date(value)) +} diff --git a/apps/desktop/lib/api.ts b/apps/desktop/lib/api.ts new file mode 100644 index 00000000..ca566c40 --- /dev/null +++ b/apps/desktop/lib/api.ts @@ -0,0 +1,52 @@ +"use client" + +import { getAuthToken } from "@lib/token-provider" +import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" +import type { z } from "zod" + +const DEFAULT_API_URL = "https://api.supermemory.ai" + +export type DocumentsResponse = z.infer< + typeof DocumentsWithMemoriesResponseSchema +> +export type DocumentWithMemories = DocumentsResponse["documents"][number] + +function apiUrl() { + return process.env.NEXT_PUBLIC_BACKEND_URL ?? DEFAULT_API_URL +} + +async function desktopFetch(path: string, init: RequestInit = {}) { + const token = await getAuthToken() + if (!token) { + throw new Error("No stored desktop token") + } + + const response = await fetch(`${apiUrl().replace(/\/$/, "")}${path}`, { + ...init, + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + "X-App-Source": "desktop", + ...init.headers, + }, + }) + + if (!response.ok) { + const body = await response.text() + throw new Error(`Request failed (${response.status}): ${body}`) + } + + return (await response.json()) as T +} + +export function listDocuments() { + return desktopFetch("/v3/documents/documents", { + method: "POST", + body: JSON.stringify({ + page: 1, + limit: 24, + sort: "createdAt", + order: "desc", + }), + }) +}