Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export function parseMemoriesFromDataset(
.filter((memory) => memory.length > 0 && memory !== ",")
}

export function renumberIncludedMemories(memories: string[]): string[] {
return memories.map((memory, index) => {
const text = memory.replace(/^\d+\.\s*/, "").replace(/\s+$/, "")
return `${index + 1}. ${text} \n`
})
}

export function showMemorySuggestion(
platform: string,
input: SuggestionInput,
Expand Down
95 changes: 57 additions & 38 deletions apps/browser-extension/entrypoints/content/t3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,29 @@ import {
} from "../../utils/storage"
import { createT3InputBarElement, DOMUtils } from "../../utils/ui-components"
import {
buildSupermemoryText,
parseMemoriesFromDataset,
renumberIncludedMemories,
serializeMemoriesForDataset,
} from "./memory-suggestion"

let t3DebounceTimeout: NodeJS.Timeout | null = null
let t3RouteObserver: MutationObserver | null = null
let t3UrlCheckInterval: NodeJS.Timeout | null = null
let t3ObserverThrottle: NodeJS.Timeout | null = null
let t3IncludedPopup: {
el: HTMLElement
onClick: (event: MouseEvent) => void
timer: ReturnType<typeof setTimeout>
} | null = null

function disposeT3IncludedPopup() {
if (!t3IncludedPopup) return
document.removeEventListener("click", t3IncludedPopup.onClick)
clearTimeout(t3IncludedPopup.timer)
t3IncludedPopup.el.remove()
t3IncludedPopup = null
}

export function initializeT3() {
if (!DOMUtils.isOnDomain(DOMAINS.T3)) {
Expand Down Expand Up @@ -57,6 +72,7 @@ function setupT3RouteChangeDetection() {

const checkForRouteChange = () => {
if (window.location.href !== currentUrl) {
disposeT3IncludedPopup()
currentUrl = window.location.href
setTimeout(() => {
addSupermemoryIconToT3Input()
Expand Down Expand Up @@ -235,7 +251,9 @@ async function getRelatedMemoriesForT3(actionSource: string) {
}

if (textareaElement) {
textareaElement.dataset.supermemories = `\n\nSupermemories of user (only for the reference): ${response.data}`
textareaElement.dataset.supermemories = buildSupermemoryText(
response.data,
)

iconElement.dataset.memoriesData = serializeMemoriesForDataset(
response.data,
Expand Down Expand Up @@ -274,6 +292,8 @@ function updateT3IconFeedback(
iconElement.dataset.originalHtml = iconElement.innerHTML
}

disposeT3IncludedPopup()

const feedbackDiv = document.createElement("div")
feedbackDiv.style.cssText = `
display: flex;
Expand Down Expand Up @@ -409,68 +429,65 @@ function updateT3IconFeedback(
popup.style.display = "block"
})

document.addEventListener("click", (e) => {
const onClick = (e: MouseEvent) => {
if (!popup.contains(e.target as Node)) {
popup.style.display = "none"
}
})
}
document.addEventListener("click", onClick)
t3IncludedPopup = {
el: popup,
onClick,
timer: setTimeout(disposeT3IncludedPopup, 300000),
}

content.querySelectorAll("button[data-memory-index]").forEach((button) => {
const htmlButton = button as HTMLButtonElement
htmlButton.addEventListener("click", () => {
const index = Number.parseInt(htmlButton.dataset.memoryIndex || "0", 10)
const memoryItem = htmlButton.parentElement

if (memoryItem) {
content.removeChild(memoryItem)
}
htmlButton.parentElement?.remove()

const currentMemories = parseMemoriesFromDataset(
const remainingMemories = 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 =
serializeMemoriesForDataset(currentMemories)
remainingMemories.splice(index, 1)
const remaining = renumberIncludedMemories(remainingMemories)

const textareaElement =
(document.querySelector("textarea") as HTMLTextAreaElement) ||
(document.querySelector('div[contenteditable="true"]') as HTMLElement)

// Only wipe when nothing remains — `<= 1` used to discard the last kept memory.
if (remaining.length === 0) {
if (textareaElement?.dataset.supermemories) {
delete textareaElement.dataset.supermemories
}
delete iconElement.dataset.memoriesData
iconElement.innerHTML = iconElement.dataset.originalHtml || ""
delete iconElement.dataset.originalHtml
disposeT3IncludedPopup()
return
}

iconElement.dataset.memoriesData =
serializeMemoriesForDataset(remaining)
if (textareaElement) {
textareaElement.dataset.supermemories = `\n\nSupermemories of user (only for the reference): ${updatedMemories}`
textareaElement.dataset.supermemories =
buildSupermemoryText(remaining)
}

content
.querySelectorAll("button[data-memory-index]")
.forEach((btn, newIndex) => {
const htmlBtn = btn as HTMLButtonElement
htmlBtn.dataset.memoryIndex = newIndex.toString()
htmlBtn.dataset.memoryIndex = String(newIndex)
const label = htmlBtn.previousElementSibling
if (label) {
label.textContent = remaining[newIndex].trim()
}
})

if (currentMemories.length <= 1) {
if (textareaElement?.dataset.supermemories) {
delete textareaElement.dataset.supermemories
delete iconElement.dataset.memoriesData
iconElement.innerHTML = iconElement.dataset.originalHtml || ""
delete iconElement.dataset.originalHtml
}
popup.style.display = "none"
if (document.body.contains(popup)) {
document.body.removeChild(popup)
}
}
})
})

setTimeout(() => {
if (document.body.contains(popup)) {
document.body.removeChild(popup)
}
}, 300000)
}

iconElement.innerHTML = ""
Expand Down Expand Up @@ -562,6 +579,7 @@ function setupT3PromptCapture() {
if (textareaElement?.dataset.supermemories) {
delete textareaElement.dataset.supermemories
}
disposeT3IncludedPopup()
}

const handleT3SendButtonClick = async (event: Event) => {
Expand Down Expand Up @@ -717,6 +735,7 @@ async function setupT3AutoFetch() {
if (textareaElement.dataset.supermemories) {
delete textareaElement.dataset.supermemories
}
disposeT3IncludedPopup()
}
}, UI_CONFIG.AUTO_SEARCH_DEBOUNCE_DELAY)
}
Expand Down
1 change: 1 addition & 0 deletions apps/browser-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dev:firefox": "wxt -b firefox",
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"check-types": "bun run compile",
"zip": "wxt zip",
"zip:firefox": "wxt zip -b firefox",
"compile": "tsc --noEmit",
Expand Down
22 changes: 22 additions & 0 deletions apps/docs/self-hosting/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@ bunx supermemory local

The installer detects your OS and architecture, downloads the right binary, verifies it, and (when run interactively) prompts you for an LLM API key. Supported platforms: macOS (Apple Silicon & Intel), Linux (x64 & arm64).

### Pin or change versions

Pass an explicit version to install (or roll back to) a specific release instead of `latest`:

```bash
curl -fsSL https://supermemory.ai/install | bash -s -- 0.0.3
```

<Warning>
Before rolling back, back up your [data directory](#where-things-live). The installer replaces the binary, but an older server may not understand data or schema changes made by a newer release.
</Warning>

Release tags are `server-v<version>` on [GitHub Releases](https://github.com/supermemoryai/supermemory/releases) (for example [`server-v0.0.3`](https://github.com/supermemoryai/supermemory/releases/tag/server-v0.0.3)).

To move to the newest release later:

```bash
supermemory-server upgrade
```

The binary may also print an “update available” notification on startup. If you intentionally pinned an older version (for example while debugging a regression), you can ignore that message until you are ready to upgrade.

## Run

```bash
Expand Down
1 change: 1 addition & 0 deletions apps/memory-graph-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "portless",
"dev:app": "next dev --port ${PORT:-3004}",
"build": "next build",
"check-types": "tsc --noEmit",
"start": "next start"
},
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions apps/web/components/chat/home-chat-composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export function HomeChatComposer({
const [attachmentDrafts, setAttachmentDrafts] = useState<
ChatAttachmentDraft[]
>([])
const [selectedModel, setSelectedModel] = useState<ModelId>("grok-4.3")
const [selectedModel, setSelectedModel] = useState<ModelId>("grok-4.5")
const [reasoningEffort, setReasoningEffort] = useState<ReasoningEffort>(
getDefaultReasoningEffort("grok-4.3"),
getDefaultReasoningEffort("grok-4.5"),
)
const { selectedProject } = useProject()
const [chatSpaceProjects, setChatSpaceProjects] = useState<string[]>([
Expand Down
4 changes: 2 additions & 2 deletions apps/web/components/chat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,11 @@ export function ChatSidebar({
>([])
const [isChatDraggingFiles, setIsChatDraggingFiles] = useState(false)
const [selectedModel, setSelectedModel] = useState<ModelId>(
initialSelectedModel ?? "grok-4.3",
initialSelectedModel ?? "grok-4.5",
)
const [reasoningEffort, setReasoningEffort] = useState<ReasoningEffort>(
initialReasoningEffort ??
getDefaultReasoningEffort(initialSelectedModel ?? "grok-4.3"),
getDefaultReasoningEffort(initialSelectedModel ?? "grok-4.5"),
)
const selectedModelRef = useRef(selectedModel)
selectedModelRef.current = selectedModel
Expand Down
3 changes: 1 addition & 2 deletions apps/web/components/chat/model-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ export default function ChatModelSelector({
minimal = false,
dropdownDirection = "up",
}: ChatModelSelectorProps = {}) {
const [internalModel, setInternalModel] =
useState<ModelId>("claude-sonnet-4.6")
const [internalModel, setInternalModel] = useState<ModelId>("claude-sonnet-5")
const [isOpen, setIsOpen] = useState(false)
const containerRef = useRef<HTMLDivElement>(null)

Expand Down
6 changes: 3 additions & 3 deletions apps/web/lib/chat-stream-error.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { ModelId } from "@/lib/models"

const OTHER_MODELS: ModelId[] = [
"gpt-5.1",
"claude-sonnet-4.6",
"gemini-2.5-pro",
"gpt-5.6-terra",
"claude-sonnet-5",
"gemini-3.1-pro-preview",
]

function flattenError(e: unknown): string {
Expand Down
24 changes: 12 additions & 12 deletions apps/web/lib/models.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
export const models = [
{
id: "grok-4.3",
name: "Grok 4.3",
id: "grok-4.5",
name: "Grok 4.5",
description: "xAI's latest model",
},
{
id: "gpt-5.1",
name: "GPT 5.1",
id: "gpt-5.6-terra",
name: "GPT 5.6",
description: "OpenAI's latest model",
},
{
id: "claude-sonnet-4.6",
name: "Claude Sonnet 4.6",
id: "claude-sonnet-5",
name: "Claude Sonnet 5",
description: "Anthropic's advanced model",
},
{
id: "gemini-2.5-pro",
name: "Gemini 3 Pro",
id: "gemini-3.1-pro-preview",
name: "Gemini 3.1 Pro",
description: "Google's most capable model",
},
] as const
Expand All @@ -25,10 +25,10 @@ export type ModelId = (typeof models)[number]["id"]
export type ReasoningEffort = "instant" | "thinking"

export const modelNames: Record<ModelId, { name: string; version: string }> = {
"grok-4.3": { name: "Grok", version: "4.3" },
"gpt-5.1": { name: "GPT", version: "5.1" },
"claude-sonnet-4.6": { name: "Claude", version: "4.6" },
"gemini-2.5-pro": { name: "Gemini", version: "3 Pro" },
"grok-4.5": { name: "Grok", version: "4.5" },
"gpt-5.6-terra": { name: "GPT", version: "5.6" },
"claude-sonnet-5": { name: "Claude", version: "Sonnet 5" },
"gemini-3.1-pro-preview": { name: "Gemini", version: "3.1 Pro" },
}

export const reasoningOptions: Array<{
Expand Down
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"dev": "portless",
"dev:app": "next dev --port ${PORT:-3000}",
"build": "next build",
"check-types": "tsc --noEmit",
"start": "next start",
"lint": "biome check --write",
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
Expand Down
5 changes: 4 additions & 1 deletion packages/hooks/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"name": "@repo/hooks",
"version": "0.0.0",
"private": true
"private": true,
"scripts": {
"check-types": "tsc --noEmit"
}
}
3 changes: 3 additions & 0 deletions packages/lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"check-types": "tsc --noEmit"
},
"exports": {
"./*": "./*"
},
Expand Down
7 changes: 7 additions & 0 deletions packages/tools/src/shared/forget-memory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const DEFAULT_BASE_URL = "https://api.supermemory.ai"
const FETCH_TIMEOUT_MS = 30_000

export interface ForgetMemoryParams {
containerTag: string
Expand All @@ -7,6 +8,10 @@ export interface ForgetMemoryParams {
reason?: string
}

export interface ForgetMemoryRequestOptions {
signal?: AbortSignal
}

/**
* Marks a memory as forgotten via `DELETE /v4/memories`.
*
Expand All @@ -19,6 +24,7 @@ export async function forgetMemoryRequest(
apiKey: string,
params: ForgetMemoryParams,
baseUrl: string = DEFAULT_BASE_URL,
options?: ForgetMemoryRequestOptions,
): Promise<void> {
const response = await fetch(`${baseUrl}/v4/memories`, {
method: "DELETE",
Expand All @@ -27,6 +33,7 @@ export async function forgetMemoryRequest(
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(params),
signal: options?.signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS),
})

if (!response.ok) {
Expand Down
Loading
Loading