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
22 changes: 14 additions & 8 deletions apps/web/components/chat/input/chain-of-thought.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,24 @@ export function ChainOfThought({ messages }: { messages: UIMessage[] }) {
agentMessage?: UIMessage
}> = []

let lastUserPair: {
userMessage: UIMessage
agentMessage?: UIMessage
} | null = null

for (let i = 0; i < messages.length; i++) {
const message = messages[i]
if (!message) continue

if (message.role === "user") {
// Find the next assistant message after this user message
const agentMessage = messages
.slice(i + 1)
.find((msg) => msg.role === "assistant")
messagePairs.push({
userMessage: message,
agentMessage,
})
lastUserPair = { userMessage: message }
messagePairs.push(lastUserPair)
} else if (
message.role === "assistant" &&
lastUserPair &&
!lastUserPair.agentMessage
) {
lastUserPair.agentMessage = message
}
}

Expand Down
5 changes: 3 additions & 2 deletions apps/web/components/chat/message/user-message.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client"

import { memo } from "react"
import { Copy, Check } from "lucide-react"
import type { UIMessage } from "@ai-sdk/react"

Expand All @@ -9,7 +10,7 @@ interface UserMessageProps {
onCopy: (messageId: string, text: string) => void
}

export function UserMessage({
export const UserMessage = memo(function UserMessage({
message,
copiedMessageId,
onCopy,
Expand Down Expand Up @@ -38,4 +39,4 @@ export function UserMessage({
</button>
</div>
)
}
})
10 changes: 7 additions & 3 deletions apps/web/components/document-cards/file-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import { useState } from "react"
import { memo, useState } from "react"
import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
import type { z } from "zod"
import { dmSansClassName } from "@/lib/fonts"
Expand Down Expand Up @@ -43,7 +43,11 @@ function getFileTypeInfo(document: DocumentWithMemories): {
}
}

export function FilePreview({ document }: { document: DocumentWithMemories }) {
export const FilePreview = memo(function FilePreview({
document,
}: {
document: DocumentWithMemories
}) {
const [imageError, setImageError] = useState(false)
const { extension, color } = getFileTypeInfo(document)

Expand Down Expand Up @@ -107,4 +111,4 @@ export function FilePreview({ document }: { document: DocumentWithMemories }) {
)}
</div>
)
}
})
6 changes: 3 additions & 3 deletions apps/web/components/document-cards/website-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import { useState } from "react"
import { memo, useState } from "react"
import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
import type { z } from "zod"

Expand All @@ -12,7 +12,7 @@ type OgData = {
image?: string
}

