Skip to content

Commit 405f359

Browse files
authored
feat: Implement Usecase Demo Agent orchestration and UI components (#14)
- Added backend orchestration for usecase demo agent runs in `usecase_demo.py`. - Created documentation for CSV ticket guidance in `CSV_AI_GUIDANCE.md`. - Developed frontend components for usecase demo description and page in `UsecaseDemoDescription.jsx` and `UsecaseDemoPage.jsx`. - Introduced demo definitions for usecase demos in `demoDefinitions.js`. - Implemented result views for structured table and markdown in `resultViews.jsx`. - Added utility functions for handling usecase demo runs in `usecaseDemoUtils.js`. - Included a network diagram in `net.drawio`.
1 parent fe4998e commit 405f359

19 files changed

Lines changed: 1948 additions & 403 deletions

README.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Quart + Vite + React Demo Application
22

3-
**Task:** Visualize the CSV tickets with richer panels (status, priority, timeline, geography, SLA). **How would you like to view the tickets?**
3+
**Current Task:** Document usecase demo ideas from the CSV-backed ticket dataset and build/iterate pages like `/usecase_demo_1` where each demo page has:
4+
- a short summary
5+
- editable agent prompt(s)
6+
- a button that launches the agent run in background
7+
- visible results (table/visualization)
48

59
> A teaching-oriented full-stack sample that pairs a Python Quart backend with a React + FluentUI frontend, real-time Server-Sent Events (SSE), and Playwright tests.
610
@@ -27,6 +31,7 @@ All deep-dive guides now live under `docs/` for easier discovery:
2731
- [Pydantic Architecture](docs/PYDANTIC_ARCHITECTURE.md) – how models, validation, and operations fit together
2832
- [Unified Architecture](docs/UNIFIED_ARCHITECTURE.md) – REST + MCP integration details and extension ideas
2933
- [Troubleshooting](docs/TROUBLESHOOTING.md) – common issues and fixes for setup, dev, and tests
34+
- [CSV AI Guidance](docs/CSV_AI_GUIDANCE.md) – how AI agents should query and reason over CSV ticket data
3035

3136

3237

@@ -38,7 +43,7 @@ All deep-dive guides now live under `docs/` for easier discovery:
3843
2. Run the automated bootstrap: `./setup.sh` (creates the repo-level `.venv`, installs frontend deps, installs Playwright, checks for Ollama)
3944
3. (Optional) Install Ollama for LLM features: `curl -fsSL https://ollama.com/install.sh | sh && ollama pull llama3.2:1b`
4045
4. Start all servers: `./start-dev.sh` *(or)* use the VS Code "Full Stack: Backend + Frontend" launch config
41-
5. Open `http://localhost:3001`, switch to the **Tasks** tab, and create a task—the backend and frontend are now synced
46+
5. Open `http://localhost:3001/usecase_demo_1` and start documenting your usecase demo idea on that page
4247
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"}]}'`
4348
7. (Optional) Run the Playwright suite from the repo root: `npm run test:e2e`
4449

@@ -95,9 +100,9 @@ Use the “Full Stack: Backend + Frontend” launch config to start backend + fr
95100

96101
### Smoke test checklist
97102
- Visit `http://localhost:3001`
98-
- Dashboard tab should show a ticking clock (SSE via `/api/time-stream`)
99-
- Tasks tab should show three sample tasks (seeded by `TaskService.initialize_sample_data()`)
100-
- Create a task, mark it complete, delete it—confirm state updates instantly
103+
- Tickets tab should render CSV ticket table + stats from `/api/csv-tickets*`
104+
- Usecase Demo tab (`/usecase_demo_1`) should show editable prompt + background run controls
105+
- Fields tab should list mapped CSV fields from `/api/csv-tickets/fields`
101106

102107
## Docker (one command delivery)
103108

@@ -113,9 +118,10 @@ docker run --rm -p 5001:5001 quart-react-demo
113118
- Hot reloading is not part of the container flow—use the regular dev servers for iterative work and Docker for demos or deployment.
114119

115120
## Using the app
116-
- **Dashboard tab:** Streams `{"time","date","timestamp"}` via EventSource; connection errors show inline.
117-
- **Tasks tab:** Uses FluentUI `DataGrid` + dialogs; `frontend/src/features/tasks/TaskList.jsx` keeps calculations (`getTaskStats`) separate from actions (API calls).
118-
- **About tab:** Summarizes tech choices and linkable resources.
121+
- **Tickets tab (`/csvtickets`):** Shows CSV-backed ticket table, filtering, sorting, and pagination.
122+
- **Usecase Demo tab (`/usecase_demo_1`):** Main demo page for documenting usecase demo ideas with editable prompts and background agent runs.
123+
- **Fields tab (`/fields`):** Lists mapped CSV schema fields available to UI/MCP/agent flows.
124+
- **Agent tab (`/agent`):** Chat-style agent interface for CSV ticket analysis.
119125
- **Ollama API (backend only):**
120126
- `POST /api/ollama/chat` — Chat with local LLM (supports conversation history)
121127
- `GET /api/ollama/models` — List available models

backend/agents.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,13 +309,19 @@ def _build_csv_tools(self) -> list[StructuredTool]:
309309
import json
310310
service = get_csv_ticket_service()
311311

312-
def _csv_list_tickets(status: str | None = None, assigned_group: str | None = None, has_assignee: bool | None = None) -> str:
312+
def _csv_list_tickets(
313+
status: str | None = None,
314+
assigned_group: str | None = None,
315+
has_assignee: bool | None = None,
316+
limit: int = 50,
317+
) -> str:
313318
try:
314319
status_enum = TicketStatus(status.lower()) if status else None
315320
except Exception:
316321
status_enum = None
317322
tickets = service.list_tickets(status=status_enum, assigned_group=assigned_group, has_assignee=has_assignee)
318-
return json.dumps([t.model_dump() for t in tickets[:200]], default=str)
323+
bounded_limit = max(1, min(limit, 100))
324+
return json.dumps([t.model_dump() for t in tickets[:bounded_limit]], default=str)
319325

320326
def _csv_get_ticket(ticket_id: str) -> str:
321327
try:
@@ -327,7 +333,7 @@ def _csv_get_ticket(ticket_id: str) -> str:
327333
return json.dumps({"error": "not found"})
328334
return json.dumps(ticket.model_dump(), default=str)
329335

330-
def _csv_search_tickets(query: str, limit: int = 50) -> str:
336+
def _csv_search_tickets(query: str, limit: int = 25) -> str:
331337
q = query.lower()
332338
tickets = service.list_tickets()
333339
matched = []
@@ -356,7 +362,12 @@ def _csv_ticket_fields() -> str:
356362
StructuredTool.from_function(
357363
func=_csv_list_tickets,
358364
name="csv_list_tickets",
359-
description="List tickets from CSV with optional filters: status (new, assigned, in_progress, pending, resolved, closed, cancelled), assigned_group, has_assignee (true/false). Returns JSON array.",
365+
description=(
366+
"List tickets from CSV with optional filters: status "
367+
"(new, assigned, in_progress, pending, resolved, closed, cancelled), "
368+
"assigned_group, has_assignee (true/false), and limit (default 50, max 100). "
369+
"Returns JSON array."
370+
),
360371
),
361372
StructuredTool.from_function(
362373
func=_csv_get_ticket,

backend/app.py

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import os
2121
from datetime import datetime
2222
from pathlib import Path
23+
from uuid import UUID
2324

2425
# Load environment variables from .env file
2526
from dotenv import load_dotenv
@@ -35,11 +36,13 @@
3536

3637
# CSV ticket service
3738
from csv_data import Ticket, get_csv_ticket_service
39+
from usecase_demo import UsecaseDemoRunCreate, usecase_demo_run_service
3840

3941
# FastMCP client for direct ticket MCP calls (no AI)
4042
from fastmcp import Client as MCPClient
4143
from mcp_handler import handle_mcp_request
4244
from operations import (
45+
CSV_TICKET_FIELDS,
4346
op_create_task,
4447
op_delete_task,
4548
op_get_task,
@@ -177,6 +180,47 @@ async def rest_run_agent():
177180
return jsonify({"error": str(e)}), 500
178181

179182

183+
# ============================================================================
184+
# USECASE DEMO AGENT RUN ENDPOINTS
185+
# ============================================================================
186+
187+
@app.route("/api/usecase-demo/agent-runs", methods=["POST"])
188+
async def create_usecase_demo_agent_run():
189+
"""Queue a background agent run using the provided prompt."""
190+
try:
191+
data = await request.get_json() or {}
192+
payload = UsecaseDemoRunCreate(**data)
193+
run = await usecase_demo_run_service.create_run(payload)
194+
return jsonify(run.model_dump(mode="json")), 202
195+
except ValidationError as e:
196+
return jsonify({"error": str(e)}), 400
197+
except Exception as e:
198+
return jsonify({"error": str(e)}), 500
199+
200+
201+
@app.route("/api/usecase-demo/agent-runs", methods=["GET"])
202+
async def list_usecase_demo_agent_runs():
203+
"""List recent background agent runs."""
204+
try:
205+
limit = request.args.get("limit", default=20, type=int)
206+
runs = await usecase_demo_run_service.list_runs(limit=limit or 20)
207+
return jsonify({"runs": [run.model_dump(mode="json") for run in runs]}), 200
208+
except Exception as e:
209+
return jsonify({"error": str(e)}), 500
210+
211+
212+
@app.route("/api/usecase-demo/agent-runs/<run_id>", methods=["GET"])
213+
async def get_usecase_demo_agent_run(run_id: str):
214+
"""Fetch one background run by ID."""
215+
try:
216+
run = await usecase_demo_run_service.get_run(run_id)
217+
if run is None:
218+
return jsonify({"error": "Run not found"}), 404
219+
return jsonify(run.model_dump(mode="json")), 200
220+
except Exception as e:
221+
return jsonify({"error": str(e)}), 500
222+
223+
180224
# ============================================================================
181225
# TICKET MCP EXAMPLE - Direct FastMCP client usage (no AI)
182226
# ============================================================================
@@ -403,31 +447,6 @@ async def get_qa_tickets():
403447
print(f"📊 Loaded {_csv_loaded} tickets from CSV")
404448

405449

406-
# Define which fields are available for display
407-
CSV_TICKET_FIELDS = [
408-
{"name": "id", "label": "ID", "type": "uuid"},
409-
{"name": "summary", "label": "Summary", "type": "string"},
410-
{"name": "status", "label": "Status", "type": "enum"},
411-
{"name": "priority", "label": "Priority", "type": "enum"},
412-
{"name": "assignee", "label": "Assignee", "type": "string"},
413-
{"name": "assigned_group", "label": "Assigned Group", "type": "string"},
414-
{"name": "requester_name", "label": "Requester", "type": "string"},
415-
{"name": "requester_email", "label": "Email", "type": "string"},
416-
{"name": "city", "label": "City", "type": "string"},
417-
{"name": "country", "label": "Country", "type": "string"},
418-
{"name": "service", "label": "Service", "type": "string"},
419-
{"name": "incident_type", "label": "Incident Type", "type": "string"},
420-
{"name": "product_name", "label": "Product", "type": "string"},
421-
{"name": "manufacturer", "label": "Manufacturer", "type": "string"},
422-
{"name": "created_at", "label": "Created", "type": "datetime"},
423-
{"name": "updated_at", "label": "Updated", "type": "datetime"},
424-
{"name": "urgency", "label": "Urgency", "type": "string"},
425-
{"name": "impact", "label": "Impact", "type": "string"},
426-
{"name": "resolution", "label": "Resolution", "type": "string"},
427-
{"name": "notes", "label": "Notes", "type": "string"},
428-
]
429-
430-
431450
@app.route("/api/csv-tickets/fields", methods=["GET"])
432451
async def get_csv_ticket_fields():
433452
"""Get metadata about available CSV ticket fields."""
@@ -539,6 +558,46 @@ def get_sort_key(ticket: Ticket):
539558
})
540559

541560

561+
@app.route("/api/csv-tickets/<ticket_id>", methods=["GET"])
562+
async def get_csv_ticket(ticket_id: str):
563+
"""
564+
Get one CSV ticket by ID.
565+
566+
Query params:
567+
- fields: optional comma-separated list of fields to include
568+
"""
569+
try:
570+
parsed_id = UUID(ticket_id)
571+
except ValueError:
572+
return jsonify({"error": "Invalid ticket ID"}), 400
573+
574+
ticket = _csv_ticket_service.get_ticket(parsed_id)
575+
if ticket is None:
576+
return jsonify({"error": "Ticket not found"}), 404
577+
578+
fields_param = request.args.get("fields", "")
579+
if fields_param:
580+
selected_fields = [f.strip() for f in fields_param.split(",") if f.strip()]
581+
else:
582+
selected_fields = list(ticket.model_fields.keys())
583+
584+
result = {}
585+
for field in selected_fields:
586+
val = getattr(ticket, field, None)
587+
if val is None:
588+
result[field] = None
589+
elif hasattr(val, "value"):
590+
result[field] = val.value
591+
elif hasattr(val, "isoformat"):
592+
result[field] = val.isoformat()
593+
elif hasattr(val, "hex"):
594+
result[field] = str(val)
595+
else:
596+
result[field] = val
597+
598+
return jsonify(result), 200
599+
600+
542601
@app.route("/api/csv-tickets/stats", methods=["GET"])
543602
async def get_csv_ticket_stats():
544603
"""Get statistics about CSV tickets."""

0 commit comments

Comments
 (0)