Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions field-logic/src/components/AudioRecorder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ export const AudioRecorder: React.FC<Props> = ({ sessionId, nodeId, onRecordingC
setAudioUrl(url);

// Save to Dexie DB
await saveAudio(sessionId, nodeId, blob);
// We pass the nodeId as the "key" reference for now
onRecordingComplete(nodeId);
const blobKey = await saveAudio(sessionId, nodeId, blob);

// We pass the unique blobKey reference instead of nodeId
onRecordingComplete(blobKey);

// Stop all tracks
stream.getTracks().forEach(track => track.stop());
Expand Down
29 changes: 25 additions & 4 deletions field-logic/src/components/SurveyWizard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import { SurveyEngine } from '../lib/engine';
import type { SurveyDefinition, SurveyNode } from '../lib/types';
import { SurveyEngine } from '../lib/SurveyEngine';
import type { SurveyDefinition, SurveyNode } from '../types/schema';
import { AudioRecorder } from './AudioRecorder';
import '../styles/main.css';

interface Props {
Expand Down Expand Up @@ -30,7 +31,7 @@ export const SurveyWizard: React.FC<Props> = ({ definition }) => {
return;
}

const nextNode = engine.next(inputValue);
const nextNode = engine.submitAnswer(inputValue);
setCurrentNode(nextNode);
setInputValue(null); // Reset input for next question
};
Expand All @@ -48,8 +49,10 @@ export const SurveyWizard: React.FC<Props> = ({ definition }) => {
return <div className="survey-container">Loading...</div>;
}

const { sessionId } = engine.getSession();

const renderContent = () => {
const { type, content } = currentNode;
const { type, content, id: nodeId } = currentNode;

switch (type) {
case 'info':
Expand Down Expand Up @@ -116,6 +119,24 @@ export const SurveyWizard: React.FC<Props> = ({ definition }) => {
</div>
);

case 'audio':
return (
<div>
<h1>{content.question}</h1>
{content.helper_text && <p className="helper">{content.helper_text}</p>}
<AudioRecorder
sessionId={sessionId}
nodeId={nodeId}
onRecordingComplete={(blobKey) => handleChoiceSelect(blobKey)}
/>
{inputValue && (
<p style={{ marginTop: '0.5rem', color: '#10b981', fontWeight: '500' }}>
✓ Recording captured
</p>
)}
</div>
);

case 'summary':
return (
<div>
Expand Down
28 changes: 25 additions & 3 deletions field-logic/src/lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface ResponseRecord {
}

export interface AudioBlobRecord {
id?: string; // Compound index key [sessionId+nodeId]
id: string; // Unique ID: sessionId_nodeId_timestamp
sessionId: string;
nodeId: string;
blob: Blob;
Expand All @@ -27,6 +27,19 @@ export class FieldLogicDB extends Dexie {
responses: '[sessionId+nodeId], sessionId, nodeId',
blobs: '[sessionId+nodeId], sessionId, nodeId, synced'
});

this.version(2).stores({
blobs: 'id, sessionId, nodeId, [sessionId+nodeId], synced'
}).upgrade(async tx => {
// Migrate existing blobs to have a unique string ID if they don't have one
return tx.table('blobs').toCollection().modify(record => {
if (!record.id) {
// Use existing fields to recreate the ID convention
const ts = record.timestamp || Date.now();
record.id = `${record.sessionId}_${record.nodeId}_${ts}`;
}
});
});
}
}

Expand All @@ -44,20 +57,29 @@ export const saveResponse = async (sessionId: string, nodeId: string, value: any

// Helper to save Audio Blob
export const saveAudio = async (sessionId: string, nodeId: string, blob: Blob) => {
const timestamp = Date.now();
const id = `${sessionId}_${nodeId}_${timestamp}`;
await db.blobs.put({
id,
sessionId,
nodeId,
blob,
timestamp: Date.now(),
timestamp,
synced: false
});
return id;
};

// Get all responses for a session
export const getSessionResponses = async (sessionId: string) => {
return await db.responses.where('sessionId').equals(sessionId).toArray();
};

// Get the latest audio blob for a specific node in a session
export const getAudioBlob = async (sessionId: string, nodeId: string) => {
return await db.blobs.get({ sessionId, nodeId });
return await db.blobs.where({ sessionId, nodeId }).first();
};

export const getAudioBlobById = async (id: string) => {
return await db.blobs.get(id);
};
12 changes: 6 additions & 6 deletions field-logic/src/lib/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export const flushAudioBlobs = async (token: string = 'test_token') => {
const results = [];

for (const record of unsyncedBlobs) {
const { sessionId, nodeId, blob } = record;
const filename = `${sessionId}_${nodeId}.webm`;
const { id, sessionId, nodeId, blob } = record;
const filename = `${id}.webm`;

try {
// A. Upload
Expand All @@ -36,15 +36,15 @@ export const flushAudioBlobs = async (token: string = 'test_token') => {
await saveResponse(sessionId, nodeId, metadata);

// C. Delete local blob (free space)
// Using compound key [sessionId, nodeId]
await db.blobs.where({ sessionId, nodeId }).delete();
// Using the unique primary key 'id'
await db.blobs.delete(id);

console.log(`Synced & Flushed: ${filename}`);
results.push({ sessionId, nodeId, status: 'synced', url });
results.push({ id, sessionId, nodeId, status: 'synced', url });

} catch (e) {
console.error(`Failed to sync ${filename}`, e);
results.push({ sessionId, nodeId, status: 'failed', error: e });
results.push({ id, sessionId, nodeId, status: 'failed', error: e });
}
}
return results;
Expand Down