export function WebsitePreview({
export const WebsitePreview = memo(function WebsitePreview({
document,
ogData,
}: {
Expand Down Expand Up @@ -40,4 +40,4 @@ export function WebsitePreview({
) : null}
</div>
)
}
})
5 changes: 3 additions & 2 deletions apps/web/components/document-cards/youtube-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client"

import { memo } from "react"
import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
import type { z } from "zod"
import { dmSansClassName } from "@/lib/fonts"
Expand All @@ -9,7 +10,7 @@ import { extractYouTubeVideoId } from "../utils"
type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema>
type DocumentWithMemories = DocumentsResponse["documents"][0]

export function YoutubePreview({
export const YoutubePreview = memo(function YoutubePreview({
document,
}: {
document: DocumentWithMemories
Expand Down Expand Up @@ -49,4 +50,4 @@ export function YoutubePreview({
</div>
</div>
)
}
})
87 changes: 66 additions & 21 deletions apps/web/components/memories-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,48 @@ type OgData = {
image?: string
}

const ogCache = new Map<string, OgData>()
const ogInflight = new Map<string, Promise<OgData | null>>()
const ogFailures = new Map<string, number>()
const OG_FAILURE_TTL = 30_000

function fetchOgData(url: string): Promise<OgData | null> {
const cached = ogCache.get(url)
if (cached) return Promise.resolve(cached)

const failedAt = ogFailures.get(url)
if (failedAt && Date.now() - failedAt < OG_FAILURE_TTL) {
return Promise.resolve(null)
}

const inflight = ogInflight.get(url)
if (inflight) return inflight

const promise = fetch(`/api/og?url=${encodeURIComponent(url)}`)
.then((res) => {
if (!res.ok) throw new Error("Failed")
return res.json()
})
.then((data) => {
const result: OgData = { title: data?.title, image: data?.image }
if (!result.title && !result.image) {
throw new Error("Empty metadata")
}
ogCache.set(url, result)
ogInflight.delete(url)
ogFailures.delete(url)
return result
})
.catch(() => {
ogInflight.delete(url)
ogFailures.set(url, Date.now())
return null
})

ogInflight.set(url, promise)
return promise
}

const PAGE_SIZE = 100
const MAX_TOTAL = 1000

Expand Down Expand Up @@ -682,7 +724,6 @@ const DocumentCard = memo(
const [rotation, setRotation] = useState({ rotateX: 0, rotateY: 0 })
const cardRef = useRef<HTMLButtonElement>(null)
const [ogData, setOgData] = useState<OgData | null>(null)
const [isLoadingOg, setIsLoadingOg] = useState(false)

const ogImage = (document as DocumentWithMemories & { ogImage?: string })
.ogImage
Expand All @@ -699,27 +740,31 @@ const DocumentCard = memo(
const hideURL = document.url?.includes("docs.googleapis.com")

useEffect(() => {
if (needsOgData && !ogData && !isLoadingOg && document.url) {
setIsLoadingOg(true)
fetch(`/api/og?url=${encodeURIComponent(document.url)}`)
.then((res) => {
if (!res.ok) throw new Error("Failed")
return res.json()
})
.then((data) => {
setOgData({
title: data?.title,
image: data?.image,
})
})
.catch(() => {
setOgData({})
})
.finally(() => {
setIsLoadingOg(false)
})
if (!needsOgData || ogData || !document.url) return

let timeoutId: ReturnType<typeof setTimeout>
let mounted = true

const attemptFetch = () => {
if (!mounted || !document.url) return
fetchOgData(document.url).then((data) => {
if (!mounted) return
if (data) {
setOgData(data)
} else {
// Retry when the global TTL expires
timeoutId = setTimeout(attemptFetch, 30_000)
}
})
}

attemptFetch()

return () => {
mounted = false
clearTimeout(timeoutId)
}
}, [needsOgData, ogData, isLoadingOg, document.url])
}, [needsOgData, ogData, document.url])

useEffect(() => {
if (isSelectionMode) setRotation({ rotateX: 0, rotateY: 0 })
Expand Down
4 changes: 2 additions & 2 deletions apps/web/components/memory-graph/hooks/use-graph-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export function useGraphApi(options: UseGraphApiOptions = {}) {
hasNextPage,
fetchNextPage,
} = useInfiniteQuery<ApiDocumentsResponse, Error>({
queryKey: ["graph-documents", containerTags?.join(",")],
queryKey: ["documents-with-memories", containerTags, []],
initialPageParam: 1,
queryFn: async ({ pageParam }) => {
const response = await $fetch("@post/documents/documents", {
Expand All @@ -128,7 +128,7 @@ export function useGraphApi(options: UseGraphApiOptions = {}) {
const { currentPage, totalPages } = lastPage.pagination
return currentPage < totalPages ? currentPage + 1 : undefined
},
staleTime: 30 * 1000,
staleTime: 5 * 60 * 1000,
enabled,
})

Expand Down
18 changes: 10 additions & 8 deletions apps/web/components/onboarding/setup/chat-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,9 @@ export function ChatSidebar({ formData }: ChatSidebarProps) {
setIsLoading(true)

try {
const documentIds: string[] = []

for (const draft of draftDocs) {
const promises = draftDocs.map(async (draft) => {
if (draft.kind === "x_research" && xResearchStatus !== "correct") {
continue
return null
}

try {
Expand All @@ -397,13 +395,17 @@ export function ChatSidebar({ formData }: ChatSidebarProps) {
},
})

if (docResponse.data?.id) {
documentIds.push(docResponse.data.id)
}
return docResponse.data?.id
} catch (error) {
console.warn("Error creating document:", error)
return null
}
}
})

const results = await Promise.all(promises)
const documentIds = results.filter(
(id): id is string => id !== null && id !== undefined,
)

if (documentIds.length > 0) {
await pollForMemories(documentIds)
Expand Down
6 changes: 3 additions & 3 deletions apps/web/stores/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export function areUIMessageArraysEqual(
return false
}

// Compare the entire message using JSON serialization as a fallback
// This handles all properties including parts, toolInvocations, etc.
if (JSON.stringify(msgA) !== JSON.stringify(msgB)) {
if (msgA.content !== msgB.content) return false

if (JSON.stringify(msgA.parts) !== JSON.stringify(msgB.parts)) {
return false
}
}
Expand Down
5 changes: 4 additions & 1 deletion packages/lib/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ export function getSubscriptionStatus(
): SubscriptionStatusMap {
const statusMap: SubscriptionStatusMap = { ...DEFAULT_SUBSCRIPTION_STATUS }
if (!products) return statusMap

const productMap = new Map(products.map((p) => [p.id, p]))

for (const tier of PLAN_TIERS) {
const product = products.find((p) => p.id === tier)
const product = productMap.get(tier)
statusMap[tier] = {
allowed: product?.status === "active",
status: product?.status ?? null,
Expand Down
2 changes: 1 addition & 1 deletion packages/openai-sdk-python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "supermemory-openai-sdk"
version = "1.0.2"
version = "1.0.3"
description = "Memory tools for OpenAI function calling with supermemory"
readme = "README.md"
license = "MIT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,9 +554,12 @@ async def wait_for_background_tasks(self, timeout: Optional[float] = 10.0) -> No
f"Background tasks did not complete within {timeout}s timeout"
)
# Cancel remaining tasks
for task in self._background_tasks:
if not task.done():
task.cancel()
tasks_to_cancel = [task for task in self._background_tasks if not task.done()]
for task in tasks_to_cancel:
task.cancel()

if tasks_to_cancel:
await asyncio.gather(*tasks_to_cancel, return_exceptions=True)
raise

def cancel_background_tasks(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion packages/openai-sdk-python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading