Skip to content

Commit 15613c2

Browse files
committed
feat: change "All Spaces" to "Nova Spaces" with multi-select support (supermemoryai#731)
### Implemented nova spaces multi-select ##### Summary - Renamed "All Spaces" to "Nova Spaces" - now filters to only nova content (sm_project_*) - Added multi-select spaces support via "Select Spaces" modal - Projects API now returns `{ nova, developer }` instead of `{ projects }` ##### Changes - `selectedProject` → `selectedProjects[]` (array-based selection) - New `SelectSpacesModal` component for picking multiple spaces - Selected spaces appear at top in modal - Search works by containerTag - Graphs filter by selected spaces
1 parent d7a1ef7 commit 15613c2

19 files changed

Lines changed: 925 additions & 406 deletions

apps/web/app/(app)/page.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function ViewErrorFallback() {
6363
export default function NewPage() {
6464
const isMobile = useIsMobile()
6565
const { user, session } = useAuth()
66-
const { selectedProject } = useProject()
66+
const { selectedProject, isNovaSpaces, novaContainerTags } = useProject()
6767
const { viewMode, setViewMode } = useViewMode()
6868
const queryClient = useQueryClient()
6969

@@ -88,7 +88,10 @@ export default function NewPage() {
8888
const [isSearchOpen, setIsSearchOpen] = useQueryState("search", searchParam)
8989
const [searchPrefill, setSearchPrefill] = useQueryState("q", qParam)
9090
const [docId, setDocId] = useQueryState("doc", docParam)
91-
const [isFullscreen, setIsFullscreen] = useQueryState("fullscreen", fullscreenParam)
91+
const [isFullscreen, setIsFullscreen] = useQueryState(
92+
"fullscreen",
93+
fullscreenParam,
94+
)
9295
const [isChatOpen, setIsChatOpen] = useQueryState("chat", chatParam)
9396

9497
// Ephemeral local state (not worth URL-encoding)
@@ -399,6 +402,7 @@ export default function NewPage() {
399402
if (!open) setSearchPrefill("")
400403
}}
401404
projectId={selectedProject}
405+
novaContainerTags={isNovaSpaces ? novaContainerTags : undefined}
402406
onOpenDocument={handleOpenDocument}
403407
onAddMemory={() => {
404408
analytics.addDocumentModalOpened()

apps/web/components/connect-ai-modal.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export function ConnectAIModal({
155155
if (response.error) {
156156
throw new Error(response.error?.message || "Failed to load projects")
157157
}
158-
return response.data?.projects || []
158+
return (response.data?.projects || []) as Project[]
159159
},
160160
staleTime: 30 * 1000,
161161
})
@@ -803,7 +803,9 @@ export function ConnectAIModal({
803803
className="bg-input border-border text-foreground"
804804
id="mcpUrl"
805805
onBlur={handleBlur}
806-
onChange={(e) => handleChange(e.target.value)}
806+
onChange={(
807+
e: React.ChangeEvent<HTMLInputElement>,
808+
) => handleChange(e.target.value)}
807809
placeholder="https://mcp.supermemory.ai/your-user-id/sse"
808810
value={state.value}
809811
/>

apps/web/components/new/add-document/index.tsx

Lines changed: 72 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ export function AddDocument({
105105
const [addParam, setAddParam] = useQueryState("add", addDocumentParam)
106106
const activeTab: TabType = addParam ?? "note"
107107
const setActiveTab = useCallback(
108-
(tab: TabType) => { setAddParam(tab) },
108+
(tab: TabType) => {
109+
setAddParam(tab)
110+
},
109111
[setAddParam],
110112
)
111113
const { selectedProject: globalSelectedProject } = useProject()
@@ -199,20 +201,23 @@ export function AddDocument({
199201
setFileData(data)
200202
}, [])
201203

202-
// Button click handler
203204
const handleButtonClick = () => {
204-
if (activeTab === "note") {
205-
handleNoteSubmit(noteContent)
206-
} else if (activeTab === "link") {
207-
handleLinkSubmit(linkData)
208-
} else if (activeTab === "file") {
209-
if (fileData.file) {
210-
handleFileSubmit(
211-
fileData as { file: File; title: string; description: string },
212-
)
213-
} else {
214-
toast.error("Please select a file")
215-
}
205+
switch (activeTab) {
206+
case "note":
207+
handleNoteSubmit(noteContent)
208+
break
209+
case "link":
210+
handleLinkSubmit(linkData)
211+
break
212+
case "file":
213+
if (fileData.file) {
214+
handleFileSubmit(
215+
fileData as { file: File; title: string; description: string },
216+
)
217+
} else {
218+
toast.error("Please select a file")
219+
}
220+
break
216221
}
217222
}
218223

@@ -250,53 +255,78 @@ export function AddDocument({
250255
</div>
251256

252257
{!isMobile && (
253-
<div
254-
data-testid="usage-counter"
255-
className="flex flex-col gap-3 mr-4"
256-
>
258+
<div data-testid="usage-counter" className="flex flex-col gap-3 mr-4">
257259
<div className="flex flex-col gap-2">
258260
<div className="flex justify-between items-center">
259-
<span className={cn("text-[#FAFAFA] text-sm font-medium", dmSansClassName())}>
261+
<span
262+
className={cn(
263+
"text-[#FAFAFA] text-sm font-medium",
264+
dmSansClassName(),
265+
)}
266+
>
260267
Credits
261268
</span>
262-
<span className={cn("text-sm font-medium", hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]", dmSansClassName())}>
263-
{isLoadingUsage ? "…" : `${tokensToCredits(tokensUsed)} / ${tokensToCredits(tokensLimit)}`}
269+
<span
270+
className={cn(
271+
"text-sm font-medium",
272+
hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]",
273+
dmSansClassName(),
274+
)}
275+
>
276+
{isLoadingUsage
277+
? "…"
278+
: `${tokensToCredits(tokensUsed)} / ${tokensToCredits(tokensLimit)}`}
264279
</span>
265280
</div>
266281
<div className="h-2 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
267282
<div
268283
className="h-full rounded-[40px]"
269284
style={{
270285
width: `${tokensPercent}%`,
271-
background: tokensPercent > 80
272-
? "#ef4444"
273-
: hasPaidPlan
274-
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
275-
: "#0054AD",
286+
background:
287+
tokensPercent > 80
288+
? "#ef4444"
289+
: hasPaidPlan
290+
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
291+
: "#0054AD",
276292
}}
277293
/>
278294
</div>
279295
</div>
280296

281297
<div className="flex flex-col gap-2">
282298
<div className="flex justify-between items-center">
283-
<span className={cn("text-[#FAFAFA] text-sm font-medium", dmSansClassName())}>
299+
<span
300+
className={cn(
301+
"text-[#FAFAFA] text-sm font-medium",
302+
dmSansClassName(),
303+
)}
304+
>
284305
Search Queries
285306
</span>
286-
<span className={cn("text-sm font-medium", hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]", dmSansClassName())}>
287-
{isLoadingUsage ? "…" : `${formatUsageNumber(searchesUsed)} / ${formatUsageNumber(searchesLimit)}`}
307+
<span
308+
className={cn(
309+
"text-sm font-medium",
310+
hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]",
311+
dmSansClassName(),
312+
)}
313+
>
314+
{isLoadingUsage
315+
? "…"
316+
: `${formatUsageNumber(searchesUsed)} / ${formatUsageNumber(searchesLimit)}`}
288317
</span>
289318
</div>
290319
<div className="h-2 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
291320
<div
292321
className="h-full rounded-[40px]"
293322
style={{
294323
width: `${searchesPercent}%`,
295-
background: searchesPercent > 80
296-
? "#ef4444"
297-
: hasPaidPlan
298-
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
299-
: "#0054AD",
324+
background:
325+
searchesPercent > 80
326+
? "#ef4444"
327+
: hasPaidPlan
328+
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
329+
: "#0054AD",
300330
}}
301331
/>
302332
</div>
@@ -327,8 +357,10 @@ export function AddDocument({
327357
dmSansClassName(),
328358
)}
329359
style={{
330-
background: "linear-gradient(182.37deg, #0ff0d2 -91.53%, #5bd3fb -67.8%, #1e0ff0 95.17%)",
331-
boxShadow: "1px 1px 2px 0px #1A88FF inset, 0 2px 10px 0 rgba(5, 1, 0, 0.20)",
360+
background:
361+
"linear-gradient(182.37deg, #0ff0d2 -91.53%, #5bd3fb -67.8%, #1e0ff0 95.17%)",
362+
boxShadow:
363+
"1px 1px 2px 0px #1A88FF inset, 0 2px 10px 0 rgba(5, 1, 0, 0.20)",
332364
}}
333365
>
334366
{isUpgrading ? (
@@ -387,8 +419,10 @@ export function AddDocument({
387419
>
388420
{!isMobile && (
389421
<SpaceSelector
390-
value={localSelectedProject}
391-
onValueChange={setLocalSelectedProject}
422+
selectedProjects={[localSelectedProject]}
423+
onValueChange={(projects) =>
424+
setLocalSelectedProject(projects[0] ?? localSelectedProject)
425+
}
392426
variant="insideOut"
393427
/>
394428
)}

0 commit comments

Comments
 (0)