mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-05-19 07:42:43 +00:00
fix(web): sentry issues across the web app (#570)
Fixes all following sentry issues - CONSUMER-APP-FF - CONSUMER-APP-1T - CONSUMER-APP-86 - CONSUMER-APP-7H - CONSUMER-APP-4F - CONSUMER-APP-7X
This commit is contained in:
parent
8f1eda6b60
commit
10ada4a1e2
8 changed files with 193 additions and 86 deletions
|
|
@ -62,6 +62,9 @@ export function GraphDialog() {
|
|||
return response.data
|
||||
},
|
||||
getNextPageParam: (lastPage, allPages) => {
|
||||
if (!lastPage || !lastPage.pagination) return undefined
|
||||
if (!Array.isArray(allPages)) return undefined
|
||||
|
||||
const loaded = allPages.reduce(
|
||||
(acc, p) => acc + (p.documents?.length ?? 0),
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ export function Memories() {
|
|||
return response.data
|
||||
},
|
||||
getNextPageParam: (lastPage, allPages) => {
|
||||
if (!lastPage || !lastPage.pagination) return undefined
|
||||
if (!Array.isArray(allPages)) return undefined
|
||||
|
||||
const loaded = allPages.reduce(
|
||||
(acc, p) => acc + (p.documents?.length ?? 0),
|
||||
0,
|
||||
|
|
@ -177,42 +180,22 @@ export function Memories() {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative h-full mx-4 md:mx-24">
|
||||
<MasonryMemoryList
|
||||
documents={allDocuments}
|
||||
error={error}
|
||||
hasMore={hasMore}
|
||||
isLoading={isPending}
|
||||
isLoadingMore={isLoadingMore}
|
||||
loadMoreDocuments={loadMoreDocuments}
|
||||
totalLoaded={totalLoaded}
|
||||
>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
{!isMobile ? (
|
||||
<ConnectAIModal
|
||||
onOpenChange={setShowConnectAIModal}
|
||||
open={showConnectAIModal}
|
||||
>
|
||||
<div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6">
|
||||
<div className="relative z-10 text-slate-200 text-center">
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setShowAddMemoryView(true)
|
||||
setShowConnectAIModal(false)
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Add your first memory
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ConnectAIModal>
|
||||
) : (
|
||||
<div className="relative h-full mx-4 md:mx-24">
|
||||
<MasonryMemoryList
|
||||
documents={allDocuments}
|
||||
error={error}
|
||||
hasMore={hasMore}
|
||||
isLoading={isPending}
|
||||
isLoadingMore={isLoadingMore}
|
||||
loadMoreDocuments={loadMoreDocuments}
|
||||
totalLoaded={totalLoaded}
|
||||
>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
{!isMobile ? (
|
||||
<ConnectAIModal
|
||||
onOpenChange={setShowConnectAIModal}
|
||||
open={showConnectAIModal}
|
||||
>
|
||||
<div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6">
|
||||
<div className="relative z-10 text-slate-200 text-center">
|
||||
<div className="flex flex-col gap-3">
|
||||
|
|
@ -221,6 +204,7 @@ export function Memories() {
|
|||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setShowAddMemoryView(true)
|
||||
setShowConnectAIModal(false)
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
|
|
@ -229,17 +213,34 @@ export function Memories() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</MasonryMemoryList>
|
||||
</ConnectAIModal>
|
||||
) : (
|
||||
<div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6">
|
||||
<div className="relative z-10 text-slate-200 text-center">
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setShowAddMemoryView(true)
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Add your first memory
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</MasonryMemoryList>
|
||||
|
||||
{showAddMemoryView && (
|
||||
<AddMemoryView
|
||||
initialTab="note"
|
||||
onClose={() => setShowAddMemoryView(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
{showAddMemoryView && (
|
||||
<AddMemoryView
|
||||
initialTab="note"
|
||||
onClose={() => setShowAddMemoryView(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { useChat, useCompletion } from "@ai-sdk/react"
|
||||
import { useChat, useCompletion, type UIMessage } from "@ai-sdk/react"
|
||||
import { cn } from "@lib/utils"
|
||||
import { Button } from "@ui/components/button"
|
||||
import { DefaultChatTransport } from "ai"
|
||||
|
|
@ -21,6 +21,7 @@ import { usePersistentChat, useProject } from "@/stores"
|
|||
import { useGraphHighlights } from "@/stores/highlights"
|
||||
import { modelNames, ModelIcon } from "@/lib/models"
|
||||
import { Spinner } from "../../spinner"
|
||||
import { areUIMessageArraysEqual } from "@/stores/chat"
|
||||
|
||||
interface MemoryResult {
|
||||
documentId?: string
|
||||
|
|
@ -247,6 +248,10 @@ export function ChatMessages() {
|
|||
const activeChatIdRef = useRef<string | null>(null)
|
||||
const shouldGenerateTitleRef = useRef<boolean>(false)
|
||||
const hasRunInitialMessageRef = useRef<boolean>(false)
|
||||
const lastSavedMessagesRef = useRef<UIMessage[] | null>(null)
|
||||
const lastSavedActiveIdRef = useRef<string | null>(null)
|
||||
const lastLoadedChatIdRef = useRef<string | null>(null)
|
||||
const lastLoadedMessagesRef = useRef<UIMessage[] | null>(null)
|
||||
|
||||
const { setDocumentIds } = useGraphHighlights()
|
||||
|
||||
|
|
@ -282,6 +287,10 @@ export function ChatMessages() {
|
|||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
lastLoadedMessagesRef.current = messages
|
||||
}, [messages])
|
||||
|
||||
useEffect(() => {
|
||||
activeChatIdRef.current = currentChatId ?? id ?? null
|
||||
}, [currentChatId, id])
|
||||
|
|
@ -326,20 +335,56 @@ export function ChatMessages() {
|
|||
}, [id, currentChatId, setCurrentChatId])
|
||||
|
||||
useEffect(() => {
|
||||
const msgs = getCurrentConversation()
|
||||
if (msgs && msgs.length > 0) {
|
||||
setMessages(msgs)
|
||||
} else if (!currentChatId) {
|
||||
setMessages([])
|
||||
if (currentChatId !== lastLoadedChatIdRef.current) {
|
||||
lastLoadedMessagesRef.current = null
|
||||
lastSavedMessagesRef.current = null
|
||||
}
|
||||
|
||||
if (currentChatId === lastLoadedChatIdRef.current) {
|
||||
setInput("")
|
||||
return
|
||||
}
|
||||
|
||||
const msgs = getCurrentConversation()
|
||||
|
||||
if (msgs && msgs.length > 0) {
|
||||
const currentMessages = lastLoadedMessagesRef.current
|
||||
if (!currentMessages || !areUIMessageArraysEqual(currentMessages, msgs)) {
|
||||
lastLoadedMessagesRef.current = msgs
|
||||
setMessages(msgs)
|
||||
}
|
||||
} else if (!currentChatId) {
|
||||
if (
|
||||
lastLoadedMessagesRef.current &&
|
||||
lastLoadedMessagesRef.current.length > 0
|
||||
) {
|
||||
lastLoadedMessagesRef.current = []
|
||||
setMessages([])
|
||||
}
|
||||
}
|
||||
|
||||
lastLoadedChatIdRef.current = currentChatId
|
||||
setInput("")
|
||||
}, [currentChatId, getCurrentConversation, setMessages])
|
||||
|
||||
useEffect(() => {
|
||||
const activeId = currentChatId ?? id
|
||||
if (activeId && messages.length > 0) {
|
||||
setConversation(activeId, messages)
|
||||
if (!activeId || messages.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (activeId !== lastSavedActiveIdRef.current) {
|
||||
lastSavedMessagesRef.current = null
|
||||
lastSavedActiveIdRef.current = activeId
|
||||
}
|
||||
|
||||
const lastSaved = lastSavedMessagesRef.current
|
||||
if (lastSaved && areUIMessageArraysEqual(lastSaved, messages)) {
|
||||
return
|
||||
}
|
||||
|
||||
lastSavedMessagesRef.current = messages
|
||||
setConversation(activeId, messages)
|
||||
}, [messages, currentChatId, id, setConversation])
|
||||
|
||||
const { complete } = useCompletion({
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { indexedDBStorage } from "./indexeddb-storage"
|
|||
/**
|
||||
* Deep equality check for UIMessage arrays to prevent unnecessary state updates
|
||||
*/
|
||||
function areUIMessageArraysEqual(a: UIMessage[], b: UIMessage[]): boolean {
|
||||
export function areUIMessageArraysEqual(a: UIMessage[], b: UIMessage[]): boolean {
|
||||
if (a === b) return true
|
||||
if (a.length !== b.length) return false
|
||||
|
||||
|
|
|
|||
|
|
@ -38,17 +38,23 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||
// biome-ignore lint/correctness/useExhaustiveDependencies: ignoring the setActiveOrg dependency
|
||||
useEffect(() => {
|
||||
if (session?.session.activeOrganizationId) {
|
||||
authClient.organization.getFullOrganization().then((org) => {
|
||||
// TODO: Uncomment this when we have a way to handle consumer organizations better way
|
||||
//if (org.metadata?.isConsumer === true) {
|
||||
setOrg(org)
|
||||
//} else {
|
||||
// const consumerOrg = orgs?.find((o) => o.metadata?.isConsumer === true)
|
||||
// if (consumerOrg) {
|
||||
// setActiveOrg(consumerOrg.slug)
|
||||
// }
|
||||
//}
|
||||
})
|
||||
authClient.organization
|
||||
.getFullOrganization()
|
||||
.then((org) => {
|
||||
// TODO: Uncomment this when we have a way to handle consumer organizations better way
|
||||
//if (org.metadata?.isConsumer === true) {
|
||||
setOrg(org)
|
||||
//} else {
|
||||
// const consumerOrg = orgs?.find((o) => o.metadata?.isConsumer === true)
|
||||
// if (consumerOrg) {
|
||||
// setActiveOrg(consumerOrg.slug)
|
||||
// }
|
||||
//}
|
||||
})
|
||||
.catch((error) => {
|
||||
// Silently handle organization fetch failures to prevent unhandled rejections
|
||||
console.error("Failed to fetch organization:", error)
|
||||
})
|
||||
}
|
||||
}, [session?.session.activeOrganizationId, orgs])
|
||||
|
||||
|
|
@ -68,7 +74,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
if (pendingMethod) {
|
||||
const now = Date.now()
|
||||
const ts = pendingTsRaw ? Number.parseInt(pendingTsRaw, 10) : NaN
|
||||
const ts = pendingTsRaw ? Number.parseInt(pendingTsRaw, 10) : Number.NaN
|
||||
const isFresh = Number.isFinite(ts) && now - ts < 10 * 60 * 1000 // 10 minutes TTL
|
||||
|
||||
if (isFresh) {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ function PostHogPageTracking() {
|
|||
$current_url: url,
|
||||
path: pathname,
|
||||
search_params: searchParams.toString(),
|
||||
page_type: getPageType(pathname),
|
||||
page_type: getPageType(),
|
||||
org_slug: getOrgSlug(pathname),
|
||||
}
|
||||
|
||||
|
|
@ -39,16 +39,21 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
|||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY
|
||||
if (posthogKey){
|
||||
posthog.init(posthogKey, {
|
||||
api_host: process.env.NEXT_PUBLIC_BACKEND_URL + "/orange",
|
||||
ui_host: "https://us.i.posthog.com",
|
||||
person_profiles: "identified_only",
|
||||
capture_pageview: false,
|
||||
capture_pageleave: true,
|
||||
})}
|
||||
else{
|
||||
console.warn("PostHog API key is not set. PostHog will not be initialized.")
|
||||
const backendUrl =
|
||||
process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai"
|
||||
|
||||
if (posthogKey) {
|
||||
posthog.init(posthogKey, {
|
||||
api_host: `${backendUrl}/orange`,
|
||||
ui_host: "https://us.i.posthog.com",
|
||||
person_profiles: "identified_only",
|
||||
capture_pageview: false,
|
||||
capture_pageleave: true,
|
||||
})
|
||||
} else {
|
||||
console.warn(
|
||||
"PostHog API key is not set. PostHog will not be initialized.",
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
|
@ -75,7 +80,7 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
|||
)
|
||||
}
|
||||
|
||||
function getPageType(pathname: string): string {
|
||||
function getPageType(): string {
|
||||
return "other"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -761,6 +761,7 @@ export const GraphWebGLCanvas = memo<GraphCanvasProps>(
|
|||
}}
|
||||
>
|
||||
<Application
|
||||
preference="webgl"
|
||||
antialias
|
||||
autoDensity
|
||||
backgroundColor={0x0f1419}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,27 @@ export function LoginPage({
|
|||
} catch {}
|
||||
}
|
||||
|
||||
function isNetworkError(error: unknown): boolean {
|
||||
if (!(error instanceof Error)) return false;
|
||||
const message = error.message.toLowerCase();
|
||||
return (
|
||||
message.includes("load failed") ||
|
||||
message.includes("networkerror") ||
|
||||
message.includes("failed to fetch") ||
|
||||
message.includes("network request failed")
|
||||
);
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown): string {
|
||||
if (isNetworkError(error)) {
|
||||
return "Network error. Please check your connection and try again.";
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return "An unexpected error occurred. Please try again.";
|
||||
}
|
||||
|
||||
// If we land back on this page with an error, clear any pending marker
|
||||
useEffect(() => {
|
||||
if (params.get("error")) {
|
||||
|
|
@ -121,13 +142,10 @@ export function LoginPage({
|
|||
method: "magic_link",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
email_domain: email.split("@")[1] || "unknown",
|
||||
is_network_error: isNetworkError(error),
|
||||
});
|
||||
|
||||
setError(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to send login link. Please try again.",
|
||||
);
|
||||
setError(getErrorMessage(error));
|
||||
setIsLoading(false);
|
||||
setIsLoadingEmail(false);
|
||||
return;
|
||||
|
|
@ -321,6 +339,7 @@ export function LoginPage({
|
|||
onClick={() => {
|
||||
if (isLoading) return;
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
posthog.capture("login_attempt", {
|
||||
method: "social",
|
||||
provider: "google",
|
||||
|
|
@ -331,6 +350,19 @@ export function LoginPage({
|
|||
callbackURL: getCallbackURL(),
|
||||
provider: "google",
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Google login error:", error);
|
||||
posthog.capture("login_failed", {
|
||||
method: "social",
|
||||
provider: "google",
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Unknown error",
|
||||
is_network_error: isNetworkError(error),
|
||||
});
|
||||
setError(getErrorMessage(error));
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
|
@ -385,6 +417,7 @@ export function LoginPage({
|
|||
onClick={() => {
|
||||
if (isLoading) return;
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
posthog.capture("login_attempt", {
|
||||
method: "social",
|
||||
provider: "github",
|
||||
|
|
@ -395,6 +428,19 @@ export function LoginPage({
|
|||
callbackURL: getCallbackURL(),
|
||||
provider: "github",
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("GitHub login error:", error);
|
||||
posthog.capture("login_failed", {
|
||||
method: "social",
|
||||
provider: "github",
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Unknown error",
|
||||
is_network_error: isNetworkError(error),
|
||||
});
|
||||
setError(getErrorMessage(error));
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue