Frontend for an AI-powered technical support agent that helps field technicians get instant answers from product documentation.
A solar energy installer on a rooftop with an inverter error code needs an answer now — not after a phone queue, an email chain, and a callback the next day. The backend provides the AI agent that searches product documentation and generates precise, cited answers. This frontend gives the technician a way to use it: a single chat interface where they type a question or photograph an error code and receive a streamed response with document citations.
The chat displays the agent's work in real time. When the agent searches documentation, the interface shows a tool status indicator. As the answer generates, tokens stream into the conversation word by word. Source citations appear below each response with document name and page number. Sessions persist across visits — the technician can return to a previous conversation, search session history, or start a new thread. Images can be attached via drag-and-drop for the agent to analyze alongside the question. The entire interface is in German, the working language of the installers.
The application also serves as the product landing page — hero section, product showcase, installation gallery, testimonials, and contact information — with a protected chat area behind a full authentication system: registration with 8-digit email verification, password recovery, and automatic idle logout after 30 minutes.
| Layer | Technology |
|---|---|
| Build tool | Vite 7.2 (ESM, HMR, code splitting) |
| UI framework | React 18.3 |
| Routing | React Router 6.30 (lazy loading, protected routes) |
| Styling | Tailwind CSS 4.1 (CSS-first config) + DaisyUI 5.5 (custom nikola theme) |
| Markdown | marked + DOMPurify (XSS-safe HTML rendering) |
| Real-time streaming | Custom SSE client built on Fetch API ReadableStream |
| Security | JWT authentication + react-idle-timer (30-min auto-logout) |
| Utilities | clsx (conditional class composition) |
| Capability | Description |
|---|---|
| Chat Experience | |
| SSE streaming | Token-by-token response display with live tool status indicators during document search |
| Markdown rendering | XSS-sanitized markdown with Tailwind Typography prose styling |
| Source citations | Each response displays linked document name and page number citations below the answer |
| Image upload | Drag-and-drop or click-to-attach photos for AI analysis with inline preview before sending |
| Streaming link masking | Incomplete markdown links are replaced with shimmer placeholders during streaming, then resolved on completion |
| Session Management | |
| Persistent sessions | Sidebar with session history grouped by date (Heute, Gestern, Letzte 7 Tage, Älter) |
| Session operations | Create, rename, pin, delete, and search sessions with optimistic UI updates and rollback on failure |
| URL-based routing | Each session has a deep-linkable /chat/:sessionId route |
| Authentication | |
| JWT auth flow | Register, verify email (8-digit code), login, forgot password, reset password — five dedicated views |
| Protected routes | ProtectedRoute component redirects unauthenticated users to login with return-path preservation |
| Auto-logout | 30-minute idle timeout via react-idle-timer with toast notification on session expiry |
| Global 401 handling | Any expired-token API response triggers automatic cleanup and redirect to login |
| Performance | |
| Code splitting | Every page lazy-loaded via React.lazy + Suspense with spinner fallbacks |
| Rate limit handling | 429 responses trigger a countdown banner showing seconds until the next message is allowed |
| Intersection animations | Landing page sections animate in on scroll via IntersectionObserver |
| Landing Page | |
| Multi-section layout | Hero, products, gallery, about, testimonials, and contact sections with responsive design |
| Mobile-first | Full mobile, tablet, and desktop support with collapsible sidebar and hamburger navigation |
flowchart TD
A[05-pages] --> B[04-layouts]
A --> C[03-features]
A --> D[02-shared]
A --> E[01-ui]
B --> C
B --> D
B --> E
C --> D
C --> E
D --> E
A1["HomePage, ChatPage\nLoginPage, RegisterPage\nVerifyEmailPage, ForgotPasswordPage\nResetPasswordPage"] -.-> A
B1["MainLayout\nChatLayout"] -.-> B
C1["auth, chat, hero\nproducts, gallery, about\ntestimonials, contact"] -.-> C
D1["hooks, utils, context\nconstants, components\nNavbar, Footer, ProtectedRoute"] -.-> D
E1["Button, Input, Card\nModal, Badge, Spinner\nMarkdownContent, IconButton"] -.-> E
style A fill:#dbeafe,stroke:#93c5fd,color:#1e3a5f
style B fill:#f8fafc,stroke:#cbd5e1,color:#334155
style C fill:#fef3c7,stroke:#fcd34d,color:#713f12
style D fill:#fef3c7,stroke:#fcd34d,color:#713f12
style E fill:#d1fae5,stroke:#6ee7b7,color:#064e3b
style A1 fill:#f8fafc,stroke:#cbd5e1,color:#334155
style B1 fill:#f8fafc,stroke:#cbd5e1,color:#334155
style C1 fill:#f8fafc,stroke:#cbd5e1,color:#334155
style D1 fill:#f8fafc,stroke:#cbd5e1,color:#334155
style E1 fill:#f8fafc,stroke:#cbd5e1,color:#334155
flowchart LR
U[User Input] --> CI[ChatInput]
CI --> UC[useChat Hook]
UC --> SSE[SSE Client]
SSE --> API[Backend API]
SSE --- E1[metadata → session ID]
SSE --- E2[tool_start → status indicator]
SSE --- E3[token → streaming content]
SSE --- E4[sources → citation list]
SSE --- E5[done → final message]
UC --> CM[ChatMessage]
CM --> MD[MarkdownContent]
MD --> R[Rendered Response]
style U fill:#dbeafe,stroke:#93c5fd,color:#1e3a5f
style CI fill:#f8fafc,stroke:#cbd5e1,color:#334155
style UC fill:#f8fafc,stroke:#cbd5e1,color:#334155
style SSE fill:#fef3c7,stroke:#fcd34d,color:#713f12
style API fill:#fef3c7,stroke:#fcd34d,color:#713f12
style CM fill:#f8fafc,stroke:#cbd5e1,color:#334155
style MD fill:#f8fafc,stroke:#cbd5e1,color:#334155
style R fill:#d1fae5,stroke:#6ee7b7,color:#064e3b
Imports flow strictly downward. A layer may import from any layer below it but never from a layer above. Every layer exports through barrel files (index.js) using named exports only.
The frontend consumes the backend's POST /chat/stream endpoint via a custom SSE client built on the Fetch API's ReadableStream.
| Event | Handler | What the UI does |
|---|---|---|
metadata |
onMetadata |
Stores session ID; creates sidebar entry for new sessions |
tool_start |
onToolStart |
Displays tool status label (e.g., "Dokumente durchsuchen...") |
token |
onToken |
Appends content to streaming message; masks incomplete markdown links with shimmer placeholders |
sources |
onSources |
Stores citation array for display below the completed message |
done |
onDone |
Finalizes message in history; resolves masked links; refreshes session list |
error |
onError |
Shows toast notification; handles 429 with countdown timer; preserves partial content on connection loss |
| Route | Page | Auth | Layout |
|---|---|---|---|
/ |
HomePage | — | MainLayout |
/chat |
ChatPage | Required | ChatLayout |
/chat/:sessionId |
ChatPage | Required | ChatLayout |
/login |
LoginPage | — | Standalone |
/register |
RegisterPage | — | Standalone |
/verify-email |
VerifyEmailPage | — | Standalone |
/forgot-password |
ForgotPasswordPage | — | Standalone |
/reset-password |
ResetPasswordPage | — | Standalone |
📁 src/
├── 01-ui/ # Stateless UI primitives
│ ├── Button.jsx # Primary, secondary, accent, outline, ghost variants
│ ├── Input.jsx # Form input with error state and forwardRef
│ ├── Card.jsx # Content container with Card.Body and Card.Title
│ ├── Modal.jsx # Dialog overlay
│ ├── Badge.jsx # Status indicators
│ ├── Spinner.jsx # Loading spinner
│ ├── IconButton.jsx # Icon-only button
│ ├── SectionHeading.jsx # Landing page section titles
│ ├── MarkdownContent.jsx # XSS-safe markdown renderer (marked + DOMPurify)
│ └── index.js # Barrel export
│
├── 02-shared/ # Cross-feature infrastructure
│ ├── components/ # Navbar, Footer, ProtectedRoute, LazyImage,
│ │ # ErrorBoundary, ToastContainer, ScrollToTop
│ ├── hooks/ # useClickOutside, useDebounce, useFetch,
│ │ # useIntersection, useLocalStorage,
│ │ # useMediaQuery, useScrollPosition
│ ├── utils/ # api-client (apiFetch, apiPost, apiPut, apiPatch, apiDelete),
│ │ # sse-client (connectSSE), token-store, retry (withRetry)
│ ├── context/ # AuthProvider + useAuth, ToastProvider + useToast
│ └── constants/ # routes, endpoints, brand, landing-data
│
├── 03-features/ # Domain slices
│ ├── auth/ # auth.service (register, verify, login, forgot/reset password),
│ │ # useIdleLogout (30-min timeout)
│ ├── chat/ # chat.service, useChat, useChatStream, useAuthImage,
│ │ │ # maskIncompleteLinks, groupSessionsByDate
│ │ └── components/ # ChatInput, ChatMessage, ChatSources,
│ │ # ChatTypingIndicator, ChatImagePreview,
│ │ # AuthImage, MessageActionBar
│ ├── hero/components/ # HeroSection
│ ├── products/components/ # ProductsSection
│ ├── gallery/components/ # GallerySection
│ ├── about/components/ # AboutSection
│ ├── testimonials/components/ # TestimonialsSection
│ └── contact/components/ # ContactSection
│
├── 04-layouts/ # Page shells
│ ├── MainLayout.jsx # Landing: Navbar + content + Footer
│ └── ChatLayout.jsx # Chat: full-viewport with idle logout
│
├── 05-pages/ # Thin orchestration pages
│ ├── home-page/HomePage.jsx # Composes all landing sections
│ ├── chat-page/ # ChatSidebar + ChatMainArea + ChatWelcome
│ │ └── components/ # Page-specific chat subcomponents
│ ├── login-page/ # Email + password login
│ ├── register-page/ # Registration form
│ ├── verify-email-page/ # 8-digit email verification
│ ├── forgot-password-page/ # Password recovery request
│ └── reset-password-page/ # Password reset with code
│
├── App.jsx # ToastProvider → AuthProvider → RouterProvider
├── routes.jsx # React Router config with lazy loading
├── index.css # Tailwind imports, DaisyUI plugin, custom "nikola" theme, animations
└── main.jsx # ReactDOM entry point
When the user types a message, useChat coordinates the flow: upload any attached images first, then call useChatStream.sendStreaming(). The custom SSE client opens a POST connection to /chat/stream using the Fetch API with ReadableStream. The Authorization: Bearer <token> header is attached from the in-memory token store, which stays synchronized with the React AuthContext. For new conversations, the backend returns a session ID in the first metadata event; for existing sessions, the stored ID is sent in the request body.
The SSE client parses the response byte stream line by line, dispatching each event to its registered callback. A tool_start event replaces the typing indicator with a tool status label. As token events arrive, content appends to the streaming message in real time. Incomplete markdown links during streaming are masked with shimmer placeholders using a regex-based detection in maskIncompleteLinks — this prevents broken [text](partial-url fragments from rendering. If the connection drops mid-stream, partial content is preserved in the conversation rather than discarded.
When the done event fires, the complete answer is stored in message history and the session list refreshes. MarkdownContent converts the final markdown to HTML via marked, sanitizes it through DOMPurify, and renders it with Tailwind Typography prose classes. Source citations — document name and page number pairs returned in the sources event — are displayed below the message as linked references.
- Node.js 18+
- The backend running at
http://localhost:8000(see backend README)
# 1. Install dependencies
npm install
# 2. Configure environment
cp .env.example .env
# Edit .env — set VITE_API_BASE_URL if backend is not at localhost:8000
# 3. Start development server
npm run dev
# App: http://localhost:5173
# 4. Build for production
npm run build
npm run previewBuilt in focused, mergeable increments — each PR self-contained and deployable:
| PR | Title | What was built |
|---|---|---|
| #1–3 | Project Setup | Vite scaffold, ESLint + Prettier, 5-layer architecture with barrel exports |
| #4 | Shared Layer + UI Primitives | 9 UI components, 7 custom hooks, API client, SSE client, route and endpoint constants |
| #5 | Landing Page | Hero, products, gallery, about, testimonials, contact sections with responsive layout and scroll animations |
| #6 | Chat Core | Chat service, session management, message history, useChat orchestration hook |
| #7 | Chat Streaming | SSE streaming with tool status indicators, markdown rendering, typing indicator |
| #8 | Chat Polish + UX | Markdown improvements, message action bar, mobile UX enhancements |
| #9 | Production Hardening | Error handling, rate limiting (429 + countdown), image upload with drag-and-drop and 20 MB limit |
| #10–13 | Testing + Bug Fixes | Image upload race condition, session history loading, malformed request fix |
| #14–17 | Chat UI Overhaul | Spacious layout, message actions, source-link masking during streaming, collapsible sidebar |
| #18 | Full-Page Chat | Replace floating widget with dedicated /chat route, ChatLayout, idle logout integration |
| #19 | Full Authentication | JWT auth system: register, 8-digit email verification, login, forgot/reset password, ProtectedRoute |
The codebase follows strict conventions: named exports only, barrel files (index.js) at every layer, @/ path aliases, PascalCase .jsx components as function declarations, and use-kebab-case.js hooks. Tailwind CSS 4 uses CSS-first configuration — no tailwind.config.js — with a custom DaisyUI theme defined in index.css using oklch colors calibrated for WCAG AA contrast.
| Variable | Description |
|---|---|
VITE_API_BASE_URL |
Backend API URL (default: http://localhost:8000) |
All configuration follows Vite's environment variable convention (VITE_ prefix). The production build embeds these values at build time via import.meta.env.
Released under the MIT License.