This commit is contained in:
abhinav7x94 2026-08-26 04:01:27 +05:30 committed by GitHub
commit 551429b4cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 239 additions and 81 deletions

View file

@ -16,8 +16,9 @@ import {
import {
acceptMemorySuggestion,
clearMemorySuggestion,
clearPendingMemorySuggestion,
createMemorySuggestionPayload,
hasAcceptedSupermemoryContext,
serializeMemoriesForDataset,
setMemoryMarkerStatus,
showLoadingSuggestion,
showMarkerPopover,
@ -164,21 +165,19 @@ async function getRelatedMemoriesForChatGPT(actionSource: string) {
const userQuery =
document.getElementById("prompt-textarea")?.textContent || ""
const icon = document.querySelectorAll(
const iconElement = document.querySelectorAll(
'[id*="sm-chatgpt-input-bar-element-before-composer"]',
)[0]
const iconElement = icon as HTMLElement
)[0] as HTMLElement | undefined
const currentPromptElement = document.getElementById("prompt-textarea")
clearPendingMemorySuggestion("chatgpt", currentPromptElement, iconElement)
if (!iconElement) {
console.warn("ChatGPT icon element not found, cannot update feedback")
return
}
if (isAutoSearch) {
const promptElement = document.getElementById("prompt-textarea")
if (promptElement) {
showLoadingSuggestion("chatgpt", promptElement)
if (currentPromptElement) {
showLoadingSuggestion("chatgpt", currentPromptElement)
}
setMemoryMarkerStatus(iconElement, "searching")
} else {
@ -201,21 +200,23 @@ async function getRelatedMemoriesForChatGPT(actionSource: string) {
timeoutPromise,
])
if (response?.success && response?.data) {
const memorySuggestion = response?.success
? createMemorySuggestionPayload(response.data)
: null
if (memorySuggestion) {
const promptElement = document.getElementById("prompt-textarea")
if (promptElement) {
const memoryText = showMemorySuggestion(
"chatgpt",
promptElement,
response.data,
memorySuggestion,
)
debugChatGPT("memory suggestion rendered", {
memoryLength: memoryText.length,
})
iconElement.dataset.memoriesData = serializeMemoriesForDataset(
response.data,
)
iconElement.dataset.memoriesData = memorySuggestion.memoriesData
if (isAutoSearch) {
setMemoryMarkerStatus(iconElement, "found")
@ -234,6 +235,11 @@ async function getRelatedMemoriesForChatGPT(actionSource: string) {
}
} else {
console.warn("No memories found or API response invalid")
clearPendingMemorySuggestion(
"chatgpt",
document.getElementById("prompt-textarea"),
iconElement,
)
if (isAutoSearch) {
setMemoryMarkerStatus(iconElement, "none")
} else {
@ -245,7 +251,12 @@ async function getRelatedMemoriesForChatGPT(actionSource: string) {
try {
const icon = document.querySelectorAll(
'[id*="sm-chatgpt-input-bar-element-before-composer"]',
)[0] as HTMLElement
)[0] as HTMLElement | undefined
clearPendingMemorySuggestion(
"chatgpt",
document.getElementById("prompt-textarea"),
icon,
)
if (icon) {
if (
actionSource === POSTHOG_EVENT_KEY.CHATGPT_CHAT_MEMORIES_AUTO_SEARCHED

View file

@ -16,8 +16,9 @@ import {
import {
acceptMemorySuggestion,
clearMemorySuggestion,
clearPendingMemorySuggestion,
createMemorySuggestionPayload,
hasAcceptedSupermemoryContext,
serializeMemoriesForDataset,
setMemoryMarkerStatus,
showLoadingSuggestion,
showMarkerPopover,
@ -401,24 +402,25 @@ async function getRelatedMemoriesForClaude(actionSource: string) {
queryLength: userQuery.length,
})
const iconElement = document.querySelector(
'[id*="sm-claude-input-bar-element"]',
) as HTMLElement | null
const currentPromptInput = getClaudePromptInput()
clearPendingMemorySuggestion("claude", currentPromptInput, iconElement)
if (!userQuery.trim()) {
debugClaude("memory search skipped because query is empty")
return
}
const icon = document.querySelector('[id*="sm-claude-input-bar-element"]')
const iconElement = icon as HTMLElement
if (!iconElement) {
console.warn("Claude icon element not found, cannot update feedback")
return
}
if (isAutoSearch) {
const input = getClaudePromptInput()
if (input) {
showLoadingSuggestion("claude", input)
if (currentPromptInput) {
showLoadingSuggestion("claude", currentPromptInput)
}
setMemoryMarkerStatus(iconElement, "searching")
} else {
@ -445,24 +447,24 @@ async function getRelatedMemoriesForClaude(actionSource: string) {
success: response?.success,
})
if (response?.success && response?.data) {
const textareaElement = document.querySelector(
'div[contenteditable="true"]',
) as HTMLElement
const memorySuggestion = response?.success
? createMemorySuggestionPayload(response.data)
: null
if (memorySuggestion) {
const textareaElement = getClaudePromptInput()
if (textareaElement) {
const memoryText = showMemorySuggestion(
"claude",
textareaElement,
response.data,
memorySuggestion,
)
debugClaude("memory suggestion rendered", {
memoryLength: memoryText.length,
})
iconElement.dataset.memoriesData = serializeMemoriesForDataset(
response.data,
)
iconElement.dataset.memoriesData = memorySuggestion.memoriesData
if (isAutoSearch) {
setMemoryMarkerStatus(iconElement, "found")
@ -481,6 +483,11 @@ async function getRelatedMemoriesForClaude(actionSource: string) {
}
} else {
console.warn("No memories found or API response invalid for Claude")
clearPendingMemorySuggestion(
"claude",
getClaudePromptInput(),
iconElement,
)
if (isAutoSearch) {
setMemoryMarkerStatus(iconElement, "none")
} else {
@ -492,7 +499,8 @@ async function getRelatedMemoriesForClaude(actionSource: string) {
try {
const icon = document.querySelector(
'[id*="sm-claude-input-bar-element"]',
) as HTMLElement
) as HTMLElement | null
clearPendingMemorySuggestion("claude", getClaudePromptInput(), icon)
if (icon) {
if (
actionSource === POSTHOG_EVENT_KEY.CLAUDE_CHAT_MEMORIES_AUTO_SEARCHED

View file

@ -16,8 +16,9 @@ import {
import {
acceptMemorySuggestion,
clearMemorySuggestion,
clearPendingMemorySuggestion,
createMemorySuggestionPayload,
hasAcceptedSupermemoryContext,
serializeMemoriesForDataset,
setMemoryMarkerStatus,
showLoadingSuggestion,
showMarkerPopover,
@ -376,15 +377,16 @@ async function getRelatedMemoriesForGemini(actionSource: string) {
queryLength: userQuery.length,
})
const iconElement = document.querySelector(
`[id*="${ELEMENT_IDS.GEMINI_INPUT_BAR_ELEMENT}"]`,
) as HTMLElement | null
clearPendingMemorySuggestion("gemini", input, iconElement)
if (!userQuery) {
debugGemini("memory search skipped because query is empty")
return
}
const iconElement = document.querySelector(
`[id*="${ELEMENT_IDS.GEMINI_INPUT_BAR_ELEMENT}"]`,
) as HTMLElement | null
if (!iconElement) {
console.warn("Gemini icon element not found, cannot update feedback")
return
@ -412,15 +414,17 @@ async function getRelatedMemoriesForGemini(actionSource: string) {
actionSource,
}),
timeoutPromise,
])) as { success?: boolean; data?: string }
])) as { success?: boolean; data?: unknown }
debugGemini("memory search response", response)
if (response?.success && response?.data && input) {
const memoryText = showMemorySuggestion("gemini", input, response.data)
iconElement.dataset.memoriesData = serializeMemoriesForDataset(
response.data,
)
const memorySuggestion = response?.success
? createMemorySuggestionPayload(response.data)
: null
if (memorySuggestion && input) {
const memoryText = showMemorySuggestion("gemini", input, memorySuggestion)
iconElement.dataset.memoriesData = memorySuggestion.memoriesData
iconElement.dataset.supermemories = memoryText
if (isAutoSearch) {
setMemoryMarkerStatus(iconElement, "found")
@ -430,6 +434,8 @@ async function getRelatedMemoriesForGemini(actionSource: string) {
return
}
clearPendingMemorySuggestion("gemini", input, iconElement)
if (isAutoSearch) {
setMemoryMarkerStatus(iconElement, "none")
} else {
@ -440,6 +446,7 @@ async function getRelatedMemoriesForGemini(actionSource: string) {
const iconElement = document.querySelector(
`[id*="${ELEMENT_IDS.GEMINI_INPUT_BAR_ELEMENT}"]`,
) as HTMLElement | null
clearPendingMemorySuggestion("gemini", getGeminiPromptInput(), iconElement)
if (iconElement) {
if (
actionSource === POSTHOG_EVENT_KEY.GEMINI_CHAT_MEMORIES_AUTO_SEARCHED

View file

@ -0,0 +1,77 @@
import { describe, expect, it } from "bun:test"
import {
acceptMemorySuggestion,
buildSupermemoryText,
clearPendingMemoryState,
createMemorySuggestionPayload,
serializeMemoriesForDataset,
} from "./memory-suggestion"
describe("memory suggestion formatting", () => {
it("does not build an injectable prompt for empty memories", () => {
for (const memories of [[], [" ", "\n"], " ", null, undefined]) {
expect(serializeMemoriesForDataset(memories)).toBe("")
expect(buildSupermemoryText(memories)).toBe("")
expect(createMemorySuggestionPayload(memories)).toBeNull()
}
})
it("keeps the existing prompt format for non-empty memories", () => {
const memories = ["1. First memory \n", "2. Second memory \n"]
const suggestionText =
"\n\nSupermemories of user (only for the reference): 1. First memory \n2. Second memory"
expect(buildSupermemoryText(memories)).toBe(suggestionText)
expect(createMemorySuggestionPayload(memories)).toEqual({
suggestionText,
memoriesData: JSON.stringify(["1. First memory", "2. Second memory"]),
})
})
it("clears only pending memory state", () => {
let popoverRemoved = false
const input = {
dataset: {
supermemories: "old pending memories",
supermemoriesInjected: "true",
},
}
const icon = {
dataset: {
memoriesData: '["old pending memories"]',
supermemories: "old suggestion",
},
querySelector(selector: string) {
expect(selector).toBe("[data-supermemory-marker-popover]")
return {
remove() {
popoverRemoved = true
},
}
},
}
clearPendingMemoryState(input, icon)
expect(input.dataset.supermemories).toBeUndefined()
expect(input.dataset.supermemoriesInjected).toBe("true")
expect(icon.dataset.memoriesData).toBeUndefined()
expect(icon.dataset.supermemories).toBeUndefined()
expect(popoverRemoved).toBe(true)
let prevented = false
const accepted = acceptMemorySuggestion(
{
key: "Tab",
preventDefault() {
prevented = true
},
stopPropagation() {},
} as KeyboardEvent,
"chatgpt",
input as unknown as HTMLElement,
)
expect(accepted).toBe(false)
expect(prevented).toBe(false)
})
})

View file

@ -4,12 +4,17 @@ const SUGGESTION_ATTR = "data-supermemory-memory-suggestion"
const SUPERMEMORY_PREFIX = "Supermemories of user (only for the reference):"
const SUPERMEMORY_BLUE = "#1A88FF"
export function buildSupermemoryText(memories: unknown): string {
const memoryText = Array.isArray(memories)
? memories.join("").trim()
: String(memories || "").trim()
interface DatasetTarget {
dataset: Record<string, string | undefined>
}
return `\n\n${SUPERMEMORY_PREFIX} ${memoryText}`
interface PendingMemoryIcon extends DatasetTarget {
querySelector(selectors: string): { remove(): void } | null
}
export interface MemorySuggestionPayload {
suggestionText: string
memoriesData: string
}
function normalizeMemoryList(memories: unknown): string[] {
@ -29,6 +34,39 @@ export function serializeMemoriesForDataset(memories: unknown): string {
return list.length > 0 ? JSON.stringify(list) : ""
}
export function createMemorySuggestionPayload(
memories: unknown,
): MemorySuggestionPayload | null {
const memoriesData = serializeMemoriesForDataset(memories)
if (!memoriesData) return null
const memoryText = Array.isArray(memories)
? memories.join("").trim()
: String(memories ?? "").trim()
if (!memoryText) return null
return {
suggestionText: `\n\n${SUPERMEMORY_PREFIX} ${memoryText}`,
memoriesData,
}
}
export function buildSupermemoryText(memories: unknown): string {
return createMemorySuggestionPayload(memories)?.suggestionText ?? ""
}
export function clearPendingMemoryState(
input: DatasetTarget | null | undefined,
icon?: PendingMemoryIcon | null,
) {
if (input) delete input.dataset.supermemories
if (icon) {
delete icon.dataset.memoriesData
delete icon.dataset.supermemories
icon.querySelector("[data-supermemory-marker-popover]")?.remove()
}
}
export function parseMemoriesFromDataset(
raw: string | null | undefined,
): string[] {
@ -55,9 +93,9 @@ export function renumberIncludedMemories(memories: string[]): string[] {
export function showMemorySuggestion(
platform: string,
input: SuggestionInput,
memories: unknown,
payload: MemorySuggestionPayload,
): string {
const suggestionText = buildSupermemoryText(memories)
const suggestionText = payload.suggestionText
input.dataset.supermemories = suggestionText
delete input.dataset.supermemoriesInjected
@ -185,6 +223,15 @@ export function removeMemorySuggestion(platform: string) {
}
}
export function clearPendingMemorySuggestion(
platform: string,
input: SuggestionInput | null,
icon?: HTMLElement | null,
) {
removeMemorySuggestion(platform)
clearPendingMemoryState(input, icon)
}
export function acceptMemorySuggestion(
event: KeyboardEvent,
platform: string,
@ -230,10 +277,7 @@ export function clearMemorySuggestion(
platform: string,
input: SuggestionInput | null,
) {
removeMemorySuggestion(platform)
if (input?.dataset.supermemories) {
delete input.dataset.supermemories
}
clearPendingMemorySuggestion(platform, input)
if (input?.dataset.supermemoriesInjected) {
delete input.dataset.supermemoriesInjected
}

View file

@ -12,6 +12,8 @@ import {
import { createT3InputBarElement, DOMUtils } from "../../utils/ui-components"
import {
buildSupermemoryText,
clearPendingMemoryState,
createMemorySuggestionPayload,
parseMemoriesFromDataset,
renumberIncludedMemories,
serializeMemoriesForDataset,
@ -35,6 +37,24 @@ function disposeT3IncludedPopup() {
t3IncludedPopup = null
}
function getT3PromptInput(): HTMLTextAreaElement | HTMLElement | null {
const supermemoryContainer = document.querySelector(
'[data-supermemory-icon-added="true"]',
)
const composerTextarea =
supermemoryContainer?.parentElement?.previousElementSibling?.querySelector(
"textarea",
) as HTMLTextAreaElement | null
return (
composerTextarea ||
(document.querySelector('div[contenteditable="true"]') as
| HTMLTextAreaElement
| HTMLElement
| null)
)
}
export function initializeT3() {
if (!DOMUtils.isOnDomain(DOMAINS.T3)) {
return
@ -201,14 +221,16 @@ async function getRelatedMemoriesForT3(actionSource: string) {
}
}
const iconElement = document.querySelector(
'[id*="sm-t3-input-bar-element"]',
) as HTMLElement | null
clearPendingMemoryState(getT3PromptInput(), iconElement)
disposeT3IncludedPopup()
if (!userQuery.trim()) {
return
}
const icon = document.querySelector('[id*="sm-t3-input-bar-element"]')
const iconElement = icon as HTMLElement
if (!iconElement) {
console.warn("T3 icon element not found, cannot update feedback")
return
@ -232,32 +254,17 @@ async function getRelatedMemoriesForT3(actionSource: string) {
timeoutPromise,
])
if (response?.success && response?.data) {
let textareaElement = null
const supermemoryContainer = document.querySelector(
'[data-supermemory-icon-added="true"]',
)
if (supermemoryContainer?.parentElement?.previousElementSibling) {
textareaElement =
supermemoryContainer.parentElement.previousElementSibling.querySelector(
"textarea",
)
}
const memorySuggestion = response?.success
? createMemorySuggestionPayload(response.data)
: null
if (!textareaElement) {
textareaElement = document.querySelector(
'div[contenteditable="true"]',
) as HTMLElement
}
if (memorySuggestion) {
const textareaElement = getT3PromptInput()
if (textareaElement) {
textareaElement.dataset.supermemories = buildSupermemoryText(
response.data,
)
textareaElement.dataset.supermemories = memorySuggestion.suggestionText
iconElement.dataset.memoriesData = serializeMemoriesForDataset(
response.data,
)
iconElement.dataset.memoriesData = memorySuggestion.memoriesData
updateT3IconFeedback("Included Memories", iconElement)
} else {
@ -266,6 +273,8 @@ async function getRelatedMemoriesForT3(actionSource: string) {
}
} else {
console.warn("No memories found or API response invalid for T3")
clearPendingMemoryState(getT3PromptInput(), iconElement)
disposeT3IncludedPopup()
updateT3IconFeedback("No memories found", iconElement)
}
} catch (error) {
@ -273,7 +282,9 @@ async function getRelatedMemoriesForT3(actionSource: string) {
try {
const icon = document.querySelector(
'[id*="sm-t3-input-bar-element"]',
) as HTMLElement
) as HTMLElement | null
clearPendingMemoryState(getT3PromptInput(), icon)
disposeT3IncludedPopup()
if (icon) {
updateT3IconFeedback("Error fetching memories", icon)
}