From 795f28771f4944b5ab3302672d3004b06233b67f Mon Sep 17 00:00:00 2001 From: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com> Date: Fri, 8 Aug 2025 19:58:42 +0530 Subject: [PATCH 01/12] feat: enhance document loading and error handling across components - Introduced LoadingSpinner and ErrorState components for consistent loading and error feedback. - Updated DocumentDialog, AppSidebar, and various chat components to utilize new loading and error handling mechanisms. - Improved user experience by providing visual feedback during data fetching and error states. - Refactored useQuery hooks to include loading and error states for better data management. --- src/components/app-sidebar/index.tsx | 62 +++++++--- src/components/chat/input/document-list.tsx | 36 +++++- src/components/chat/input/toolbar/index.tsx | 27 +++- .../chat/input/toolbar/projects-dropdown.tsx | 96 +++++++++------ src/components/chat/messages/index.tsx | 35 +++++- src/components/chat/messages/user-message.tsx | 100 +++++++++++---- src/components/chat/panels/mcp/index.tsx | 34 +++++- .../chat/panels/projects/chat-list.tsx | 28 ++++- .../chat/panels/projects/details.tsx | 32 ++++- .../chat/panels/projects/document-list.tsx | 44 +++++-- src/components/chat/panels/projects/list.tsx | 33 ++++- src/components/document-dialog.tsx | 106 ++++++++++++---- src/components/topnav.tsx | 37 ++++-- src/components/ui/accordion.tsx | 6 +- src/components/ui/error-state.tsx | 115 ++++++++++++++++++ src/components/ui/loading-spinner.tsx | 28 +++++ src/hooks/chats/use-chats.ts | 14 ++- src/hooks/chats/use-documents.ts | 3 + src/hooks/chats/use-mcp.ts | 10 +- src/hooks/chats/use-messages.ts | 11 +- src/hooks/chats/use-stream.ts | 14 ++- src/routes/__root.tsx | 58 +++++---- src/routes/auth.tsx | 4 +- src/routes/chat.$chatId.lazy.tsx | 25 +++- src/routes/settings/apiKeys.tsx | 34 +++--- src/routes/settings/profile.tsx | 27 +++- temp.md | 40 +++--- 27 files changed, 865 insertions(+), 194 deletions(-) create mode 100644 src/components/ui/error-state.tsx create mode 100644 src/components/ui/loading-spinner.tsx diff --git a/src/components/app-sidebar/index.tsx b/src/components/app-sidebar/index.tsx index c27f0c19..d066cf8b 100644 --- a/src/components/app-sidebar/index.tsx +++ b/src/components/app-sidebar/index.tsx @@ -8,11 +8,20 @@ import { SidebarGroupContent, } from "@/components/ui/sidebar"; import { useNavigate } from "@tanstack/react-router"; -import { SearchIcon, XIcon, PlusIcon, FolderIcon } from "lucide-react"; +import { + SearchIcon, + XIcon, + PlusIcon, + FolderIcon, + PinIcon, + HistoryIcon, +} from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ChatItem } from "@/components/app-sidebar/chat-item"; import { useInfiniteChats, useSearchChats } from "@/hooks/chats/use-chats"; +import { LoadingSpinner } from "@/components/ui/loading-spinner"; +import { ErrorState } from "@/components/ui/error-state"; import type { Doc } from "../../../convex/_generated/dataModel"; import { Accordion, @@ -26,7 +35,14 @@ import { useAtom } from "jotai"; export function AppSidebar() { const navigate = useNavigate(); const { pinnedChats, historyChats, status, loadMore } = useInfiniteChats(); - const { searchQuery, setSearchQuery, searchResults } = useSearchChats(); + const { + searchQuery, + setSearchQuery, + searchResults, + isSearching, + isLoadingSearch, + isSearchError, + } = useSearchChats(); const loadMoreRef = React.useRef(null); const [pinnedChatsAccordionOpen, setPinnedChatsAccordionOpen] = useAtom( pinnedChatsAccordionOpenAtom @@ -82,7 +98,6 @@ export function AppSidebar() { }, [loadMore, status]); // Determine whether to show search results or regular chat lists - const isSearching = searchQuery.trim().length > 0; const searchPinnedChats = isSearching ? searchResults.filter((chat) => chat.pinned) : []; @@ -98,7 +113,7 @@ export function AppSidebar() { 0bs - + -
+
@@ -151,7 +166,7 @@ export function AppSidebar() { {displayPinnedChats.length > 0 && ( - + - +
- Pinned - + + + Pinned + +
- +
{displayPinnedChats.map((chat) => renderChatItem(chat))} @@ -179,13 +197,27 @@ export function AppSidebar() { )} - + - - {isSearching ? "Search Results" : "History"} + + {!isSearching && } + {isSearching ? "Search Results" : "Previous Chats"}
-
+
+ {isSearching && isLoadingSearch && ( +
+ +
+ )} + {isSearching && isSearchError && ( +
+ +
+ )} {displayHistoryChats.map((chat) => renderChatItem(chat))} {!isSearching && status === "CanLoadMore" && ( ))} {/* Render project name with X button on hover */} - {project && ( + {isLoadingProject && ( + + )} + {isProjectError && ( + + )} + {project && !isLoadingProject && !isProjectError && (
- {projects?.page.slice(0, 3).map((project) => ( - { - if (chatId === "new") { - setNewChat((prev) => ({ ...prev, projectId: project._id })); - } else { - updateChatMutation({ - chatId, - updates: { - projectId: project._id, - }, - }); - } - onCloseDropdown(); - setResizePanelOpen(true); - setSelectedPanelTab("projects"); - }} - > - {isFetchingProjects ?
Fetching...
: project.name} -
- ))} - - { - e.preventDefault(); - onCloseDropdown(); - setProjectDialogOpen(true); - }} - > - - Add New Project - + {isProjectsError ? ( + + ) : ( + <> + {isLoadingProjects ? ( + + + + Loading projects... + + + ) : ( + projects?.page?.slice(0, 3).map((project: any) => ( + { + if (chatId === "new") { + setNewChat((prev) => ({ + ...prev, + projectId: project._id, + })); + } else { + updateChatMutation({ + chatId, + updates: { + projectId: project._id, + }, + }); + } + onCloseDropdown(); + setResizePanelOpen(true); + setSelectedPanelTab("projects"); + }} + > + {project.name} + + )) + )} + + { + e.preventDefault(); + onCloseDropdown(); + setProjectDialogOpen(true); + }} + > + + Add New Project + + + )}
); diff --git a/src/components/chat/messages/index.tsx b/src/components/chat/messages/index.tsx index fdf1cd5d..249cc549 100644 --- a/src/components/chat/messages/index.tsx +++ b/src/components/chat/messages/index.tsx @@ -1,4 +1,5 @@ import { useMessages } from "../../../hooks/chats/use-messages"; +import { ErrorState } from "@/components/ui/error-state"; import { useMemo } from "react"; import { ScrollArea } from "@/components/ui/scroll-area"; import { MessagesList } from "./messages"; @@ -7,10 +8,12 @@ import { TriangleAlertIcon } from "lucide-react"; import type { Id } from "../../../../convex/_generated/dataModel"; import { streamStatusAtom, userLoadableAtom } from "@/store/chatStore"; import { useAtomValue } from "jotai"; +import { LoadingSpinner } from "@/components/ui/loading-spinner"; export const ChatMessages = ({ chatId }: { chatId: Id<"chats"> | "new" }) => { const userLoadable = useAtomValue(userLoadableAtom); - const { isLoading, isEmpty } = useMessages({ chatId }); + const { isLoading, isEmpty, isError, error, isStreamError, streamError } = + useMessages({ chatId }); const streamStatus = useAtomValue(streamStatusAtom); @@ -38,11 +41,41 @@ export const ChatMessages = ({ chatId }: { chatId: Id<"chats"> | "new" }) => { if (isLoading) { return (
+
Loading messages...
); } + if (isError || error) { + return ( +
+ +
+ ); + } + + if (isStreamError || streamError) { + return ( +
+ +
+ ); + } + if (isEmpty) { return (
diff --git a/src/components/chat/messages/user-message.tsx b/src/components/chat/messages/user-message.tsx index ac954a13..e9453c98 100644 --- a/src/components/chat/messages/user-message.tsx +++ b/src/components/chat/messages/user-message.tsx @@ -6,6 +6,8 @@ import type { MessageGroup, } from "../../../../convex/chatMessages/helpers"; import { Button } from "@/components/ui/button"; +import { LoadingSpinner } from "@/components/ui/loading-spinner"; +import { ErrorState } from "@/components/ui/error-state"; import { documentDialogOpenAtom } from "@/store/chatStore"; import { useSetAtom } from "jotai"; import { api } from "../../../../convex/_generated/api"; @@ -27,15 +29,44 @@ const DocumentButton = ({ fileId: string; setDocumentDialogOpen: (id: Id<"documents"> | undefined) => void; }) => { - const { data: documentData } = useQuery({ + const { + data: documentData, + isLoading: isLoadingDocument, + isError: isDocumentError, + error: documentError, + } = useQuery({ ...convexQuery(api.documents.queries.get, { documentId: fileId as Id<"documents">, }), }); + if (isLoadingDocument) { + return ( +
+ +
+ ); + } + + if (isDocumentError || documentError) { + return ( +
+ +
+ ); + } + return ( +
+ + +
+ + {doc.name} + ); })} @@ -193,7 +251,7 @@ export const UserMessage = memo( }, [content, item.message._id, setDocumentDialogOpen]); return ( -
+
{isEditing ? (
setEditedText(e.target.value)} minHeight={32} maxHeight={120} - className="bg-transparent resize-none border-none min-w-96 max-w-full ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 outline-none focus-visible:outline-none text-base" + className="bg-transparent resize-none border-none min-w-[100vw] max-w-full ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 outline-none focus-visible:outline-none text-base" autoFocus placeholder="Edit your message..." /> @@ -221,7 +279,7 @@ export const UserMessage = memo( {renderedContent} )} -
+
{ - const { getAllMCPs, toggleMCP, handleDelete, restartMCP } = useMCPs(); + const { + getAllMCPs, + toggleMCP, + handleDelete, + restartMCP, + isLoading, + isError, + error, + } = useMCPs(); const mcps = getAllMCPs(); @@ -17,7 +27,27 @@ export const MCPPanel = () => {
- {mcps?.page.map((mcp) => ( + {isLoading && ( +
+ +
+ )} + {isError || + (error && ( +
+ +
+ ))} + {mcps?.page?.length === 0 && mcps && !isLoading && !isError && ( +
+
Currently no MCPs are running.
+
+ )} + {mcps?.page?.map((mcp) => ( { const navigate = useNavigate(); - const { data: chats } = useQuery({ + const { + data: chats, + isLoading: isLoadingChats, + isError: isChatsError, + error: chatsError, + } = useQuery({ ...convexQuery(api.chats.queries.getByProjectId, { projectId }), }); + if (isLoadingChats) { + return ( +
+ +
+ ); + } + + if (isChatsError || chatsError) { + return ( +
+ +
+ ); + } + if (!chats || chats.length === 0) { return ( diff --git a/src/components/chat/panels/projects/details.tsx b/src/components/chat/panels/projects/details.tsx index 8c762c30..dae86051 100644 --- a/src/components/chat/panels/projects/details.tsx +++ b/src/components/chat/panels/projects/details.tsx @@ -3,6 +3,8 @@ import { useQuery, useMutation } from "@tanstack/react-query"; import { convexQuery, useConvexMutation } from "@convex-dev/react-query"; import { api } from "../../../../../convex/_generated/api"; import { XIcon } from "lucide-react"; +import { LoadingSpinner } from "@/components/ui/loading-spinner"; +import { ErrorState } from "@/components/ui/error-state"; import { Button } from "@/components/ui/button"; import { AutosizeTextarea } from "@/components/ui/autosize-textarea"; import { useDebouncedCallback } from "use-debounce"; @@ -19,7 +21,12 @@ export const ProjectDetails = ({ projectId }: ProjectDetailsProps) => { const chatId = useAtomValue(chatIdAtom); const navigate = useNavigate(); const router = useRouter(); - const { data: project } = useQuery({ + const { + data: project, + isLoading: isLoadingProject, + isError: isProjectError, + error: projectError, + } = useQuery({ ...convexQuery( api.projects.queries.get, projectId ? { projectId } : "skip" @@ -42,6 +49,29 @@ export const ProjectDetails = ({ projectId }: ProjectDetailsProps) => { }); }, 1000); + if (isLoadingProject) { + return ( +
+ +
+ ); + } + + if (isProjectError || projectError) { + return ( +
+ +
+ ); + } + if (!project) return null; return ( diff --git a/src/components/chat/panels/projects/document-list.tsx b/src/components/chat/panels/projects/document-list.tsx index 4202b95f..ec75fcc0 100644 --- a/src/components/chat/panels/projects/document-list.tsx +++ b/src/components/chat/panels/projects/document-list.tsx @@ -5,13 +5,20 @@ import { api } from "../../../../../convex/_generated/api"; import type { Id } from "../../../../../convex/_generated/dataModel"; import { ProjectDocumentListItem } from "./document-list-item"; import { Toggle } from "@/components/ui/toggle"; +import { LoadingSpinner } from "@/components/ui/loading-spinner"; +import { ErrorState } from "@/components/ui/error-state"; export function ProjectDocumentList({ projectId, }: { projectId: Id<"projects">; }) { - const { data: projectDocuments } = useQuery({ + const { + data: projectDocuments, + isLoading: isLoadingProjectDocs, + isError: isProjectDocumentsError, + error: projectDocumentsError, + } = useQuery({ ...convexQuery( api.projectDocuments.queries.getAll, projectId @@ -22,6 +29,7 @@ export function ProjectDocumentList({ : "skip" ), }); + const { mutate: toggleSelectAll, isPending: isTogglingSelectAll } = useMutation({ mutationFn: useConvexMutation( @@ -30,7 +38,7 @@ export function ProjectDocumentList({ }); const handleSelectAll = async (checked: boolean) => { - await toggleSelectAll({ + toggleSelectAll({ projectId, selected: checked, }); @@ -57,7 +65,7 @@ export function ProjectDocumentList({ handleSelectAll(pressed)} - disabled={isTogglingSelectAll} + disabled={isTogglingSelectAll || isLoadingProjectDocs} className="w-36 gap-2 cursor-pointer rounded-lg flex items-center justify-center" variant={allSelected ? "default" : "outline"} > @@ -84,12 +92,30 @@ export function ProjectDocumentList({
- {projectDocuments.projectDocuments.map((projectDocument) => ( - - ))} + {isLoadingProjectDocs ? ( +
+ +

+ Loading project documents... +

+
+ ) : isProjectDocumentsError || projectDocumentsError ? ( +
+ +
+ ) : ( + projectDocuments.projectDocuments.map((projectDocument) => ( + + )) + )}
); diff --git a/src/components/chat/panels/projects/list.tsx b/src/components/chat/panels/projects/list.tsx index 704075ab..de65e040 100644 --- a/src/components/chat/panels/projects/list.tsx +++ b/src/components/chat/panels/projects/list.tsx @@ -2,6 +2,8 @@ import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { PlusIcon, TrashIcon } from "lucide-react"; +import { LoadingSpinner } from "@/components/ui/loading-spinner"; +import { ErrorState } from "@/components/ui/error-state"; import { useQuery, useMutation } from "@tanstack/react-query"; import { convexQuery, useConvexMutation } from "@convex-dev/react-query"; import { api } from "../../../../../convex/_generated/api"; @@ -16,7 +18,12 @@ export const ProjectsList = () => { const chatId = useAtomValue(chatIdAtom); const navigate = useNavigate(); const router = useRouter(); - const { data: allProjects } = useQuery({ + const { + data: allProjects, + isLoading: isLoadingProjects, + isError: isProjectsError, + error: projectsError, + } = useQuery({ ...convexQuery(api.projects.queries.getAll, { paginationOpts: { numItems: 20, cursor: null }, }), @@ -34,6 +41,30 @@ export const ProjectsList = () => { const isOnProjectsRoute = useLocation().pathname === "/projects"; + if (isLoadingProjects) { + return ( +
+
+ + Loading projects... +
+
+ ); + } + + if (isProjectsError || projectsError) { + return ( +
+ +
+ ); + } + return ( <>
{ const setDocumentDialogOpen = useSetAtom(documentDialogOpenAtom); const [previewUrl, setPreviewUrl] = useState(null); - const { data: document } = useQuery({ + const { + data: document, + isLoading: isLoadingDocument, + isError: isDocumentError, + error: documentError, + } = useQuery({ ...convexQuery( api.documents.queries.get, documentDialogOpen ? { documentId: documentDialogOpen } : "skip" @@ -44,37 +51,40 @@ export const DocumentDialog = () => { setPreviewUrl(null); const loadPreviewUrl = async () => { if (!document) return; - switch (tag) { - case "image": - case "pdf": - case "file": { - // Only files need download URL - const url = await generateDownloadUrl({ - documentId: document._id!, - }); - setPreviewUrl(url); - break; - } - case "url": - case "site": { - setPreviewUrl(document.key as string); - break; - } - case "youtube": { - setPreviewUrl(`https://www.youtube.com/embed/${document.key}`); - break; - } - default: - if (["file", "text", "github"].includes(document.type)) { + try { + switch (tag) { + case "image": + case "pdf": + case "file": { const url = await generateDownloadUrl({ documentId: document._id!, }); setPreviewUrl(url); break; - } else { + } + case "url": + case "site": { setPreviewUrl(document.key as string); + break; + } + case "youtube": { + setPreviewUrl(`https://www.youtube.com/embed/${document.key}`); + break; } - break; + default: + if (["file", "text", "github"].includes(document.type)) { + const url = await generateDownloadUrl({ + documentId: document._id!, + }); + setPreviewUrl(url); + break; + } else { + setPreviewUrl(document.key as string); + } + break; + } + } catch (e) { + setPreviewUrl(null); } }; loadPreviewUrl(); @@ -85,6 +95,42 @@ export const DocumentDialog = () => { return null; } + if (isLoadingDocument) { + return ( + setDocumentDialogOpen(undefined)} + > + + +
+ Loading document... +
+
+
+ ); + } + + if (isDocumentError || documentError) { + return ( + setDocumentDialogOpen(undefined)} + > + +
+ +
+
+
+ ); + } + const handleDownload = async () => { if (!document || tag !== "file") return; const url = await generateDownloadUrl({ @@ -131,6 +177,16 @@ export const DocumentDialog = () => { )}
+ {isDocumentError || + (documentError && ( +
+ +
+ ))} {previewUrl && (
{(() => { diff --git a/src/components/topnav.tsx b/src/components/topnav.tsx index 2768a2ef..0f994662 100644 --- a/src/components/topnav.tsx +++ b/src/components/topnav.tsx @@ -10,11 +10,17 @@ import { PanelRightCloseIcon, PlusIcon, Settings2Icon } from "lucide-react"; import { PanelRightOpenIcon } from "lucide-react"; import { Button } from "./ui/button"; import { useAtom, useAtomValue, useSetAtom } from "jotai"; -import { useLocation, useNavigate, useParams } from "@tanstack/react-router"; +import { + Navigate, + useLocation, + useNavigate, + useParams, +} from "@tanstack/react-router"; import { useQuery } from "@tanstack/react-query"; import { convexQuery } from "@convex-dev/react-query"; import { api } from "../../convex/_generated/api"; import { useEffect } from "react"; +import { LoadingSpinner } from "@/components/ui/loading-spinner"; export function TopNav() { const [resizePanelOpen, setResizePanelOpen] = useAtom(resizePanelOpenAtom); @@ -25,10 +31,18 @@ export function TopNav() { const setUser = useSetAtom(userAtom); const location = useLocation(); - const { data: user } = useQuery({ + const { + data: user, + isError: isErrorUser, + isLoading: isLoadingUser, + } = useQuery({ ...convexQuery(api.auth.getUser, {}), }); + if (isErrorUser) { + return ; + } + useEffect(() => { if (user) { setUser(user); @@ -40,14 +54,17 @@ export function TopNav() { const isOnChatRoute = !!params.chatId; const isSettingsRoute = location.pathname.startsWith("/settings"); - // Global shortcut for toggling resizable panel (Ctrl/Cmd+I) + // Minimal global shortcut for toggling resizable panel (Ctrl/Cmd+I) useEffect(() => { if (!isOnChatRoute) return; const handleKeyDown = (event: KeyboardEvent) => { - if ( - event.key === "i" && - (event.metaKey || event.ctrlKey) - ) { + const tag = (event.target as HTMLElement)?.tagName; + const isEditable = + tag === "INPUT" || + tag === "TEXTAREA" || + (event.target as HTMLElement)?.isContentEditable; + if (isEditable) return; + if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "i") { event.preventDefault(); setResizePanelOpen((open) => { if (!open) setSelectedArtifact(undefined); @@ -55,7 +72,6 @@ export function TopNav() { }); } }; - window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [isOnChatRoute, setResizePanelOpen, setSelectedArtifact]); @@ -82,6 +98,11 @@ export function TopNav() {
+ {isLoadingUser && ( +
+ +
+ )} {!resizePanelOpen ? ( ); }; @@ -102,7 +102,7 @@ const EditingDocumentList = ({ if (isLoadingDocuments) { return (
- Loading… +
); } @@ -110,7 +110,10 @@ const EditingDocumentList = ({ if (isDocumentsError) { return (
- +
); } @@ -251,7 +254,7 @@ export const UserMessage = memo( }, [content, item.message._id, setDocumentDialogOpen]); return ( -
+
{isEditing ? (
{
)} - {isError || - (error && ( -
- -
- ))} + {(isError || error) && ( +
+ +
+ )} {mcps?.page?.length === 0 && mcps && !isLoading && !isError && (
Currently no MCPs are running.
diff --git a/src/components/chat/panels/projects/chat-list.tsx b/src/components/chat/panels/projects/chat-list.tsx index 1de3ae0e..afd230a7 100644 --- a/src/components/chat/panels/projects/chat-list.tsx +++ b/src/components/chat/panels/projects/chat-list.tsx @@ -19,20 +19,23 @@ export const ProjectChatList = ({ projectId }: ProjectChatListProps) => { data: chats, isLoading: isLoadingChats, isError: isChatsError, - error: chatsError, } = useQuery({ ...convexQuery(api.chats.queries.getByProjectId, { projectId }), }); if (isLoadingChats) { return ( -
+
); } - if (isChatsError || chatsError) { + if (isChatsError) { return (
{ ); } - if (isProjectError || projectError) { + if (isProjectError) { return ( -
+
diff --git a/src/components/chat/panels/projects/list.tsx b/src/components/chat/panels/projects/list.tsx index de65e040..fbe72e36 100644 --- a/src/components/chat/panels/projects/list.tsx +++ b/src/components/chat/panels/projects/list.tsx @@ -52,7 +52,7 @@ export const ProjectsList = () => { ); } - if (isProjectsError || projectsError) { + if (isProjectsError) { return (
{ api.documents.queries.get, documentDialogOpen ? { documentId: documentDialogOpen } : "skip" ), - enabled: !!documentDialogOpen, }); const { mutateAsync: generateDownloadUrl } = useMutation({ @@ -95,6 +94,25 @@ export const DocumentDialog = () => { return null; } + const handleDownload = async () => { + if (!document || tag !== "file") return; + const url = await generateDownloadUrl({ + documentId: document._id!, + }); + if (url) { + window.open(url, "_blank"); + } + }; + + const handleOpen = () => { + if (!document) return; + if (tag === "url" || tag === "site") { + window.open(document.key as string, "_blank"); + } else if (tag === "youtube") { + window.open(`https://youtube.com/watch?v=${document.key}`, "_blank"); + } + }; + if (isLoadingDocument) { return ( { ); } - const handleDownload = async () => { - if (!document || tag !== "file") return; - const url = await generateDownloadUrl({ - documentId: document._id!, - }); - if (url) { - window.open(url, "_blank"); - } - }; - - const handleOpen = () => { - if (!document) return; - if (tag === "url" || tag === "site") { - window.open(document.key as string, "_blank"); - } else if (tag === "youtube") { - window.open(`https://youtube.com/watch?v=${document.key}`, "_blank"); - } - }; - return ( - - {showIcon && ( + {showIcon && ( + - )} - + + )} {children}
diff --git a/src/components/ui/error-state.tsx b/src/components/ui/error-state.tsx index 3eb82199..050d76db 100644 --- a/src/components/ui/error-state.tsx +++ b/src/components/ui/error-state.tsx @@ -68,7 +68,14 @@ const ErrorState = React.forwardRef( return ""; }, [error, description]); - if (!error && !title && !description) return null; + // Only return null if nothing at all would render + if ( + !showIcon && + (!showTitle || !title) && + (!showDescription || !errorMessage) + ) { + return null; + } return (
+
-

{label}

+ {label ?

{label}

: null}
); } diff --git a/src/hooks/chats/use-documents.ts b/src/hooks/chats/use-documents.ts index e7f32d6a..30117448 100644 --- a/src/hooks/chats/use-documents.ts +++ b/src/hooks/chats/use-documents.ts @@ -14,7 +14,6 @@ export const useRemoveDocument = () => { api.chats.queries.get, chatId !== "new" ? { chatId } : "skip" ), - enabled: chatId !== "new", }); const { mutate: updateChatInputMutation } = useMutation({ diff --git a/src/hooks/chats/use-messages.ts b/src/hooks/chats/use-messages.ts index ae080c79..e8cb84a9 100644 --- a/src/hooks/chats/use-messages.ts +++ b/src/hooks/chats/use-messages.ts @@ -60,8 +60,8 @@ export const useMessages = ({ chatId }: { chatId: Id<"chats"> | "new" }) => { isEmpty: currentThread.length === 0, isError: isMessagesError, error: messagesError, - isStreamError: Boolean((streamData as any)?.isError), - streamError: (streamData as any)?.error, + isStreamError: Boolean(streamData?.isError), + streamError: streamData?.error, }; }; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index ade6b982..ef7c0e26 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -14,6 +14,7 @@ import { useAtomValue, useSetAtom } from "jotai"; import { useConvexAuth } from "convex/react"; import { AppSidebar } from "@/components/app-sidebar"; import { TopNav } from "@/components/topnav"; +import { useEffect } from "react"; export const Route = createRootRoute({ head: () => ({ @@ -72,12 +73,11 @@ export const Route = createRootRoute({ // Ensure sidebar and right resizable panel are closed on settings pages // and keep them hidden there. - if (isSettingsRoute && sidebarOpen) { - setSidebarOpen(false); - } - if (isSettingsRoute) { + useEffect(() => { + if (!isSettingsRoute) return; + sidebarOpen && setSidebarOpen(false); setResizePanelOpen(false); - } + }, [isSettingsRoute, sidebarOpen, setSidebarOpen, setResizePanelOpen]); if (isLoading) { return ( From b062b969c7c56f93fb873d5229a64af0a8f30530 Mon Sep 17 00:00:00 2001 From: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com> Date: Mon, 18 Aug 2025 17:20:27 +0530 Subject: [PATCH 03/12] chore: update subproject commit reference in MCP service --- services/mcps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/mcps b/services/mcps index a202aeaa..1d83b412 160000 --- a/services/mcps +++ b/services/mcps @@ -1 +1 @@ -Subproject commit a202aeaa8424127d354886fc270959c85856b928 +Subproject commit 1d83b4120aea6ec474f184d2720dc0a507149143 From 3e246db6b670577255cd8d4bc37c0f28d69343c0 Mon Sep 17 00:00:00 2001 From: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com> Date: Mon, 18 Aug 2025 18:57:11 +0530 Subject: [PATCH 04/12] fix: clean up code formatting and improve observer logic in AppSidebar and useDocuments hooks - Removed unnecessary line breaks and adjusted formatting for better readability. - Enhanced observer logic in AppSidebar to unobserve the loadMoreElement when it is intersecting. - Updated useRemoveDocument and useUploadDocuments hooks for consistent code style. --- src/components/app-sidebar/index.tsx | 5 +++-- src/hooks/chats/use-documents.ts | 29 +++++++++++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/components/app-sidebar/index.tsx b/src/components/app-sidebar/index.tsx index 5f5c3e9e..0d281b3c 100644 --- a/src/components/app-sidebar/index.tsx +++ b/src/components/app-sidebar/index.tsx @@ -45,7 +45,7 @@ export function AppSidebar() { } = useSearchChats(); const loadMoreRef = React.useRef(null); const [pinnedChatsAccordionOpen, setPinnedChatsAccordionOpen] = useAtom( - pinnedChatsAccordionOpenAtom, + pinnedChatsAccordionOpenAtom ); const handleNewChat = () => { @@ -80,6 +80,7 @@ export function AppSidebar() { (entries) => { const [entry] = entries; if (entry.isIntersecting) { + observer.unobserve(loadMoreElement); loadMore(15); } }, @@ -87,7 +88,7 @@ export function AppSidebar() { root: null, rootMargin: "0px", threshold: 0.1, - }, + } ); observer.observe(loadMoreElement); diff --git a/src/hooks/chats/use-documents.ts b/src/hooks/chats/use-documents.ts index 3faafb58..eeb40ce7 100644 --- a/src/hooks/chats/use-documents.ts +++ b/src/hooks/chats/use-documents.ts @@ -12,7 +12,7 @@ export const useRemoveDocument = () => { const { data: chatInputQuery } = useQuery({ ...convexQuery( api.chats.queries.get, - chatId !== "new" ? { chatId } : "skip", + chatId !== "new" ? { chatId } : "skip" ), }); @@ -29,19 +29,26 @@ export const useRemoveDocument = () => { } const filteredDocuments = chatInputQuery.documents.filter( - (id) => id !== documentId, + (id) => id !== documentId ); - updateChatInputMutation({ - chatId: chatId, - updates: { - documents: filteredDocuments, + updateChatInputMutation( + { + chatId: chatId, + updates: { + documents: filteredDocuments, + }, }, - }); + { + onError: () => { + toast("Failed to remove document"); + }, + } + ); } else { setNewChat((prev) => { const filteredDocuments = prev.documents.filter( - (id) => id !== documentId, + (id) => id !== documentId ); return { ...prev, documents: filteredDocuments }; }); @@ -56,7 +63,7 @@ export const useUploadDocuments = ( }: { type: "file" | "url" | "site" | "youtube" | "text" | "github"; chat?: Doc<"chats">; - } = { type: "file" }, + } = { type: "file" } ) => { const chatId = useAtomValue(chatIdAtom); const { mutateAsync: updateChatMutation } = useMutation({ @@ -104,7 +111,7 @@ export const useUploadDocuments = ( size: file.size, key: storageId, }); - }), + }) ); // Update chat input with new documents @@ -125,7 +132,7 @@ export const useUploadDocuments = ( } toast( - `${files.length} file${files.length > 1 ? "s" : ""} uploaded successfully`, + `${files.length} file${files.length > 1 ? "s" : ""} uploaded successfully` ); return documentIds; From e3dededc738befd0c4a671f4f2e1e636186a2531 Mon Sep 17 00:00:00 2001 From: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com> Date: Sat, 23 Aug 2025 07:02:37 +0530 Subject: [PATCH 05/12] refactor: simplify sidebar component layout - Removed unnecessary icons from the Pinned and Previous Chats sections in the AppSidebar component for a cleaner UI. - Adjusted the SidebarGroupLabel to streamline the display of section titles. --- src/components/app-sidebar/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/app-sidebar/index.tsx b/src/components/app-sidebar/index.tsx index fe4b0ac0..ac054751 100644 --- a/src/components/app-sidebar/index.tsx +++ b/src/components/app-sidebar/index.tsx @@ -178,10 +178,7 @@ export function AppSidebar() { >
- - - Pinned - + Pinned
@@ -199,7 +196,6 @@ export function AppSidebar() { - {!isSearching && } {isSearching ? "Search Results" : "Previous Chats"}
From d003490e5c544057d5a0f944896fbe223b7ea38e Mon Sep 17 00:00:00 2001 From: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com> Date: Sat, 23 Aug 2025 07:19:13 +0530 Subject: [PATCH 06/12] refactor: clean up imports and improve layout in TopNav and AppSidebar - Removed unused GearIcon import from TopNav component. - Simplified imports in AppSidebar by removing unnecessary icons and loading spinner. - Adjusted layout properties for SidebarGroup and SidebarGroupLabel for better UI consistency. --- src/components/app-sidebar/index.tsx | 55 +++++++--------------------- src/components/topnav.tsx | 1 - 2 files changed, 13 insertions(+), 43 deletions(-) diff --git a/src/components/app-sidebar/index.tsx b/src/components/app-sidebar/index.tsx index ac054751..549259f9 100644 --- a/src/components/app-sidebar/index.tsx +++ b/src/components/app-sidebar/index.tsx @@ -8,20 +8,11 @@ import { SidebarGroupContent, } from "@/components/ui/sidebar"; import { useNavigate } from "@tanstack/react-router"; -import { - SearchIcon, - XIcon, - PlusIcon, - FolderIcon, - PinIcon, - HistoryIcon, -} from "lucide-react"; +import { SearchIcon, XIcon, PlusIcon, FolderIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ChatItem } from "@/components/app-sidebar/chat-item"; import { useInfiniteChats, useSearchChats } from "@/hooks/chats/use-chats"; -import { LoadingSpinner } from "@/components/ui/loading-spinner"; -import { ErrorState } from "@/components/ui/error-state"; import type { Doc } from "../../../convex/_generated/dataModel"; import { Accordion, @@ -35,14 +26,7 @@ import { useAtom } from "jotai"; export function AppSidebar() { const navigate = useNavigate(); const { pinnedChats, historyChats, status, loadMore } = useInfiniteChats(); - const { - searchQuery, - setSearchQuery, - searchResults, - isSearching, - isLoadingSearch, - isSearchError, - } = useSearchChats(); + const { searchQuery, setSearchQuery, searchResults } = useSearchChats(); const loadMoreRef = React.useRef(null); const [pinnedChatsAccordionOpen, setPinnedChatsAccordionOpen] = useAtom( pinnedChatsAccordionOpenAtom @@ -80,7 +64,6 @@ export function AppSidebar() { (entries) => { const [entry] = entries; if (entry.isIntersecting) { - observer.unobserve(loadMoreElement); loadMore(15); } }, @@ -99,6 +82,7 @@ export function AppSidebar() { }, [loadMore, status]); // Determine whether to show search results or regular chat lists + const isSearching = searchQuery.trim().length > 0; const searchPinnedChats = isSearching ? searchResults.filter((chat) => chat.pinned) : []; @@ -114,7 +98,7 @@ export function AppSidebar() { 0bs - + - - - )} - {!resizePanelOpen ? (