feat(ui): display livePartial / transcript_delta in ChatBubble as 'Hearing:' indicator
- REST STT now sends delta (full text) so UI lights up immediately with what was heard. - Works as 'live' for the final transcript (true partials would stream words if Realtime was available). - Per PLAN item 3.
This commit is contained in:
@@ -28,6 +28,7 @@ export default function App() {
|
||||
sendText,
|
||||
startRecording,
|
||||
stopRecording,
|
||||
livePartial,
|
||||
} = useConversation();
|
||||
|
||||
const [currentSceneId, setCurrentSceneId] = useState('cozy-room');
|
||||
@@ -143,7 +144,7 @@ export default function App() {
|
||||
|
||||
{/* Column 3: Chat + Text Input */}
|
||||
<div className="space-y-4">
|
||||
<ChatBubble messages={messages} isKiraSpeaking={isKiraSpeaking} userName={userName} />
|
||||
<ChatBubble messages={messages} isKiraSpeaking={isKiraSpeaking} userName={userName} livePartial={livePartial} />
|
||||
|
||||
{/* Text input fallback */}
|
||||
<div className="glass-card p-3">
|
||||
|
||||
@@ -11,9 +11,10 @@ interface Props {
|
||||
messages: Message[];
|
||||
isKiraSpeaking: boolean;
|
||||
userName?: string;
|
||||
livePartial?: string;
|
||||
}
|
||||
|
||||
export default function ChatBubble({ messages, isKiraSpeaking }: Props) {
|
||||
export default function ChatBubble({ messages, isKiraSpeaking, livePartial }: Props) {
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -27,6 +28,15 @@ export default function ChatBubble({ messages, isKiraSpeaking }: Props) {
|
||||
<span className={`w-2 h-2 rounded-full ${isKiraSpeaking ? 'bg-kira-pink animate-pulse' : 'bg-kira-mint'}`} />
|
||||
</h3>
|
||||
|
||||
{livePartial && (
|
||||
<div className="mb-2 px-3 py-1.5 bg-kira-lav/20 text-kira-plum/70 text-xs rounded-xl flex items-center gap-2">
|
||||
<span>👂</span>
|
||||
<span className="font-medium">Hearing:</span>
|
||||
<span className="truncate">{livePartial}</span>
|
||||
<span className="animate-pulse">...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1 overflow-y-auto space-y-2 scrollbar-thin pr-1">
|
||||
{messages.length === 0 && (
|
||||
<div className="text-xs text-kira-plum/30 text-center py-6">
|
||||
|
||||
@@ -39,6 +39,7 @@ export function useConversation() {
|
||||
});
|
||||
const [loadingPrefs, setLoadingPrefs] = useState(true);
|
||||
const [micError, setMicError] = useState<string | null>(null);
|
||||
const [livePartial, setLivePartial] = useState<string>('');
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
@@ -109,7 +110,11 @@ export function useConversation() {
|
||||
break;
|
||||
|
||||
case 'transcript_delta':
|
||||
// Streaming partial transcript — could show as typing indicator
|
||||
if (msg.text) {
|
||||
setLivePartial(msg.text);
|
||||
// Clear after short delay so it doesn't stick (for REST full-text case)
|
||||
setTimeout(() => setLivePartial(''), 1500);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'speaking_start':
|
||||
@@ -282,6 +287,7 @@ export function useConversation() {
|
||||
preferences,
|
||||
loadingPrefs,
|
||||
micError,
|
||||
livePartial,
|
||||
identify,
|
||||
setPreference,
|
||||
sendText,
|
||||
|
||||
Reference in New Issue
Block a user