From f8bf1641808af54fc4266872ed4d5886f3cc5607 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Thu, 28 May 2026 11:55:18 -0300 Subject: [PATCH] feat(memory): implement F7 Studio UI layer for /dashboard/memory Converts the monolithic memory page into a 3-tab Studio layout (Memories | Playground | Engine) with URL-driven tab state, 8 new React components, 2 SWR hooks, 50+ i18n keys, and 8 Vitest unit tests covering all new components (45/45 passing). --- .../memory/components/EditMemoryModal.tsx | 161 +++++ .../components/EmbeddingSourceSelector.tsx | 175 +++++ .../memory/components/MemoryConceptCard.tsx | 45 ++ .../memory/components/MemoryEngineStatus.tsx | 90 +++ .../memory/components/QdrantConfigCard.tsx | 419 +++++++++++ .../memory/components/RerankConfigCard.tsx | 97 +++ .../memory/components/RetrievePreview.tsx | 104 +++ .../memory/components/tabs/EngineTab.tsx | 132 ++++ .../memory/components/tabs/MemoriesTab.tsx | 658 ++++++++++++++++++ .../memory/components/tabs/PlaygroundTab.tsx | 153 ++++ .../dashboard/memory/hooks/useEngineStatus.ts | 28 + .../memory/hooks/useMemorySettings.ts | 48 ++ src/app/(dashboard)/dashboard/memory/page.tsx | 445 ++---------- src/i18n/messages/en.json | 148 +++- src/i18n/messages/pt-BR.json | 148 +++- tests/unit/ui/edit-memory-modal.test.tsx | 340 +++++++++ .../ui/embedding-source-selector.test.tsx | 188 +++++ tests/unit/ui/engine-tab.test.tsx | 247 +++++++ tests/unit/ui/memories-tab.test.tsx | 341 +++++++++ tests/unit/ui/memory-page.test.tsx | 156 +++++ tests/unit/ui/playground-tab.test.tsx | 243 +++++++ tests/unit/ui/qdrant-config-card.test.tsx | 337 +++++++++ tests/unit/ui/rerank-config-card.test.tsx | 242 +++++++ 23 files changed, 4543 insertions(+), 402 deletions(-) create mode 100644 src/app/(dashboard)/dashboard/memory/components/EditMemoryModal.tsx create mode 100644 src/app/(dashboard)/dashboard/memory/components/EmbeddingSourceSelector.tsx create mode 100644 src/app/(dashboard)/dashboard/memory/components/MemoryConceptCard.tsx create mode 100644 src/app/(dashboard)/dashboard/memory/components/MemoryEngineStatus.tsx create mode 100644 src/app/(dashboard)/dashboard/memory/components/QdrantConfigCard.tsx create mode 100644 src/app/(dashboard)/dashboard/memory/components/RerankConfigCard.tsx create mode 100644 src/app/(dashboard)/dashboard/memory/components/RetrievePreview.tsx create mode 100644 src/app/(dashboard)/dashboard/memory/components/tabs/EngineTab.tsx create mode 100644 src/app/(dashboard)/dashboard/memory/components/tabs/MemoriesTab.tsx create mode 100644 src/app/(dashboard)/dashboard/memory/components/tabs/PlaygroundTab.tsx create mode 100644 src/app/(dashboard)/dashboard/memory/hooks/useEngineStatus.ts create mode 100644 src/app/(dashboard)/dashboard/memory/hooks/useMemorySettings.ts create mode 100644 tests/unit/ui/edit-memory-modal.test.tsx create mode 100644 tests/unit/ui/embedding-source-selector.test.tsx create mode 100644 tests/unit/ui/engine-tab.test.tsx create mode 100644 tests/unit/ui/memories-tab.test.tsx create mode 100644 tests/unit/ui/memory-page.test.tsx create mode 100644 tests/unit/ui/playground-tab.test.tsx create mode 100644 tests/unit/ui/qdrant-config-card.test.tsx create mode 100644 tests/unit/ui/rerank-config-card.test.tsx diff --git a/src/app/(dashboard)/dashboard/memory/components/EditMemoryModal.tsx b/src/app/(dashboard)/dashboard/memory/components/EditMemoryModal.tsx new file mode 100644 index 0000000000..e6111e8b44 --- /dev/null +++ b/src/app/(dashboard)/dashboard/memory/components/EditMemoryModal.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Modal, Button, Input, Select } from "@/shared/components"; +import { useTranslations } from "next-intl"; + +interface Memory { + id: string; + type: "factual" | "episodic" | "procedural" | "semantic"; + key: string; + content: string; + metadata: Record; +} + +interface Props { + memory: Memory | null; + isOpen: boolean; + onClose: () => void; + onSaved: () => void; +} + +export default function EditMemoryModal({ memory, isOpen, onClose, onSaved }: Props) { + const t = useTranslations("memory"); + const [type, setType] = useState<"factual" | "episodic" | "procedural" | "semantic">("factual"); + const [key, setKey] = useState(""); + const [content, setContent] = useState(""); + const [metadataStr, setMetadataStr] = useState("{}"); + const [metadataError, setMetadataError] = useState(""); + const [isSaving, setIsSaving] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + if (memory && isOpen) { + setType(memory.type); + setKey(memory.key); + setContent(memory.content); + setMetadataStr(JSON.stringify(memory.metadata ?? {}, null, 2)); + setMetadataError(""); + setError(""); + } + }, [memory, isOpen]); + + const handleMetadataChange = (value: string) => { + setMetadataStr(value); + try { + JSON.parse(value); + setMetadataError(""); + } catch { + setMetadataError(t("editModal.metadataInvalid")); + } + }; + + const handleSave = async () => { + if (!memory) return; + if (metadataError) return; + setIsSaving(true); + setError(""); + try { + let metadata: Record = {}; + try { + metadata = JSON.parse(metadataStr); + } catch { + setError(t("editModal.metadataInvalid")); + return; + } + const res = await fetch(`/api/memory/${memory.id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type, key, content, metadata }), + }); + if (res.ok) { + onSaved(); + onClose(); + } else { + const data = await res.json().catch(() => null); + setError(data?.error?.message ?? t("editModal.saveFailed")); + } + } catch { + setError(t("editModal.saveFailed")); + } finally { + setIsSaving(false); + } + }; + + return ( + + + + + } + > +
+ {error && ( +
+ {error} +
+ )} +
+ + +
+
+ + setKey(e.target.value)} + placeholder={t("keyPlaceholder")} + className="w-full" + /> +
+
+ +