Stream dictation via OpenAI Realtime and simplify retry UI

This commit is contained in:
Mohamed Boudra
2026-01-23 18:55:14 +07:00
parent 912a694221
commit 0ff0293a97
22 changed files with 1885 additions and 582 deletions
@@ -18,7 +18,6 @@ interface DictationControlsProps {
onRetry?: () => void;
onDiscard?: () => void;
disabled?: boolean;
retryStatusText?: string;
}
function formatDuration(seconds: number): string {
@@ -42,13 +41,11 @@ export function DictationControls({
onRetry,
onDiscard,
disabled = false,
retryStatusText,
}: DictationControlsProps) {
const { theme } = useUnistyles();
const isRetrying = status === "retrying";
const isFailed = status === "failed";
const showActiveState = isRecording || isProcessing || isRetrying || isFailed;
const actionsDisabled = isProcessing || isRetrying;
const showActiveState = isRecording || isProcessing || isFailed;
const actionsDisabled = isProcessing;
const handleCancel = isFailed && onDiscard ? onDiscard : onCancel;
if (!showActiveState) {
@@ -123,13 +120,6 @@ export function DictationControls({
</>
)}
</View>
{retryStatusText && (isRetrying || isFailed) && (
<Text
style={[styles.statusLabel, { color: theme.colors.mutedForeground }]}
>
{retryStatusText}
</Text>
)}
</View>
);
}
@@ -149,12 +139,11 @@ export function DictationOverlay({
onAcceptAndSend,
onRetry,
onDiscard,
}: Omit<DictationControlsProps, "onStart" | "disabled" | "retryStatusText">) {
}: Omit<DictationControlsProps, "onStart" | "disabled">) {
const { theme } = useUnistyles();
const isRetrying = status === "retrying";
const isFailed = status === "failed";
const showActiveState = isRecording || isProcessing || isRetrying || isFailed;
const actionsDisabled = isProcessing || isRetrying;
const showActiveState = isRecording || isProcessing || isFailed;
const actionsDisabled = isProcessing;
const handleCancel = isFailed && onDiscard ? onDiscard : onCancel;
if (!showActiveState) {
@@ -210,7 +210,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
});
useEffect(() => {
if (isDictating || isDictationProcessing || dictationStatus === "retrying") {
if (isDictating || isDictationProcessing) {
return;
}
sendAfterTranscriptRef.current = false;
@@ -246,7 +246,6 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
const showOverlay =
isDictating ||
isDictationProcessing ||
dictationStatus === "retrying" ||
dictationStatus === "failed";
overlayTransition.value = withTiming(showOverlay ? 1 : 0, {
duration: 200,
+4 -4
View File
@@ -88,7 +88,7 @@ export function VolumeMeter({ volume, isMuted = false, isDetecting = false, isSp
return;
}
if (volume > 0.01) {
if (volume > 0.001) {
// Active volume - animate heights based on volume
const target1 = MIN_HEIGHT + (MAX_HEIGHT * volume * 1.2);
const target2 = MIN_HEIGHT + (MAX_HEIGHT * volume * 1.05);
@@ -136,7 +136,7 @@ export function VolumeMeter({ volume, isMuted = false, isDetecting = false, isSp
const baseOpacity = isMuted ? 0.3 : isActive ? 0.9 : 0.5;
const volumeBoost = isMuted ? 0 : volume * 0.3;
return {
height: line1Height.value * (isMuted || volume > 0.01 ? 1 : line1Pulse.value),
height: line1Height.value * (isMuted || volume > 0.001 ? 1 : line1Pulse.value),
opacity: baseOpacity + volumeBoost,
};
});
@@ -146,7 +146,7 @@ export function VolumeMeter({ volume, isMuted = false, isDetecting = false, isSp
const baseOpacity = isMuted ? 0.3 : isActive ? 0.9 : 0.5;
const volumeBoost = isMuted ? 0 : volume * 0.3;
return {
height: line2Height.value * (isMuted || volume > 0.01 ? 1 : line2Pulse.value),
height: line2Height.value * (isMuted || volume > 0.001 ? 1 : line2Pulse.value),
opacity: baseOpacity + volumeBoost,
};
});
@@ -156,7 +156,7 @@ export function VolumeMeter({ volume, isMuted = false, isDetecting = false, isSp
const baseOpacity = isMuted ? 0.3 : isActive ? 0.9 : 0.5;
const volumeBoost = isMuted ? 0 : volume * 0.3;
return {
height: line3Height.value * (isMuted || volume > 0.01 ? 1 : line3Pulse.value),
height: line3Height.value * (isMuted || volume > 0.001 ? 1 : line3Pulse.value),
opacity: baseOpacity + volumeBoost,
};
});