Skip to content

Commit b439e7e

Browse files
committed
fix: chat messages saving broken (supermemoryai#730)
1. New chat messages saving to old thread (race condition) 2. Feedback modal null handling issue (boolean | null not coerced to boolean) 3. Wrong icon on Integrations tab (was Cable, now Sun) 4. Wrong icon on Graph tab (was LayoutGridIcon, now GraphIcon) 5. Missing cursor pointer on header tabs 6. Default view was "graph" instead of "list"
1 parent 727f177 commit b439e7e

7 files changed

Lines changed: 136 additions & 114 deletions

File tree

apps/web/app/(app)/settings/page.tsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Support from "@/components/new/settings/support"
1414
import { useRouter } from "next/navigation"
1515
import { useIsMobile } from "@hooks/use-mobile"
1616
import { analytics } from "@/lib/analytics"
17+
import { Sun } from "lucide-react"
1718

1819
const TABS = ["account", "integrations", "connections", "support"] as const
1920
type SettingsTab = (typeof TABS)[number]
@@ -52,23 +53,7 @@ const NAV_ITEMS: NavItem[] = [
5253
id: "integrations",
5354
label: "Integrations",
5455
description: "Save, sync and search memories across tools",
55-
icon: (
56-
<svg
57-
xmlns="http://www.w3.org/2000/svg"
58-
width="20"
59-
height="20"
60-
viewBox="0 0 24 24"
61-
fill="none"
62-
stroke="currentColor"
63-
strokeWidth="2"
64-
strokeLinecap="round"
65-
strokeLinejoin="round"
66-
aria-hidden="true"
67-
>
68-
<circle cx="12" cy="12" r="3" />
69-
<path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83" />
70-
</svg>
71-
),
56+
icon: <Sun className="size-5" />,
7257
},
7358
{
7459
id: "connections",

apps/web/components/new/chat/index.tsx

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -138,16 +138,36 @@ export function ChatSidebar({
138138
)
139139
const pendingFollowUpGenerations = useRef<Set<string>>(new Set())
140140
const messagesContainerRef = useRef<HTMLDivElement>(null)
141+
const sentQueuedMessageRef = useRef<string | null>(null)
141142
const { selectedProject } = useProject()
142143
const { viewMode } = useViewMode()
143144
const { user } = useAuth()
144145
const [threadId, setThreadId] = useQueryState("thread", threadParam)
145-
const fallbackChatId = useMemo(() => generateId(), [])
146+
const [fallbackChatId, setFallbackChatId] = useState(() => generateId())
146147
const currentChatId = threadId ?? fallbackChatId
148+
const chatIdRef = useRef(currentChatId)
149+
chatIdRef.current = currentChatId
147150
const setCurrentChatId = useCallback(
148151
(id: string) => setThreadId(id),
149152
[setThreadId],
150153
)
154+
const chatTransport = useMemo(
155+
() =>
156+
new DefaultChatTransport({
157+
api: `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/v2`,
158+
credentials: "include",
159+
prepareSendMessagesRequest: ({ messages }) => ({
160+
body: {
161+
messages,
162+
metadata: {
163+
chatId: chatIdRef.current,
164+
projectId: selectedProject,
165+
},
166+
},
167+
}),
168+
}),
169+
[currentChatId, selectedProject],
170+
)
151171
const [pendingThreadLoad, setPendingThreadLoad] = useState<{
152172
id: string
153173
messages: UIMessage[]
@@ -174,10 +194,7 @@ export function ChatSidebar({
174194

175195
const { messages, sendMessage, status, setMessages, stop } = useChat({
176196
id: currentChatId ?? undefined,
177-
transport: new DefaultChatTransport({
178-
api: `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/v2`,
179-
credentials: "include",
180-
}),
197+
transport: chatTransport,
181198
onFinish: async (result) => {
182199
if (result.message.role !== "assistant") return
183200

@@ -376,9 +393,13 @@ export function ChatSidebar({
376393

377394
const handleNewChat = useCallback(() => {
378395
analytics.newChatCreated()
396+
const newChatId = generateId()
397+
chatIdRef.current = newChatId
398+
setMessages([])
379399
setThreadId(null)
400+
setFallbackChatId(newChatId)
380401
setInput("")
381-
}, [setThreadId])
402+
}, [setThreadId, setMessages])
382403

383404
const fetchThreads = useCallback(async () => {
384405
setIsLoadingThreads(true)
@@ -492,14 +513,23 @@ export function ChatSidebar({
492513
isChatOpen &&
493514
queuedMessage &&
494515
status !== "submitted" &&
495-
status !== "streaming"
516+
status !== "streaming" &&
517+
sentQueuedMessageRef.current !== queuedMessage
496518
) {
519+
sentQueuedMessageRef.current = queuedMessage
497520
analytics.chatMessageSent({ source: "highlight" })
498521
sendMessage({ text: queuedMessage })
499522
onConsumeQueuedMessage?.()
500523
}
501524
}, [isChatOpen, queuedMessage, status, sendMessage, onConsumeQueuedMessage])
502525

526+
// Reset the sent message ref when queued message is consumed
527+
useEffect(() => {
528+
if (!queuedMessage) {
529+
sentQueuedMessageRef.current = null
530+
}
531+
}, [queuedMessage])
532+
503533
// Scroll to bottom when a new user message is added
504534
useEffect(() => {
505535
const lastMessage = messages[messages.length - 1]
@@ -867,12 +897,11 @@ export function ChatSidebar({
867897
)}
868898
</div>
869899
))}
870-
{(status === "submitted" || status === "streaming") &&
871-
messages[messages.length - 1]?.role === "user" && (
872-
<div className="flex gap-2">
873-
<SuperLoader label="Thinking..." />
874-
</div>
875-
)}
900+
{(status === "submitted" || status === "streaming") && (
901+
<div className="flex gap-2">
902+
<SuperLoader label="Thinking..." />
903+
</div>
904+
)}
876905
</div>
877906
</div>
878907

apps/web/components/new/header.tsx

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
Settings,
1212
Home,
1313
Code2,
14-
Cable,
14+
Sun,
1515
ExternalLink,
1616
HelpCircle,
1717
MenuIcon,
@@ -22,6 +22,7 @@ import { Button } from "@ui/components/button"
2222
import { cn } from "@lib/utils"
2323
import { dmSansClassName } from "@/lib/fonts"
2424
import { Tabs, TabsList, TabsTrigger } from "@ui/components/tabs"
25+
import { GraphIcon } from "@/components/new/integration-icons"
2526
import {
2627
DropdownMenu,
2728
DropdownMenuContent,
@@ -48,28 +49,26 @@ interface HeaderProps {
4849
onOpenSearch?: () => void
4950
}
5051

51-
export function Header({
52-
onAddMemory,
53-
onOpenChat,
54-
onOpenSearch,
55-
}: HeaderProps) {
52+
export function Header({ onAddMemory, onOpenChat, onOpenSearch }: HeaderProps) {
5653
const { user } = useAuth()
5754
const { selectedProject } = useProject()
5855
const { switchProject } = useProjectMutations()
5956
const router = useRouter()
6057
const isMobile = useIsMobile()
6158
const { resetOrgOnboarded } = useOrgOnboarding()
62-
const [isFeedbackOpen, setIsFeedbackOpen] = useQueryState("feedback", feedbackParam)
59+
const [feedbackOpen, setFeedbackOpen] = useQueryState(
60+
"feedback",
61+
feedbackParam,
62+
)
63+
const isFeedbackOpen = feedbackOpen ?? false
6364
const { viewMode, setViewMode } = useViewMode()
6465

6566
const handleTryOnboarding = () => {
6667
resetOrgOnboarded()
6768
router.push("/onboarding?step=input&flow=welcome")
6869
}
6970

70-
const handleFeedback = () => {
71-
setIsFeedbackOpen(true)
72-
}
71+
const handleFeedback = () => setFeedbackOpen(true)
7372

7473
const displayName =
7574
user?.displayUsername ||
@@ -155,13 +154,15 @@ export function Header({
155154
{!isMobile && (
156155
<Tabs
157156
value={viewMode === "list" ? "grid" : viewMode}
158-
onValueChange={(v) => setViewMode(v === "grid" ? "list" : v as "graph" | "integrations")}
157+
onValueChange={(v) =>
158+
setViewMode(v === "grid" ? "list" : (v as "graph" | "integrations"))
159+
}
159160
>
160161
<TabsList className="rounded-full border border-[#161F2C] h-11! z-10!">
161162
<TabsTrigger
162163
value="grid"
163164
className={cn(
164-
"rounded-full data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4",
165+
"rounded-full data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4 cursor-pointer",
165166
dmSansClassName(),
166167
)}
167168
>
@@ -171,21 +172,21 @@ export function Header({
171172
<TabsTrigger
172173
value="graph"
173174
className={cn(
174-
"rounded-full dark:data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4",
175+
"rounded-full dark:data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4 cursor-pointer",
175176
dmSansClassName(),
176177
)}
177178
>
178-
<LayoutGridIcon className="size-4" />
179+
<GraphIcon className="size-4" />
179180
Graph
180181
</TabsTrigger>
181182
<TabsTrigger
182183
value="integrations"
183184
className={cn(
184-
"rounded-full dark:data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4",
185+
"rounded-full dark:data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4 cursor-pointer",
185186
dmSansClassName(),
186187
)}
187188
>
188-
<Cable className="size-4" />
189+
<Sun className="size-4" />
189190
Integrations
190191
</TabsTrigger>
191192
</TabsList>
@@ -232,7 +233,7 @@ export function Header({
232233
onClick={() => setViewMode("integrations")}
233234
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
234235
>
235-
<Cable className="h-4 w-4 text-[#737373]" />
236+
<Sun className="h-4 w-4 text-[#737373]" />
236237
Integrations
237238
</DropdownMenuItem>
238239
<DropdownMenuItem
@@ -410,7 +411,7 @@ export function Header({
410411
</div>
411412
<FeedbackModal
412413
isOpen={isFeedbackOpen}
413-
onClose={() => setIsFeedbackOpen(false)}
414+
onClose={() => setFeedbackOpen(false)}
414415
/>
415416
</div>
416417
)

apps/web/components/new/integration-icons.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import Image from "next/image"
2+
import { Rotate3d } from "lucide-react"
3+
4+
export { Rotate3d as GraphIcon }
25

36
export function ChromeIcon({ className }: { className?: string }) {
47
return (

0 commit comments

Comments
 (0)