Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 26 additions & 17 deletions apps/web/app/(app)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { Header } from "@/components/new/header"
import { ChatSidebar } from "@/components/new/chat"
import { MemoriesGrid } from "@/components/new/memories-grid"
import { GraphLayoutView } from "@/components/new/graph-layout-view"
import { IntegrationsView } from "@/components/new/integrations-view"
import { AnimatedGradientBackground } from "@/components/new/animated-gradient-background"
import { AddDocumentModal } from "@/components/new/add-document"
import { MCPModal } from "@/components/new/mcp-modal"
import { DocumentModal } from "@/components/new/document-modal"
import { DocumentsCommandPalette } from "@/components/new/documents-command-palette"
import { FullscreenNoteModal } from "@/components/new/fullscreen-note-modal"
Expand All @@ -17,6 +17,7 @@ import { HotkeysProvider } from "react-hotkeys-hook"
import { useHotkeys } from "react-hotkeys-hook"
import { AnimatePresence } from "motion/react"
import { useIsMobile } from "@hooks/use-mobile"
import { useAuth } from "@lib/auth-context"
import { useProject } from "@/stores"
import {
useQuickNoteDraftReset,
Expand All @@ -31,7 +32,6 @@ import { useViewMode } from "@/lib/view-mode-context"
import { cn } from "@lib/utils"
import {
addDocumentParam,
mcpParam,
searchParam,
qParam,
docParam,
Expand All @@ -44,13 +44,29 @@ type DocumentWithMemories = DocumentsResponse["documents"][0]

export default function NewPage() {
const isMobile = useIsMobile()
const { user, session } = useAuth()
const { selectedProject } = useProject()
const { viewMode } = useViewMode()
const { viewMode, setViewMode } = useViewMode()
const queryClient = useQueryClient()

// Chrome extension auth: send session token via postMessage so the content script can store it
useEffect(() => {
const url = new URL(window.location.href)
if (!url.searchParams.get("extension-auth-success")) return
const sessionToken = session?.token
const userData = { email: user?.email, name: user?.name, userId: user?.id }
if (sessionToken && userData.email) {
window.postMessage(
{ token: encodeURIComponent(sessionToken), userData },
window.location.origin,
)
url.searchParams.delete("extension-auth-success")
window.history.replaceState({}, "", url.toString())
}
}, [user, session])

// URL-driven modal states
const [addDoc, setAddDoc] = useQueryState("add", addDocumentParam)
const [isMCPOpen, setIsMCPOpen] = useQueryState("mcp", mcpParam)
const [isSearchOpen, setIsSearchOpen] = useQueryState("search", searchParam)
const [searchPrefill, setSearchPrefill] = useQueryState("q", qParam)
const [docId, setDocId] = useQueryState("doc", docParam)
Expand Down Expand Up @@ -284,10 +300,6 @@ export default function NewPage() {
analytics.addDocumentModalOpened()
setAddDoc("note")
}}
onOpenMCP={() => {
analytics.mcpModalOpened()
setIsMCPOpen(true)
}}
onOpenChat={() => setIsChatOpen(true)}
onOpenSearch={() => {
analytics.searchOpened({ source: "header" })
Expand All @@ -302,7 +314,11 @@ export default function NewPage() {
)}
>
<div className={cn("relative z-10 flex flex-col md:flex-row h-full")}>
{viewMode === "graph" && !isMobile ? (
{viewMode === "integrations" ? (
<div className="flex-1 p-4 md:p-6 md:pr-0 pt-2!">
<IntegrationsView />
</div>
) : viewMode === "graph" && !isMobile ? (
<div className="flex-1">
<GraphLayoutView isChatOpen={chatOpen} />
</div>
Expand Down Expand Up @@ -354,10 +370,6 @@ export default function NewPage() {
onClose={() => setAddDoc(null)}
defaultTab={addDoc ?? undefined}
/>
<MCPModal
isOpen={isMCPOpen}
onClose={() => setIsMCPOpen(false)}
/>
<DocumentsCommandPalette
open={isSearchOpen}
onOpenChange={(open) => {
Expand All @@ -370,10 +382,7 @@ export default function NewPage() {
analytics.addDocumentModalOpened()
setAddDoc("note")
}}
onOpenMCP={() => {
analytics.mcpModalOpened()
setIsMCPOpen(true)
}}
onOpenIntegrations={() => setViewMode("integrations")}
initialSearch={searchPrefill}
/>
<DocumentModal
Expand Down
130 changes: 97 additions & 33 deletions apps/web/components/new/add-document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { useProject } from "@/stores"
import { toast } from "sonner"
import { useDocumentMutations } from "../../../hooks/use-document-mutations"
import { useCustomer } from "autumn-js/react"
import { useMemoriesUsage } from "@/hooks/use-memories-usage"
import { useTokenUsage } from "@/hooks/use-token-usage"
import { tokensToCredits, formatUsageNumber } from "@/lib/billing-utils"
import { SpaceSelector } from "../space-selector"
import { useIsMobile } from "@hooks/use-mobile"
import { addDocumentParam } from "@/lib/search-params"
Expand Down Expand Up @@ -131,12 +132,16 @@ export function AddDocument({

const autumn = useCustomer()
const {
memoriesUsed,
memoriesLimit,
hasProProduct,
isLoading: isLoadingMemories,
usagePercent,
} = useMemoriesUsage(autumn)
tokensUsed,
tokensLimit,
tokensPercent,
searchesUsed,
searchesLimit,
searchesPercent,
hasPaidPlan,
isLoading: isLoadingUsage,
} = useTokenUsage(autumn)
const [isUpgrading, setIsUpgrading] = useState(false)

useEffect(() => {
setLocalSelectedProject(globalSelectedProject)
Expand Down Expand Up @@ -246,37 +251,96 @@ export function AddDocument({

{!isMobile && (
<div
data-testid="memories-counter"
className="bg-[#1B1F24] rounded-2xl p-4 mr-4"
style={{
boxShadow:
"0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset",
}}
data-testid="usage-counter"
className="flex flex-col gap-3 mr-4"
>
<div className="flex justify-between items-center">
<span
className={cn(
"text-white text-[16px] font-medium",
dmSansClassName(),
)}
>
Memories
</span>
<span className={cn("text-[#737373] text-sm", dmSansClassName())}>
{isLoadingMemories
? "…"
: hasProProduct
? "Unlimited"
: `${memoriesUsed}/${memoriesLimit}`}
</span>
<div className="flex flex-col gap-2">
<div className="flex justify-between items-center">
<span className={cn("text-[#FAFAFA] text-sm font-medium", dmSansClassName())}>
Credits
</span>
<span className={cn("text-sm font-medium", hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]", dmSansClassName())}>
{isLoadingUsage ? "…" : `${tokensToCredits(tokensUsed)} / ${tokensToCredits(tokensLimit)}`}
</span>
</div>
<div className="h-2 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
<div
className="h-full rounded-[40px]"
style={{
width: `${tokensPercent}%`,
background: tokensPercent > 80
? "#ef4444"
: hasPaidPlan
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
: "#0054AD",
}}
/>
</div>
</div>
{!hasProProduct && (
<div className="h-1.5 bg-[#0D121A] rounded-full overflow-hidden mt-2">

<div className="flex flex-col gap-2">
<div className="flex justify-between items-center">
<span className={cn("text-[#FAFAFA] text-sm font-medium", dmSansClassName())}>
Search Queries
</span>
<span className={cn("text-sm font-medium", hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]", dmSansClassName())}>
{isLoadingUsage ? "…" : `${formatUsageNumber(searchesUsed)} / ${formatUsageNumber(searchesLimit)}`}
</span>
</div>
<div className="h-2 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
<div
className="h-full bg-[#2261CA] rounded-full"
style={{ width: `${usagePercent}%` }}
className="h-full rounded-[40px]"
style={{
width: `${searchesPercent}%`,
background: searchesPercent > 80
? "#ef4444"
: hasPaidPlan
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
: "#0054AD",
}}
/>
</div>
</div>

{!hasPaidPlan && (
<button
type="button"
onClick={async () => {
setIsUpgrading(true)
try {
await autumn.attach({
productId: "api_pro",
successUrl: "https://app.supermemory.ai/settings#account",
})
window.location.reload()
} catch (error) {
console.error(error)
setIsUpgrading(false)
}
}}
disabled={isUpgrading}
className={cn(
"relative w-full h-9 rounded-[10px] flex items-center justify-center",
"text-[#FAFAFA] font-medium text-[13px]",
"disabled:opacity-60 disabled:cursor-not-allowed",
"cursor-pointer transition-opacity hover:opacity-90",
dmSansClassName(),
)}
style={{
background: "linear-gradient(182.37deg, #0ff0d2 -91.53%, #5bd3fb -67.8%, #1e0ff0 95.17%)",
boxShadow: "1px 1px 2px 0px #1A88FF inset, 0 2px 10px 0 rgba(5, 1, 0, 0.20)",
}}
>
{isUpgrading ? (
<>
<Loader2 className="size-3 animate-spin mr-1.5" />
Upgrading...
</>
) : (
"Upgrade to Pro"
)}
<div className="absolute inset-0 pointer-events-none rounded-[inherit] shadow-[inset_1px_1px_2px_1px_#1A88FF]" />
</button>
)}
</div>
)}
Expand Down
12 changes: 6 additions & 6 deletions apps/web/components/new/documents-command-palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ interface DocumentsCommandPaletteProps {
projectId: string
onOpenDocument: (document: DocumentWithMemories) => void
onAddMemory?: () => void
onOpenMCP?: () => void
onOpenIntegrations?: () => void
initialSearch?: string
}

Expand All @@ -48,7 +48,7 @@ export function DocumentsCommandPalette({
projectId,
onOpenDocument,
onAddMemory,
onOpenMCP,
onOpenIntegrations,
initialSearch = "",
}: DocumentsCommandPaletteProps) {
const isMobile = useIsMobile()
Expand Down Expand Up @@ -95,13 +95,13 @@ export function DocumentsCommandPalette({
action: () => { close(); onAddMemory() },
}]
: []),
...(onOpenMCP
...(onOpenIntegrations
? [{
kind: "action" as const,
id: "mcp",
label: "Open MCP",
id: "integrations",
label: "Open Integrations",
icon: <Code2 className="size-4 text-[#737373]" />,
action: () => { close(); onOpenMCP() },
action: () => { close(); onOpenIntegrations() },
}]
: []),
]
Expand Down
30 changes: 16 additions & 14 deletions apps/web/components/new/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Settings,
Home,
Code2,
Cable,
ExternalLink,
HelpCircle,
MenuIcon,
Expand Down Expand Up @@ -43,14 +44,12 @@ import { feedbackParam } from "@/lib/search-params"

interface HeaderProps {
onAddMemory?: () => void
onOpenMCP?: () => void
onOpenChat?: () => void
onOpenSearch?: () => void
}

export function Header({
onAddMemory,
onOpenMCP,
onOpenChat,
onOpenSearch,
}: HeaderProps) {
Expand Down Expand Up @@ -155,8 +154,8 @@ export function Header({
</div>
{!isMobile && (
<Tabs
value={viewMode === "list" ? "grid" : "graph"}
onValueChange={(v) => setViewMode(v === "grid" ? "list" : "graph")}
value={viewMode === "list" ? "grid" : viewMode}
onValueChange={(v) => setViewMode(v === "grid" ? "list" : v as "graph" | "integrations")}
>
<TabsList className="rounded-full border border-[#161F2C] h-11! z-10!">
<TabsTrigger
Expand All @@ -179,6 +178,16 @@ export function Header({
<LayoutGridIcon className="size-4" />
Graph
</TabsTrigger>
<TabsTrigger
value="integrations"
className={cn(
"rounded-full dark:data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4",
dmSansClassName(),
)}
>
<Cable className="size-4" />
Integrations
</TabsTrigger>
</TabsList>
</Tabs>
)}
Expand Down Expand Up @@ -220,11 +229,11 @@ export function Header({
Add memory
</DropdownMenuItem>
<DropdownMenuItem
onClick={onOpenMCP}
onClick={() => setViewMode("integrations")}
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
>
<Code2 className="h-4 w-4 text-[#737373]" />
MCP
<Cable className="h-4 w-4 text-[#737373]" />
Integrations
</DropdownMenuItem>
<DropdownMenuItem
onClick={onOpenChat}
Expand Down Expand Up @@ -271,13 +280,6 @@ export function Header({
C
</span>
</Button>
<Button
variant="headers"
className="rounded-full text-base gap-2 h-10!"
onClick={onOpenMCP}
>
<div className="flex items-center gap-2">MCP</div>
</Button>
<Button
variant="headers"
className="rounded-full text-base gap-2 h-10!"
Expand Down
Loading
Loading