mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-15 23:31:05 +00:00
Merge remote-tracking branch 'origin/main' into vorflux/graph-perf-consolidation
This commit is contained in:
commit
f4ab06ee15
3 changed files with 140 additions and 4 deletions
|
|
@ -21,7 +21,6 @@ import {
|
|||
Check,
|
||||
ChevronDownIcon,
|
||||
HistoryIcon,
|
||||
PanelRightCloseIcon,
|
||||
Plus,
|
||||
SearchIcon,
|
||||
SquarePenIcon,
|
||||
|
|
@ -33,11 +32,12 @@ import { cn } from "@lib/utils"
|
|||
import { dmSansClassName } from "@/lib/fonts"
|
||||
import ChatInput from "./input"
|
||||
import ChatModelSelector from "./model-selector"
|
||||
import { getNovaChatErrorCopy } from "@/lib/chat-stream-error"
|
||||
import { GradientLogo, LogoBgGradient } from "@ui/assets/Logo"
|
||||
import { useProject } from "@/stores"
|
||||
import { useContainerTags } from "@/hooks/use-container-tags"
|
||||
import { getChatSpaceDisplayLabel } from "@/lib/chat-space-label"
|
||||
import type { ModelId } from "@/lib/models"
|
||||
import { modelNames, type ModelId } from "@/lib/models"
|
||||
import { SuperLoader } from "../superloader"
|
||||
import { UserMessage } from "./message/user-message"
|
||||
import { AgentMessage } from "./message/agent-message"
|
||||
|
|
@ -197,11 +197,32 @@ export function ChatSidebar({
|
|||
return () => window.removeEventListener("scroll", handleWindowScroll)
|
||||
}, [isMobile, viewMode])
|
||||
|
||||
const { messages, sendMessage, status, setMessages, stop } = useChat({
|
||||
const {
|
||||
messages,
|
||||
sendMessage,
|
||||
status,
|
||||
setMessages,
|
||||
stop,
|
||||
error,
|
||||
clearError,
|
||||
} = useChat({
|
||||
id: currentChatId ?? undefined,
|
||||
transport: chatTransport,
|
||||
})
|
||||
|
||||
const chatStreamError = useMemo(
|
||||
() => (error ? getNovaChatErrorCopy(error, selectedModel) : null),
|
||||
[error, selectedModel],
|
||||
)
|
||||
|
||||
const handleModelChange = useCallback(
|
||||
(modelId: ModelId) => {
|
||||
setSelectedModel(modelId)
|
||||
clearError()
|
||||
},
|
||||
[clearError],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (pendingThreadLoad && currentChatId === pendingThreadLoad.id) {
|
||||
setMessages(pendingThreadLoad.messages)
|
||||
|
|
@ -550,7 +571,7 @@ export function ChatSidebar({
|
|||
<div className="flex items-center gap-3 min-w-0 flex-1 mr-2">
|
||||
<ChatModelSelector
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={setSelectedModel}
|
||||
onModelChange={handleModelChange}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -826,6 +847,57 @@ export function ChatSidebar({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{chatStreamError && (
|
||||
<div
|
||||
role="alert"
|
||||
className={cn(
|
||||
"mx-4 mb-2 rounded-lg bg-amber-950/40 px-3 py-2 text-sm text-amber-50/95",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
<div className="flex justify-between gap-2 items-start">
|
||||
<div className="min-w-0">
|
||||
<p className="font-medium leading-snug">
|
||||
{chatStreamError.title}
|
||||
</p>
|
||||
<p className="text-xs text-amber-100/70 mt-1 leading-snug">
|
||||
{chatStreamError.body}
|
||||
</p>
|
||||
{chatStreamError.otherModels.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{chatStreamError.otherModels.map((id) => {
|
||||
const m = modelNames[id]
|
||||
return (
|
||||
<Button
|
||||
key={id}
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="h-8 text-xs rounded-full bg-[#141922] border-[#73737333] hover:bg-[#1a2230] text-white/90"
|
||||
onClick={() => {
|
||||
handleModelChange(id)
|
||||
analytics.modelChanged({ model: id })
|
||||
}}
|
||||
>
|
||||
Switch to {m.name} {m.version}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearError}
|
||||
className="shrink-0 p-1 rounded-md text-amber-200/50 hover:text-amber-100/90 hover:bg-white/5"
|
||||
aria-label="Dismiss error"
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ChatInput
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,24 @@
|
|||
|
||||
import * as Sentry from "@sentry/nextjs"
|
||||
|
||||
function sentryShouldDropExpectedNonActionableError(event: {
|
||||
message?: string
|
||||
exception?: { values?: Array<{ type?: string; value?: string }> }
|
||||
}): boolean {
|
||||
const patterns = [
|
||||
/user location is not supported/i,
|
||||
/this email domain is not allowed/i,
|
||||
]
|
||||
const matches = (s: string | undefined) =>
|
||||
s != null && patterns.some((re) => re.test(s))
|
||||
|
||||
if (matches(event.message)) return true
|
||||
for (const ex of event.exception?.values ?? []) {
|
||||
if (matches(ex.value)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Sentry.init({
|
||||
dsn: "https://2451ebfd1a7490f05fa7776482df81b6@o4508385422802944.ingest.us.sentry.io/4509872269819904",
|
||||
|
||||
|
|
@ -25,6 +43,11 @@ Sentry.init({
|
|||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
|
||||
beforeSend(event) {
|
||||
if (sentryShouldDropExpectedNonActionableError(event)) return null
|
||||
return event
|
||||
},
|
||||
})
|
||||
|
||||
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart
|
||||
|
|
|
|||
41
apps/web/lib/chat-stream-error.ts
Normal file
41
apps/web/lib/chat-stream-error.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import type { ModelId } from "@/lib/models"
|
||||
|
||||
const OTHER_MODELS: ModelId[] = ["gpt-5", "claude-sonnet-4.5"]
|
||||
|
||||
function flattenError(e: unknown): string {
|
||||
if (e == null) return ""
|
||||
if (typeof e === "string") return e
|
||||
if (e instanceof Error) {
|
||||
const parts = [e.message]
|
||||
for (let c: unknown = e.cause; c instanceof Error; c = c.cause) {
|
||||
parts.push(c.message)
|
||||
}
|
||||
return parts.join(" ")
|
||||
}
|
||||
return String(e)
|
||||
}
|
||||
|
||||
export function getNovaChatErrorCopy(error: unknown, model: ModelId) {
|
||||
const msg = flattenError(error)
|
||||
const geminiGeo =
|
||||
/user location is not supported/i.test(msg) ||
|
||||
(/failed_precondition/i.test(msg) && /location is not supported/i.test(msg))
|
||||
|
||||
if (geminiGeo) {
|
||||
return {
|
||||
title: "This model isn't available in your region",
|
||||
body: "Gemini can't be used from your location. Try another model above.",
|
||||
otherModels: OTHER_MODELS.filter((id) => id !== model),
|
||||
}
|
||||
}
|
||||
|
||||
const body =
|
||||
msg.length > 200
|
||||
? `${msg.slice(0, 197).trim()}…`
|
||||
: msg || "Try again or switch models."
|
||||
return {
|
||||
title: "Something went wrong",
|
||||
body,
|
||||
otherModels: model === "gemini-2.5-pro" ? OTHER_MODELS : [],
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue