Building a responsive AI chat prototype with projects, attachments, and local persistence
D
DaveAuthor
·8 min read
How to build a responsive AI chat prototype with local persistence and file attachments
AI chat interfaces are no longer simple text boxes with a send button. The latest prototypes, like the one examined here, push far beyond the basics: supporting persistent multi-conversation workspaces, safe file attachments, public sharing, and a polished, mobile-friendly UI. Building a responsive AI chat prototype with local persistence means you deliver an app that feels reliable, interactive, and ready for real work—not just demo chats. Here’s how a modern stack delivers this, and what you need to focus on if you want to build or extend a similar workspace today.
Building a feature-complete AI chat workspace is fundamentally different from building a basic chat UI.
Core challenge: You have to juggle more than one chat at once, keep histories persistent, link files and messages, and support everything across devices without backend chaos.
Multi-conversation management: Users expect to work on several threads at once. Each conversation needs a unique chat ID and title, reliable history, and quick switching. Temporary chats and stable IDs become baseline features.
Project workspaces: Modern users treat chats as ongoing projects—planning, writing, debugging, reviewing files. The app needs solid project-level context, not just a flat thread.
Persistent history: Relying only on browser memory loses chats on refresh. Persisting chat state in browser localStorage (up to ~5MB per domain) enables reloads and offline use, but introduces new sync, privacy, and quota tradeoffs.
File attachments: Users want to review files, send code snippets, or get AI explanations for uploads. Handling attachments means safely caching them, preventing malicious uploads, and ensuring UI performance.
Response regeneration and sharing: Each message may have multiple AI-generated versions. Switching between, copying, or sharing them means a dynamic, solid API.
UI complexity: Cleanly managing the above—without turning the codebase into a mess—means splitting responsibilities, isolating state, and designing sane workflows.
Mobile-first design: With users expecting chat anywhere, layouts and interactions must adapt between desktop and mobile, including touch and small screen optimizations.
Takeaway: The Claude-style chat workspace model enables capable workflows, but exponentially increases architecture and UX complexity. Without careful design, features will collide and state will leak.
Opinion: A modern AI chat prototype needs more than just JavaScript—it needs a coordinated stack that supports performance, safety, and fast iteration. Here’s what delivers.
Next.js 16 with App Router: This provides structured routing for conversations and project workspaces, API endpoints for backend logic (like AI proxying), and a browser-first SSR/ISR workflow. The App Router simplifies complex page/state management (docs).
React 19 + TypeScript: React 19 powers component-based UI with hooks and built-in concurrency, while TypeScript prevents type errors as features and state multiply (React docs, TypeScript docs).
Tailwind CSS 4: Provides utility-first styling—glassmorphism effects, responsive breakpoints, and dark/light themes are quick and maintainable. Class extraction and props-based variants allow deep theming with minimal code (Tailwind docs).
Google Gemini REST API (proxied): All chat requests flow through a server-side Next.js API route that relays them to Google Gemini, keeping the API key server-only and out of the client. This shields backend credentials and adds a catch-all firewall layer (Gemini API docs).
Testing with Vitest and React Testing Library: Repeatable UI/unit tests and headless browser tests ensure features (state sync, attachment, titles, etc.) stay stable during change (Vitest docs, RTL docs).
Persistence via browser localStorage: All chat, conversation, and project data is saved in localStorage. It’s fast, supports offline/refresh persistence, and does not require external accounts or cloud setup. Watch for quota: browsers commonly cap at ~5MB. (MDN localStorage docs)
Sample project structure:
// pages/api/chat.ts (Next.js 16 App Route API)export default async function handler(req, res) { const { contents } = req.body; // Proxy chat to Gemini API here // Keep your Gemini API key server-side const geminiRes = await fetch(GEMINI_API_URL, { method: "POST", headers: { "Authorization": `Bearer ${process.env.GEMINI_KEY}` }, body: JSON.stringify({ contents }) }); const result = await geminiRes.json(); res.status(200).json(result);}
Takeaway: Modern AI chat prototypes are only possible with a coordinated stack—think Next.js 16, React 19, TypeScript, Tailwind 4, and server-proxied AI APIs.
One codebase. iOS, Android, and web.
The Fitness Kit ships with auth, a database, and a backend already connected — no setup. Live demo at fitness-preview.otf-kit.dev.
Answer: Store all chats, projects, and attachment indexes inside browser localStorage using structured keys and compact serialization. You get offline capability and privacy by default, but must respect storage limits.
localStorage mechanics: Each browser domain can store ≈5MB (may vary by browser/version). That’s enough for hundreds of chats, conversation metadata, and lightweight attachment indexes, but not for storing big binary files or endless history.
Data model: Structure keys by user/session/project, e.g. chat:0001, project:main, and store JSON blobs for each (or in a single master state if you need atomicity). Attachments are stored as URLs or references—not their full data—to fit within quotas.
// Chat record (simplified)type ChatRecord = { id: string projectId: string history: { role: "user" | "assistant"; content: string }[] attachments: AttachmentMeta[] lastActive: number}// Save to localStoragelocalStorage.setItem(`chat:${id}`, JSON.stringify(chatRecord))// Read from localStorageconst chat = JSON.parse(localStorage.getItem(`chat:${id}`) ?? '{}')
Sync and offline handling: When a user moves between devices or clears storage, history is lost unless you implement export/import or cloud sync. For privacy and simplicity, this demo is offline-only.
Tradeoffs: You avoid backend complexity and GDPR linkages, but accept possible data loss (localStorage isn’t forever), quota limits, and less “magic” sync. For many users, this is the right balance for private session history.
Takeaway: localStorage is a simple, solid way to persist AI chat data for individuals—but don’t use it for enterprise-grade sharing, and watch size limits.
Clear answer: Only lightweight file metadata and URLs should be stored locally; actual files are linked by session, cached in-memory, or temporarily stored with controlled APIs. Attachments are never sent unless explicitly included by the user.
Supported files: The demo focuses on safe types—PDFs, images, plain text, markdown, and code snippets. Avoid executable or scriptable files for safety.
Caching and linking: When a user attaches a file, a temporary URL (via URL.createObjectURL) or metadata reference is stored and linked to the active conversation. Attachment previews are enabled for images, names and types for others.
UI/UX: The chat message UI shows attachment icons or inline previews. On mobile, touch/hold is supported for attachments. There are strict file size limits (e.g., <2MB per file) and overall quotas to avoid blowing through localStorage.
Security: Always sanitize filenames, strip offending metadata, and—crucially—never execute or preview unsafe types. Attachment previews are sanitized at the UI tier.
// Accept and preview an uploadfunction handleFileUpload(file: File) { if (file.size > MAX_ATTACHMENT_SIZE) return alert('File too large'); const objectUrl = URL.createObjectURL(file); setAttachments(prev => [...prev, { name: file.name, url: objectUrl, type: file.type }]);}
Takeaway: Attachments add real workflow value, but only when handled safely—with clear limits, never storing full files in localStorage, and never trusting user input.
Direct answer: Use Tailwind CSS 4 for glassmorphism effect and responsive breakpoints; design layouts for flexible resizing and mobile, with clean project and chat navigation.
Glassmorphism styling: Semi-transparent cards, blur effects, and gradient backgrounds are possible with Tailwind’s backdrop-blur, opacity, and color utilities. Real examples:
<div className="bg-white/30 backdrop-blur-md rounded-xl p-4 shadow-lg"> {/* chat history and input */}</div>
Responsive layouts: Tailwind’s responsive prefixes (sm:, md:, lg:, xl:) allow conditional flex/glass layouts. The workspace sidebar, chat list, and main thread stack cleanly on mobile.
Touch-interaction and mobile UI: Main actions are easily tapped. Tab interfaces and swipes allow conversation switching, while chat input remains fixed and accessible.
Project workspaces and tabbed navigation: Each workspace includes multiple chats, quickly accessed by tabs or a sidebar. Active chat and attachments are visible at a glance.
Takeaway: Glassmorphism and mobile-first layouts are not difficult once you standardize with Tailwind CSS 4; they create a workspace-like feel and keep users coming back.
Clone or scaffold the project using Next.js 16 and React 19.
npx create-next-app@16 --ts# Install React 19 if not includednpm install react@19
Configure your Gemini API key server-side.
Add to .env.local:
GEMINI_KEY=your-google-gemini-api-key
Proxy all Gemini requests through /api/chat as shown above.
Local persistence:
No backend required—persistence is handled by browser localStorage. You can extend with IndexedDB or migrate to cloud for more solid needs.
File attachments:
Use safe file type detection and local preview logic. To support new types, update the attachment handler and local UI.
Add project/sharing features:
Implement public share links using unique chat IDs; project workspaces can be added via new data models or dynamic routes.
Write tests:
Add or extend test cases using Vitest and React Testing Library.
npm install vitest @testing-library/reactnpx vitest run
Takeaway: The entire stack—Next.js 16, React 19, Gemini API, Tailwind 4, Vitest—runs locally with minimal config. You can deploy to Vercel, Netlify, or your own infra.
A truly responsive AI chat prototype with local persistence and solid file attachment support is not just possible—it’s within reach using today’s frameworks. By structuring your code with project workspaces, clean localStorage patterns, supported attachment types, and resilient UI design, you create a capable, maintainable application that fits modern workflows. Now is the time to take the next step: explore, fork, or extend this stack and build your own advanced, mobile-friendly AI chat app.
OTF Fitness Kit
Stop wiring.
Start shipping.
Auth, DB, and backend already connected — no Supabase setup needed
iOS + Android + web from one codebase
CLAUDE.md pre-tuned + 40+ tested AI prompts included