Add real-time WebSocket task progress with multi-job drawer

Replace 5s HTTP polling with WebSocket-based real-time updates for task
progress. Celery workers publish progress to Redis pub/sub channels;
a FastAPI WebSocket endpoint subscribes and forwards to the browser.
Polling is kept as a 30s fallback when WebSocket is unavailable.

The task progress drawer now supports multiple concurrent jobs with a
tab bar for switching between scrape and POI distance tasks.

Backend:
- Add services/task_progress_publisher.py (Redis pub/sub bridge)
- Add api/ws_routes.py (WebSocket endpoint with JWT auth)
- Publish progress from listing_tasks and poi_tasks
- Publish REVOKED via pub/sub on cancel/clear to fix stuck UI

Frontend:
- Add useTaskWebSocket hook with reconnection and keepalive
- Add TaskState and WS message types
- TaskIndicator: WS-driven updates with polling fallback
- TaskProgressDrawer: multi-job tabs, POI phase timeline
- Guard against WS overwriting local cancel state
This commit is contained in:
Viktor Barzin 2026-02-09 21:31:45 +00:00
parent 73d19e29d5
commit 8559c4b461
No known key found for this signature in database
GPG key ID: 0EB088298288D958
11 changed files with 774 additions and 72 deletions

View file

@ -1,4 +1,5 @@
import type { AuthUser } from '@/auth/types';
import type { TaskState } from '@/types';
import { Button } from './ui/button';
import { Separator } from './ui/separator';
import { LogOut, Home, Filter } from 'lucide-react';
@ -16,6 +17,9 @@ interface HeaderProps {
showFilterToggle?: boolean;
onTaskCancelled?: () => void;
onTaskCompleted?: () => void;
wsTasks?: Record<string, TaskState>;
wsConnected?: boolean;
wsSubscribe?: (taskId: string) => void;
}
export function Header({
@ -26,6 +30,9 @@ export function Header({
showFilterToggle = false,
onTaskCancelled,
onTaskCompleted,
wsTasks,
wsConnected,
wsSubscribe,
}: HeaderProps) {
const handleLogout = async () => {
if (user.provider === 'passkey') {
@ -50,7 +57,13 @@ export function Header({
<HealthIndicator />
{/* Task Indicator */}
<TaskIndicator taskID={taskID ?? null} onTaskCancelled={onTaskCancelled} onTaskCompleted={onTaskCompleted} />
<TaskIndicator
taskID={taskID ?? null}
onTaskCancelled={onTaskCancelled}
onTaskCompleted={onTaskCompleted}
wsTasks={wsTasks}
wsConnected={wsConnected}
/>
{/* Filter Toggle (mobile) */}
{showFilterToggle && (