package main import ( "sync" "time" ) // ProcessingJob tracks the live progress of a document being processed type ProcessingJob struct { ID string `json:"id"` Filename string `json:"filename"` Step string `json:"step"` // "converting", "ocr", "classifying", "embedding", "done", "error" Detail string `json:"detail"` // e.g., "Page 2/5" StartedAt int64 `json:"started_at"` ElapsedMs int64 `json:"elapsed_ms"` } var ( activeJobs = make(map[string]*ProcessingJob) jobsMu sync.RWMutex ) // StartJob creates a new processing job tracker func StartJob(id, filename string) { jobsMu.Lock() defer jobsMu.Unlock() activeJobs[id] = &ProcessingJob{ ID: id, Filename: filename, Step: "starting", StartedAt: time.Now().UnixMilli(), } } // UpdateJob updates the step and detail of an active job func UpdateJob(id, step, detail string) { jobsMu.Lock() defer jobsMu.Unlock() if job, ok := activeJobs[id]; ok { job.Step = step job.Detail = detail job.ElapsedMs = time.Now().UnixMilli() - job.StartedAt } } // FinishJob removes a completed job func FinishJob(id string) { jobsMu.Lock() defer jobsMu.Unlock() delete(activeJobs, id) } // GetActiveJobs returns a snapshot of all active processing jobs func GetActiveJobs() []ProcessingJob { jobsMu.RLock() defer jobsMu.RUnlock() jobs := make([]ProcessingJob, 0, len(activeJobs)) now := time.Now().UnixMilli() for _, job := range activeJobs { j := *job j.ElapsedMs = now - j.StartedAt jobs = append(jobs, j) } return jobs }