mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-06 08:16:03 +00:00
finalise org context and profile.md at space level
This commit is contained in:
parent
9bd79cdb80
commit
2f61a708a1
4 changed files with 150 additions and 96 deletions
|
|
@ -55,10 +55,17 @@ import {
|
|||
CheckIcon,
|
||||
LayoutGrid,
|
||||
Loader,
|
||||
MoreHorizontal,
|
||||
Trash2Icon,
|
||||
UserRound,
|
||||
XIcon,
|
||||
} from "lucide-react"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@ui/components/dropdown-menu"
|
||||
import { useProcessingDocuments } from "@/hooks/use-processing-documents"
|
||||
import { TimelineView } from "./timeline-view"
|
||||
import { SpaceProfilePanel } from "@/components/space-profile-panel"
|
||||
|
|
@ -633,32 +640,53 @@ export function MemoriesGrid({
|
|||
<AlignLeft className="size-3.5" />
|
||||
Timeline
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={profileOpen}
|
||||
className={cn(
|
||||
"inline-flex h-full items-center justify-center gap-1.5 rounded-full border px-2.5 text-xs font-medium cursor-pointer transition-colors",
|
||||
profileOpen
|
||||
? "border-[#2261CA33] bg-[#00173C] text-white"
|
||||
: "border-transparent text-[#737373] hover:bg-white/5",
|
||||
)}
|
||||
onClick={handleToggleProfile}
|
||||
>
|
||||
<UserRound className="size-3.5" />
|
||||
Profile
|
||||
</button>
|
||||
</div>
|
||||
{onEnterSelectionMode && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Select documents"
|
||||
title="Select documents"
|
||||
className="size-8 flex items-center justify-center rounded-full border border-[#161F2C] bg-[#0D121A] hover:bg-[#00173C] hover:border-[#2261CA33] transition-colors cursor-pointer"
|
||||
onClick={onEnterSelectionMode}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="More memory actions"
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"inline-flex h-8 items-center justify-center gap-1.5 rounded-full border border-[#161F2C] bg-[#0D121A] px-2.5 text-xs font-medium transition-colors cursor-pointer",
|
||||
profileOpen
|
||||
? "border-[#2261CA33] bg-[#00173C] text-white"
|
||||
: "text-[#737373] hover:bg-white/5",
|
||||
)}
|
||||
>
|
||||
<MoreHorizontal className="size-3.5" />
|
||||
More
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className={cn(
|
||||
"min-w-[180px] rounded-xl border border-[#2E3033] p-1.5 shadow-[0px_1.5px_20px_0px_rgba(0,0,0,0.65)]",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(180deg, #0A0E14 0%, #05070A 100%)",
|
||||
}}
|
||||
>
|
||||
<BoxSelect className="size-4 text-[#737373]" />
|
||||
</button>
|
||||
)}
|
||||
{onEnterSelectionMode && (
|
||||
<DropdownMenuItem
|
||||
onSelect={onEnterSelectionMode}
|
||||
className="gap-2 rounded-md px-3 py-2.5 text-sm font-medium text-white cursor-pointer hover:bg-[#293952]/40"
|
||||
>
|
||||
<BoxSelect className="size-4 text-[#737373]" />
|
||||
Select memories
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onSelect={handleToggleProfile}
|
||||
className="gap-2 rounded-md px-3 py-2.5 text-sm font-medium text-white cursor-pointer hover:bg-[#293952]/40"
|
||||
>
|
||||
<UserRound className="size-4 text-[#737373]" />
|
||||
Space profile
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -837,7 +865,11 @@ export function MemoriesGrid({
|
|||
</div>
|
||||
<AnimatePresence initial={false}>
|
||||
{profileOpen && !isMobile && (
|
||||
<SpaceProfilePanel containerTag={selectedProject} isOpen />
|
||||
<SpaceProfilePanel
|
||||
containerTag={selectedProject}
|
||||
isOpen
|
||||
onClose={() => setProfileOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ type ContextTemplate = {
|
|||
prompt: string
|
||||
}
|
||||
|
||||
const SURFACE_SHADOW =
|
||||
"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"
|
||||
|
||||
const CONTEXT_TEMPLATES: ContextTemplate[] = [
|
||||
{
|
||||
id: "personal-general",
|
||||
|
|
@ -100,12 +103,14 @@ function PillButton({
|
|||
disabled={disabled}
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"inline-flex h-9 items-center justify-center gap-2 rounded-[9px] border px-3 text-[13px] font-medium transition-colors cursor-pointer disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"inline-flex h-9 items-center justify-center gap-2 rounded-full border px-4 text-[13px] font-semibold transition-opacity cursor-pointer disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"bg-[#0D121A] text-[#FAFAFA] hover:opacity-80",
|
||||
"shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.7)]",
|
||||
variant === "primary"
|
||||
? "border-transparent bg-[#0054AD] text-[#FAFAFA] hover:bg-[#0B65C9]"
|
||||
? "border-transparent"
|
||||
: variant === "danger"
|
||||
? "border-red-500/20 bg-red-500/10 text-red-300 hover:bg-red-500/15"
|
||||
: "border-[#161F2C] bg-[#0D121A] text-[#FAFAFA] hover:bg-[#00173C] hover:border-[#2261CA33]",
|
||||
? "border-transparent"
|
||||
: "border-transparent",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
|
@ -224,7 +229,7 @@ export function OrgContext() {
|
|||
</div>
|
||||
|
||||
{enabled && !isManaging && savedPrompt && (
|
||||
<div className="rounded-[12px] border border-[#161F2C] bg-[#0D121A] p-4">
|
||||
<div className="rounded-[12px] border border-white/[0.08] bg-[#0D121A] p-4">
|
||||
<p className="line-clamp-6 whitespace-pre-wrap text-[13px] leading-relaxed text-[#A3A3A3]">
|
||||
{savedPrompt}
|
||||
</p>
|
||||
|
|
@ -245,54 +250,56 @@ export function OrgContext() {
|
|||
)}
|
||||
|
||||
{enabled && isManaging && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="flex flex-col gap-2">
|
||||
<span className="text-[13px] font-medium text-[#FAFAFA]">
|
||||
What should Nova focus on?
|
||||
</span>
|
||||
<textarea
|
||||
value={prompt}
|
||||
onChange={(event) => setPrompt(event.target.value)}
|
||||
placeholder="Describe what Nova should extract, skip, and prioritize when turning your content into memories..."
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"min-h-[180px] w-full resize-y rounded-[12px] border border-[#161F2C] bg-[#0D121A] p-4 text-[13px] leading-relaxed text-[#FAFAFA] placeholder:text-[#525966] focus:outline-none focus:ring-1 focus:ring-[#2261CA66]",
|
||||
)}
|
||||
maxLength={750}
|
||||
/>
|
||||
<span className="self-end text-[11px] text-[#737373]">
|
||||
{prompt.length}/750
|
||||
</span>
|
||||
</label>
|
||||
<div className="overflow-hidden rounded-[18px] border border-white/[0.08] bg-[#1B1F24]">
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<label className="flex flex-col gap-2">
|
||||
<span className="text-[13px] font-medium text-[#FAFAFA]">
|
||||
What should Nova focus on?
|
||||
</span>
|
||||
<textarea
|
||||
value={prompt}
|
||||
onChange={(event) => setPrompt(event.target.value)}
|
||||
placeholder="Describe what Nova should extract, skip, and prioritize when turning your content into memories..."
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"min-h-[180px] w-full resize-y rounded-[14px] border border-white/[0.08] bg-[#0D121A] p-4 text-[13px] leading-relaxed text-[#FAFAFA] placeholder:text-[#525966] focus:border-white/[0.14] focus:outline-none",
|
||||
)}
|
||||
maxLength={750}
|
||||
/>
|
||||
<span className="self-end text-[11px] text-[#737373]">
|
||||
{prompt.length}/750
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
{CONTEXT_TEMPLATES.map((template) => {
|
||||
const isSelected = selectedTemplateId === template.id
|
||||
return (
|
||||
<button
|
||||
key={template.id}
|
||||
type="button"
|
||||
onClick={() => setPrompt(template.prompt)}
|
||||
className={cn(
|
||||
"rounded-[10px] border p-3 text-left transition-colors cursor-pointer",
|
||||
"bg-[#0D121A] hover:bg-[#121A24]",
|
||||
isSelected
|
||||
? "border-[#1C2B3E] bg-[#1C2B3E]"
|
||||
: "border-white/10",
|
||||
)}
|
||||
>
|
||||
<p className="text-[14px] font-medium text-[#FAFAFA]">
|
||||
{template.label}
|
||||
</p>
|
||||
<p className="mt-1 text-[12px] leading-snug text-[#737373]">
|
||||
{template.description}
|
||||
</p>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
{CONTEXT_TEMPLATES.map((template) => {
|
||||
const isSelected = selectedTemplateId === template.id
|
||||
return (
|
||||
<button
|
||||
key={template.id}
|
||||
type="button"
|
||||
onClick={() => setPrompt(template.prompt)}
|
||||
className={cn(
|
||||
"rounded-[14px] border p-4 text-left transition-colors cursor-pointer",
|
||||
"bg-[#14161A] hover:bg-[#121820]",
|
||||
isSelected
|
||||
? "border-white/[0.16] bg-[#171B22]"
|
||||
: "border-white/[0.08]",
|
||||
)}
|
||||
>
|
||||
<p className="text-[14px] font-medium text-[#FAFAFA]">
|
||||
{template.label}
|
||||
</p>
|
||||
<p className="mt-1 text-[12px] leading-snug text-[#737373]">
|
||||
{template.description}
|
||||
</p>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<div className="flex justify-end gap-2 border-t border-white/[0.08] bg-[#171B22] px-4 py-3">
|
||||
<PillButton onClick={handleCancel}>CANCEL</PillButton>
|
||||
<PillButton
|
||||
onClick={handleSave}
|
||||
|
|
@ -318,8 +325,11 @@ export function OrgContext() {
|
|||
>
|
||||
<DialogContent
|
||||
showCloseButton={false}
|
||||
style={{
|
||||
boxShadow: SURFACE_SHADOW,
|
||||
}}
|
||||
className={cn(
|
||||
"sm:max-w-[440px] border-none bg-[#1B1F24] p-0 gap-0 rounded-[22px] overflow-hidden",
|
||||
"sm:max-w-[440px] border border-white/[0.12] bg-[#1B1F24] p-0 gap-0 rounded-[22px] overflow-hidden",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
|
|
@ -347,13 +357,7 @@ export function OrgContext() {
|
|||
: "Nova will stop using this guidance for new content, and the saved context will be cleared."}
|
||||
</p>
|
||||
</div>
|
||||
<DialogPrimitive.Close
|
||||
className="flex size-7 shrink-0 items-center justify-center rounded-full border border-[rgba(115,115,115,0.2)] bg-[#0D121A] text-[#737373] transition-opacity hover:opacity-100 focus:outline-hidden cursor-pointer"
|
||||
style={{
|
||||
boxShadow:
|
||||
"inset 1.313px 1.313px 3.938px 0px rgba(0,0,0,0.7)",
|
||||
}}
|
||||
>
|
||||
<DialogPrimitive.Close className="flex size-7 shrink-0 items-center justify-center rounded-full bg-[#0D121A] text-[#737373] shadow-inside-out transition-opacity hover:opacity-80 focus:outline-hidden cursor-pointer">
|
||||
<X className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
|
|
|
|||
|
|
@ -19,15 +19,19 @@ export function SpaceProfileModal({
|
|||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
showCloseButton={false}
|
||||
className={cn(
|
||||
"w-[94%]! max-w-[440px]! max-h-[82dvh] border-none bg-[#14161A] p-4 rounded-[22px]",
|
||||
"shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)]",
|
||||
"w-[94%]! max-w-[440px]! max-h-[82dvh] border border-white/[0.08] bg-[#1B1F24] p-4 rounded-[22px]",
|
||||
"shadow-[0_2.842px_14.211px_rgba(0,0,0,0.25),inset_0.711px_0.711px_0.711px_rgba(255,255,255,0.10)]",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
<DialogTitle className="sr-only">Space Profile</DialogTitle>
|
||||
<div className="flex min-h-[420px] flex-col">
|
||||
<SpaceProfileContent containerTag={containerTag} />
|
||||
<SpaceProfileContent
|
||||
containerTag={containerTag}
|
||||
onClose={() => onOpenChange(false)}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { Brain } from "lucide-react"
|
||||
import { Brain, X } from "lucide-react"
|
||||
import { motion } from "motion/react"
|
||||
import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts"
|
||||
import { useSpaceProfile } from "@/hooks/use-space-profile"
|
||||
|
|
@ -9,15 +9,17 @@ import { cn } from "@lib/utils"
|
|||
type SpaceProfilePanelProps = {
|
||||
containerTag: string
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
type SpaceProfileContentProps = {
|
||||
containerTag: string
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
function CountBadge({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<span className="inline-flex h-[18px] min-w-[22px] items-center justify-center rounded-[4px] bg-[#1C2B3E] px-1.5 text-[10px] font-semibold text-[#A3A3A3]">
|
||||
<span className="inline-flex h-[18px] min-w-[22px] items-center justify-center rounded-[4px] bg-white/[0.05] px-1.5 text-[10px] font-semibold text-[#A3A3A3]">
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
|
|
@ -38,9 +40,9 @@ function LoadingState() {
|
|||
|
||||
function EmptyState() {
|
||||
return (
|
||||
<div className="flex min-h-[180px] flex-col items-center justify-center rounded-[14px] border border-[#161F2C] bg-[#0D121A] px-5 py-8 text-center">
|
||||
<div className="mb-3 flex size-10 items-center justify-center rounded-full bg-[#00173C]">
|
||||
<Brain className="size-4 text-[#4BA0FA]" />
|
||||
<div className="flex min-h-[180px] flex-col items-center justify-center rounded-[14px] border border-white/[0.08] bg-[#14161A] px-5 py-8 text-center">
|
||||
<div className="mb-3 flex size-10 items-center justify-center rounded-full bg-[#0D121A] shadow-inside-out">
|
||||
<Brain className="size-4 text-[#A3A3A3]" />
|
||||
</div>
|
||||
<p className="text-[14px] font-medium text-[#FAFAFA]">No profile yet</p>
|
||||
<p className="mt-1 text-[12px] leading-relaxed text-[#737373]">
|
||||
|
|
@ -87,6 +89,7 @@ function ProfileSection({ label, items }: { label: string; items: string[] }) {
|
|||
|
||||
export function SpaceProfileContent({
|
||||
containerTag,
|
||||
onClose,
|
||||
}: SpaceProfileContentProps) {
|
||||
const { data, isLoading, error } = useSpaceProfile(containerTag)
|
||||
const keyFacts = data?.static ?? []
|
||||
|
|
@ -95,7 +98,7 @@ export function SpaceProfileContent({
|
|||
|
||||
return (
|
||||
<div className={cn(dmSansClassName(), "flex min-h-0 flex-1 flex-col")}>
|
||||
<div className="border-b border-[#161F2C] pb-3">
|
||||
<div className="flex items-start justify-between gap-3 border-b border-white/[0.08] pb-3">
|
||||
<div>
|
||||
<h3
|
||||
className={cn(
|
||||
|
|
@ -109,13 +112,23 @@ export function SpaceProfileContent({
|
|||
What Nova knows in this space
|
||||
</p>
|
||||
</div>
|
||||
{onClose ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Close space profile"
|
||||
onClick={onClose}
|
||||
className="flex size-7 shrink-0 items-center justify-center rounded-full bg-[#0D121A] text-[#737373] shadow-inside-out transition-opacity hover:opacity-80 cursor-pointer"
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto pt-4 scrollbar-thin">
|
||||
{isLoading ? (
|
||||
<LoadingState />
|
||||
) : error ? (
|
||||
<p className="rounded-[12px] border border-red-500/20 bg-red-500/10 p-3 text-[13px] text-red-300">
|
||||
<p className="rounded-[12px] border border-white/[0.08] bg-[#14161A] p-3 text-[13px] text-[#A3A3A3]">
|
||||
Failed to load space profile.
|
||||
</p>
|
||||
) : totalCount === 0 ? (
|
||||
|
|
@ -134,6 +147,7 @@ export function SpaceProfileContent({
|
|||
export function SpaceProfilePanel({
|
||||
containerTag,
|
||||
isOpen,
|
||||
onClose,
|
||||
}: SpaceProfilePanelProps) {
|
||||
if (!isOpen) return null
|
||||
|
||||
|
|
@ -144,11 +158,11 @@ export function SpaceProfilePanel({
|
|||
exit={{ opacity: 0, width: 0, x: 12 }}
|
||||
transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] }}
|
||||
className={cn(
|
||||
"hidden h-full w-[300px] shrink-0 flex-col rounded-[14px] bg-[#14161A] p-4 md:flex",
|
||||
"shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)]",
|
||||
"hidden h-full w-[300px] shrink-0 flex-col rounded-[18px] border border-white/[0.08] bg-[#1B1F24] p-4 md:flex",
|
||||
"shadow-[0_2.842px_14.211px_rgba(0,0,0,0.25),inset_0.711px_0.711px_0.711px_rgba(255,255,255,0.10)]",
|
||||
)}
|
||||
>
|
||||
<SpaceProfileContent containerTag={containerTag} />
|
||||
<SpaceProfileContent containerTag={containerTag} onClose={onClose} />
|
||||
</motion.aside>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue