feat: app improvements (#454)

This commit is contained in:
MaheshtheDev 2025-10-06 15:51:31 +00:00
parent 0ca737f364
commit cdfb942fa8
10 changed files with 86 additions and 235 deletions

View file

@ -7,7 +7,7 @@ import { ChatMessages } from "@/components/views/chat/chat-messages"
export default function ChatPage() {
const params = useParams()
const { setCurrentChatId, getCurrentChat } = usePersistentChat()
const { setCurrentChatId } = usePersistentChat()
const chatId = params.id as string
@ -17,17 +17,9 @@ export default function ChatPage() {
}
}, [chatId, setCurrentChatId])
const currentChat = getCurrentChat()
return (
<div className="flex flex-col w-full">
<div className="flex flex-col h-[93vh]">
<div className="flex justify-center w-full">
<h1 className="text-lg font-semibold flex-1 truncate text-center">
{currentChat?.title || "New Chat"}
</h1>
</div>
<div className="flex-1 flex justify-center min-h-0 w-full px-4">
<div className="flex flex-col min-h-0 w-full max-w-4xl">
<ChatMessages />

View file

@ -220,7 +220,7 @@ export function MCPForm() {
<Button
variant="link"
size="lg"
className="text-black/40 font-medium! text-lg underline w-fit px-0! cursor-pointer"
className="text-white not-odd:font-medium! text-lg underline w-fit px-0! cursor-pointer"
onClick={nextStep}
>
Continue
@ -238,7 +238,7 @@ export function MCPForm() {
<Button
variant="link"
size="lg"
className="text-black/40 font-medium! text-lg underline w-fit px-0! cursor-pointer"
className="text-white font-medium! text-lg underline w-fit px-0! cursor-pointer"
onClick={nextStep}
>
Skip For Now

View file

@ -26,7 +26,6 @@ export function OnboardingBackground({ children }: OnboardingBackgroundProps) {
backgroundPosition: "bottom",
backgroundRepeat: "no-repeat",
transform: `scale(${zoomScale})`,
transformOrigin: "center bottom",
}}
/>
<div className="relative z-10 w-full max-w-4xl mx-auto">{children}</div>

View file

@ -62,7 +62,7 @@ export function ChatInput() {
className="w-full text-foreground placeholder-muted-foreground rounded-md outline-none resize-none text-base leading-relaxed px-6 py-4 bg-transparent"
rows={2}
/>
<div className="flex items-center gap-2 w-full justify-between bg-accent py-2 px-3 rounded-b-[14px]">
<div className="flex items-center gap-2 w-full justify-between py-2 px-3 rounded-b-[14px]">
<ProjectSelector />
<Button
onClick={handleSend}

View file

@ -10,7 +10,6 @@ import {
DialogTitle,
} from "@repo/ui/components/dialog"
import { Input } from "@repo/ui/components/input"
import { Label } from "@repo/ui/components/label"
import { Loader2 } from "lucide-react"
import { AnimatePresence, motion } from "motion/react"
import { useState } from "react"
@ -47,7 +46,7 @@ export function CreateProjectDialog({
<AnimatePresence>
{open && (
<Dialog onOpenChange={onOpenChange} open={open}>
<DialogContent className="sm:max-w-2xl bg-[#0f1419] backdrop-blur-xl border-white/10 text-white">
<DialogContent className="sm:max-w-2xl backdrop-blur-xl">
<motion.div
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
@ -55,7 +54,7 @@ export function CreateProjectDialog({
>
<DialogHeader>
<DialogTitle>Create New Project</DialogTitle>
<DialogDescription className="text-white/60">
<DialogDescription>
Give your project a unique name
</DialogDescription>
</DialogHeader>
@ -66,9 +65,7 @@ export function CreateProjectDialog({
initial={{ opacity: 0, y: 10 }}
transition={{ delay: 0.1 }}
>
<Label htmlFor="projectName">Project Name</Label>
<Input
className="bg-white/5 border-white/10 text-white"
id="projectName"
onChange={(e) => setProjectName(e.target.value)}
onKeyDown={(e) => {
@ -79,47 +76,31 @@ export function CreateProjectDialog({
placeholder="My Awesome Project"
value={projectName}
/>
<p className="text-xs text-white/50">
<p className="text-xs">
This will help you organize your memories
</p>
</motion.div>
</div>
<DialogFooter>
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
<Button onClick={handleClose} type="button" variant="outline">
Cancel
</Button>
<Button
disabled={
createProjectMutation.isPending || !projectName.trim()
}
onClick={handleCreate}
type="button"
>
<Button
className="bg-white/5 hover:bg-white/10 border-white/10 text-white"
onClick={handleClose}
type="button"
variant="outline"
>
Cancel
</Button>
</motion.div>
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Button
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
disabled={
createProjectMutation.isPending || !projectName.trim()
}
onClick={handleCreate}
type="button"
>
{createProjectMutation.isPending ? (
<>
<Loader2 className="h-4 w-4 animate-spin mr-2" />
Creating...
</>
) : (
"Create Project"
)}
</Button>
</motion.div>
{createProjectMutation.isPending ? (
<>
<Loader2 className="h-4 w-4 animate-spin mr-2" />
Creating...
</>
) : (
"Create Project"
)}
</Button>
</DialogFooter>
</motion.div>
</DialogContent>

View file

@ -6,11 +6,12 @@ import {
Plus,
SunIcon,
MonitorIcon,
Network,
User,
CreditCard,
Chrome,
LogOut,
WaypointsIcon,
Gauge,
} from "lucide-react"
import {
DropdownMenuContent,
@ -25,17 +26,19 @@ import { useAuth } from "@lib/auth-context"
import { ConnectAIModal } from "./connect-ai-modal"
import { useTheme } from "next-themes"
import { cn } from "@lib/utils"
import { useRouter } from "next/navigation"
import { usePathname, useRouter } from "next/navigation"
import { MCPIcon } from "./menu"
import { authClient } from "@lib/auth"
import { analytics } from "@/lib/analytics"
import { useGraphModal } from "@/stores"
import { useGraphModal, usePersistentChat } from "@/stores"
export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
const { user } = useAuth()
const { theme, setTheme } = useTheme()
const router = useRouter()
const { setIsOpen: setGraphModalOpen } = useGraphModal()
const { getCurrentChat } = usePersistentChat()
const pathname = usePathname()
const handleSignOut = () => {
analytics.userSignedOut()
@ -46,20 +49,31 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
return (
<div className="flex items-center justify-between w-full p-3 md:p-4">
<div className="flex items-center gap-2 md:gap-3 justify-between w-full">
<Link
className="pointer-events-auto"
href={
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://app.supermemory.ai"
}
rel="noopener noreferrer"
>
<LogoFull className="h-8 hidden md:block" />
<Logo className="h-8 md:hidden" />
</Link>
<div className="flex items-center gap-1.5 md:gap-2">
<Link
className="pointer-events-auto"
href={
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://app.supermemory.ai"
}
rel="noopener noreferrer"
>
{getCurrentChat()?.title && pathname.includes("/chat") ? (
<div className="flex items-center gap-4">
<Logo className="h-6 block text-foreground" />
<span className="truncate">{getCurrentChat()?.title}</span>
</div>
) : (
<>
<LogoFull className="h-8 hidden md:block" />
<Logo className="h-8 md:hidden text-foreground" />
</>
)}
</Link>
</div>
<div className="flex items-center gap-1.5 md:gap-3">
<div className="flex items-center gap-1.5 md:gap-2">
<Button
variant="secondary"
size="sm"
@ -76,15 +90,14 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
variant="ghost"
size="sm"
onClick={() => setGraphModalOpen(true)}
className="gap-1.5"
>
<Network className="h-4 w-4" />
<span className="hidden sm:inline">Graph View</span>
<WaypointsIcon className="h-5 w-5" />
{/*<span className="hidden md:inline">Graph View</span>*/}
</Button>
<ConnectAIModal>
<Button variant="ghost" size="sm" className="gap-1.5">
<MCPIcon className="h-4 w-4" />
<span className="hidden lg:inline">Connect to AI (MCP)</span>
{/*<span className="hidden lg:inline">Connect to AI (MCP)</span>*/}
</Button>
</ConnectAIModal>
<DropdownMenu>
@ -103,15 +116,21 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => router.push("/settings")}>
<User className="h-4 w-4 mr-2" />
<User className="h-4 w-4" />
Profile
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => router.push("/settings/billing")}
>
<CreditCard className="h-4 w-4 mr-2" />
<CreditCard className="h-4 w-4" />
Billing
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => router.push("/settings/integrations")}
>
<Gauge className="h-4 w-4" />
Integrations
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
window.open(
@ -121,7 +140,7 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
)
}}
>
<Chrome className="h-4 w-4 mr-2" />
<Chrome className="h-4 w-4" />
Chrome Extension
</DropdownMenuItem>
<DropdownMenuItem
@ -188,7 +207,7 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => handleSignOut()}>
<LogOut className="h-4 w-4 mr-2" />
<LogOut className="h-4 w-4" />
Logout
</DropdownMenuItem>
</DropdownMenuContent>

View file

@ -25,7 +25,7 @@ import {
SelectTrigger,
SelectValue,
} from "@repo/ui/components/select"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { useQuery } from "@tanstack/react-query"
import {
ChevronDown,
FolderIcon,
@ -36,7 +36,6 @@ import {
} from "lucide-react"
import { AnimatePresence, motion } from "motion/react"
import { useState } from "react"
import { toast } from "sonner"
import { useProjectMutations } from "@/hooks/use-project-mutations"
import { useProjectName } from "@/hooks/use-project-name"
import { useProject } from "@/stores"
@ -52,7 +51,6 @@ interface Project {
}
export function ProjectSelector() {
const queryClient = useQueryClient()
const [isOpen, setIsOpen] = useState(false)
const [showCreateDialog, setShowCreateDialog] = useState(false)
const { selectedProject } = useProject()
@ -69,13 +67,6 @@ export function ProjectSelector() {
action: "move",
targetProjectId: DEFAULT_PROJECT_ID,
})
const [expDialog, setExpDialog] = useState<{
open: boolean
projectId: string
}>({
open: false,
projectId: "",
})
const { data: projects = [], isLoading } = useQuery({
queryKey: ["projects"],
@ -91,30 +82,6 @@ export function ProjectSelector() {
staleTime: 30 * 1000,
})
const enableExperimentalMutation = useMutation({
mutationFn: async (projectId: string) => {
const response = await $fetch(
`@post/projects/${projectId}/enable-experimental`,
)
if (response.error) {
throw new Error(
response.error?.message || "Failed to enable experimental mode",
)
}
return response.data
},
onSuccess: () => {
toast.success("Experimental mode enabled for project")
queryClient.invalidateQueries({ queryKey: ["projects"] })
setExpDialog({ open: false, projectId: "" })
},
onError: (error) => {
toast.error("Failed to enable experimental mode", {
description: error instanceof Error ? error.message : "Unknown error",
})
},
})
const handleProjectSelect = (containerTag: string) => {
switchProject(containerTag)
setIsOpen(false)
@ -138,7 +105,7 @@ export function ProjectSelector() {
</span>
<motion.div
animate={{ rotate: isOpen ? 180 : 0 }}
transition={{ duration: 0.15 }}
transition={{ duration: 0.25 }}
>
<ChevronDown className="h-3 w-3" />
</motion.div>
@ -165,33 +132,30 @@ export function ProjectSelector() {
<div className="p-1.5 max-h-64 overflow-y-auto">
<Button
variant="ghost"
className={`flex items-center justify-between p-2 rounded-md transition-colors cursor-pointer ${
className={`flex items-center w-full justify-between p-2 rounded-md transition-colors cursor-pointer ${
selectedProject === DEFAULT_PROJECT_ID
? "bg-accent"
: "hover:bg-accent/50"
: "hover:bg-accent/20"
}`}
onClick={() => handleProjectSelect(DEFAULT_PROJECT_ID)}
>
<div className="flex items-center gap-2">
<FolderIcon className="h-3.5 w-3.5" />
<span className="text-xs font-medium">Default</span>
<span className="text-xs font-medium">Default Project</span>
</div>
</Button>
{/* User Projects */}
{projects
.filter((p: Project) => p.containerTag !== DEFAULT_PROJECT_ID)
.map((project: Project, index: number) => (
<motion.div
.map((project: Project) => (
<div
key={project.id}
className={`flex items-center justify-between p-2 rounded-md transition-colors group ${
selectedProject === project.containerTag
? "bg-accent"
: "hover:bg-accent/50"
}`}
initial={{ opacity: 0, x: -5 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.03 }}
>
<button
className="flex items-center gap-2 flex-1 cursor-pointer"
@ -208,45 +172,18 @@ export function ProjectSelector() {
<div className="flex items-center gap-1">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<motion.button
className="p-1 hover:bg-accent rounded transition-all"
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={(e) => e.stopPropagation()}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
<MoreHorizontal className="h-3 w-3" />
</motion.button>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{/* Show experimental toggle only if NOT experimental and NOT default project */}
{!project.isExperimental &&
project.containerTag !== DEFAULT_PROJECT_ID && (
<DropdownMenuItem
className="text-blue-600 dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300 cursor-pointer text-xs"
onClick={(e) => {
e.stopPropagation()
setExpDialog({
open: true,
projectId: project.id,
})
setIsOpen(false)
}}
>
<div className="h-3 w-3 mr-2 rounded border border-blue-600 dark:border-blue-400" />
Enable Experimental Mode
</DropdownMenuItem>
)}
{project.isExperimental && (
<DropdownMenuItem
className="text-blue-600/50 dark:text-blue-300/50 text-xs"
disabled
>
<div className="h-3 w-3 mr-2 rounded bg-blue-600 dark:bg-blue-400" />
Experimental Mode Active
</DropdownMenuItem>
)}
<DropdownMenuItem
className="text-red-600 dark:text-red-400 hover:text-red-500 dark:hover:text-red-300 cursor-pointer text-xs"
className="cursor-pointer text-xs hover:text-red-500"
onClick={(e) => {
e.stopPropagation()
setDeleteDialog({
@ -262,13 +199,13 @@ export function ProjectSelector() {
setIsOpen(false)
}}
>
<Trash2 className="h-3 w-3 mr-2" />
<Trash2 className="h-3 w-3" />
Delete Project
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</motion.div>
</div>
))}
<motion.div
@ -489,82 +426,6 @@ export function ProjectSelector() {
</Dialog>
)}
</AnimatePresence>
{/* Experimental Mode Confirmation Dialog */}
<AnimatePresence>
{expDialog.open && (
<Dialog
onOpenChange={(open) => setExpDialog({ ...expDialog, open })}
open={expDialog.open}
>
<DialogContent className="sm:max-w-lg">
<motion.div
animate={{ opacity: 1, scale: 1 }}
className="flex flex-col gap-4"
exit={{ opacity: 0, scale: 0.95 }}
initial={{ opacity: 0, scale: 0.95 }}
>
<DialogHeader>
<DialogTitle>Enable Experimental Mode?</DialogTitle>
<DialogDescription>
Experimental mode enables beta features and advanced memory
relationships for this project.
<br />
<br />
<span className="text-yellow-600 dark:text-yellow-400 font-medium">
Warning:
</span>{" "}
This action is{" "}
<span className="text-red-600 dark:text-red-400 font-bold">
irreversible
</span>
. Once enabled, you cannot return to regular mode for this
project.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Button
onClick={() =>
setExpDialog({ open: false, projectId: "" })
}
type="button"
variant="outline"
>
Cancel
</Button>
</motion.div>
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Button
className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white"
disabled={enableExperimentalMutation.isPending}
onClick={() =>
enableExperimentalMutation.mutate(expDialog.projectId)
}
type="button"
>
{enableExperimentalMutation.isPending ? (
<>
<Loader2 className="h-4 w-4 animate-spin mr-2" />
Enabling...
</>
) : (
"Enable Experimental Mode"
)}
</Button>
</motion.div>
</DialogFooter>
</motion.div>
</DialogContent>
</Dialog>
)}
</AnimatePresence>
</div>
)
}

View file

@ -1021,7 +1021,6 @@ export function AddMemoryView({
>
<Label htmlFor="projectName">Project Name</Label>
<Input
className="bg-white/5 border-white/10"
id="projectName"
onChange={(e) => setNewProjectName(e.target.value)}
placeholder="My Awesome Project"

View file

@ -16,7 +16,7 @@ export const Logo = ({
<title>supermemory Logo</title>
<path
d="M205.864 66.263h-76.401V0h-24.684v71.897c0 7.636 3.021 14.97 8.391 20.373l62.383 62.777 17.454-17.564-46.076-46.365h58.948v-24.84l-.015-.015ZM12.872 30.517l46.075 46.365H0v24.84h76.4v66.264h24.685V96.089c0-7.637-3.021-14.97-8.39-20.374l-62.37-62.762-17.453 17.564Z"
fill="#EFEFEF"
fill="currentColor"
/>
</svg>
);

View file

@ -65,7 +65,7 @@
--destructive: oklch(0.6368 0.2078 25.3313);
--border: oklch(0.9366 0.0017 247.8401);
--input: oklch(0.9702 0 0);
--ring: oklch(1 0 0);
--ring: oklch(0 0 0);
--chart-1: oklch(0.7197 0.1448 266.6983);
--chart-2: oklch(0.8101 0.1488 148.2032);
--chart-3: oklch(0.7507 0.1353 58.4182);
@ -125,7 +125,7 @@
--destructive: oklch(0.6368 0.2078 25.3313);
--border: oklch(0.3715 0 0);
--input: oklch(0.262 0.0074 285.8781);
--ring: oklch(0 0 0);
--ring: oklch(1 0 0);
--chart-1: oklch(0.7197 0.1448 266.6983);
--chart-2: oklch(0.8101 0.1488 148.2032);
--chart-3: oklch(0.7507 0.1353 58.4182);