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,
|
sendText,
|
||||||
startRecording,
|
startRecording,
|
||||||
stopRecording,
|
stopRecording,
|
||||||
|
livePartial,
|
||||||
} = useConversation();
|
} = useConversation();
|
||||||
|
|
||||||
const [currentSceneId, setCurrentSceneId] = useState('cozy-room');
|
const [currentSceneId, setCurrentSceneId] = useState('cozy-room');
|
||||||
@@ -143,7 +144,7 @@ export default function App() {
|
|||||||
|
|
||||||
{/* Column 3: Chat + Text Input */}
|
{/* Column 3: Chat + Text Input */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<ChatBubble messages={messages} isKiraSpeaking={isKiraSpeaking} userName={userName} />
|
<ChatBubble messages={messages} isKiraSpeaking={isKiraSpeaking} userName={userName} livePartial={livePartial} />
|
||||||
|
|
||||||
{/* Text input fallback */}
|
{/* Text input fallback */}
|
||||||
<div className="glass-card p-3">
|
<div className="glass-card p-3">
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ interface Props {
|
|||||||
messages: Message[];
|
messages: Message[];
|
||||||
isKiraSpeaking: boolean;
|
isKiraSpeaking: boolean;
|
||||||
userName?: string;
|
userName?: string;
|
||||||
|
livePartial?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatBubble({ messages, isKiraSpeaking }: Props) {
|
export default function ChatBubble({ messages, isKiraSpeaking, livePartial }: Props) {
|
||||||
const bottomRef = useRef<HTMLDivElement>(null);
|
const bottomRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
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'}`} />
|
<span className={`w-2 h-2 rounded-full ${isKiraSpeaking ? 'bg-kira-pink animate-pulse' : 'bg-kira-mint'}`} />
|
||||||
</h3>
|
</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">
|
<div className="flex-1 overflow-y-auto space-y-2 scrollbar-thin pr-1">
|
||||||
{messages.length === 0 && (
|
{messages.length === 0 && (
|
||||||
<div className="text-xs text-kira-plum/30 text-center py-6">
|
<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 [loadingPrefs, setLoadingPrefs] = useState(true);
|
||||||
const [micError, setMicError] = useState<string | null>(null);
|
const [micError, setMicError] = useState<string | null>(null);
|
||||||
|
const [livePartial, setLivePartial] = useState<string>('');
|
||||||
|
|
||||||
const wsRef = useRef<WebSocket | null>(null);
|
const wsRef = useRef<WebSocket | null>(null);
|
||||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||||
@@ -109,7 +110,11 @@ export function useConversation() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'transcript_delta':
|
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;
|
break;
|
||||||
|
|
||||||
case 'speaking_start':
|
case 'speaking_start':
|
||||||
@@ -282,6 +287,7 @@ export function useConversation() {
|
|||||||
preferences,
|
preferences,
|
||||||
loadingPrefs,
|
loadingPrefs,
|
||||||
micError,
|
micError,
|
||||||
|
livePartial,
|
||||||
identify,
|
identify,
|
||||||
setPreference,
|
setPreference,
|
||||||
sendText,
|
sendText,
|
||||||
|
|||||||
Reference in New Issue
Block a user