Skip to content

Commit ca7d92a

Browse files
committed
refactor: drop isRecordLike pass-through wrappers and return audioLevels directly
useSpeechToText returns its stable, in-place-filled Float32Array instead of a nullable ref; MicButton and the composer, search, and user-input props follow.
1 parent 52f8b4d commit ca7d92a

9 files changed

Lines changed: 30 additions & 38 deletions

File tree

‎apps/sim/app/o/[organizationId]/home/components/composer/composer.test.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ beforeEach(() => {
175175
mocks.speech.mockReturnValue({
176176
isSupported: true,
177177
isListening: false,
178-
audioLevelsRef: { current: new Float32Array(5) },
178+
audioLevels: new Float32Array(5),
179179
toggleListening: mocks.toggleListening,
180180
resetTranscript: mocks.resetTranscript,
181181
})

‎apps/sim/app/o/[organizationId]/home/components/composer/composer.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ export function Composer({
249249
)
250250
const voiceControl = voice.isSupported && (
251251
<MicButton
252-
audioLevelsRef={voice.audioLevelsRef}
252+
audioLevels={voice.audioLevels}
253253
isListening={voice.isListening}
254254
onToggle={voice.toggleListening}
255255
/>

‎apps/sim/app/o/[organizationId]/search/search.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ function SearchField({ userId, initialValue, onSubmit }: SearchFieldProps) {
8888
voiceControl={
8989
voice.isSupported && (
9090
<MicButton
91-
audioLevelsRef={voice.audioLevelsRef}
91+
audioLevels={voice.audioLevels}
9292
isListening={voice.isListening}
9393
onToggle={voice.toggleListening}
9494
/>

‎apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/mic-button/mic-button.tsx‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { memo, type RefObject, useEffect, useRef } from 'react'
3+
import { memo, useEffect, useRef } from 'react'
44
import { Button, cn, Tooltip, usePrefersReducedMotion } from '@sim/emcn'
55
import { Mic } from '@sim/emcn/icons'
66

@@ -12,16 +12,16 @@ const WAVEFORM_EASING = 0.24
1212

1313
interface MicButtonProps {
1414
isListening: boolean
15-
audioLevelsRef: RefObject<Float32Array | null>
15+
audioLevels: Float32Array
1616
onToggle: () => void
1717
}
1818

1919
interface VoiceWaveformProps {
20-
audioLevelsRef: RefObject<Float32Array | null>
20+
audioLevels: Float32Array
2121
isListening: boolean
2222
}
2323

24-
function VoiceWaveform({ audioLevelsRef, isListening }: VoiceWaveformProps) {
24+
function VoiceWaveform({ audioLevels, isListening }: VoiceWaveformProps) {
2525
const prefersReducedMotion = usePrefersReducedMotion()
2626
const barRefs = useRef<Array<SVGLineElement | null>>([])
2727

@@ -32,10 +32,8 @@ function VoiceWaveform({ audioLevelsRef, isListening }: VoiceWaveformProps) {
3232
let animationFrameId = 0
3333

3434
const draw = () => {
35-
const levels = audioLevelsRef.current
36-
3735
for (let index = 0; index < WAVEFORM_BAR_COUNT; index++) {
38-
const level = levels?.[index] ?? 0
36+
const level = audioLevels[index] ?? 0
3937
const targetHeight =
4038
WAVEFORM_MIN_HEIGHT +
4139
Math.sqrt(Math.max(0, level)) * (WAVEFORM_MAX_HEIGHT - WAVEFORM_MIN_HEIGHT)
@@ -53,7 +51,7 @@ function VoiceWaveform({ audioLevelsRef, isListening }: VoiceWaveformProps) {
5351

5452
animationFrameId = window.requestAnimationFrame(draw)
5553
return () => window.cancelAnimationFrame(animationFrameId)
56-
}, [audioLevelsRef, isListening, prefersReducedMotion])
54+
}, [audioLevels, isListening, prefersReducedMotion])
5755

5856
return (
5957
<svg aria-hidden viewBox='0 0 18 18' className='size-[18px] overflow-hidden'>
@@ -81,7 +79,7 @@ function VoiceWaveform({ audioLevelsRef, isListening }: VoiceWaveformProps) {
8179

8280
export const MicButton = memo(function MicButton({
8381
isListening,
84-
audioLevelsRef,
82+
audioLevels,
8583
onToggle,
8684
}: MicButtonProps) {
8785
return (
@@ -105,7 +103,7 @@ export const MicButton = memo(function MicButton({
105103
isListening ? 'scale-100 opacity-100 blur-none' : 'scale-[0.25] opacity-0 blur-[4px]'
106104
)}
107105
>
108-
<VoiceWaveform audioLevelsRef={audioLevelsRef} isListening={isListening} />
106+
<VoiceWaveform audioLevels={audioLevels} isListening={isListening} />
109107
</span>
110108
<Mic
111109
strokeWidth='1.25'

‎apps/sim/app/workspace/[workspaceId]/home/components/user-input/user-input.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ const UserInputImpl = forwardRef<UserInputHandle, UserInputProps>(function UserI
324324
}
325325

326326
const {
327-
audioLevelsRef,
327+
audioLevels,
328328
isListening,
329329
isSupported: isSttSupported,
330330
toggleListening,
@@ -641,7 +641,7 @@ const UserInputImpl = forwardRef<UserInputHandle, UserInputProps>(function UserI
641641
voiceControl={
642642
isSttSupported && (
643643
<MicButton
644-
audioLevelsRef={audioLevelsRef}
644+
audioLevels={audioLevels}
645645
isListening={isListening}
646646
onToggle={toggleListening}
647647
/>

‎apps/sim/hooks/use-speech-to-text.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { type RefObject, useCallback, useEffect, useRef, useState } from 'react'
3+
import { useCallback, useEffect, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { isApiClientError } from '@/lib/api/client/errors'
66
import { requestJson } from '@/lib/api/client/request'
@@ -57,7 +57,8 @@ interface UseSpeechToTextProps {
5757
interface UseSpeechToTextReturn {
5858
isListening: boolean
5959
isSupported: boolean
60-
audioLevelsRef: RefObject<Float32Array | null>
60+
/** Live input levels, filled in place while listening; the array identity never changes. */
61+
audioLevels: Float32Array
6162
toggleListening: () => void
6263
resetTranscript: () => void
6364
}
@@ -119,8 +120,8 @@ export function useSpeechToText({
119120
const streamRef = useRef<MediaStream | null>(null)
120121
const audioContextRef = useRef<AudioContext | null>(null)
121122
const processorRef = useRef<ScriptProcessorNode | null>(null)
122-
const audioLevelsRef = useRef<Float32Array | null>(null)
123-
const audioLevels = (audioLevelsRef.current ??= new Float32Array(AUDIO_LEVEL_COUNT))
123+
const levelsRef = useRef<Float32Array | null>(null)
124+
const audioLevels = (levelsRef.current ??= new Float32Array(AUDIO_LEVEL_COUNT))
124125

125126
const pcmBufferRef = useRef<Float32Array[]>([])
126127
const sendIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
@@ -440,7 +441,7 @@ export function useSpeechToText({
440441
return {
441442
isListening,
442443
isSupported,
443-
audioLevelsRef,
444+
audioLevels,
444445
toggleListening,
445446
resetTranscript,
446447
}

‎apps/sim/hooks/use-voice-input.test.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ beforeEach(() => {
3434
mocks.speech.mockReturnValue({
3535
isListening: false,
3636
isSupported: true,
37-
audioLevelsRef: { current: new Float32Array(5) },
37+
audioLevels: new Float32Array(5),
3838
toggleListening: vi.fn(),
3939
resetTranscript: vi.fn(),
4040
})

‎packages/sim-cli/src/commands/protocol/workflow-run-get.ts‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ interface WorkflowBlock {
3939
name: string
4040
}
4141

42-
function isRecord(value: unknown): value is Record<string, unknown> {
43-
return isRecordLike(value)
44-
}
45-
4642
/**
4743
* The workflow's blocks, read from its draft graph.
4844
*
@@ -56,11 +52,12 @@ async function loadWorkflowBlocks(client: SimClient, workflowId: string): Promis
5652
const raw = await client.request<unknown>(resolvePath(operation.path, { workflowId }), {
5753
method: operation.method,
5854
})
59-
const state = isRecord(raw) && isRecord(raw.data) ? raw.data : raw
60-
const blocks = isRecord(state) && isRecord(state.blocks) ? Object.entries(state.blocks) : []
55+
const state = isRecordLike(raw) && isRecordLike(raw.data) ? raw.data : raw
56+
const blocks =
57+
isRecordLike(state) && isRecordLike(state.blocks) ? Object.entries(state.blocks) : []
6158
return blocks.map(([key, block]) => ({
62-
id: isRecord(block) && typeof block.id === 'string' ? block.id : key,
63-
name: isRecord(block) && typeof block.name === 'string' ? block.name : '',
59+
id: isRecordLike(block) && typeof block.id === 'string' ? block.id : key,
60+
name: isRecordLike(block) && typeof block.name === 'string' ? block.name : '',
6461
}))
6562
}
6663

@@ -148,7 +145,7 @@ function resolveSelection(
148145
* matches it.
149146
*/
150147
function keyByTyped(payload: unknown, typedBy: ReadonlyMap<string, string>): unknown {
151-
if (!isRecord(payload) || !isRecord(payload.blockOutputs)) return payload
148+
if (!isRecordLike(payload) || !isRecordLike(payload.blockOutputs)) return payload
152149
const blockOutputs: Record<string, unknown> = {}
153150
for (const [key, value] of Object.entries(payload.blockOutputs)) {
154151
blockOutputs[typedBy.get(key) ?? key] = value

‎packages/testing/src/mocks/human-in-the-loop-manager.mock.ts‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,10 @@ class MockResumeAdmissionError extends Error {
1616
}
1717
}
1818

19-
function isMockRecord(value: unknown): value is Record<string, unknown> {
20-
return isRecordLike(value)
21-
}
22-
2319
function isPausedOutputForContext(output: unknown, contextId: string): boolean {
24-
if (!isMockRecord(output)) return false
20+
if (!isRecordLike(output)) return false
2521
const metadata = output._pauseMetadata
26-
return isMockRecord(metadata) && metadata.contextId === contextId
22+
return isRecordLike(metadata) && metadata.contextId === contextId
2723
}
2824

2925
interface MockAggregationState {
@@ -124,7 +120,7 @@ export const humanInTheLoopManagerMockFns = {
124120
mergedOutput: Record<string, unknown>
125121
): void => {
126122
for (const scope of Object.values(state.loopExecutions ?? {})) {
127-
if (!isMockRecord(scope) || !isMockRecord(scope.currentIterationOutputs)) continue
123+
if (!isRecordLike(scope) || !isRecordLike(scope.currentIterationOutputs)) continue
128124
const outputs = scope.currentIterationOutputs
129125
const pausedEntry =
130126
outputs[stateBlockKey] !== undefined
@@ -144,7 +140,7 @@ export const humanInTheLoopManagerMockFns = {
144140
}
145141

146142
for (const scope of Object.values(state.parallelExecutions ?? {})) {
147-
if (!isMockRecord(scope) || !isMockRecord(scope.branchOutputs)) continue
143+
if (!isRecordLike(scope) || !isRecordLike(scope.branchOutputs)) continue
148144
const branches = scope.branchOutputs
149145
for (const [branchIndex, branchOutputs] of Object.entries(branches)) {
150146
if (!Array.isArray(branchOutputs)) continue

0 commit comments

Comments
 (0)