Feat/enhanced timestamped transcript (#501)

* feat: add additional timestamps in call transcript optionally

* fi: timestamp precision to millisec instead of micro

* fix: address enhanced transcript review issues

* fix: non vad user turn timestamp
This commit is contained in:
Sabiha Khan 2026-07-07 14:47:43 +05:30 committed by GitHub
parent d9b9a1efc8
commit ac01f7775e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 441 additions and 15 deletions

View file

@ -78,6 +78,7 @@ export const ConfigurationsDialog = ({
turn_start_min_words: turnStartMinWords,
provisional_vad_pause_secs: provisionalVadPauseSecs,
turn_stop_strategy: turnStopStrategy,
transcript_configuration: resolvedWorkflowConfigurations.transcript_configuration,
context_compaction_enabled: contextCompactionEnabled,
}, name);
onOpenChange(false);

View file

@ -48,6 +48,7 @@ import {
DEFAULT_PROVISIONAL_VAD_PAUSE_SECS,
DEFAULT_TURN_START_MIN_WORDS,
DEFAULT_VOICEMAIL_DETECTION_CONFIGURATION,
resolveWorkflowConfigurations,
TURN_START_STRATEGY_OPTIONS,
type TurnStartStrategy,
type TurnStopStrategy,
@ -293,6 +294,9 @@ function GeneralSection({
const [contextCompactionEnabled, setContextCompactionEnabled] = useState(
workflowConfigurations.context_compaction_enabled,
);
const [includeTranscriptEndTimestamps, setIncludeTranscriptEndTimestamps] = useState(
workflowConfigurations.transcript_configuration?.include_end_timestamps ?? false,
);
const [isSaving, setIsSaving] = useState(false);
const [isUploadingAudio, setIsUploadingAudio] = useState(false);
const [audioUploadError, setAudioUploadError] = useState<string | null>(null);
@ -314,9 +318,11 @@ function GeneralSection({
turnStartMinWords !== workflowConfigurations.turn_start_min_words ||
provisionalVadPauseSecs !== workflowConfigurations.provisional_vad_pause_secs ||
turnStopStrategy !== workflowConfigurations.turn_stop_strategy ||
contextCompactionEnabled !== workflowConfigurations.context_compaction_enabled
contextCompactionEnabled !== workflowConfigurations.context_compaction_enabled ||
includeTranscriptEndTimestamps !==
(workflowConfigurations.transcript_configuration?.include_end_timestamps ?? false)
);
}, [name, workflowName, ambientNoiseConfig, maxCallDuration, maxUserIdleTimeout, smartTurnStopSecs, turnStartStrategy, turnStartMinWords, provisionalVadPauseSecs, turnStopStrategy, contextCompactionEnabled, workflowConfigurations]);
}, [name, workflowName, ambientNoiseConfig, maxCallDuration, maxUserIdleTimeout, smartTurnStopSecs, turnStartStrategy, turnStartMinWords, provisionalVadPauseSecs, turnStopStrategy, contextCompactionEnabled, includeTranscriptEndTimestamps, workflowConfigurations]);
useUnsavedChanges("general", isDirty);
@ -393,6 +399,10 @@ function GeneralSection({
provisional_vad_pause_secs: provisionalVadPauseSecs,
turn_stop_strategy: turnStopStrategy,
context_compaction_enabled: contextCompactionEnabled,
transcript_configuration: {
...(workflowConfigurations.transcript_configuration ?? {}),
include_end_timestamps: includeTranscriptEndTimestamps,
},
},
name,
);
@ -686,6 +696,34 @@ function GeneralSection({
<Separator />
{/* Transcript */}
<div className="space-y-4">
<div>
<h3 className="text-sm font-medium">Transcript</h3>
<p className="text-xs text-muted-foreground mt-0.5">
Include start and stop timestamps for each speaker in the uploaded transcript.
</p>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="transcript-end-timestamps-enabled" className="text-sm">
Enhanced Timestamped Transcript
</Label>
<Switch
id="transcript-end-timestamps-enabled"
checked={includeTranscriptEndTimestamps}
onCheckedChange={setIncludeTranscriptEndTimestamps}
/>
</div>
<div className="rounded-md border bg-muted/20 p-3">
<pre className="whitespace-pre-wrap text-xs leading-relaxed text-muted-foreground">
{`[2026-07-06T10:00:00.000Z -> 2026-07-06T10:00:04.800Z] assistant: Can you confirm your date of birth?
[2026-07-06T10:00:06.200Z -> 2026-07-06T10:00:08.700Z] user: January fifth, nineteen ninety.`}
</pre>
</div>
</div>
<Separator />
{/* Context Compaction */}
<div className="space-y-4">
<div>
@ -1458,6 +1496,9 @@ function WorkflowSettingsInner({
initialWorkflowConfigurations,
user,
});
const resolvedWorkflowConfigurationsForRender = workflowConfigurations
? resolveWorkflowConfigurations(workflowConfigurations)
: null;
useEffect(() => {
if (hasFetchedModelConfiguration.current) return;
@ -1532,18 +1573,18 @@ function WorkflowSettingsInner({
<div className="mx-auto flex max-w-5xl gap-8 px-6 py-8">
{/* Sections */}
<div className="min-w-0 flex-1 space-y-8">
{workflowConfigurations && (
{resolvedWorkflowConfigurationsForRender && (
<>
{/* General */}
<GeneralSection
workflowConfigurations={workflowConfigurations}
workflowConfigurations={resolvedWorkflowConfigurationsForRender}
workflowName={workflowName || workflow.name}
workflowId={workflowId}
onSave={saveWorkflowConfigurations}
/>
<WorkflowModelOverridesSection
workflowConfigurations={workflowConfigurations}
workflowConfigurations={resolvedWorkflowConfigurationsForRender}
workflowName={workflowName}
onSave={saveWorkflowConfigurations}
modelConfigurationDefaults={modelConfigurationDefaults}
@ -1563,7 +1604,7 @@ function WorkflowSettingsInner({
{/* Voicemail Detection */}
<VoicemailSection
workflowConfigurations={workflowConfigurations}
workflowConfigurations={resolvedWorkflowConfigurationsForRender}
workflowName={workflowName}
onSave={saveWorkflowConfigurations}
/>

View file

@ -38,6 +38,7 @@ export interface RealtimeFeedbackEvent {
final?: boolean;
user_id?: string;
timestamp?: string;
end_timestamp?: string;
function_name?: string;
tool_call_id?: string;
arguments?: unknown;

View file

@ -60,6 +60,14 @@ export const DEFAULT_VOICEMAIL_DETECTION_CONFIGURATION: VoicemailDetectionConfig
long_speech_timeout: 8.0,
};
export interface TranscriptConfiguration {
include_end_timestamps: boolean;
}
export const DEFAULT_TRANSCRIPT_CONFIGURATION: TranscriptConfiguration = {
include_end_timestamps: false,
};
export interface ModelOverrides {
llm?: {
provider?: string;
@ -115,6 +123,7 @@ export type WorkflowConfigurations = WorkflowConfigurationBase & {
turn_stop_strategy: TurnStopStrategy; // Strategy for detecting end of user turn
dictionary?: string; // Comma-separated words for voice agent to listen for
voicemail_detection?: VoicemailDetectionConfiguration;
transcript_configuration: TranscriptConfiguration;
context_compaction_enabled: boolean; // Summarize context on node transitions to remove stale tool calls
model_overrides?: ModelOverrides; // Per-workflow model configuration overrides
model_configuration_v2_override?: OrganizationAiModelConfigurationV2; // Full v2 model configuration override
@ -134,6 +143,7 @@ const FALLBACK_WORKFLOW_CONFIGURATIONS: WorkflowConfigurations = {
provisional_vad_pause_secs: DEFAULT_PROVISIONAL_VAD_PAUSE_SECS,
turn_stop_strategy: 'transcription', // Default to transcription-based detection
dictionary: '',
transcript_configuration: DEFAULT_TRANSCRIPT_CONFIGURATION,
context_compaction_enabled: false,
};
@ -186,5 +196,10 @@ export function resolveWorkflowConfigurations(
configurations?.context_compaction_enabled
?? defaults?.context_compaction_enabled
?? FALLBACK_WORKFLOW_CONFIGURATIONS.context_compaction_enabled,
transcript_configuration: {
...DEFAULT_TRANSCRIPT_CONFIGURATION,
...(defaults?.transcript_configuration as Partial<TranscriptConfiguration> | undefined),
...(configurations?.transcript_configuration as Partial<TranscriptConfiguration> | undefined),
},
};
}