From e86104b645bd92ba242f26e2565609b54001e20d Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Mon, 6 Oct 2025 16:25:38 +0000 Subject: [PATCH] feat: multiple models & ui improvements (#455) --- .../(navigation)/settings/support/page.tsx | 2 +- apps/web/components/chat-input.tsx | 52 +++++-- .../components/chrome-extension-button.tsx | 8 +- apps/web/components/header.tsx | 37 +++-- apps/web/components/model-selector.tsx | 146 ++++++++++++++++++ .../components/views/chat/chat-messages.tsx | 31 +++- apps/web/components/views/profile.tsx | 2 +- packages/ui/components/tooltip.tsx | 4 +- 8 files changed, 253 insertions(+), 29 deletions(-) create mode 100644 apps/web/components/model-selector.tsx diff --git a/apps/web/app/(navigation)/settings/support/page.tsx b/apps/web/app/(navigation)/settings/support/page.tsx index de4b3c78..7dc2a207 100644 --- a/apps/web/app/(navigation)/settings/support/page.tsx +++ b/apps/web/app/(navigation)/settings/support/page.tsx @@ -69,7 +69,7 @@ export default function SupportPage() { What's included in the Pro plan?

- Pro includes 5,000 memories (vs 200 in free), 10 connections to + Pro includes unlimited memories (vs 200 in free), 10 connections to external services like Google Drive and Notion, advanced search features, and priority support.

diff --git a/apps/web/components/chat-input.tsx b/apps/web/components/chat-input.tsx index 8a6edbf8..38f89815 100644 --- a/apps/web/components/chat-input.tsx +++ b/apps/web/components/chat-input.tsx @@ -1,20 +1,44 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { useRouter } from "next/navigation" import { generateId } from "@lib/generate-id" import { usePersistentChat } from "@/stores/chat" import { ArrowUp } from "lucide-react" import { Button } from "@ui/components/button" import { ProjectSelector } from "./project-selector" +import { ModelSelector } from "./model-selector" import { useAuth } from "@lib/auth-context" export function ChatInput() { const [message, setMessage] = useState("") + const [selectedModel, setSelectedModel] = useState< + "gpt-5" | "claude-sonnet-4.5" | "gemini-2.5-pro" + >("gemini-2.5-pro") const router = useRouter() const { setCurrentChatId } = usePersistentChat() const { user } = useAuth() + useEffect(() => { + const savedModel = localStorage.getItem("selectedModel") as + | "gpt-5" + | "claude-sonnet-4.5" + | "gemini-2.5-pro" + if ( + savedModel && + ["gpt-5", "claude-sonnet-4.5", "gemini-2.5-pro"].includes(savedModel) + ) { + setSelectedModel(savedModel) + } + }, []) + + const handleModelChange = ( + modelId: "gpt-5" | "claude-sonnet-4.5" | "gemini-2.5-pro", + ) => { + setSelectedModel(modelId) + localStorage.setItem("selectedModel", modelId) + } + const handleSend = () => { if (!message.trim()) return @@ -22,8 +46,8 @@ export function ChatInput() { setCurrentChatId(newChatId) - // Store the initial message in sessionStorage for the chat page to pick up sessionStorage.setItem(`chat-initial-${newChatId}`, message.trim()) + sessionStorage.setItem(`chat-model-${newChatId}`, selectedModel) router.push(`/chat/${newChatId}`) @@ -64,15 +88,21 @@ export function ChatInput() { />
- +
+ + +
diff --git a/apps/web/components/chrome-extension-button.tsx b/apps/web/components/chrome-extension-button.tsx index 8f2b74c8..3f7510e3 100644 --- a/apps/web/components/chrome-extension-button.tsx +++ b/apps/web/components/chrome-extension-button.tsx @@ -26,7 +26,11 @@ export function ChromeExtensionButton() { useEffect(() => { const dismissed = localStorage.getItem("chrome-extension-dismissed") === "true" + const minimized = + localStorage.getItem("chrome-extension-minimized") === "true" + setIsDismissed(dismissed) + setIsMinimized(minimized) const checkExtension = () => { const message = { action: "check-extension" } @@ -35,9 +39,10 @@ export function ChromeExtensionButton() { setIsExtensionInstalled(false) setIsChecking(false) // Auto-minimize after 3 seconds if extension is not installed and not dismissed - if (!dismissed) { + if (!dismissed && !minimized) { setTimeout(() => { setIsMinimized(true) + localStorage.setItem("chrome-extension-minimized", "true") }, 3000) } }, 1000) @@ -79,6 +84,7 @@ export function ChromeExtensionButton() { const handleDismiss = () => { localStorage.setItem("chrome-extension-dismissed", "true") + localStorage.removeItem("chrome-extension-minimized") setIsDismissed(true) } diff --git a/apps/web/components/header.tsx b/apps/web/components/header.tsx index 0d3682c0..7e6c800a 100644 --- a/apps/web/components/header.tsx +++ b/apps/web/components/header.tsx @@ -22,6 +22,7 @@ import { import { DropdownMenuItem } from "@ui/components/dropdown-menu" import { DropdownMenu } from "@ui/components/dropdown-menu" import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar" +import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/components/tooltip" import { useAuth } from "@lib/auth-context" import { ConnectAIModal } from "./connect-ai-modal" import { useTheme } from "next-themes" @@ -86,19 +87,31 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) { c - + + + + + +

Graph View

+
+
- + + + + + +

Connect to AI (MCP)

+
+
diff --git a/apps/web/components/model-selector.tsx b/apps/web/components/model-selector.tsx new file mode 100644 index 00000000..38b68f9f --- /dev/null +++ b/apps/web/components/model-selector.tsx @@ -0,0 +1,146 @@ +"use client" + +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"] + +interface ModelSelectorProps { + selectedModel?: ModelId + onModelChange?: (modelId: ModelId) => void + disabled?: boolean +} + +export function ModelSelector({ + selectedModel = "gemini-2.5-pro", + onModelChange, + disabled = false, +}: ModelSelectorProps) { + const [isOpen, setIsOpen] = useState(false) + + const currentModel = models.find((m) => m.id === selectedModel) || models[0] + + const handleModelSelect = (modelId: ModelId) => { + onModelChange?.(modelId) + setIsOpen(false) + } + + return ( +
+ + + {isOpen && ( + <> + + ))} +
+ + + )} + + ) +} diff --git a/apps/web/components/views/chat/chat-messages.tsx b/apps/web/components/views/chat/chat-messages.tsx index db0ae209..2224005e 100644 --- a/apps/web/components/views/chat/chat-messages.tsx +++ b/apps/web/components/views/chat/chat-messages.tsx @@ -216,20 +216,32 @@ export function ChatMessages() { getCurrentChat, } = usePersistentChat() + const storageKey = `chat-model-${currentChatId}` + 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") const activeChatIdRef = useRef(null) const shouldGenerateTitleRef = useRef(false) const hasRunInitialMessageRef = useRef(false) const { setDocumentIds } = useGraphHighlights() + console.log("selectedModel", selectedModel) + const { messages, sendMessage, status, stop, setMessages, id, regenerate } = useChat({ id: currentChatId ?? undefined, transport: new DefaultChatTransport({ api: `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat`, credentials: "include", - body: { metadata: { projectId: selectedProject } }, + body: { + metadata: { + projectId: selectedProject, + model: selectedModel, + }, + }, }), maxSteps: 2, onFinish: (result) => { @@ -254,6 +266,23 @@ export function ChatMessages() { activeChatIdRef.current = currentChatId ?? id ?? null }, [currentChatId, id]) + useEffect(() => { + if (currentChatId) { + const savedModel = sessionStorage.getItem(storageKey) as + | "gpt-5" + | "claude-sonnet-4.5" + | "gemini-2.5-pro" + + if ( + savedModel && + ["gpt-5", "claude-sonnet-4.5", "gemini-2.5-pro"].includes(savedModel) + ) { + console.log("savedModel", savedModel) + setSelectedModel(savedModel) + } + } + }, [currentChatId]) + useEffect(() => { if (currentChatId && !hasRunInitialMessageRef.current) { // Check if there's an initial message from the home page in sessionStorage diff --git a/apps/web/components/views/profile.tsx b/apps/web/components/views/profile.tsx index 5aa21231..cd4e6085 100644 --- a/apps/web/components/views/profile.tsx +++ b/apps/web/components/views/profile.tsx @@ -301,7 +301,7 @@ export function ProfileView() {
    -
  • +
  • Unlimited memories
  • diff --git a/packages/ui/components/tooltip.tsx b/packages/ui/components/tooltip.tsx index 32a78f06..daea283d 100644 --- a/packages/ui/components/tooltip.tsx +++ b/packages/ui/components/tooltip.tsx @@ -43,7 +43,7 @@ function TooltipContent({ {children} - + );