mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-11-01 07:02:53 +00:00
Merge 8129fec5d6 into 570ed22b6c
This commit is contained in:
commit
2f8c977f17
6 changed files with 125 additions and 18 deletions
|
|
@ -17,6 +17,7 @@ import {
|
|||
acceptMemorySuggestion,
|
||||
clearMemorySuggestion,
|
||||
hasAcceptedSupermemoryContext,
|
||||
serializeMemoriesForDataset,
|
||||
setMemoryMarkerStatus,
|
||||
showLoadingSuggestion,
|
||||
showMarkerPopover,
|
||||
|
|
@ -212,7 +213,9 @@ async function getRelatedMemoriesForChatGPT(actionSource: string) {
|
|||
memoryLength: memoryText.length,
|
||||
})
|
||||
|
||||
iconElement.dataset.memoriesData = String(response.data)
|
||||
iconElement.dataset.memoriesData = serializeMemoriesForDataset(
|
||||
response.data,
|
||||
)
|
||||
|
||||
if (isAutoSearch) {
|
||||
setMemoryMarkerStatus(iconElement, "found")
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
acceptMemorySuggestion,
|
||||
clearMemorySuggestion,
|
||||
hasAcceptedSupermemoryContext,
|
||||
serializeMemoriesForDataset,
|
||||
setMemoryMarkerStatus,
|
||||
showLoadingSuggestion,
|
||||
showMarkerPopover,
|
||||
|
|
@ -459,7 +460,9 @@ async function getRelatedMemoriesForClaude(actionSource: string) {
|
|||
memoryLength: memoryText.length,
|
||||
})
|
||||
|
||||
iconElement.dataset.memoriesData = String(response.data)
|
||||
iconElement.dataset.memoriesData = serializeMemoriesForDataset(
|
||||
response.data,
|
||||
)
|
||||
|
||||
if (isAutoSearch) {
|
||||
setMemoryMarkerStatus(iconElement, "found")
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
acceptMemorySuggestion,
|
||||
clearMemorySuggestion,
|
||||
hasAcceptedSupermemoryContext,
|
||||
serializeMemoriesForDataset,
|
||||
setMemoryMarkerStatus,
|
||||
showLoadingSuggestion,
|
||||
showMarkerPopover,
|
||||
|
|
@ -417,7 +418,9 @@ async function getRelatedMemoriesForGemini(actionSource: string) {
|
|||
|
||||
if (response?.success && response?.data && input) {
|
||||
const memoryText = showMemorySuggestion("gemini", input, response.data)
|
||||
iconElement.dataset.memoriesData = String(response.data)
|
||||
iconElement.dataset.memoriesData = serializeMemoriesForDataset(
|
||||
response.data,
|
||||
)
|
||||
iconElement.dataset.supermemories = memoryText
|
||||
if (isAutoSearch) {
|
||||
setMemoryMarkerStatus(iconElement, "found")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
import { describe, expect, it } from "bun:test"
|
||||
import {
|
||||
parseMemoriesFromDataset,
|
||||
serializeMemoriesForDataset,
|
||||
} from "./memory-suggestion"
|
||||
|
||||
describe("memory dataset serialization", () => {
|
||||
it("round-trips a memory that contains a comma as a single item", () => {
|
||||
const memories = ["Lives in Austin, Texas", "Prefers dark mode"]
|
||||
const stored = serializeMemoriesForDataset(memories)
|
||||
expect(parseMemoriesFromDataset(stored)).toEqual(memories)
|
||||
})
|
||||
|
||||
it("round-trips a memory that contains a newline as a single item", () => {
|
||||
const memories = ["Shipping address:\n123 Main St", "Likes coffee"]
|
||||
const stored = serializeMemoriesForDataset(memories)
|
||||
expect(parseMemoriesFromDataset(stored)).toEqual(memories)
|
||||
})
|
||||
|
||||
it("trims and drops empty entries when serializing", () => {
|
||||
const stored = serializeMemoriesForDataset([" keep ", "", " "])
|
||||
expect(parseMemoriesFromDataset(stored)).toEqual(["keep"])
|
||||
})
|
||||
|
||||
it("serializes an empty list to an empty string for truthiness checks", () => {
|
||||
expect(serializeMemoriesForDataset([])).toBe("")
|
||||
expect(serializeMemoriesForDataset(undefined)).toBe("")
|
||||
})
|
||||
|
||||
it("returns an empty array for empty or missing input", () => {
|
||||
expect(parseMemoriesFromDataset("")).toEqual([])
|
||||
expect(parseMemoriesFromDataset(null)).toEqual([])
|
||||
expect(parseMemoriesFromDataset(undefined)).toEqual([])
|
||||
})
|
||||
|
||||
it("falls back to the legacy comma/newline split for non-JSON values", () => {
|
||||
expect(parseMemoriesFromDataset("first,second\nthird")).toEqual([
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
])
|
||||
})
|
||||
|
||||
it("wraps a single non-array value into one item", () => {
|
||||
expect(
|
||||
parseMemoriesFromDataset(serializeMemoriesForDataset("solo")),
|
||||
).toEqual(["solo"])
|
||||
})
|
||||
})
|
||||
|
|
@ -12,6 +12,52 @@ export function buildSupermemoryText(memories: unknown): string {
|
|||
return `\n\n${SUPERMEMORY_PREFIX} ${memoryText}`
|
||||
}
|
||||
|
||||
function normalizeMemoryList(memories: unknown): string[] {
|
||||
const list = Array.isArray(memories)
|
||||
? memories
|
||||
: memories == null
|
||||
? []
|
||||
: [memories]
|
||||
return list
|
||||
.map((memory) => (typeof memory === "string" ? memory : String(memory)))
|
||||
.map((memory) => memory.trim())
|
||||
.filter((memory) => memory.length > 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the memory list for storage on a `data-*` attribute. Memories are
|
||||
* free text that can contain commas and newlines, so they are stored as JSON
|
||||
* rather than joined into a single string, otherwise a memory with a comma in
|
||||
* it is split into fragments when the popup reads it back. Returns an empty
|
||||
* string for an empty list so existing truthiness checks on the attribute
|
||||
* (memories present vs not) keep working.
|
||||
*/
|
||||
export function serializeMemoriesForDataset(memories: unknown): string {
|
||||
const list = normalizeMemoryList(memories)
|
||||
return list.length > 0 ? JSON.stringify(list) : ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Read back a memory list written by {@link serializeMemoriesForDataset}.
|
||||
* Falls back to the legacy comma/newline split so any value written by older
|
||||
* code (or a plain joined string) still renders.
|
||||
*/
|
||||
export function parseMemoriesFromDataset(
|
||||
raw: string | null | undefined,
|
||||
): string[] {
|
||||
if (!raw) return []
|
||||
try {
|
||||
const parsed = JSON.parse(raw)
|
||||
if (Array.isArray(parsed)) return normalizeMemoryList(parsed)
|
||||
} catch {
|
||||
// Not JSON — fall through to the legacy delimiter split.
|
||||
}
|
||||
return raw
|
||||
.split(/[,\n]/)
|
||||
.map((memory) => memory.trim())
|
||||
.filter((memory) => memory.length > 0 && memory !== ",")
|
||||
}
|
||||
|
||||
export function showMemorySuggestion(
|
||||
platform: string,
|
||||
input: SuggestionInput,
|
||||
|
|
@ -305,10 +351,7 @@ export function showMarkerPopover(
|
|||
color: rgba(255, 255, 255, 0.76);
|
||||
`
|
||||
|
||||
memories
|
||||
.split(/[,\n]/)
|
||||
.map((memory) => memory.trim())
|
||||
.filter((memory) => memory.length > 0 && memory !== ",")
|
||||
parseMemoriesFromDataset(memories)
|
||||
.slice(0, 5)
|
||||
.forEach((memory) => {
|
||||
const item = document.createElement("div")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import {
|
|||
autoCapturePromptsEnabled,
|
||||
} from "../../utils/storage"
|
||||
import { createT3InputBarElement, DOMUtils } from "../../utils/ui-components"
|
||||
import {
|
||||
parseMemoriesFromDataset,
|
||||
serializeMemoriesForDataset,
|
||||
} from "./memory-suggestion"
|
||||
|
||||
let t3DebounceTimeout: NodeJS.Timeout | null = null
|
||||
let t3RouteObserver: MutationObserver | null = null
|
||||
|
|
@ -233,7 +237,9 @@ async function getRelatedMemoriesForT3(actionSource: string) {
|
|||
if (textareaElement) {
|
||||
textareaElement.dataset.supermemories = `\n\nSupermemories of user (only for the reference): ${response.data}`
|
||||
|
||||
iconElement.dataset.memoriesData = response.data
|
||||
iconElement.dataset.memoriesData = serializeMemoriesForDataset(
|
||||
response.data,
|
||||
)
|
||||
|
||||
updateT3IconFeedback("Included Memories", iconElement)
|
||||
} else {
|
||||
|
|
@ -329,11 +335,9 @@ function updateT3IconFeedback(
|
|||
overflow-y: auto;
|
||||
`
|
||||
|
||||
const memoriesText = iconElement.dataset.memoriesData || ""
|
||||
const individualMemories = memoriesText
|
||||
.split(/[,\n]/)
|
||||
.map((memory) => memory.trim())
|
||||
.filter((memory) => memory.length > 0 && memory !== ",")
|
||||
const individualMemories = parseMemoriesFromDataset(
|
||||
iconElement.dataset.memoriesData,
|
||||
)
|
||||
|
||||
individualMemories.forEach((memory, index) => {
|
||||
const memoryItem = document.createElement("div")
|
||||
|
|
@ -421,15 +425,17 @@ function updateT3IconFeedback(
|
|||
content.removeChild(memoryItem)
|
||||
}
|
||||
|
||||
const currentMemories = (iconElement.dataset.memoriesData || "")
|
||||
.split(/[,\n]/)
|
||||
.map((memory) => memory.trim())
|
||||
.filter((memory) => memory.length > 0 && memory !== ",")
|
||||
const currentMemories = parseMemoriesFromDataset(
|
||||
iconElement.dataset.memoriesData,
|
||||
)
|
||||
currentMemories.splice(index, 1)
|
||||
|
||||
// Injected prompt keeps its existing joined-text form; the popup's
|
||||
// own data is stored as JSON so comma-bearing memories stay intact.
|
||||
const updatedMemories = currentMemories.join(" ,")
|
||||
|
||||
iconElement.dataset.memoriesData = updatedMemories
|
||||
iconElement.dataset.memoriesData =
|
||||
serializeMemoriesForDataset(currentMemories)
|
||||
|
||||
const textareaElement =
|
||||
(document.querySelector("textarea") as HTMLTextAreaElement) ||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue