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:
URL or identifier for the captured image.
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:
New status:
pending - Queued for processing
identifying - Face detection/identification in progress
identified - Successfully identified
failed - Processing failed
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:
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)