QueryNox is a client-rendered Application built on React and Vite. The architecture is designed around a clear separation of concerns, leveraging a robust provider pattern for state management, a centralized routing system for navigation, and a well-defined API layer for communication with the backend.
The application initializes in src/main.tsx, which renders the root App component. The App component (src/App.tsx) is responsible for setting up all the core context providers, creating a nested structure that supplies the entire application with necessary state and functionality.
Data Flow:
- Initialization:
main.tsx->App.tsx. - Provider Setup:
SystemProvider->TanStackQueryProvider->ClerkProvider->ChatProvider->UserProvider. This order is crucial to ensure dependencies are met (e.g.,ChatProvidermay need user data fromClerkProvider). - Routing:
RouterProvider(from TanStack Router) consumes the route tree and renders the appropriate page component based on the URL. - Page Render: Page components (from
src/pages/) are rendered. - Data Fetching: Pages and their sub-components use custom hooks (e.g.,
useQueryUserChats) which internally use TanStack Query to fetch data from the backend. - State Management:
- Server State: TanStack Query caches and manages all data from the backend.
- UI/Client State: React Contexts (
SystemContext,ChatContext) manage client-side state like theme, sidebar status, and active chat UI state.
- User Interaction: User actions in components (e.g., sending a message in
InputBar.tsx) trigger mutation hooks (useMutationChat) or update context state. This follows a unidirectional data flow, where state updates re-render the necessary components.
The application relies heavily on the React Context API for managing global and shared state.
- Purpose: Manages system-level UI state that persists across sessions.
- State:
darkmode: boolean: The current theme of the application.isSidebarOpen: boolean: The state of the main chat sidebar.
- Functionality:
- It uses
localStorageto persist thedarkmodeandisSidebarOpenstates between sessions. - An effect hook (
useEffect) toggles thedarkclass on the<html>element to apply Tailwind CSS's dark mode styles.
- It uses
- Purpose: To hold application-specific user data fetched from the backend, separate from Clerk's authentication object.
- State:
user: UserType | null: Stores detailed user information, such as subscription status (isPro), usage quotas, etc.
- Functionality: This context acts as a simple wrapper around a
useStatehook, providing theuserobject and asetUserfunction to the rest of the app. It's populated inChat.tsxafter a successful fetch fromuseQueryUserInfo.
- Purpose: The central state machine for all chat-related functionality. This is the most complex context.
- State:
chats: ChatType[]: An array of all chat sessions for the logged-in user.newChat: ChatType: A state object representing a new, unsaved chat session. It holds the model selection, files, web search toggle, etc., before the first message is sent.activeChatIndex: number: The index in thechatsarray that corresponds to the currently viewed chat.-1indicates a new chat.activeChat: ChatType: A memoized object that represents the currently active chat, derived fromchats[activeChatIndex]ornewChat.streamingResponse: {chatid:string, content:string}: Holds the content of a streaming AI response as it arrives.chatError: {chatid:string, content:string}: Holds any error messages related to a specific chat.chatStatus: {chatid:string, content:string}: Holds status updates during a chat query (e.g., "Searching the web...").
- Functionality: This provider encapsulates all the logic for managing the chat UI state. An effect hook synchronizes
activeChatbased onactiveChatIndex,chats, andnewChat, ensuring the UI always displays the correct conversation.
TanStack Query is used to manage all asynchronous operations and server state. This includes data fetching, caching, and mutations. Custom hooks are created for each API endpoint to provide a clean, reusable interface.
useQueryUserChats: Fetches the list of all chat sessions for a user.useQueryMessages: Fetches the message history for a specific chat ID.useQueryModels: Fetches the list of available AI models from the backend.useQueryUserInfo: Fetches the detailed user profile (pro status, etc.).
These hooks abstract away the TanStack Query implementation details and provide simple data, error, and loading states to the components.
useMutationChat: This is the core mutation for handling non-streaming chat messages (primarily for image generation models).- Functionality: It wraps TanStack Query's
useMutation. ThemutationFnconstructs aFormDataobject containing the prompt, model details, and any uploaded files. - Authentication: It uses Clerk's
useAuthhook to get the JWT (getToken()) which is then passed in theAuthorizationheader of the API request. - Error Handling: It has a robust
try-catchblock that specifically handlesAxiosErrorto propagate backend validation errors to the UI.
- Functionality: It wraps TanStack Query's
As seen in src/App.tsx, the application is wrapped in a series of providers.
// src/App.tsx (simplified)
<SystemProvider>
<TanStackQueryProvider>
<ClerkProvider>
<ChatProvider>
<UserProvider>
<RouterProvider router={router} />
</UserProvider>
</ChatProvider>
</ClerkProvider>
</TanStackQueryProvider>
</SystemProvider>This strict hierarchy ensures that hooks like useChatContext can safely access values from useUser (Clerk) because ClerkProvider is a parent of ChatProvider.
This is the most critical data flow in the application.
- User Input: The user types a message in the
TextareaAutosizecomponent withinInputBar.tsx. - Trigger: The user clicks the "Send" button or presses Enter, which calls the
sendChatStreamfunction inInputBar.tsx. - State Update (Optimistic UI):
- A new
chatQueryobject is created with a temporary, client-generated UUID. - The
ChatContextstate is updated immediately. If it's a new chat, thenewChatobject'schatQueriesarray is populated. If it's an existing chat, thechatsarray is updated for theactiveChatIndex. - This optimistic update makes the user's message appear in the
Conversation.tsxcomponent instantly.
- A new
- Streaming with SSE:
- The
sendChatStreamfunction calls thestreamSSEhook (src/pages/chat/apis/fetch/streamSSE.tsx). streamSSEuses the browser's nativefetchAPI to make a POST request to the backend's/streamendpoint. It sends aFormDataobject. TheAccept: 'text/event-stream'header is crucial.- It gets a
ReadableStreamfrom the response and uses aTextDecoderto process the incoming data chunk by chunk.
- The
- Processing Stream Events:
- The
streamSSEhook takes callback functions (onData,onError). - Inside the
whileloop, it parses the data from the stream. The backend sends events likedata: {"type": "content", "content": "..."}. - Based on the
typeof the event (status,content,complete,error), it calls the appropriate callback.
- The
- Live Context Updates:
- The callbacks in
InputBar.tsxupdate theChatContextstate in real-time. case 'content': ThestreamingResponse.contentstate is continuously appended with new tokens.case 'status': ThechatStatusis updated to show messages like "Analyzing documents...".case 'complete': When the stream is finished, the backend sends the final, savedchatQueryobject. ThehandleSuccessfulMutationfunction is called.
- The callbacks in
- Finalizing the State:
handleSuccessfulMutationreplaces the optimistically-created chat query with the final version from the backend (which has a permanent_idfrom the database).- If it was a new chat, the new chat session is added to the top of the
chatsarray,activeChatIndexis set to0, and the user is navigated to the new URL (/chat/:chatId). ThenewChatstate is reset.
- UI Re-render:
- The
Conversation.tsxcomponent is subscribed toChatContext. AsstreamingResponse,chats, andchatErrorstates change, it re-renders to display the incoming message, status, or errors. - It uses
@uiw/react-markdown-previewto render the Markdown content.
- The
The project uses @t3-oss/env-core and Zod to provide compile-time and runtime validation of environment variables.
- Client-Side Variables: Enforces that all client-side variables exposed to Vite must be prefixed with
VITE_. - Validation: Defines a Zod schema (
client) to ensure that variables likeVITE_BACKEND_HOSTare present and are valid URLs. - Type Safety: This setup provides full type safety for
import.meta.env, preventing runtime errors caused by missing or misconfigured environment variables.