Compare GPT-4o and DeepSeek-R1 side-by-side in real-time.
Getting Started • Architecture • Features • API Reference • Contributing
Dual AI Chat is an enterprise-grade web application that lets you send a single prompt to two AI models simultaneously and compare their responses in real-time, side-by-side. Built on Azure AI Foundry, it streams responses token-by-token using Server-Sent Events (SSE), so you see answers appear as they're generated.
| Problem | Solution |
|---|---|
| Switching between ChatGPT and other AI tools is tedious | One prompt, two responses, one screen |
| Hard to evaluate which model is better for a task | Side-by-side comparison with latency metrics |
| No enterprise-ready open-source comparison tool exists | Production-grade monorepo with Docker, CI/CD, tests |
| AI responses feel slow waiting for completion | Real-time SSE streaming shows tokens as they arrive |
- Dual View — See both AI responses simultaneously
- Single View — Focus on one model at a time (GPT-4o or DeepSeek-R1)
- Dark/Light Theme — Toggle with smooth transitions, persisted to localStorage
- Streaming Responses — Tokens appear in real-time as they're generated
- Markdown Rendering — Code blocks with syntax highlighting (Prism.js)
- Chat History — Conversations saved locally with quick-access sidebar
- System Prompt Editor — Customize the AI's behavior per conversation
- Copy/Retry/Cancel — Full message action toolbar
- Responsive — Works on desktop and tablet viewports
- Keyboard Shortcuts —
Enterto send,Shift+Enterfor new line
User types a message
|
v
React Frontend (Vite SPA)
|
| POST /api/v1/chat/stream (fetch + ReadableStream)
v
FastAPI Backend
|
|--- CORSMiddleware (validates origin)
|--- RequestLoggingMiddleware (logs timing)
|--- Pydantic validation (validates request body)
v
LLM Service Layer
|
+---> AzureOpenAIService ---> Azure AI Foundry (GPT-4o-mini)
| (AsyncAzureOpenAI SDK)
|
+---> DeepSeekService ------> Azure AI Foundry (DeepSeek-R1)
(AsyncOpenAI SDK)
|
v
SSE Stream (token-by-token)
|
v
React renders each token in real-time
- Frontend sends a
POSTrequest with the user's message and selected model - CORS middleware validates the request origin
- Request logging captures method, path, and starts a timer
- Pydantic validates the request body against the
ChatRequestschema - LLM Service selects the correct Azure AI Foundry client for the chosen model
- Azure SDK streams tokens back using async generators
- FastAPI wraps each token in an SSE event (
data: {"type":"delta","content":"..."}) - Frontend reads the stream and appends each token to the message bubble
- Done event carries latency and model metadata for the UI badge
| Technology | Version | Purpose |
|---|---|---|
| Python | 3.12 | Runtime — 25% faster than 3.11 |
| FastAPI | 0.115+ | Async web framework with auto-generated OpenAPI docs |
| Pydantic | 2.x | Request/response validation with type safety |
| OpenAI SDK | 1.x | AsyncAzureOpenAI + AsyncOpenAI for Azure endpoints |
| Uvicorn | 0.34+ | ASGI server with HTTP/1.1 support |
| python-dotenv | 1.x | Environment variable loading from .env |
| Technology | Version | Purpose |
|---|---|---|
| React | 18.x | UI library with concurrent rendering |
| TypeScript | 5.6 | Strict type safety across the codebase |
| Vite | 6.x | Build tool — instant HMR, optimized production builds |
| Tailwind CSS | 3.4 | Utility-first CSS with HSL custom property theming |
| Radix UI | Latest | Accessible, unstyled UI primitives (Dialog, ScrollArea) |
| Framer Motion | Latest | Smooth animations for sidebar, messages, transitions |
| Prism.js | Latest | Syntax highlighting in AI-generated code blocks |
| Technology | Purpose |
|---|---|
| Docker | Multi-stage builds for both frontend and backend |
| Docker Compose | One-command orchestration with health checks |
| Nginx | Serves static frontend + reverse proxies API requests |
| GitHub Actions | CI pipeline — lint, test, build on every push/PR |
| npm Workspaces | Monorepo dependency management |
| Turborepo | Intelligent build caching and task orchestration |
| Makefile | Developer task shortcuts (make dev-backend, make test) |
| Model | Provider | Strengths |
|---|---|---|
| GPT-4o-mini | Azure AI Foundry | Fast, versatile, great for general tasks |
| DeepSeek-R1 | Azure AI Foundry | Deep chain-of-thought reasoning, math, logic |
- Python 3.11+ (python.org)
- Node.js 18+ (nodejs.org)
- Azure account with an Azure AI Foundry resource (GPT-4o-mini and DeepSeek-R1 deployed)
git clone https://github.com/VamsiReddy17/DualAIChat-WebApp.git
cd DualAIChat-WebApp
# Create environment file from template
cp .env.example .envEdit .env with your Azure credentials:
# Azure AI Foundry — GPT-4o-mini
AZURE_ENDPOINT=https://your-resource.services.ai.azure.com/
AZURE_KEY=your-azure-api-key
AZURE_API_VERSION=2024-12-01-preview
AZURE_DEPLOYMENT=gpt-4o-mini
# Azure AI Foundry — DeepSeek-R1
DEEPSEEK_ENDPOINT=https://your-resource.services.ai.azure.com
DEEPSEEK_API_KEY=your-deepseek-api-key
DEEPSEEK_DEPLOYMENT=DeepSeek-R1# Frontend + shared packages (from project root)
npm install
# Backend (Python)
cd apps/backend
pip install -r requirements.txt
cd ../..Option A — One-Click (Windows):
.\scripts\windows\start.batLaunches both servers in separate terminals and opens the browser automatically. To stop everything:
.\scripts\windows\stop.bat
Option B — Using Makefile (macOS / Linux):
make dev-backend # Terminal 1 → http://localhost:8000
make dev-frontend # Terminal 2 → http://localhost:5173Option C — Manual:
# Terminal 1: Backend
cd apps/backend && python run.py
# Terminal 2: Frontend
cd apps/frontend && npm run devNavigate to http://localhost:5173 — you should see the Dual AI Chat interface.
Deploy both services with a single command:
# Build and start
docker compose up --build -d
# Check status
docker compose ps
# View logs
docker compose logs -f
# Stop
docker compose down| Service | URL | Container |
|---|---|---|
| Frontend | http://localhost | dualai-frontend (Nginx) |
| Backend | http://localhost:8000 | dualai-backend (Uvicorn) |
The frontend Nginx container proxies /api/ and /health requests to the backend automatically.
GET /healthResponse (200):
{
"status": "healthy",
"models": {
"gpt-4o-mini": "available",
"deepseek-r1": "available"
}
}POST /api/v1/chat/completions
Content-Type: application/json
{
"message": "Explain quantum computing",
"model": "gpt-4",
"system_prompt": "You are a helpful assistant."
}POST /api/v1/chat/stream
Content-Type: application/json
{
"message": "Write a Python sort function",
"model": "deepseek",
"stream": true
}SSE Events:
| Event Type | Payload | When |
|---|---|---|
delta |
{"type":"delta","content":"token"} |
Each generated token |
done |
{"type":"done","latency":1.23,"model":"gpt-4"} |
Stream complete |
error |
{"type":"error","content":"message"} |
On failure |
cURL Example:
curl -N -X POST http://localhost:8000/api/v1/chat/stream \
-H "Content-Type: application/json" \
-d '{"message": "Hello!", "model": "gpt-4", "stream": true}'DualAIChat-WebApp/
├── apps/
│ ├── backend/ # Python FastAPI service
│ │ ├── app/
│ │ │ ├── main.py # Application entry, CORS, middleware
│ │ │ ├── api/v1/endpoints/ # Route handlers
│ │ │ │ └── chat.py # /chat/completions & /chat/stream
│ │ │ ├── core/
│ │ │ │ ├── config.py # Pydantic-settings (.env loader)
│ │ │ │ └── logging.py # Structured logging setup
│ │ │ ├── middleware/
│ │ │ │ └── request_logging.py # Request timing middleware
│ │ │ ├── schemas/
│ │ │ │ └── chat.py # ChatRequest/ChatResponse models
│ │ │ └── services/
│ │ │ └── llm_service.py # AzureOpenAI + DeepSeek clients
│ │ ├── tests/ # pytest test suite
│ │ ├── Dockerfile # Multi-stage production build
│ │ ├── requirements.txt
│ │ └── run.py # Dev server launcher
│ │
│ └── frontend/ # React + Vite SPA
│ ├── src/
│ │ ├── api/chat.ts # HTTP client + SSE stream reader
│ │ ├── components/
│ │ │ ├── chat/ # ChatWindow, MessageBubble
│ │ │ ├── layout/ # ThemeToggle, ErrorBoundary
│ │ │ └── ui/ # Button, Dialog, DualAILogo
│ │ ├── hooks/useChat.ts # Core chat state management
│ │ ├── styles/globals.css # HSL theme variables
│ │ └── App.tsx # Root component
│ ├── Dockerfile # Multi-stage → Nginx
│ ├── nginx.conf # SPA routing + API proxy
│ └── vite.config.ts
│
├── packages/ # Shared workspace packages
│ ├── shared-types/ # TypeScript API contracts
│ └── typescript-config/ # Base + React tsconfig
│
├── scripts/ # Automation scripts
│ ├── windows/
│ │ ├── start.bat # One-click start (backend + frontend)
│ │ └── stop.bat # One-click stop (kills both servers)
│ ├── notion-docs.py # Notion documentation generator
│ ├── notion-subpages.py # Sub-subpage builder
│ └── notion-icons.py # Page icon updater
│
├── docs/
│ ├── images/ # README images
│ ├── architecture.md # Detailed architecture docs
│ └── setup.md # Step-by-step setup guide
│
├── archive/legacy/ # Previous version (reference)
│
├── .github/workflows/ci.yml # GitHub Actions CI pipeline
├── docker-compose.yml # Container orchestration
├── Makefile # Developer task runner
├── turbo.json # Turborepo config
├── package.json # npm workspaces root
└── .env.example # Environment template
| Script | Description |
|---|---|
.\scripts\windows\start.bat |
Start backend + frontend, open browser |
.\scripts\windows\stop.bat |
Stop both servers and close terminals |
| Command | Description |
|---|---|
make dev-backend |
Start FastAPI dev server with auto-reload |
make dev-frontend |
Start Vite dev server with HMR |
make install |
Install all dependencies (Python + Node) |
make test-backend |
Run pytest on backend |
make lint-frontend |
Run ESLint on frontend |
make docker-up |
Build & start Docker containers |
make docker-down |
Stop & remove containers |
make clean |
Remove all build artifacts and caches |
make help |
Show all available commands |
| Variable | Required | Default | Description |
|---|---|---|---|
AZURE_ENDPOINT |
Yes | — | Azure AI Foundry endpoint URL |
AZURE_KEY |
Yes | — | Azure AI Foundry API key |
AZURE_API_VERSION |
No | 2024-12-01-preview |
Azure API version |
AZURE_DEPLOYMENT |
No | gpt-4o-mini |
GPT deployment name |
DEEPSEEK_ENDPOINT |
Yes* | — | Azure AI Foundry endpoint |
DEEPSEEK_API_KEY |
No | Falls back to AZURE_KEY |
DeepSeek API key |
DEEPSEEK_DEPLOYMENT |
No | DeepSeek-R1 |
DeepSeek model name |
PORT |
No | 8000 |
Backend server port |
BACKEND_CORS_ORIGINS |
No | [] |
Additional allowed origins (JSON) |
NOTION_TOKEN |
No | — | Notion API token (for doc scripts) |
*Required only for DeepSeek-R1 functionality.
Full documentation is maintained in Notion with 9 main sections and 16+ sub-pages:
| Section | Topics Covered |
|---|---|
| Tech Stack & Tools | Python/FastAPI deep dive, React/TS patterns, Tailwind theming |
| Architecture | SSE streaming flow, error handling strategy, monorepo decisions |
| Backend | LLM services, config management, middleware pipeline |
| Frontend | useChat hook, component reference, state management |
| AI Models | GPT-4o-mini setup, DeepSeek-R1 setup, Azure configuration |
| DevOps | Docker multi-stage builds, CI/CD pipeline, Nginx config |
| API Reference | Endpoint specs, SSE protocol, error codes |
| Phase 1 | Summary of everything built |
| Phase 2 | Roadmap and planned features |
- Authentication — Azure AD / OAuth 2.0 login
- Conversation persistence — PostgreSQL + Prisma
- Additional models — Claude, Gemini, Llama via Azure
- Prompt templates — Saved system prompts library
- Export conversations — PDF, Markdown, JSON
- Usage analytics — Token counts, costs, response time charts
- Rate limiting — Per-user quotas with Redis
- WebSocket upgrade — Bidirectional real-time communication
- Mobile responsive — Full mobile-first redesign
- Admin dashboard — Model configuration, user management
- Fork the repository
- Create a feature branch:
git checkout -b feat/amazing-feature - Commit your changes:
git commit -m "feat: add amazing feature" - Push to the branch:
git push origin feat/amazing-feature - Open a Pull Request
Please follow the existing code style and include tests for new features.
Private — All rights reserved. This project is not open-source.
Built with Azure AI Foundry, React, and FastAPI
Phase 1 Complete • Designed for enterprise-grade AI comparison


