diff --git a/apps/web/app/onboarding/bio-form.tsx b/apps/web/app/onboarding/bio-form.tsx index 63f359ec0..21b0e34bf 100644 --- a/apps/web/app/onboarding/bio-form.tsx +++ b/apps/web/app/onboarding/bio-form.tsx @@ -32,7 +32,46 @@ export function BioForm() { } return (
-
+
+
+ + {bio ? ( + + + + ) : ( + + + + )} + +

Step {getStepNumberFor("bio")} of {totalSteps} @@ -53,45 +92,6 @@ export function BioForm() { value={bio} onChange={(e) => setBio(e.target.value)} /> - - {bio ? ( - - - - ) : ( - - - - )} -

) } diff --git a/apps/web/app/onboarding/extension-form.tsx b/apps/web/app/onboarding/extension-form.tsx index 721acb4fa..75f845573 100644 --- a/apps/web/app/onboarding/extension-form.tsx +++ b/apps/web/app/onboarding/extension-form.tsx @@ -816,7 +816,7 @@ export function ExtensionForm() { const { totalSteps, nextStep, getStepNumberFor } = useOnboarding() return (
-
+

@@ -827,21 +827,29 @@ export function ExtensionForm() { Install the Chrome extension

- {/* Install the Supermemory extension to start saving and organizing everything that matters. */} Bring Supermemory everywhere

-
+
+ Chrome Add to Chrome @@ -891,17 +899,6 @@ export function ExtensionForm() {
-
- -
) } diff --git a/apps/web/app/onboarding/mcp-form.tsx b/apps/web/app/onboarding/mcp-form.tsx index d3152e428..161642c44 100644 --- a/apps/web/app/onboarding/mcp-form.tsx +++ b/apps/web/app/onboarding/mcp-form.tsx @@ -58,7 +58,46 @@ export function MCPForm() { return (
-
+
+
+ + {!isInstalling ? ( + + + + ) : ( + + + + )} + +

Step {getStepNumberFor("mcp")} of {totalSteps} @@ -207,45 +246,6 @@ export function MCPForm() {

- - {!isInstalling ? ( - - - - ) : ( - - - - )} -
) } diff --git a/apps/web/components/graph-dialog.tsx b/apps/web/components/graph-dialog.tsx index 9378b5925..2e74b3b12 100644 --- a/apps/web/components/graph-dialog.tsx +++ b/apps/web/components/graph-dialog.tsx @@ -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, diff --git a/apps/web/components/memories.tsx b/apps/web/components/memories.tsx index 161d4f140..faf146c52 100644 --- a/apps/web/components/memories.tsx +++ b/apps/web/components/memories.tsx @@ -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 ( - <> -
- -
- {!isMobile ? ( - -
-
-
- -
-
-
-
- ) : ( +
+ +
+ {!isMobile ? ( +
@@ -221,6 +204,7 @@ export function Memories() { onClick={(e) => { e.stopPropagation() setShowAddMemoryView(true) + setShowConnectAIModal(false) }} type="button" > @@ -229,17 +213,34 @@ export function Memories() {
- )} -
-
- - {showAddMemoryView && ( - setShowAddMemoryView(false)} - /> - )} -
- + + ) : ( +
+
+
+ +
+
+
+ )} +
+
+ + {showAddMemoryView && ( + setShowAddMemoryView(false)} + /> + )} +
) -} \ No newline at end of file +} diff --git a/apps/web/components/model-selector.tsx b/apps/web/components/model-selector.tsx index d0e299743..f71e7d588 100644 --- a/apps/web/components/model-selector.tsx +++ b/apps/web/components/model-selector.tsx @@ -4,26 +4,7 @@ import { useState } from "react" import { Button } from "@repo/ui/components/button" import { ChevronDown } from "lucide-react" import { motion } from "motion/react" - -const models = [ - { - id: "gpt-5", - name: "GPT 5", - description: "OpenAI's latest model", - }, - { - id: "claude-sonnet-4.5", - name: "Claude Sonnet 4.5", - description: "Anthropic's advanced model", - }, - { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - description: "Google's most capable model", - }, -] as const - -type ModelId = (typeof models)[number]["id"] +import { models, type ModelId, ModelIcon } from "@/lib/models" interface ModelSelectorProps { selectedModel?: ModelId @@ -54,28 +35,7 @@ export function ModelSelector({ onClick={() => !disabled && setIsOpen(!isOpen)} disabled={disabled} > - - - - - - - - - - + {currentModel.name} diff --git a/apps/web/components/views/chat/chat-messages.tsx b/apps/web/components/views/chat/chat-messages.tsx index 22bc8a2a7..0c795ec08 100644 --- a/apps/web/components/views/chat/chat-messages.tsx +++ b/apps/web/components/views/chat/chat-messages.tsx @@ -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" @@ -19,7 +19,9 @@ import { Streamdown } from "streamdown" import { TextShimmer } from "@/components/text-shimmer" 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 @@ -242,17 +244,14 @@ export function ChatMessages() { const [input, setInput] = useState("") const [selectedModel, setSelectedModel] = useState< "gpt-5" | "claude-sonnet-4.5" | "gemini-2.5-pro" - >( - (sessionStorage.getItem(storageKey) as - | "gpt-5" - | "claude-sonnet-4.5" - | "gemini-2.5-pro") || - "gemini-2.5-pro" || - "gemini-2.5-pro", - ) + >("gemini-2.5-pro") const activeChatIdRef = useRef(null) const shouldGenerateTitleRef = useRef(false) const hasRunInitialMessageRef = useRef(false) + const lastSavedMessagesRef = useRef(null) + const lastSavedActiveIdRef = useRef(null) + const lastLoadedChatIdRef = useRef(null) + const lastLoadedMessagesRef = useRef(null) const { setDocumentIds } = useGraphHighlights() @@ -288,11 +287,16 @@ export function ChatMessages() { }, }) + useEffect(() => { + lastLoadedMessagesRef.current = messages + }, [messages]) + useEffect(() => { activeChatIdRef.current = currentChatId ?? id ?? null }, [currentChatId, id]) useEffect(() => { + if (typeof window === "undefined") return if (currentChatId) { const savedModel = sessionStorage.getItem(storageKey) as | "gpt-5" @@ -309,6 +313,7 @@ export function ChatMessages() { }, [currentChatId, storageKey]) useEffect(() => { + if (typeof window === "undefined") return if (currentChatId && !hasRunInitialMessageRef.current) { // Check if there's an initial message from the home page in sessionStorage const storageKey = `chat-initial-${currentChatId}` @@ -330,20 +335,56 @@ export function ChatMessages() { }, [id, currentChatId, setCurrentChatId]) useEffect(() => { + if (currentChatId !== lastLoadedChatIdRef.current) { + lastLoadedMessagesRef.current = null + lastSavedMessagesRef.current = null + } + + if (currentChatId === lastLoadedChatIdRef.current) { + setInput("") + return + } + const msgs = getCurrentConversation() + if (msgs && msgs.length > 0) { - setMessages(msgs) + const currentMessages = lastLoadedMessagesRef.current + if (!currentMessages || !areUIMessageArraysEqual(currentMessages, msgs)) { + lastLoadedMessagesRef.current = msgs + setMessages(msgs) + } } else if (!currentChatId) { - setMessages([]) + 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({ @@ -637,7 +678,17 @@ export function ChatMessages() { className="w-full text-foreground placeholder:text-muted-foreground rounded-md outline-none resize-none text-base leading-relaxed px-3 py-3 bg-transparent" rows={3} /> -
+
+
+ + + {modelNames[selectedModel]} + +