A production-ready, full-stack AI-powered customer support chat widget built for the Spur take-home assignment. Users can ask questions about the fictional e-commerce store "ShopEase" and receive instant, contextual, and guardrailed replies powered by Llama 3.3 via Groq.
- Live Demo: https://spur-chat-omega.vercel.app
- 🤖 AI-Powered Customer Support: Intelligent assistant preset with ShopEase store knowledge (FAQ, shipping, returns, and cancellation policies).
- 💬 Persistent Chat Sessions: Automatically resumes conversation history across page reloads using
localStorage. - ⚡ Optimistic UI Updates: Instantly renders user messages and typing indicators (
...) for a snappy user experience. - 🗄️ Robust Local Database: Fully automated SQLite schema initialization on backend startup using
better-sqlite3. - 🔒 Security & Validation:
- Environment variables validation on startup (fails fast on missing keys).
- Payload size limits (
10kbbody limit) and message length validation (1000chars max) to prevent abuse.
- 🧩 Clean Layered Architecture: Modular codebase split into routes, services, and queries on the backend; component-hook model on the frontend.
- 🔄 Swappable LLM Providers: Centralized LLM logic makes it easy to switch between OpenAI, Anthropic, or Gemini.
The following diagram illustrates the flow of messages between the frontend interface, backend server, SQLite local database, and Groq's LLM engine:
graph TD
subgraph Frontend [React Frontend]
UI[ChatWidget Component] --> Hook[useChat React Hook]
Hook --> API[API Client axios]
end
subgraph Backend [Express Node.js Server]
API --> Routes[Chat Router]
Routes --> DbQueries[DB Queries queries.ts]
Routes --> LLMService[LLM Service llmService.ts]
DbQueries --> SQLite[(SQLite chat.db)]
LLMService --> GroqAPI[Groq SDK Client]
end
GroqAPI --> Llama[Llama 3.3 70B Model]
spur-chat/
├── backend/
│ ├── src/
│ │ ├── routes/
│ │ │ └── chat.ts # POST /message & GET /history routes
│ │ ├── services/
│ │ │ └── llmService.ts # Groq SDK handler, prompt context & fallback rules
│ │ ├── db/
│ │ │ ├── schema.ts # Table initializers & configurations
│ │ │ └── queries.ts # SQL queries for conversations and messages
│ │ ├── middleware/
│ │ │ └── errorHandler.ts # Unhandled error middleware
│ │ ├── config.ts # Port, Keys, and Node environment state
│ │ └── index.ts # Server bootstrap & middleware setup
│ ├── .env.example
│ └── package.json
│
└── frontend/
├── src/
│ ├── api/
│ │ └── chat.ts # Axios configuration & request functions
│ ├── components/
│ │ ├── ChatWidget.tsx # Main chat layout container
│ │ ├── MessageList.tsx # List container with autoscrolling & avatars
│ │ └── MessageInput.tsx # Textarea handling input & carriage return sending
│ ├── hooks/
│ │ └── useChat.ts # Hook managing messages, loaders, typing, and sessions
│ ├── type.ts # TypeScript interface declarations
│ ├── App.tsx # Application Entry Point
│ └── App.css # Custom premium stylesheet (Glassmorphism & animations)
└── package.json
- Node.js 18 or higher
- npm
- A Groq API key (Create one for free at the Groq Console)
git clone https://github.com/Kunall7890/spur-chat.git
cd spur-chat- Navigate to the backend directory:
cd backend npm install - Create a
.envfile in thebackend/directory:GROQ_API_KEY=gsk_your_actual_groq_api_key_here PORT=3001 NODE_ENV=development
- Run the development server:
Expected Output:
npm run dev
✅ Database initialized ✅ Server running on http://localhost:3001 ✅ Environment: developmentNote: An SQLite file
chat.dbwill be auto-generated in the backend directory.
- Open a new terminal session and navigate to the frontend directory:
cd frontend npm install - Create a
.envfile in thefrontend/directory:VITE_API_URL=http://localhost:3001
- Launch the development server:
npm run dev
- Access the web app in your browser at:
http://localhost:5173
| Variable | Required | Default | Description |
|---|---|---|---|
GROQ_API_KEY |
✅ Yes | None | Authentication token for Groq API calls |
PORT |
❌ No | 3001 |
Express server port listener |
NODE_ENV |
❌ No | development |
Environment mode (development or production) |
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_API_URL |
✅ Yes | http://localhost:3001 |
Backend API entrypoint URL |
Send a new user query to get an AI-generated response.
{
"message": "What is the return policy for sale items?",
"sessionId": "4db75c9b-6e54-4ca8-9ad5-79a8bc43d0ff"
}Note: If sessionId is omitted or invalid, the backend automatically registers and returns a new session ID.
{
"success": true,
"reply": "Sale and discounted items are not eligible for returns. Regular items must be unused and returned within 7 days.",
"sessionId": "4db75c9b-6e54-4ca8-9ad5-79a8bc43d0ff"
}Retrieve all previous messages for a specific session ID to hydrate the client UI.
{
"success": true,
"sessionId": "4db75c9b-6e54-4ca8-9ad5-79a8bc43d0ff",
"messages": [
{
"id": "2db4e3e3-7740-410a-8be7-5fa2c2c034a1",
"conversation_id": "4db75c9b-6e54-4ca8-9ad5-79a8bc43d0ff",
"sender": "user",
"text": "What is the return policy for sale items?",
"created_at": "2026-06-13T12:00:00.000Z"
},
{
"id": "8aa18e95-7be0-4ca0-94e8-2849e7b233a0",
"conversation_id": "4db75c9b-6e54-4ca8-9ad5-79a8bc43d0ff",
"sender": "ai",
"text": "Sale and discounted items are not eligible for returns. Regular items must be unused and returned within 7 days.",
"created_at": "2026-06-13T12:00:03.000Z"
}
]
}Verify server status and connection timestamps.
{
"status": "ok",
"timestamp": "2026-06-14T01:00:00.000Z"
}The application uses SQLite as its primary relational datastore. The tables are described below:
-- Represents individual chat sessions
CREATE TABLE conversations (
id TEXT PRIMARY KEY,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Represents individual messages within a conversation
CREATE TABLE messages (
id TEXT PRIMARY KEY,
conversation_id TEXT NOT NULL,
sender TEXT NOT NULL CHECK(sender IN ('user', 'ai')),
text TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (conversation_id) REFERENCES conversations(id)
);To ensure lightning-fast response times and keep cost-per-request low:
- Short-Term Memory: Only the last 10 messages from the conversation are sent to the model as context.
- Output Restrictions: The model is restricted to a maximum output of 300 tokens.
- Input Restrictions: Maximum message length validated to a strict 1000 characters.
The LLM is loaded with a strict system instructions prompt outlining exact details regarding ShopEase operations:
- Core Details: Hours of support (Mon-Sat, 9AM-6PM IST), Contact email (
support@shopease.com), phone number (1800-XXX-XXXX). - Shipping Rules: Free standard shipping above ₹499 (otherwise flat-rate), shipping restricted inside India.
- Return Policy: 7 days for regular items (unworn/original packaging), sale/discount items excluded.
- Behavioral Constraints: Friendly but concise responses under 100 words. Refuses to make up answers or hallucinate details.
- SQLite vs. PostgreSQL: SQLite was selected to facilitate zero-configuration setups and zero dependency requirements for evaluation. The codebase abstracts SQL logic into queries files, making migration to postgres/mysql extremely easy.
- In-Memory Store Rules: Store knowledge is embedded directly into the system prompt. For larger stores with millions of dynamic SKUs, a Vector Database (RAG) system would be implemented.
- Stateless Token-based Sessions: Sessions are stored directly on the client browser and queried against UUID entries in SQLite. Real authentication (OAuth, JWT) was excluded to minimize project scope.
If allocated more time, the following features would be introduced:
- Response Streaming: Leverage server-sent events (SSE) or WebSockets to stream AI replies character-by-character.
- RAG Integration: Link to an external knowledge database/API to answer dynamic product catalog questions.
- Redis Caching Layer: Cache frequent responses to reduce LLM request cost.
- Robust Auth: Multi-tenant authorization systems for agents and store managers.
cd backend
npm run build
npm startcd frontend
npm run build
npm run previewKunal Jaiswal
- GitHub: @Kunall7890
- LinkedIn: Kunal Jaiswal Profile
This codebase is created strictly as part of the Spur Take-Home Assignment. It is intended solely for evaluation and demo purposes.