All blocks
Block
Voice input
Hold the mic to dictate. While the audio is being transcribed, a text-shimmer label sits above the input until the transcript drops in.
Mic → transcribing → ready to send
State: idle
@if (state() === 'transcribing') {
<pk-text-shimmer text="Transcribing audio…" class="text-sm" />
}
<pk-prompt-input [(value)]="value" (submitted)="onSubmit()">
<pk-prompt-input-textarea
[placeholder]="state() === 'recording' ? 'Listening...' : 'Speak or type'"
/>
<pk-prompt-input-actions class="mt-2 justify-between">
<pk-prompt-input-action [tooltip]="state() === 'recording' ? 'Stop' : 'Voice'">
<button hlmBtn size="icon-sm"
[variant]="state() === 'recording' ? 'destructive' : 'ghost'"
(click)="toggleVoice()">
<ng-icon hlm size="sm"
[name]="state() === 'recording' ? 'lucideSquare' : 'lucideMic'" />
</button>
</pk-prompt-input-action>
<pk-prompt-input-action tooltip="Send">
<button hlmBtn size="icon-sm" class="rounded-full"
[disabled]="state() !== 'idle' || !value().trim()"
(click)="onSubmit()">
<ng-icon hlm size="xs" name="lucideArrowUp" />
</button>
</pk-prompt-input-action>
</pk-prompt-input-actions>
</pk-prompt-input>
// Component
type State = 'idle' | 'recording' | 'transcribing';
protected readonly state = signal<State>('idle');
protected toggleVoice(): void {
if (this.state() === 'idle') {
this.state.set('recording');
// wire MediaRecorder here
} else if (this.state() === 'recording') {
this.finishRecording(); // upload + state.set('transcribing')
}
}