mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
merge
This commit is contained in:
commit
405bff4d9f
7 changed files with 136 additions and 111 deletions
|
|
@ -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: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<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" />
|
||||
</svg>
|
||||
),
|
||||
icon: <Sun className="size-5" />,
|
||||
},
|
||||
{
|
||||
id: "connections",
|
||||
|
|
|
|||
|
|
@ -138,16 +138,36 @@ export function ChatSidebar({
|
|||
)
|
||||
const pendingFollowUpGenerations = useRef<Set<string>>(new Set())
|
||||
const messagesContainerRef = useRef<HTMLDivElement>(null)
|
||||
const sentQueuedMessageRef = useRef<string | null>(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[]
|
||||
|
|
@ -175,7 +195,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`,
|
||||
api: `${process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai"}/chat/v2`,
|
||||
credentials: "include",
|
||||
body: {
|
||||
metadata: {
|
||||
|
|
@ -383,9 +403,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)
|
||||
|
|
@ -499,14 +523,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]
|
||||
|
|
@ -874,12 +907,11 @@ export function ChatSidebar({
|
|||
)}
|
||||
</div>
|
||||
))}
|
||||
{(status === "submitted" || status === "streaming") &&
|
||||
messages[messages.length - 1]?.role === "user" && (
|
||||
<div className="flex gap-2">
|
||||
<SuperLoader label="Thinking..." />
|
||||
</div>
|
||||
)}
|
||||
{(status === "submitted" || status === "streaming") && (
|
||||
<div className="flex gap-2">
|
||||
<SuperLoader label="Thinking..." />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<Tabs
|
||||
value={viewMode === "list" ? "grid" : viewMode}
|
||||
onValueChange={(v) => setViewMode(v === "grid" ? "list" : v as "graph" | "integrations")}
|
||||
onValueChange={(v) =>
|
||||
setViewMode(v === "grid" ? "list" : (v as "graph" | "integrations"))
|
||||
}
|
||||
>
|
||||
<TabsList className="rounded-full border border-[#161F2C] h-11! z-10!">
|
||||
<TabsTrigger
|
||||
value="grid"
|
||||
className={cn(
|
||||
"rounded-full data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4",
|
||||
"rounded-full data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4 cursor-pointer",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
|
|
@ -171,21 +172,21 @@ export function Header({
|
|||
<TabsTrigger
|
||||
value="graph"
|
||||
className={cn(
|
||||
"rounded-full dark:data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4",
|
||||
"rounded-full dark:data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4 cursor-pointer",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
<LayoutGridIcon className="size-4" />
|
||||
<GraphIcon className="size-4" />
|
||||
Graph
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="integrations"
|
||||
className={cn(
|
||||
"rounded-full dark:data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4",
|
||||
"rounded-full dark:data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4 cursor-pointer",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
<Cable className="size-4" />
|
||||
<Sun className="size-4" />
|
||||
Integrations
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
|
@ -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"
|
||||
>
|
||||
<Cable className="h-4 w-4 text-[#737373]" />
|
||||
<Sun className="h-4 w-4 text-[#737373]" />
|
||||
Integrations
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
|
|
@ -410,7 +411,7 @@ export function Header({
|
|||
</div>
|
||||
<FeedbackModal
|
||||
isOpen={isFeedbackOpen}
|
||||
onClose={() => setIsFeedbackOpen(false)}
|
||||
onClose={() => setFeedbackOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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: (
|
||||
<div className="flex items-center -space-x-1.5">
|
||||
<Image src="/images/plugins/claude-code.svg" alt="Claude Code" width={24} height={24} className="size-6 rounded" />
|
||||
<Image src="/images/plugins/opencode.svg" alt="OpenCode" width={24} height={24} className="size-6 rounded" />
|
||||
<Image src="/images/plugins/clawdbot.svg" alt="ClawdBot" width={24} height={24} className="size-6 rounded" />
|
||||
<Image
|
||||
src="/images/plugins/claude-code.svg"
|
||||
alt="Claude Code"
|
||||
width={24}
|
||||
height={24}
|
||||
className="size-6 rounded"
|
||||
/>
|
||||
<Image
|
||||
src="/images/plugins/opencode.svg"
|
||||
alt="OpenCode"
|
||||
width={24}
|
||||
height={24}
|
||||
className="size-6 rounded"
|
||||
/>
|
||||
<Image
|
||||
src="/images/plugins/clawdbot.svg"
|
||||
alt="ClawdBot"
|
||||
width={24}
|
||||
height={24}
|
||||
className="size-6 rounded"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
|
|
@ -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: (
|
||||
<img
|
||||
src="/onboarding/mcp.png"
|
||||
alt="MCP"
|
||||
className="size-20 h-auto"
|
||||
/>
|
||||
<img src="/onboarding/mcp.png" alt="MCP" className="size-20 h-auto" />
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -98,9 +112,7 @@ const cards: IntegrationCardDef[] = [
|
|||
id: "import",
|
||||
title: "Import Bookmarks",
|
||||
description: "Bring in X/Twitter bookmarks and turn them into memories",
|
||||
icon: (
|
||||
<img src="/onboarding/x.png" alt="X" className="size-10" />
|
||||
),
|
||||
icon: <img src="/onboarding/x.png" alt="X" className="size-10" />,
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -131,46 +143,43 @@ function DetailWrapper({
|
|||
export function IntegrationsView() {
|
||||
const [selectedCard, setSelectedCard] = useState<CardId | null>(null)
|
||||
|
||||
if (selectedCard === "mcp") {
|
||||
return <MCPDetailView onBack={() => setSelectedCard(null)} />
|
||||
}
|
||||
if (selectedCard === "import") {
|
||||
return <XBookmarksDetailView onBack={() => setSelectedCard(null)} />
|
||||
}
|
||||
if (selectedCard === "chrome") {
|
||||
return (
|
||||
<DetailWrapper onBack={() => setSelectedCard(null)}>
|
||||
<ChromeDetail />
|
||||
</DetailWrapper>
|
||||
)
|
||||
}
|
||||
if (selectedCard === "shortcuts") {
|
||||
return (
|
||||
<DetailWrapper onBack={() => setSelectedCard(null)}>
|
||||
<ShortcutsDetail />
|
||||
</DetailWrapper>
|
||||
)
|
||||
}
|
||||
if (selectedCard === "raycast") {
|
||||
return (
|
||||
<DetailWrapper onBack={() => setSelectedCard(null)}>
|
||||
<RaycastDetail />
|
||||
</DetailWrapper>
|
||||
)
|
||||
}
|
||||
if (selectedCard === "connections") {
|
||||
return (
|
||||
<DetailWrapper onBack={() => setSelectedCard(null)}>
|
||||
<ConnectionsDetail />
|
||||
</DetailWrapper>
|
||||
)
|
||||
}
|
||||
if (selectedCard === "plugins") {
|
||||
return (
|
||||
<DetailWrapper onBack={() => setSelectedCard(null)}>
|
||||
<PluginsDetail />
|
||||
</DetailWrapper>
|
||||
)
|
||||
const handleBack = () => setSelectedCard(null)
|
||||
|
||||
switch (selectedCard) {
|
||||
case "mcp":
|
||||
return <MCPDetailView onBack={handleBack} />
|
||||
case "import":
|
||||
return <XBookmarksDetailView onBack={handleBack} />
|
||||
case "chrome":
|
||||
return (
|
||||
<DetailWrapper onBack={handleBack}>
|
||||
<ChromeDetail />
|
||||
</DetailWrapper>
|
||||
)
|
||||
case "shortcuts":
|
||||
return (
|
||||
<DetailWrapper onBack={handleBack}>
|
||||
<ShortcutsDetail />
|
||||
</DetailWrapper>
|
||||
)
|
||||
case "raycast":
|
||||
return (
|
||||
<DetailWrapper onBack={handleBack}>
|
||||
<RaycastDetail />
|
||||
</DetailWrapper>
|
||||
)
|
||||
case "connections":
|
||||
return (
|
||||
<DetailWrapper onBack={handleBack}>
|
||||
<ConnectionsDetail />
|
||||
</DetailWrapper>
|
||||
)
|
||||
case "plugins":
|
||||
return (
|
||||
<DetailWrapper onBack={handleBack}>
|
||||
<PluginsDetail />
|
||||
</DetailWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -178,15 +187,10 @@ export function IntegrationsView() {
|
|||
<div className="max-w-3xl mx-auto">
|
||||
<div className="mb-6 space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Cable className="size-5 text-[#4BA0FA]" />
|
||||
<Sun className="size-5 text-white" />
|
||||
<h2 className="text-white text-xl font-medium">Integrations</h2>
|
||||
</div>
|
||||
<p
|
||||
className={cn(
|
||||
"text-[#8B8B8B] text-sm",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
<p className={cn("text-[#8B8B8B] text-sm", dmSansClassName())}>
|
||||
Connect supermemory to your tools and workflows
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -215,9 +219,7 @@ export function IntegrationsView() {
|
|||
{card.icon}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-white text-sm font-medium">
|
||||
{card.title}
|
||||
</h3>
|
||||
<h3 className="text-white text-sm font-medium">{card.title}</h3>
|
||||
<p
|
||||
className={cn(
|
||||
"text-[#8B8B8B] text-xs leading-relaxed mt-0.5",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ export const feedbackParam = parseAsBoolean.withDefault(false)
|
|||
// View & filter states
|
||||
const viewLiterals = ["graph", "list", "integrations"] as const
|
||||
export type ViewParamValue = (typeof viewLiterals)[number]
|
||||
export const viewParam = parseAsStringLiteral(viewLiterals).withDefault("graph")
|
||||
export const categoriesParam = parseAsArrayOf(parseAsString, ",").withDefault([])
|
||||
export const viewParam = parseAsStringLiteral(viewLiterals).withDefault("list")
|
||||
export const categoriesParam = parseAsArrayOf(parseAsString, ",").withDefault(
|
||||
[],
|
||||
)
|
||||
export const projectParam = parseAsString.withDefault("sm_project_default")
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function DialogContent({
|
|||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-100 sm:max-w-lg",
|
||||
className,
|
||||
)}
|
||||
data-slot="dialog-content"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue