mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-22 06:53:57 +00:00
Merge branch 'main' of https://github.com/supermemoryai/supermemory
This commit is contained in:
commit
697315b9dc
11 changed files with 364 additions and 73 deletions
|
|
@ -56,7 +56,7 @@ We welcome contributions from developers of all skill levels! Whether you're fix
|
|||
3. **Set up your environment** by copying `.env.example` to `.env.local`
|
||||
4. **Start developing** with `bun run dev`
|
||||
|
||||
For detailed guidelines, development setup, coding standards, and the complete contribution workflow, please see our [**Contributing Guide**](CONTRIBUTE.md).
|
||||
For detailed guidelines, development setup, coding standards, and the complete contribution workflow, please see our [**Contributing Guide**](CONTRIBUTING.md).
|
||||
|
||||
### Ways to Contribute
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { useEffect, useState } from "react"
|
||||
import "./App.css"
|
||||
import { validateAuthToken } from "../../utils/api"
|
||||
import { MESSAGE_TYPES, STORAGE_KEYS } from "../../utils/constants"
|
||||
import {
|
||||
useDefaultProject,
|
||||
useProjects,
|
||||
useSetDefaultProject,
|
||||
useUserData,
|
||||
} from "../../utils/query-hooks"
|
||||
import type { Project } from "../../utils/types"
|
||||
|
||||
|
|
@ -20,6 +22,7 @@ function App() {
|
|||
"save",
|
||||
)
|
||||
const [autoSearchEnabled, setAutoSearchEnabled] = useState<boolean>(false)
|
||||
const [authInvalidated, setAuthInvalidated] = useState<boolean>(false)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const { data: projects = [], isLoading: loadingProjects } = useProjects({
|
||||
|
|
@ -28,8 +31,12 @@ function App() {
|
|||
const { data: defaultProject } = useDefaultProject({
|
||||
enabled: userSignedIn,
|
||||
})
|
||||
const { data: userData, isLoading: loadingUserData } = useUserData({
|
||||
enabled: userSignedIn,
|
||||
})
|
||||
const setDefaultProjectMutation = useSetDefaultProject()
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: suppress dependency analysis
|
||||
useEffect(() => {
|
||||
const checkAuthStatus = async () => {
|
||||
try {
|
||||
|
|
@ -37,8 +44,28 @@ function App() {
|
|||
STORAGE_KEYS.BEARER_TOKEN,
|
||||
STORAGE_KEYS.AUTO_SEARCH_ENABLED,
|
||||
])
|
||||
const isSignedIn = !!result[STORAGE_KEYS.BEARER_TOKEN]
|
||||
setUserSignedIn(isSignedIn)
|
||||
const hasToken = !!result[STORAGE_KEYS.BEARER_TOKEN]
|
||||
|
||||
if (hasToken) {
|
||||
const isTokenValid = await validateAuthToken()
|
||||
|
||||
if (isTokenValid) {
|
||||
setUserSignedIn(true)
|
||||
setAuthInvalidated(false)
|
||||
} else {
|
||||
await chrome.storage.local.remove([
|
||||
STORAGE_KEYS.BEARER_TOKEN,
|
||||
STORAGE_KEYS.USER_DATA,
|
||||
STORAGE_KEYS.DEFAULT_PROJECT,
|
||||
])
|
||||
queryClient.clear()
|
||||
setUserSignedIn(false)
|
||||
setAuthInvalidated(true)
|
||||
}
|
||||
} else {
|
||||
setUserSignedIn(false)
|
||||
setAuthInvalidated(false)
|
||||
}
|
||||
|
||||
const autoSearchSetting =
|
||||
result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false
|
||||
|
|
@ -46,6 +73,7 @@ function App() {
|
|||
} catch (error) {
|
||||
console.error("Error checking auth status:", error)
|
||||
setUserSignedIn(false)
|
||||
setAuthInvalidated(false)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
|
|
@ -397,6 +425,34 @@ function App() {
|
|||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4 min-h-[200px]">
|
||||
{/* Account Section */}
|
||||
<div>
|
||||
<h3 className="text-base font-semibold text-black mb-3">
|
||||
Account
|
||||
</h3>
|
||||
<div className="p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||
{loadingUserData ? (
|
||||
<div className="text-sm text-gray-500">
|
||||
Loading account data...
|
||||
</div>
|
||||
) : userData?.email ? (
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs font-medium text-black">
|
||||
Email
|
||||
</span>
|
||||
<span className="text-sm text-gray-600">
|
||||
{userData.email}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-gray-500">
|
||||
No email found
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat Integration Section */}
|
||||
<div className="mb-4">
|
||||
<h3 className="text-base font-semibold text-black mb-3">
|
||||
Chat Integration
|
||||
|
|
@ -480,23 +536,37 @@ function App() {
|
|||
</div>
|
||||
) : (
|
||||
<div className="text-center py-2">
|
||||
<div className="mb-8">
|
||||
<h2 className="m-0 mb-4 text-sm font-normal text-black leading-tight">
|
||||
Login to unlock all chrome extension features
|
||||
</h2>
|
||||
{authInvalidated ? (
|
||||
<div className="mb-8">
|
||||
<div className="p-3 mb-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<h2 className="m-0 mb-2 text-sm font-semibold text-red-800 leading-tight">
|
||||
Session Expired
|
||||
</h2>
|
||||
<p className="m-0 text-xs text-red-600 leading-tight">
|
||||
Logged out since authentication was invalidated. Please
|
||||
login again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-8">
|
||||
<h2 className="m-0 mb-4 text-sm font-normal text-black leading-tight">
|
||||
Login to unlock all chrome extension features
|
||||
</h2>
|
||||
|
||||
<ul className="list-none p-0 m-0 text-left">
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Save any page to your supermemory
|
||||
</li>
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Import all your Twitter / X Bookmarks
|
||||
</li>
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Import your ChatGPT Memories
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul className="list-none p-0 m-0 text-left">
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Save any page to your supermemory
|
||||
</li>
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Import all your Twitter / X Bookmarks
|
||||
</li>
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Import your ChatGPT Memories
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-8">
|
||||
<p className="m-0 mb-4 text-sm text-gray-500">
|
||||
|
|
|
|||
|
|
@ -99,6 +99,35 @@ export async function setDefaultProject(project: Project): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if current bearer token is still valid
|
||||
*/
|
||||
export async function validateAuthToken(): Promise<boolean> {
|
||||
try {
|
||||
await makeAuthenticatedRequest<ProjectsResponse>("/v3/projects")
|
||||
return true
|
||||
} catch (error) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return false
|
||||
}
|
||||
console.error("Failed to validate auth token:", error)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user data from storage
|
||||
*/
|
||||
export async function getUserData(): Promise<{ email?: string } | null> {
|
||||
try {
|
||||
const result = await chrome.storage.local.get([STORAGE_KEYS.USER_DATA])
|
||||
return result[STORAGE_KEYS.USER_DATA] || null
|
||||
} catch (error) {
|
||||
console.error("Failed to get user data:", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save memory to Supermemory API
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
|||
import {
|
||||
fetchProjects,
|
||||
getDefaultProject,
|
||||
getUserData,
|
||||
saveMemory,
|
||||
searchMemories,
|
||||
setDefaultProject,
|
||||
|
|
@ -15,6 +16,7 @@ import type { MemoryPayload } from "./types"
|
|||
export const queryKeys = {
|
||||
projects: ["projects"] as const,
|
||||
defaultProject: ["defaultProject"] as const,
|
||||
userData: ["userData"] as const,
|
||||
}
|
||||
|
||||
// Projects Query
|
||||
|
|
@ -37,6 +39,16 @@ export function useDefaultProject(options?: { enabled?: boolean }) {
|
|||
})
|
||||
}
|
||||
|
||||
// User Data Query
|
||||
export function useUserData(options?: { enabled?: boolean }) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.userData,
|
||||
queryFn: getUserData,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
enabled: options?.enabled ?? true,
|
||||
})
|
||||
}
|
||||
|
||||
// Set Default Project Mutation
|
||||
export function useSetDefaultProject() {
|
||||
const queryClient = useQueryClient()
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ export function createToast(state: ToastState): HTMLElement {
|
|||
const icon = document.createElement("div")
|
||||
icon.style.cssText = "width: 20px; height: 20px; flex-shrink: 0;"
|
||||
|
||||
const text = document.createElement("span")
|
||||
text.style.fontWeight = "500"
|
||||
let textElement: HTMLElement = document.createElement("span")
|
||||
textElement.style.fontWeight = "500"
|
||||
|
||||
// Configure toast based on state
|
||||
switch (state) {
|
||||
|
|
@ -113,17 +113,17 @@ export function createToast(state: ToastState): HTMLElement {
|
|||
</svg>
|
||||
`
|
||||
icon.style.animation = "spin 1s linear infinite"
|
||||
text.textContent = "Adding to Memory..."
|
||||
textElement.textContent = "Adding to Memory..."
|
||||
break
|
||||
|
||||
case "success": {
|
||||
const iconUrl = browser.runtime.getURL("/icon-16.png")
|
||||
icon.innerHTML = `<img src="${iconUrl}" width="20" height="20" alt="Success" style="border-radius: 2px;" />`
|
||||
text.textContent = "Added to Memory"
|
||||
textElement.textContent = "Added to Memory"
|
||||
break
|
||||
}
|
||||
|
||||
case "error":
|
||||
case "error": {
|
||||
icon.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10" fill="#ef4444"/>
|
||||
|
|
@ -131,12 +131,29 @@ export function createToast(state: ToastState): HTMLElement {
|
|||
<path d="M9 9L15 15" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
`
|
||||
text.textContent = "Failed to save memory / Make sure you are logged in"
|
||||
const textContainer = document.createElement("div")
|
||||
textContainer.style.cssText =
|
||||
"display: flex; flex-direction: column; gap: 2px;"
|
||||
|
||||
const mainText = document.createElement("span")
|
||||
mainText.style.cssText = "font-weight: 500; line-height: 1.2;"
|
||||
mainText.textContent = "Failed to save memory"
|
||||
|
||||
const helperText = document.createElement("span")
|
||||
helperText.style.cssText =
|
||||
"font-size: 12px; color: #6b7280; font-weight: 400; line-height: 1.2;"
|
||||
helperText.textContent = "Make sure you are logged in"
|
||||
|
||||
textContainer.appendChild(mainText)
|
||||
textContainer.appendChild(helperText)
|
||||
|
||||
textElement = textContainer
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
toast.appendChild(icon)
|
||||
toast.appendChild(text)
|
||||
toast.appendChild(textElement)
|
||||
|
||||
return toast
|
||||
}
|
||||
|
|
@ -433,8 +450,25 @@ export const DOMUtils = {
|
|||
</svg>
|
||||
`
|
||||
icon.style.animation = ""
|
||||
text.textContent =
|
||||
"Failed to save memory / Make sure you are logged in"
|
||||
|
||||
const textContainer = document.createElement("div")
|
||||
textContainer.style.cssText =
|
||||
"display: flex; flex-direction: column; gap: 2px;"
|
||||
|
||||
const mainText = document.createElement("span")
|
||||
mainText.style.cssText = "font-weight: 500; line-height: 1.2;"
|
||||
mainText.textContent = "Failed to save memory"
|
||||
|
||||
const helperText = document.createElement("span")
|
||||
helperText.style.cssText =
|
||||
"font-size: 12px; color: #6b7280; font-weight: 400; line-height: 1.2;"
|
||||
helperText.textContent = "Make sure you are logged in"
|
||||
|
||||
textContainer.appendChild(mainText)
|
||||
textContainer.appendChild(helperText)
|
||||
|
||||
text.innerHTML = ""
|
||||
text.appendChild(textContainer)
|
||||
}
|
||||
|
||||
// Auto-dismiss
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default defineConfig({
|
|||
manifest: {
|
||||
name: "supermemory",
|
||||
homepage_url: "https://supermemory.ai",
|
||||
version: "6.0.002",
|
||||
version: "6.0.003",
|
||||
permissions: ["contextMenus", "storage", "activeTab", "webRequest", "tabs"],
|
||||
host_permissions: [
|
||||
"*://x.com/*",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { useEffect, useState } from "react"
|
|||
import { toast } from "sonner"
|
||||
import { z } from "zod/v4"
|
||||
import { analytics } from "@/lib/analytics"
|
||||
import { cn } from "@lib/utils"
|
||||
|
||||
const clients = {
|
||||
cursor: "Cursor",
|
||||
|
|
@ -79,7 +80,19 @@ export function ConnectAIModal({
|
|||
const setIsOpen = onOpenChange || setInternalIsOpen
|
||||
const [isMigrateDialogOpen, setIsMigrateDialogOpen] = useState(false)
|
||||
const [selectedProject, setSelectedProject] = useState<string | null>("none")
|
||||
const projectId = localStorage.getItem("selectedProject") ?? "default"
|
||||
const [cursorInstallTab, setCursorInstallTab] = useState<
|
||||
"oneClick" | "manual"
|
||||
>("oneClick")
|
||||
|
||||
const [projectId, setProjectId] = useState("default")
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const storedProjectId =
|
||||
localStorage.getItem("selectedProject") ?? "default"
|
||||
setProjectId(storedProjectId)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
analytics.mcpViewOpened()
|
||||
|
|
@ -164,6 +177,10 @@ export function ConnectAIModal({
|
|||
return command
|
||||
}
|
||||
|
||||
function getCursorDeeplink() {
|
||||
return "https://cursor.com/en/install-mcp?name=supermemory-mcp&config=eyJjb21tYW5kIjoibnB4IC15IG1jcC1yZW1vdGUgaHR0cHM6Ly9hcGkuc3VwZXJtZW1vcnkuYWkvbWNwIn0%3D"
|
||||
}
|
||||
|
||||
const copyToClipboard = () => {
|
||||
const command = generateInstallCommand()
|
||||
navigator.clipboard.writeText(command)
|
||||
|
|
@ -252,21 +269,142 @@ export function ConnectAIModal({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 2: Project Selection or MCP URL */}
|
||||
{/* Step 2: One-click Install for Cursor, Project Selection for others, or MCP URL */}
|
||||
{selectedClient && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium">
|
||||
2
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium">
|
||||
2
|
||||
</div>
|
||||
<h3 className="text-sm font-medium">
|
||||
{selectedClient === "cursor"
|
||||
? "Install Supermemory MCP"
|
||||
: selectedClient === "mcp-url"
|
||||
? "MCP Server URL"
|
||||
: "Select Target Project (Optional)"}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"flex-col gap-2 hidden",
|
||||
selectedClient === "cursor" && "flex",
|
||||
)}
|
||||
>
|
||||
{/* Tabs */}
|
||||
<div className="flex justify-end">
|
||||
<div className="flex bg-white/5 rounded-full p-1 border border-white/10">
|
||||
<button
|
||||
className={`px-3 py-1.5 text-xs font-medium rounded-full transition-all ${
|
||||
cursorInstallTab === "oneClick"
|
||||
? "bg-white/10 text-white border border-white/20"
|
||||
: "text-white/60 hover:text-white/80"
|
||||
}`}
|
||||
onClick={() => setCursorInstallTab("oneClick")}
|
||||
type="button"
|
||||
>
|
||||
One Click Install
|
||||
</button>
|
||||
<button
|
||||
className={`px-3 py-1.5 text-xs font-medium rounded-full transition-all ${
|
||||
cursorInstallTab === "manual"
|
||||
? "bg-white/10 text-white border border-white/20"
|
||||
: "text-white/60 hover:text-white/80"
|
||||
}`}
|
||||
onClick={() => setCursorInstallTab("manual")}
|
||||
type="button"
|
||||
>
|
||||
Manual Install
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-sm font-medium">
|
||||
{selectedClient === "mcp-url"
|
||||
? "MCP Server URL"
|
||||
: "Select Target Project (Optional)"}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{selectedClient === "mcp-url" ? (
|
||||
{selectedClient === "cursor" ? (
|
||||
<div className="space-y-4">
|
||||
{/* Tab Content */}
|
||||
{cursorInstallTab === "oneClick" ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col items-center gap-4 p-6 border border-green-500/20 rounded-lg bg-green-500/5">
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-white/80 mb-2">
|
||||
Click the button below to automatically install and
|
||||
configure Supermemory in Cursor
|
||||
</p>
|
||||
<p className="text-xs text-white/50">
|
||||
This will install the MCP server without any
|
||||
additional setup required
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href={getCursorDeeplink()}
|
||||
onClick={() => {
|
||||
analytics.mcpInstallCmdCopied()
|
||||
toast.success("Opening Cursor installer...")
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="Add Supermemory MCP server to Cursor"
|
||||
className="hover:opacity-80 transition-opacity cursor-pointer"
|
||||
height="40"
|
||||
src="https://cursor.com/deeplink/mcp-install-dark.svg"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<p className="text-xs text-white/40 text-center">
|
||||
Make sure you have Cursor installed on your system
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-white/70">
|
||||
Choose a project and follow the installation steps below
|
||||
</p>
|
||||
<div className="max-w-md">
|
||||
<Select
|
||||
disabled={isLoadingProjects}
|
||||
onValueChange={setSelectedProject}
|
||||
value={selectedProject || "none"}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select project" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-black/90 backdrop-blur-xl border-white/10">
|
||||
<SelectItem
|
||||
className="text-white hover:bg-white/10"
|
||||
value="none"
|
||||
>
|
||||
Auto-select project
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
className="text-white hover:bg-white/10"
|
||||
value="sm_project_default"
|
||||
>
|
||||
Default Project
|
||||
</SelectItem>
|
||||
{projects
|
||||
.filter(
|
||||
(p: Project) =>
|
||||
p.containerTag !== "sm_project_default",
|
||||
)
|
||||
.map((project: Project) => (
|
||||
<SelectItem
|
||||
className="text-white hover:bg-white/10"
|
||||
key={project.id}
|
||||
value={project.containerTag}
|
||||
>
|
||||
{project.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : selectedClient === "mcp-url" ? (
|
||||
<div className="space-y-2">
|
||||
<div className="relative">
|
||||
<Input
|
||||
|
|
@ -336,39 +474,47 @@ export function ConnectAIModal({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 3: Command Line */}
|
||||
{selectedClient && selectedClient !== "mcp-url" && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium">
|
||||
3
|
||||
{/* Step 3: Command Line - Show for manual installation or non-cursor clients */}
|
||||
{selectedClient &&
|
||||
selectedClient !== "mcp-url" &&
|
||||
(selectedClient !== "cursor" || cursorInstallTab === "manual") && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium">
|
||||
3
|
||||
</div>
|
||||
<h3 className="text-sm font-medium">
|
||||
{selectedClient === "cursor" &&
|
||||
cursorInstallTab === "manual"
|
||||
? "Manual Installation Command"
|
||||
: "Installation Command"}
|
||||
</h3>
|
||||
</div>
|
||||
<h3 className="text-sm font-medium">Installation Command</h3>
|
||||
|
||||
<div className="relative">
|
||||
<Input
|
||||
className="font-mono text-xs w-full pr-10"
|
||||
readOnly
|
||||
value={generateInstallCommand()}
|
||||
/>
|
||||
<Button
|
||||
className="absolute top-[-1px] right-0 cursor-pointer"
|
||||
onClick={copyToClipboard}
|
||||
variant="ghost"
|
||||
>
|
||||
<CopyIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-white/50">
|
||||
{selectedClient === "cursor" && cursorInstallTab === "manual"
|
||||
? "Copy and run this command in your terminal for manual installation (or switch to the one-click option above)"
|
||||
: "Copy and run this command in your terminal to install the MCP server"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative">
|
||||
<Input
|
||||
className="font-mono text-xs w-full pr-10"
|
||||
readOnly
|
||||
value={generateInstallCommand()}
|
||||
/>
|
||||
<Button
|
||||
className="absolute top-[-1px] right-0 cursor-pointer"
|
||||
onClick={copyToClipboard}
|
||||
variant="ghost"
|
||||
>
|
||||
<CopyIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-white/50">
|
||||
Copy and run this command in your terminal to install the MCP
|
||||
server
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Blurred Command Placeholder */}
|
||||
{/* Blurred Command Placeholder - Only show when no client selected */}
|
||||
{!selectedClient && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ export function BillingView() {
|
|||
<ul className="space-y-2">
|
||||
<li className="flex items-center gap-2 text-sm text-white/90">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
5000 memories
|
||||
Unlimited memories
|
||||
</li>
|
||||
<li className="flex items-center gap-2 text-sm text-white/90">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ export function ProfileView() {
|
|||
<ul className="space-y-2">
|
||||
<li className="flex items-center gap-2 text-sm text-white/90">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
5000 memories
|
||||
Unlimited memories
|
||||
</li>
|
||||
<li className="flex items-center gap-2 text-sm text-white/90">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ const DEFAULT_PROJECT_ID = "sm_project_default"
|
|||
const SEARCH_MEMORY_SHORTCUT_URL =
|
||||
"https://www.icloud.com/shortcuts/f2b5c544372844a38ab4c6900e2a88de"
|
||||
const ADD_MEMORY_SHORTCUT_URL =
|
||||
"https://www.icloud.com/shortcuts/ec33b029b2c7481d89eda7640dbb7688"
|
||||
"https://www.icloud.com/shortcuts/0fd3e855be444845b457f94c78c2c8d9"
|
||||
|
||||
export {
|
||||
BIG_DIMENSIONS_NEW,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue