Skip to content

Commit aa053c7

Browse files
authored
feat: Integrate Ollama LLM for AI chat functionality (#3)
- Added `httpx` dependency for async HTTP requests to Ollama API. - Implemented OllamaChat component in frontend for user interaction with the LLM. - Created backend service for handling chat requests and model listing. - Updated setup scripts to check for Ollama installation and pull required models. - Added API endpoints for chat and model listing in the backend. - Implemented end-to-end tests for Ollama integration, covering model listing and chat functionality. - Enhanced error handling and user feedback in the chat interface.
1 parent d1a7fc6 commit aa053c7

12 files changed

Lines changed: 1530 additions & 34 deletions

File tree

README.md

Lines changed: 129 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
## Tech stack at a glance
1111
- Backend: Quart, Pydantic 2, MCP JSON-RPC, Async SSE (`backend/app.py`)
1212
- Business logic: `TaskService` + models in `backend/tasks.py`
13+
- LLM Integration: Ollama with local models (`backend/ollama_service.py`)
1314
- Frontend: React 18, Vite, FluentUI components, feature-first structure under `frontend/src/features`
14-
- Tests: Playwright E2E (`tests/e2e/app.spec.js`)
15+
- Tests: Playwright E2E (`tests/e2e/app.spec.js`, `tests/e2e/ollama.spec.js`)
1516

1617
## Documentation
1718

@@ -28,10 +29,12 @@ All deep-dive guides now live under `docs/` for easier discovery:
2829

2930
## 5-minute quick start (TL;DR)
3031
1. Clone the repo: `git clone <your-fork-url> && cd python-quart-vite-react`
31-
2. Run the automated bootstrap: `./setup.sh` (creates the repo-level `.venv`, installs frontend deps, installs Playwright)
32-
3. Start both servers: `./start-dev.sh` *(or)* use the VS Code “Full Stack: Backend + Frontend” launch config
33-
4. Open `http://localhost:3001`, switch to the **Tasks** tab, and create a task—the backend and frontend are now synced
34-
5. (Optional) Run the Playwright suite from the repo root: `npm run test:e2e`
32+
2. Run the automated bootstrap: `./setup.sh` (creates the repo-level `.venv`, installs frontend deps, installs Playwright, checks for Ollama)
33+
3. (Optional) Install Ollama for LLM features: `curl -fsSL https://ollama.com/install.sh | sh && ollama pull llama3.2:1b`
34+
4. Start all servers: `./start-dev.sh` *(or)* use the VS Code "Full Stack: Backend + Frontend" launch config
35+
5. Open `http://localhost:3001`, switch to the **Tasks** tab, and create a task—the backend and frontend are now synced
36+
6. (Optional) Test Ollama integration: `curl -X POST http://localhost:5001/api/ollama/chat -H "Content-Type: application/json" -d '{"messages":[{"role":"user","content":"Say hello"}]}'`
37+
7. (Optional) Run the Playwright suite from the repo root: `npm run test:e2e`
3538

3639
## Detailed setup (first-time users)
3740

@@ -52,14 +55,34 @@ npx playwright install chromium
5255
```
5356
> Debian/Ubuntu users may also need `npx playwright install-deps` for browser libs.
5457
58+
### 4. Ollama (optional - for LLM features)
59+
```bash
60+
# Install Ollama
61+
curl -fsSL https://ollama.com/install.sh | sh
62+
63+
# Pull the lightweight model
64+
ollama pull llama3.2:1b
65+
66+
# Verify installation
67+
ollama list
68+
```
69+
70+
The app works without Ollama, but LLM endpoints (`/api/ollama/*`) will return 503 errors. For production use, consider:
71+
- **llama3.2:1b** (~1.3GB) — Fast, good for testing and simple tasks
72+
- **llama3.2:3b** (~2GB) — Better quality, still fast
73+
- **qwen2.5:3b** (~2GB) — Alternative with strong performance
74+
75+
> The `setup.sh` script checks for Ollama and provides installation instructions if not found.
76+
5577
## Run & verify
5678

5779
### Option A — Manual terminals
5880
1. **Backend:** `source .venv/bin/activate && cd backend && python app.py` → serves REST + MCP on `http://localhost:5001`
5981
2. **Frontend:** `cd frontend && npm run dev` → launches Vite dev server on `http://localhost:3001`
82+
3. **Ollama (optional):** `ollama serve` → runs LLM server on `http://localhost:11434`
6083

6184
### Option B — Helper script
62-
`./start-dev.sh` (verifies dependencies, starts both servers, stops on Ctrl+C)
85+
`./start-dev.sh` (verifies dependencies, starts backend + frontend + Ollama if available, stops all on Ctrl+C)
6386

6487
### Option C — VS Code
6588
Use the “Full Stack: Backend + Frontend” launch config to start backend + frontend with attached debuggers.
@@ -87,6 +110,10 @@ docker run --rm -p 5001:5001 quart-react-demo
87110
- **Dashboard tab:** Streams `{"time","date","timestamp"}` via EventSource; connection errors show inline.
88111
- **Tasks tab:** Uses FluentUI `DataGrid` + dialogs; `frontend/src/features/tasks/TaskList.jsx` keeps calculations (`getTaskStats`) separate from actions (API calls).
89112
- **About tab:** Summarizes tech choices and linkable resources.
113+
- **Ollama API (backend only):**
114+
- `POST /api/ollama/chat` — Chat with local LLM (supports conversation history)
115+
- `GET /api/ollama/models` — List available models
116+
- Also exposed via MCP tools: `ollama_chat`, `list_ollama_models`
90117

91118
## Architecture cheat sheet
92119
- Shows how to keep REST and MCP JSON-RPC in a single Quart process
@@ -116,46 +143,118 @@ TaskService + Pydantic models (backend/tasks.py)
116143
- `python3 -m venv .venv`
117144
- `source .venv/bin/activate`
118145
- `pip install -r requirements.txt`
119-
### Helpful commands
120-
- Reseed sample data: restart the backend (or call `TaskService.initialize_sample_data()` in a shell)
146+
## Helpful npm commands
147+
148+
| Command | Purpose |
149+
|---------|---------|
150+
| `npm run test:e2e` | Run all Playwright E2E tests |
151+
| `npm run test:e2e:ui` | Run tests in interactive UI mode |
152+
| `npm run test:e2e:report` | View test results report |
153+
| `npm run ollama:pull` | Download llama3.2:1b model |
154+
| `npm run ollama:start` | Start Ollama server manually |
155+
| `npm run ollama:status` | Check if Ollama is running |
156+
157+
## Example Ollama API calls
158+
159+
```bash
160+
# List available models
161+
curl http://localhost:5001/api/ollama/models
162+
163+
# Simple chat
164+
curl -X POST http://localhost:5001/api/ollama/chat \
165+
-H "Content-Type: application/json" \
166+
-d '{
167+
"messages": [
168+
{"role": "user", "content": "What is Python?"}
169+
],
170+
"model": "llama3.2:1b",
171+
"temperature": 0.7
172+
}'
173+
174+
# Conversation with history
175+
curl -X POST http://localhost:5001/api/ollama/chat \
176+
-H "Content-Type: application/json" \
177+
-d '{
178+
"messages": [
179+
{"role": "user", "content": "My name is Alice"},
180+
{"role": "assistant", "content": "Nice to meet you, Alice!"},
181+
{"role": "user", "content": "What is my name?"}
182+
],
183+
"model": "llama3.2:1b"
184+
}'
185+
186+
# Via MCP JSON-RPC
187+
curl -X POST http://localhost:5001/mcp \
188+
-H "Content-Type: application/json" \
189+
-d '{
190+
"jsonrpc": "2.0",
191+
"method": "tools/call",
192+
"params": {
193+
"name": "ollama_chat",
194+
"arguments": {
195+
"messages": [{"role": "user", "content": "Hello!"}]
196+
}
197+
},
198+
"id": 1
199+
}'
200+
```
121201

122202
- Node.js 18+
123203
- `cd frontend && npm install`
124204

125205
## Testing
126206

207+
The repo includes comprehensive E2E tests using Playwright:
208+
127209
```bash
128-
npm install # installs Playwright runner
129-
npx playwright install chromium
210+
# Run all tests
211+
npm run test:e2e
212+
213+
# Interactive mode with UI
214+
npm run test:e2e:ui
215+
216+
# Run specific test file
217+
npx playwright test tests/e2e/app.spec.js --project=chromium
218+
219+
# View last test report
220+
npm run test:e2e:report
130221
```
131-
- `npm run test:e2e:ui`
132-
- `npx playwright test tests/e2e/app.spec.js --project=chromium`
133-
- Tests rely on:
134-
- Sample tasks being present
135-
- Stable `data-testid` attributes in the React components
136-
- SSE payload shape `{ time, date, timestamp }`
222+
223+
**Test suites:**
224+
- `tests/e2e/app.spec.js` — Dashboard, tasks, SSE streaming
225+
- `tests/e2e/ollama.spec.js` — LLM chat, model listing, validation (requires Ollama)
226+
227+
Tests rely on:
228+
- Sample tasks being present
229+
- Stable `data-testid` attributes in the React components
230+
- SSE payload shape `{ time, date, timestamp }`
231+
- Ollama running on `localhost:11434` with `llama3.2:1b` model (for Ollama tests)
137232

138233
1. **Backend:** `source .venv/bin/activate && cd backend && python app.py` → serves REST + MCP on `http://localhost:5001`
139234
2. **Frontend:** `cd frontend && npm run dev` → launches Vite dev server on `http://localhost:3001`
140235

141-
| Issue | Fix |
236+
## Troubleshooting
142237

143-
`./start-dev.sh` (verifies dependencies, starts both servers, stops on Ctrl+C)
238+
| Issue | Fix |
239+
|-------|-----|
144240
| Port 5001 in use | `sudo lsof -i :5001` then kill the process (macOS uses 5000 for AirPlay, so backend defaults to 5001) |
145241
| `source .venv/bin/activate` fails | Recreate the env: `rm -rf .venv && python3 -m venv .venv && pip install -r backend/requirements.txt` |
146-
147-
Use the “Full Stack: Backend + Frontend” launch config to start backend + frontend with attached debuggers.
148242
| `npm install` errors | `npm cache clean --force && rm -rf node_modules package-lock.json && npm install` |
149243
| Playwright browser install fails | `sudo npx playwright install-deps && npx playwright install` |
150-
151-
- Visit `http://localhost:3001`
152-
- Dashboard tab should show a ticking clock (SSE via `/api/time-stream`)
153-
- Tasks tab should show three sample tasks (seeded by `TaskService.initialize_sample_data()`)
154-
- Create a task, mark it complete, delete it—confirm state updates instantly
155-
1. Add a `priority` field to the Pydantic models + UI
156-
2. Extend the SSE stream to broadcast task stats (remember to update `connectToTimeStream` consumers)
157-
158-
3. Persist data with SQLite or Postgres instead of `_tasks_db`
159-
4. Add more Playwright specs (filters, SSE error handling, MCP flows)
244+
| Ollama not found | Install: `curl -fsSL https://ollama.com/install.sh \| sh` then `ollama pull llama3.2:1b` |
245+
| Ollama connection error | Start server: `ollama serve` or check if running: `curl http://localhost:11434/api/tags` |
246+
| LLM responses are slow | Try a smaller model (`llama3.2:1b` is fastest) or ensure GPU acceleration is enabled |
247+
248+
See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) for more detailed solutions.
249+
250+
## Extension ideas
251+
1. Add a `priority` field to the Pydantic models + UI
252+
2. Extend the SSE stream to broadcast task stats (remember to update `connectToTimeStream` consumers)
253+
3. Persist data with SQLite or Postgres instead of `_tasks_db`
254+
4. Add more Playwright specs (filters, SSE error handling, MCP flows)
255+
5. **Build a chat UI:** Create `frontend/src/features/ollama/OllamaChat.jsx` with FluentUI components and connect to `/api/ollama/chat`
256+
6. **Smart task descriptions:** Use Ollama to auto-generate task descriptions from titles
257+
7. **Task summarization:** Summarize completed tasks using LLM
258+
8. **Multi-model comparison:** Let users select different Ollama models and compare responses
160259

161260
Happy coding! 🎉

backend/app.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
# Import Pydantic models and service
3030
from tasks import (Task, TaskCreate, TaskFilter, TaskService, TaskStats,
3131
TaskUpdate)
32+
from ollama_service import (ChatRequest, ChatResponse, ModelListResponse,
33+
OllamaService)
3234

3335
# ============================================================================
3436
# APPLICATION SETUP
@@ -37,8 +39,9 @@
3739
app = Quart(__name__)
3840
app = cors(app, allow_origin="*")
3941

40-
# Service instance
42+
# Service instances
4143
task_service = TaskService()
44+
ollama_service = OllamaService()
4245

4346

4447
# ============================================================================
@@ -157,6 +160,39 @@ async def op_get_task_stats() -> TaskStats:
157160
return task_service.get_stats()
158161

159162

163+
@operation(
164+
name="ollama_chat",
165+
description="Generate a chat completion using local Ollama LLM",
166+
http_method="POST",
167+
http_path="/api/ollama/chat"
168+
)
169+
async def op_ollama_chat(request: ChatRequest) -> ChatResponse:
170+
"""
171+
Chat with Ollama LLM.
172+
173+
Supports conversation history and configurable temperature.
174+
Pydantic automatically validates message format and parameters.
175+
176+
Returns the generated response with metadata.
177+
"""
178+
return await ollama_service.chat(request)
179+
180+
181+
@operation(
182+
name="list_ollama_models",
183+
description="List all available Ollama models on the local system",
184+
http_method="GET",
185+
http_path="/api/ollama/models"
186+
)
187+
async def op_list_ollama_models() -> ModelListResponse:
188+
"""
189+
List available Ollama models.
190+
191+
Returns list of models with name, size, and modification time.
192+
"""
193+
return await ollama_service.list_models()
194+
195+
160196
# ============================================================================
161197
# REST API WRAPPERS
162198
# These handle HTTP concerns and call the operations
@@ -229,6 +265,34 @@ async def rest_get_stats():
229265
return jsonify(stats.model_dump())
230266

231267

268+
@app.route("/api/ollama/chat", methods=["POST"])
269+
async def rest_ollama_chat():
270+
"""REST wrapper: chat with Ollama LLM."""
271+
try:
272+
data = await request.get_json()
273+
chat_request = ChatRequest(**data)
274+
response = await op_ollama_chat(chat_request)
275+
return jsonify(response.model_dump()), 200
276+
except ValidationError as e:
277+
return jsonify({"error": str(e)}), 400
278+
except ValueError as e:
279+
return jsonify({"error": str(e)}), 503
280+
except Exception as e:
281+
return jsonify({"error": str(e)}), 500
282+
283+
284+
@app.route("/api/ollama/models", methods=["GET"])
285+
async def rest_list_ollama_models():
286+
"""REST wrapper: list available Ollama models."""
287+
try:
288+
models = await op_list_ollama_models()
289+
return jsonify(models.model_dump()), 200
290+
except ValueError as e:
291+
return jsonify({"error": str(e)}), 503
292+
except Exception as e:
293+
return jsonify({"error": str(e)}), 500
294+
295+
232296
# ============================================================================
233297
# NON-TASK ENDPOINTS
234298
# ============================================================================

0 commit comments

Comments
 (0)