diff --git a/apps/web/app/(app)/settings/page.tsx b/apps/web/app/(app)/settings/page.tsx index 35f4fb609..179c03012 100644 --- a/apps/web/app/(app)/settings/page.tsx +++ b/apps/web/app/(app)/settings/page.tsx @@ -14,6 +14,7 @@ import Support from "@/components/new/settings/support" import { useRouter } from "next/navigation" import { useIsMobile } from "@hooks/use-mobile" import { analytics } from "@/lib/analytics" +import { Sun } from "lucide-react" const TABS = ["account", "integrations", "connections", "support"] as const type SettingsTab = (typeof TABS)[number] @@ -52,23 +53,7 @@ const NAV_ITEMS: NavItem[] = [ id: "integrations", label: "Integrations", description: "Save, sync and search memories across tools", - icon: ( - - ), + icon: , }, { id: "connections", diff --git a/apps/web/components/new/chat/index.tsx b/apps/web/components/new/chat/index.tsx index 657da9455..425dbc322 100644 --- a/apps/web/components/new/chat/index.tsx +++ b/apps/web/components/new/chat/index.tsx @@ -138,16 +138,36 @@ export function ChatSidebar({ ) const pendingFollowUpGenerations = useRef>(new Set()) const messagesContainerRef = useRef(null) + const sentQueuedMessageRef = useRef(null) const { selectedProject } = useProject() const { viewMode } = useViewMode() const { user } = useAuth() const [threadId, setThreadId] = useQueryState("thread", threadParam) - const fallbackChatId = useMemo(() => generateId(), []) + const [fallbackChatId, setFallbackChatId] = useState(() => generateId()) const currentChatId = threadId ?? fallbackChatId + const chatIdRef = useRef(currentChatId) + chatIdRef.current = currentChatId const setCurrentChatId = useCallback( (id: string) => setThreadId(id), [setThreadId], ) + const chatTransport = useMemo( + () => + new DefaultChatTransport({ + api: `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/v2`, + credentials: "include", + prepareSendMessagesRequest: ({ messages }) => ({ + body: { + messages, + metadata: { + chatId: chatIdRef.current, + projectId: selectedProject, + }, + }, + }), + }), + [currentChatId, selectedProject], + ) const [pendingThreadLoad, setPendingThreadLoad] = useState<{ id: string messages: UIMessage[] @@ -174,10 +194,7 @@ export function ChatSidebar({ const { messages, sendMessage, status, setMessages, stop } = useChat({ id: currentChatId ?? undefined, - transport: new DefaultChatTransport({ - api: `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/v2`, - credentials: "include", - }), + transport: chatTransport, onFinish: async (result) => { if (result.message.role !== "assistant") return @@ -376,9 +393,13 @@ export function ChatSidebar({ const handleNewChat = useCallback(() => { analytics.newChatCreated() + const newChatId = generateId() + chatIdRef.current = newChatId + setMessages([]) setThreadId(null) + setFallbackChatId(newChatId) setInput("") - }, [setThreadId]) + }, [setThreadId, setMessages]) const fetchThreads = useCallback(async () => { setIsLoadingThreads(true) @@ -492,14 +513,23 @@ export function ChatSidebar({ isChatOpen && queuedMessage && status !== "submitted" && - status !== "streaming" + status !== "streaming" && + sentQueuedMessageRef.current !== queuedMessage ) { + sentQueuedMessageRef.current = queuedMessage analytics.chatMessageSent({ source: "highlight" }) sendMessage({ text: queuedMessage }) onConsumeQueuedMessage?.() } }, [isChatOpen, queuedMessage, status, sendMessage, onConsumeQueuedMessage]) + // Reset the sent message ref when queued message is consumed + useEffect(() => { + if (!queuedMessage) { + sentQueuedMessageRef.current = null + } + }, [queuedMessage]) + // Scroll to bottom when a new user message is added useEffect(() => { const lastMessage = messages[messages.length - 1] @@ -867,12 +897,11 @@ export function ChatSidebar({ )} ))} - {(status === "submitted" || status === "streaming") && - messages[messages.length - 1]?.role === "user" && ( -
- -
- )} + {(status === "submitted" || status === "streaming") && ( +
+ +
+ )} diff --git a/apps/web/components/new/header.tsx b/apps/web/components/new/header.tsx index be6716b87..efc952a53 100644 --- a/apps/web/components/new/header.tsx +++ b/apps/web/components/new/header.tsx @@ -11,7 +11,7 @@ import { Settings, Home, Code2, - Cable, + Sun, ExternalLink, HelpCircle, MenuIcon, @@ -22,6 +22,7 @@ import { Button } from "@ui/components/button" import { cn } from "@lib/utils" import { dmSansClassName } from "@/lib/fonts" import { Tabs, TabsList, TabsTrigger } from "@ui/components/tabs" +import { GraphIcon } from "@/components/new/integration-icons" import { DropdownMenu, DropdownMenuContent, @@ -48,18 +49,18 @@ interface HeaderProps { onOpenSearch?: () => void } -export function Header({ - onAddMemory, - onOpenChat, - onOpenSearch, -}: HeaderProps) { +export function Header({ onAddMemory, onOpenChat, onOpenSearch }: HeaderProps) { const { user } = useAuth() const { selectedProject } = useProject() const { switchProject } = useProjectMutations() const router = useRouter() const isMobile = useIsMobile() const { resetOrgOnboarded } = useOrgOnboarding() - const [isFeedbackOpen, setIsFeedbackOpen] = useQueryState("feedback", feedbackParam) + const [feedbackOpen, setFeedbackOpen] = useQueryState( + "feedback", + feedbackParam, + ) + const isFeedbackOpen = feedbackOpen ?? false const { viewMode, setViewMode } = useViewMode() const handleTryOnboarding = () => { @@ -67,9 +68,7 @@ export function Header({ router.push("/onboarding?step=input&flow=welcome") } - const handleFeedback = () => { - setIsFeedbackOpen(true) - } + const handleFeedback = () => setFeedbackOpen(true) const displayName = user?.displayUsername || @@ -155,13 +154,15 @@ export function Header({ {!isMobile && ( setViewMode(v === "grid" ? "list" : v as "graph" | "integrations")} + onValueChange={(v) => + setViewMode(v === "grid" ? "list" : (v as "graph" | "integrations")) + } > @@ -171,21 +172,21 @@ export function Header({ - + Graph - + Integrations @@ -232,7 +233,7 @@ export function Header({ onClick={() => setViewMode("integrations")} className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2" > - + Integrations setIsFeedbackOpen(false)} + onClose={() => setFeedbackOpen(false)} /> ) diff --git a/apps/web/components/new/integration-icons.tsx b/apps/web/components/new/integration-icons.tsx index 54567db2e..4603346c4 100644 --- a/apps/web/components/new/integration-icons.tsx +++ b/apps/web/components/new/integration-icons.tsx @@ -1,4 +1,7 @@ import Image from "next/image" +import { Rotate3d } from "lucide-react" + +export { Rotate3d as GraphIcon } export function ChromeIcon({ className }: { className?: string }) { return ( diff --git a/apps/web/components/new/integrations-view.tsx b/apps/web/components/new/integrations-view.tsx index 5b798c2b9..507244b57 100644 --- a/apps/web/components/new/integrations-view.tsx +++ b/apps/web/components/new/integrations-view.tsx @@ -2,7 +2,7 @@ import { useState } from "react" import { cn } from "@lib/utils" -import { dmSansClassName, dmSans125ClassName } from "@/lib/fonts" +import { dmSansClassName } from "@/lib/fonts" import { Button } from "@ui/components/button" import { MCPDetailView } from "@/components/new/mcp-modal/mcp-detail-view" import { XBookmarksDetailView } from "@/components/new/onboarding/x-bookmarks-detail-view" @@ -17,7 +17,7 @@ import { RaycastIcon, } from "@/components/new/integration-icons" import { GoogleDrive, Notion, OneDrive } from "@ui/assets/icons" -import { Cable, ArrowLeft } from "lucide-react" +import { ArrowLeft, Sun } from "lucide-react" import Image from "next/image" type CardId = @@ -45,9 +45,27 @@ const cards: IntegrationCardDef[] = [ pro: true, icon: (
- Claude Code - OpenCode - ClawdBot + Claude Code + OpenCode + ClawdBot
), }, @@ -69,11 +87,7 @@ const cards: IntegrationCardDef[] = [ title: "Connect to AI", description: "Set up MCP to use your memory in Cursor, Claude, and more", icon: ( - MCP + MCP ), }, { @@ -98,9 +112,7 @@ const cards: IntegrationCardDef[] = [ id: "import", title: "Import Bookmarks", description: "Bring in X/Twitter bookmarks and turn them into memories", - icon: ( - X - ), + icon: X, }, ] @@ -131,46 +143,43 @@ function DetailWrapper({ export function IntegrationsView() { const [selectedCard, setSelectedCard] = useState(null) - if (selectedCard === "mcp") { - return setSelectedCard(null)} /> - } - if (selectedCard === "import") { - return setSelectedCard(null)} /> - } - if (selectedCard === "chrome") { - return ( - setSelectedCard(null)}> - - - ) - } - if (selectedCard === "shortcuts") { - return ( - setSelectedCard(null)}> - - - ) - } - if (selectedCard === "raycast") { - return ( - setSelectedCard(null)}> - - - ) - } - if (selectedCard === "connections") { - return ( - setSelectedCard(null)}> - - - ) - } - if (selectedCard === "plugins") { - return ( - setSelectedCard(null)}> - - - ) + const handleBack = () => setSelectedCard(null) + + switch (selectedCard) { + case "mcp": + return + case "import": + return + case "chrome": + return ( + + + + ) + case "shortcuts": + return ( + + + + ) + case "raycast": + return ( + + + + ) + case "connections": + return ( + + + + ) + case "plugins": + return ( + + + + ) } return ( @@ -178,15 +187,10 @@ export function IntegrationsView() {
- +

Integrations

-

+

Connect supermemory to your tools and workflows

@@ -215,9 +219,7 @@ export function IntegrationsView() { {card.icon}
-

- {card.title} -

+

{card.title}