refactor: integrate PlateEditor for memory input in MemoryContent and TeamMemoryManager components, enhancing user experience with alerts and improved layout

This commit is contained in:
Anish Sarkar 2026-04-09 15:06:35 +05:30
parent d1c6caddba
commit 187ad46bc9
3 changed files with 66 additions and 58 deletions

View file

@ -1,12 +1,13 @@
"use client";
import { Info } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { z } from "zod";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { PlateEditor } from "@/components/editor/plate-editor";
import { Spinner } from "@/components/ui/spinner";
import { Textarea } from "@/components/ui/textarea";
import { baseApiService } from "@/lib/apis/base-api.service";
const MEMORY_HARD_LIMIT = 25_000;
@ -69,6 +70,11 @@ export function MemoryContent() {
}
};
const handleMarkdownChange = useCallback((md: string) => {
const trimmed = md.trim();
setMemory(trimmed);
}, []);
const hasChanges = memory !== savedMemory;
const charCount = memory.length;
const isOverLimit = charCount > MEMORY_HARD_LIMIT;
@ -90,33 +96,32 @@ export function MemoryContent() {
return (
<div className="space-y-4">
<div className="rounded-lg border bg-card p-6">
<div className="space-y-4">
<div>
<Label htmlFor="user-memory">Personal Memory</Label>
<p className="mt-1 text-xs text-muted-foreground">
This is your personal memory document. The AI assistant reads it at the start of
every conversation and uses it to personalize responses. You can edit it directly
or let the assistant update it during conversations.
</p>
</div>
<Alert className="bg-muted/50 py-3 md:py-4">
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" />
<AlertDescription className="text-xs md:text-sm">
SurfSense uses this personal memory to personalize your responses across all conversations.
</AlertDescription>
</Alert>
<Textarea
id="user-memory"
value={memory}
onChange={(e) => setMemory(e.target.value)}
placeholder={"## About me\n- ...\n\n## Preferences\n- ...\n\n## Instructions\n- ..."}
className="min-h-[300px] font-mono text-sm"
/>
<div className="h-[340px] overflow-y-auto rounded-md border">
<PlateEditor
markdown={savedMemory}
onMarkdownChange={handleMarkdownChange}
preset="minimal"
defaultEditing
placeholder="Add personal context here, such as your preferences, instructions, or facts about you"
variant="default"
editorVariant="none"
className="px-4 py-4 text-xs min-h-full"
/>
</div>
<div className="flex items-center justify-between">
<span className={`text-xs ${getCounterColor()}`}>
{charCount.toLocaleString()} / {MEMORY_HARD_LIMIT.toLocaleString()} characters
{charCount > 20_000 && charCount <= MEMORY_HARD_LIMIT && " — Approaching limit"}
{isOverLimit && " — Exceeds limit"}
</span>
</div>
</div>
<div className="flex items-center justify-between">
<span className={`text-xs ${getCounterColor()}`}>
{charCount.toLocaleString()} / {MEMORY_HARD_LIMIT.toLocaleString()} characters
{charCount > 20_000 && charCount <= MEMORY_HARD_LIMIT && " — Approaching limit"}
{isOverLimit && " — Exceeds limit"}
</span>
</div>
<div className="flex justify-between">

View file

@ -188,7 +188,7 @@ export function PlateEditor({
}}
>
<EditorContainer variant={variant} className={className}>
<Editor variant={editorVariant} placeholder={placeholder} />
<Editor variant={editorVariant} placeholder={placeholder} className="min-h-full" />
</EditorContainer>
</Plate>
</EditorSaveContext.Provider>

View file

@ -2,13 +2,14 @@
import { useQuery } from "@tanstack/react-query";
import { useAtomValue } from "jotai";
import { useEffect, useState } from "react";
import { Info } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { updateSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { PlateEditor } from "@/components/editor/plate-editor";
import { Spinner } from "@/components/ui/spinner";
import { Textarea } from "@/components/ui/textarea";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
@ -39,6 +40,11 @@ export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
}
}, [searchSpace?.shared_memory_md]);
const handleMarkdownChange = useCallback((md: string) => {
const trimmed = md.trim();
setMemory(trimmed);
}, []);
const hasChanges =
!!searchSpace &&
(searchSpace.shared_memory_md || "") !== memory;
@ -94,35 +100,32 @@ export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
return (
<div className="space-y-4">
<div className="rounded-lg border bg-card p-6">
<div className="space-y-4">
<div>
<Label htmlFor="team-memory">Team Memory</Label>
<p className="mt-1 text-xs text-muted-foreground">
This is the shared memory document for this search space. The AI assistant reads
it at the start of every conversation and uses it for team-wide context. You can
edit it directly or let the assistant update it during conversations.
</p>
</div>
<Alert className="bg-muted/50 py-3 md:py-4">
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" />
<AlertDescription className="text-xs md:text-sm">
SurfSense uses this shared memory to provide team-wide context across all conversations in this search space.
</AlertDescription>
</Alert>
<Textarea
id="team-memory"
value={memory}
onChange={(e) => setMemory(e.target.value)}
placeholder={
"## Team decisions\n- ...\n\n## Conventions\n- ...\n\n## Key facts\n- ...\n\n## Current priorities\n- ..."
}
className="min-h-[300px] font-mono text-sm"
/>
<div className="h-[340px] overflow-y-auto rounded-md border">
<PlateEditor
markdown={searchSpace?.shared_memory_md || ""}
onMarkdownChange={handleMarkdownChange}
preset="minimal"
defaultEditing
placeholder="Add team context here, such as decisions, conventions, key facts, or current priorities"
variant="default"
editorVariant="none"
className="px-4 py-4 text-xs min-h-full"
/>
</div>
<div className="flex items-center justify-between">
<span className={`text-xs ${getCounterColor()}`}>
{charCount.toLocaleString()} / {MEMORY_HARD_LIMIT.toLocaleString()} characters
{charCount > 20_000 && charCount <= MEMORY_HARD_LIMIT && " — Approaching limit"}
{isOverLimit && " — Exceeds limit"}
</span>
</div>
</div>
<div className="flex items-center justify-between">
<span className={`text-xs ${getCounterColor()}`}>
{charCount.toLocaleString()} / {MEMORY_HARD_LIMIT.toLocaleString()} characters
{charCount > 20_000 && charCount <= MEMORY_HARD_LIMIT && " — Approaching limit"}
{isOverLimit && " — Exceeds limit"}
</span>
</div>
<div className="flex justify-between">