Skip to content

Temporal in Frontend

The frontend does not run Temporal directly. It talks to a backend service that owns the Temporal workers and workflows. From this project’s perspective, Temporal is an orchestration engine behind a REST API.

The core integration lives in src/services/TemporalGenerationService/TemporalGenerationService.ts. That class is the gateway between your UI and Temporal-backed workflows.

Here’s what’s happening structurally.

First, Temporal is used to orchestrate long-running generation workflows — things like image generation, batch generation, mask edits, and video merge operations. Instead of doing generation synchronously, the frontend starts a workflow and then tracks it over time.

When a user triggers generation, the app:

  • Builds a unique workflow ID (for example: gen-${card.id}-${Date.now()})
  • Sends a POST request to backend endpoints like:

  • /workflows/start-with-refinement

  • /workflows/batch/start-with-refinement
  • /workflows/start
  • /workflows/merge

These endpoints are wrappers around Temporal workflow executions.

So the flow looks like this:

User action → Frontend calls API → Backend starts Temporal workflow → Frontend polls for status → UI updates progressively.

--- config: layout: elk --- flowchart TB UI[Frontend UI] --> SVC[TemporalGenerationService] SVC --> START[Start job single or batch] START --> API[Backend API] API --> RESP[Return workflow ids] RESP --> SVC SVC --> STORE[Update PocketBase records] STORE --> UI SVC --> POLL[Polling loop per workflow id] POLL --> STATUS[Get workflow status] STATUS --> API API --> STATUS STATUS --> POLL POLL --> MAP[Map raw status to UI state] MAP --> UPDATE[Update job or batch progress] UPDATE --> UI MAP --> STOP[Stop polling when terminal] UI --> CANCEL_UI[User cancel action] CANCEL_UI --> SVC SVC --> CANCEL[Cancel job] CANCEL --> API

Now let’s break down the main Temporal usage patterns.

Single generation workflow

When generating an image (or similar content), the service sends:

  • userId
  • projectId
  • cardId
  • workflowId
  • workflowType (derived from card type and action type)
  • taskQueue
  • conversationId

The backend responds with a runId. The frontend then updates the card’s generation_started_at timestamp in PocketBase and begins tracking the workflow.

Batch generation

Batch generation calls /workflows/batch/start-with-refinement. The backend returns:

  • batchId
  • an array of workflowIds + runIds

The frontend then:

  • Tracks each individual workflow
  • Exposes aggregate batch status
  • Maps backend results to file IDs when complete

This tells me Temporal is being used to fan out multiple child workflows and aggregate progress.

Mask edit generation

There’s a specialized flow for image editing with masks. This uses /workflows/start and sends a structured input object that includes:

  • prompt
  • modelId
  • parameters (with actionType forced to "edit")
  • referenceImages
  • mask
  • userId
  • projectId

This implies the backend workflow definition expects a structured input payload — likely passed as the workflow input parameter in Temporal.

Polling instead of push

There’s no WebSocket or server-sent events here. The frontend polls every ~2 seconds (configurable) using:

/workflows/{workflowId}/status

Polling stops when the status is:

  • completed
  • failed
  • cancelled
  • terminated

The service tracks:

  • poll intervals
  • consecutive errors
  • failed workflows
  • listeners per workflow

If too many polling errors occur, it marks the workflow as failed and stops trying.

That’s a deliberate resilience layer in the client.

Status mapping

Temporal’s native status values (numeric or string) are mapped to simplified frontend statuses:

running completed failed cancelled terminated

Numeric codes like:

  • 2 → completed
  • 3 → failed
  • 4 → cancelled

are converted via mapTemporalStatus().

So the frontend is abstracting Temporal SDK status enums into UI-friendly states.

Workflow management features

The service also supports:

  • Canceling a workflow (POST /workflows/{workflowId}/cancel)
  • Fetching active workflows for a user
  • Fetching workflow history
  • Subscribing to updates via internal listener sets
  • Starting a merge workflow for video export

That last one (/workflows/merge) is clearly another Temporal workflow type used for post-production style operations.

Summary

Temporal is being used as:

  • A durable orchestrator for AI/media generation
  • A fan-out engine for batch jobs
  • A coordinator for multi-step workflows (likely refinement → generation → storage → result)
  • A state machine whose lifecycle is reflected in the UI

The frontend treats it as a job engine — start job, poll job, display progress, handle completion.

Important design implications

  1. The frontend never directly uses the Temporal SDK.
  2. All Temporal interaction is abstracted behind a backend HTTP API.
  3. Workflow IDs are generated client-side.
  4. The backend likely uses those workflow IDs as Temporal workflow IDs (which allows deterministic idempotency).
  5. Polling is the synchronization mechanism — no event streaming.