Skip to main content

Overview

The captures module manages face capture records with real-time synchronization between the backend pipeline and frontend UI. Import path:
import { api } from '@/convex/_generated/api';

Queries

listRecent

Retrieve the 20 most recent captures. Type signature:
listRecent: () => Promise<Array<{
  _id: Id<"captures">;
  imageUrl: string;
  timestamp: number;
  source: string;
  status: "pending" | "identifying" | "identified" | "failed";
  personId?: Id<"persons">;
}>>
Usage:
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';

function RecentCaptures() {
  const captures = useQuery(api.captures.listRecent);
  
  if (!captures) return <div>Loading...</div>;
  
  return (
    <ul>
      {captures.map(capture => (
        <li key={capture._id}>
          {capture.source} - {capture.status}
        </li>
      ))}
    </ul>
  );
}

Mutations

create

Create a new capture record. Type signature:
create: (args: {
  imageUrl: string;
  source: string;
}) => Promise<Id<"captures">>
Parameters:
imageUrl
string
required
URL or identifier for the captured image.
source
string
required
Source of the capture (e.g., "glasses_stream", "manual_upload").
Returns: Convex document ID of the created capture. Usage:
import { useMutation } from 'convex/react';
import { api } from '@/convex/_generated/api';

function CaptureButton() {
  const createCapture = useMutation(api.captures.create);
  
  const handleCapture = async () => {
    const captureId = await createCapture({
      imageUrl: 'https://example.com/image.jpg',
      source: 'manual_upload'
    });
    console.log('Created capture:', captureId);
  };
  
  return <button onClick={handleCapture}>Capture</button>;
}
Side effects:
  • Creates activity log entry with type "capture"
  • Sets initial status to "pending"
  • Records current timestamp

updateStatus

Update capture processing status. Type signature:
updateStatus: (args: {
  id: Id<"captures">;
  status: "pending" | "identifying" | "identified" | "failed";
  personId?: Id<"persons">;
}) => Promise<void>
Parameters:
id
Id<'captures'>
required
Capture document ID.
status
string
required
New status:
  • pending - Queued for processing
  • identifying - Face detection/identification in progress
  • identified - Successfully identified
  • failed - Processing failed
personId
Id<'persons'>
Optional. Link to identified person record.
Usage:
import { useMutation } from 'convex/react';
import { api } from '@/convex/_generated/api';

function CaptureStatus({ captureId }: { captureId: Id<"captures"> }) {
  const updateStatus = useMutation(api.captures.updateStatus);
  
  const markAsIdentified = async (personId: Id<"persons">) => {
    await updateStatus({
      id: captureId,
      status: 'identified',
      personId
    });
  };
  
  return <button onClick={() => markAsIdentified(personId)}>Mark Identified</button>;
}

store

Backend-compatible generic store (upserts by capture_id). Type signature:
store: (args: { data: any }) => Promise<Id<"captures">>
Parameters:
data
any
required
Capture data object from backend pipeline. Expected fields:
  • capture_id - Pipeline capture identifier
  • source - Source identifier
  • status - Processing status
Returns: Convex document ID (existing or newly created). Usage (Backend):
# Backend pipeline → Convex
await convex.mutation(
    "captures:store",
    data={
        "capture_id": "cap_abc123",
        "source": "telegram",
        "status": "identified"
    }
)
Behavior:
  • If capture with matching imageUrl exists (using capture_id), updates status
  • Otherwise, creates new capture record
  • Automatically maps unknown statuses to "pending"
  • Creates activity log entry on creation

Schema

interface Capture {
  _id: Id<"captures">;
  _creationTime: number;
  imageUrl: string;           // URL or capture_id from pipeline
  timestamp: number;          // Milliseconds since epoch
  source: string;             // Source identifier
  status: "pending" | "identifying" | "identified" | "failed";
  personId?: Id<"persons">;   // Link to identified person
}

Real-Time Subscriptions

Convex automatically syncs capture updates to all connected clients:
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';

function LiveCapturesFeed() {
  // Automatically re-renders when captures change
  const captures = useQuery(api.captures.listRecent);
  
  return (
    <div>
      {captures?.map(capture => (
        <CaptureCard key={capture._id} capture={capture} />
      ))}
    </div>
  );
}

Activity Logging

All capture mutations automatically log to the activityLog table:
interface ActivityLog {
  type: "capture";
  message: string;  // e.g., "New face captured via glasses_stream"
  timestamp: number;
  personId?: Id<"persons">;
}
Query recent activity:
const activity = useQuery(api.intel.recentActivity);

Integration with Pipeline

Backend FastAPI → Convex flow:
# backend/pipeline.py
class CapturePipeline:
    async def process(self, capture_id: str, data: bytes, ...):
        # Create capture record
        await self.db.store_capture({
            "capture_id": capture_id,
            "source": source,
            "status": "pending"
        })
        
        # Update status during processing
        await self.db.update_capture(capture_id, {"status": "identifying"})
        
        # Link to person on success
        person_id = await self.identify_face(...)
        await self.db.update_capture(capture_id, {
            "status": "identified",
            "person_id": person_id
        })

Error Handling

import { useMutation } from 'convex/react';
import { api } from '@/convex/_generated/api';

function CaptureUploader() {
  const createCapture = useMutation(api.captures.create);
  const [error, setError] = useState<string | null>(null);
  
  const handleUpload = async (imageUrl: string) => {
    try {
      const id = await createCapture({ imageUrl, source: 'upload' });
      console.log('Capture created:', id);
    } catch (err) {
      setError(err.message);
      console.error('Capture failed:', err);
    }
  };
  
  return (
    <div>
      {error && <div className="error">{error}</div>}
      <button onClick={() => handleUpload('...')}>Upload</button>
    </div>
  );
}

Best Practices

Use listRecent for live feeds (automatically updates)
Call updateStatus from backend as pipeline progresses
Link captures to persons via personId for traceability
Don’t store large image data in Convex (use URLs/object storage)
Status transitions should be unidirectional (pending → identifying → identified)

Build docs developers (and LLMs) love