Skip to content
Open
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
22 changes: 4 additions & 18 deletions apps/web/app/(app)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -515,12 +515,11 @@ export default function NewPage() {
const gradientTopPosition = gradientTopPositionForWidth(viewportWidth)

const isChatView = viewMode === "chat"
const isGraphMode = viewMode === "graph" && !isMobile
const isGraphMode = viewMode === "graph"
const isMemoriesDesktop = viewMode === "list" && !isMobile
const isHomeDesktop = viewMode === "dashboard" && !isMobile
const showNovaBackdrop = isGraphMode || isMemoriesDesktop || isHomeDesktop
const isDashboardShell =
viewMode === "dashboard" || (viewMode === "graph" && isMobile)
const isDashboardShell = viewMode === "dashboard"

return (
<HotkeysProvider>
Expand Down Expand Up @@ -629,7 +628,7 @@ export default function NewPage() {
<XBookmarksDetailView
onBack={() => void setViewMode("integrations")}
/>
) : viewMode === "graph" && !isMobile ? (
) : viewMode === "graph" ? (
<div className="min-h-0 min-w-0 flex-1">
<GraphLayoutView />
</div>
Expand Down Expand Up @@ -674,20 +673,7 @@ export default function NewPage() {
) : (
<DashboardView
spaceLabel={dashboardSpaceLabel}
headerNotice={
viewMode === "graph" && isMobile ? (
<div
id="graph-mobile-notice"
className="rounded-lg border border-[#2261CA33] bg-[#041127] px-3 py-2.5 text-sm text-[#8B8B8B]"
>
<span className="font-medium text-white">
Graph view is available on desktop.
</span>{" "}
Use a larger screen for the full graph, or keep
working from this home view.
</div>
) : undefined
}
headerNotice={undefined}
highlights={highlightsData?.highlights ?? []}
isLoadingHighlights={isLoadingHighlights}
onAddMemory={handleAddMemory}
Expand Down
157 changes: 134 additions & 23 deletions apps/web/components/add-document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useQueryState } from "nuqs"
import { Dialog, DialogContent, DialogTitle } from "@repo/ui/components/dialog"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import { FileTextIcon, GlobeIcon, ZapIcon, Loader2 } from "lucide-react"
import { FileTextIcon, GlobeIcon, ZapIcon, Loader2, XIcon } from "lucide-react"
import { Button } from "@ui/components/button"
import { ConnectContent } from "./connections"
import { NoteContent } from "./note"
Expand Down Expand Up @@ -35,10 +35,10 @@ export function AddDocumentModal({ isOpen, onClose }: AddDocumentModalProps) {
<Dialog open={isOpen} onOpenChange={(open: boolean) => !open && onClose()}>
<DialogContent
className={cn(
"border-none bg-[#1B1F24] flex flex-col p-3 md:p-4 gap-3",
"border-none bg-[#1B1F24] flex flex-col",
isMobile
? "w-[calc(100vw-1rem)]! h-[calc(100dvh-1rem)]! max-w-none! max-h-none! rounded-xl"
: "w-[80%]! max-w-[1000px]! h-[80%]! max-h-[800px]! rounded-[22px]",
? "top-2! left-2! translate-x-0! translate-y-0! w-[calc(100vw-1rem)]! h-[calc(100dvh-1rem)]! max-w-none! max-h-none! rounded-[18px] p-0 gap-0 overflow-hidden"
: "w-[80%]! max-w-[1000px]! h-[80%]! max-h-[800px]! rounded-[22px] p-4 gap-3",
dmSansClassName(),
)}
style={{
Expand All @@ -48,7 +48,7 @@ export function AddDocumentModal({ isOpen, onClose }: AddDocumentModalProps) {
showCloseButton={false}
>
<DialogTitle className="sr-only">Add Document</DialogTitle>
<div className="flex-1 overflow-hidden">
<div className="min-h-0 flex-1 overflow-hidden">
<AddDocument onClose={onClose} isOpen={isOpen} />
</div>
</DialogContent>
Expand Down Expand Up @@ -259,19 +259,44 @@ export function AddDocument({
activeTab === "file" && (!fileTabHasPending || isSubmitting)

return (
<div className="h-full flex flex-col md:flex-row text-white md:space-x-5 space-y-3 md:space-y-0">
<div className="flex h-full min-h-0 flex-col overflow-hidden text-white md:flex-row md:space-x-5">
<div
className={cn(
"flex flex-col justify-between",
isMobile ? "w-full" : "w-1/3",
isMobile
? "w-full shrink-0 border-b border-[#0F1621] bg-[#1B1F24] px-3 pt-3 pb-3"
: "w-1/3",
)}
>
{isMobile && (
<div className="mb-3 flex items-center justify-between">
<div>
<p
className={cn(
"text-sm font-medium text-white",
dmSansClassName(),
)}
>
Add memory
</p>
<p className="text-xs text-[#737373]">
Save something to recall later
</p>
</div>
<button
type="button"
onClick={onClose}
disabled={isSubmitting}
className="flex size-9 items-center justify-center rounded-full border border-[#1F2937] bg-[#0D121A] text-[#8B8B8B] transition-colors hover:text-white disabled:opacity-50"
aria-label="Close add memory"
>
<XIcon className="size-4" />
</button>
</div>
)}
<div
className={cn(
"flex gap-1",
isMobile
? "flex-row overflow-x-auto pb-2 scrollbar-thin"
: "flex-col",
isMobile ? "grid grid-cols-4 gap-1" : "flex flex-col gap-1",
)}
>
{tabs.map((tab) => (
Expand All @@ -288,6 +313,31 @@ export function AddDocument({
))}
</div>

{isMobile && (
<div className="mt-3 grid grid-cols-2 gap-2">
<UsageMeter
label="Credits"
value={
isLoadingUsage
? "..."
: `${tokensToCredits(tokensUsed)} / ${tokensToCredits(tokensLimit)}`
}
percent={tokensPercent}
active={hasPaidPlan}
/>
<UsageMeter
label="Searches"
value={
isLoadingUsage
? "..."
: `${formatUsageNumber(searchesUsed)} / ${formatUsageNumber(searchesLimit)}`
}
percent={searchesPercent}
active={hasPaidPlan}
/>
</div>
)}

{!isMobile && (
<div data-testid="usage-counter" className="flex flex-col gap-3 mr-4">
<div className="flex flex-col gap-2">
Expand Down Expand Up @@ -414,11 +464,11 @@ export function AddDocument({

<div
className={cn(
"flex flex-col flex-1 min-h-0 px-1",
isMobile ? "w-full" : "w-2/3",
"flex min-h-0 flex-1 flex-col",
isMobile ? "w-full px-3 pt-3" : "w-2/3 px-1",
)}
>
<div className="overflow-auto flex-1 min-h-0 scrollbar-thin">
<div className="min-h-0 flex-1 overflow-auto scrollbar-thin">
{activeTab === "note" && (
<NoteContent
onSubmit={handleNoteSubmit}
Expand Down Expand Up @@ -452,8 +502,10 @@ export function AddDocument({
</div>
<div
className={cn(
"flex gap-2 pt-3 shrink-0",
isMobile ? "flex-col" : "justify-between",
"flex shrink-0 gap-2",
isMobile
? "mx-[-0.75rem] mt-3 border-t border-[#0F1621] bg-[#1B1F24] px-3 py-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]"
: "justify-between pt-3",
)}
>
{!isMobile && (
Expand All @@ -467,13 +519,19 @@ export function AddDocument({
/>
)}
<div
className={cn("flex items-center gap-2", isMobile && "justify-end")}
className={cn(
"flex items-center gap-2",
isMobile && "w-full justify-end",
)}
>
<Button
variant="ghost"
onClick={onClose}
disabled={isSubmitting}
className="text-[#737373] cursor-pointer rounded-full"
className={cn(
"cursor-pointer rounded-full text-[#737373]",
isMobile && "h-11 px-4",
)}
>
Cancel
</Button>
Expand All @@ -484,6 +542,7 @@ export function AddDocument({
disabled={
activeTab === "file" ? fileTabSubmitDisabled : isSubmitting
}
className={cn(isMobile && "h-11 min-w-[8rem] px-5")}
>
{isSubmitting ? (
<>
Expand Down Expand Up @@ -514,6 +573,53 @@ export function AddDocument({
)
}

function UsageMeter({
label,
value,
percent,
active,
}: {
label: string
value: string
percent: number
active: boolean
}) {
const safePercent = Math.max(0, Math.min(100, percent))

return (
<div className="rounded-xl border border-[#0F1621] bg-[#10151C] px-3 py-2 shadow-inside-out">
<div className="mb-1.5 flex items-center justify-between gap-2">
<span className="truncate text-[11px] font-medium text-[#FAFAFA]">
{label}
</span>
<span
className={cn(
"shrink-0 text-[10px] font-medium",
active ? "text-[#4BA0FA]" : "text-[#8B8B8B]",
dmSansClassName(),
)}
>
{value}
</span>
</div>
<div className="h-1.5 w-full overflow-hidden rounded-full bg-[#2E353D] p-px">
<div
className="h-full rounded-full"
style={{
width: `${safePercent}%`,
background:
safePercent > 80
? "#ef4444"
: active
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
: "#0054AD",
}}
/>
</div>
</div>
)
}

function TabButton({
active,
onClick,
Expand All @@ -537,19 +643,24 @@ function TabButton({
type="button"
onClick={onClick}
className={cn(
"flex items-center gap-2 px-3 py-2 rounded-full text-left transition-colors whitespace-nowrap focus:outline-none focus:ring-0 shrink-0",
active ? "bg-[#14161A] shadow-inside-out" : "hover:bg-[#14161A]/50",
"relative flex h-14 min-w-0 flex-col items-center justify-center gap-1 rounded-xl px-1 text-center transition-colors focus:outline-none focus:ring-0",
active
? "bg-[#0F141B] text-white shadow-inside-out ring-1 ring-[#2261CA33]"
: "text-[#8B8B8B] hover:bg-[#14161A]/50",
dmSansClassName(),
)}
>
<Icon className={cn("size-4 shrink-0 text-white")} />
<Icon className="size-3.5 shrink-0" />
<span
className={cn("font-medium text-white text-sm", dmSansClassName())}
className={cn(
"min-w-0 truncate text-xs font-medium leading-none",
dmSansClassName(),
)}
>
{title.split(" ")[0]}
</span>
{isPro && (
<span className="bg-[#4BA0FA] text-black text-[8px] font-semibold px-1 py-0.5 rounded">
<span className="absolute top-1 right-1 rounded bg-[#4BA0FA] px-1 py-0.5 text-[7px] font-semibold leading-none text-black">
PRO
</span>
)}
Expand Down
3 changes: 2 additions & 1 deletion apps/web/components/add-document/note.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ export function NoteContent({
}, [isOpen, onContentChange])

return (
<div className="p-4 overflow-y-auto flex-1 w-full h-full mb-4! bg-[#14161A] shadow-inside-out rounded-[14px]">
<div className="flex h-full min-h-[45dvh] w-full flex-1 overflow-y-auto rounded-[14px] bg-[#10151C] p-3 shadow-inside-out ring-1 ring-[#202A36] md:mb-4! md:bg-[#14161A] md:p-4 md:ring-0">
<TextEditor
content={undefined}
onContentChange={handleContentChange}
onSubmit={handleSubmit}
debounceMs={0}
/>
</div>
)
Expand Down
13 changes: 9 additions & 4 deletions apps/web/components/graph-layout-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { memo, useCallback, useRef } from "react"
import { useQueryState } from "nuqs"
import Image from "next/image"
import { Share2 } from "lucide-react"
import { MemoryGraph } from "./memory-graph"
import { useProject } from "@/stores"
import { useGraphHighlights } from "@/stores/highlights"
Expand Down Expand Up @@ -30,7 +31,7 @@ export const GraphLayoutView = memo(function GraphLayoutView() {
}, [setIsShareModalOpen])

return (
<div className="relative h-full min-h-0 w-full">
<div className="relative h-full min-h-[calc(100dvh-8.5rem)] w-full md:min-h-0">
{/* Full-width graph */}
<div className="absolute inset-0">
<MemoryGraph
Expand All @@ -44,22 +45,26 @@ export const GraphLayoutView = memo(function GraphLayoutView() {
</div>

{/* Share graph button - top left */}
<div className="absolute top-4 left-4 z-15">
<div className="absolute left-3 top-3 z-15 md:left-4 md:top-4">
<Button
variant="headers"
className={cn(
"rounded-full text-base gap-2 h-10!",
"size-10 rounded-full p-0 md:size-auto md:h-10! md:px-4",
"md:gap-2 md:text-base",
dmSansClassName(),
)}
onClick={handleShare}
aria-label="Share graph"
>
<Image
src="/icons/share-graph.svg"
alt="Share"
width={16}
height={16}
className="hidden md:block"
/>
Share graph
<Share2 className="size-4 md:hidden" />
<span className="hidden md:inline">Share graph</span>
</Button>
</div>

Expand Down
Loading
Loading