Skip to content

Commit 70edf0f

Browse files
committed
feat: enable typed chat messages to reach LiveKit agent
- Add data_received event handler to agent worker - Parse chat messages from data channel (type: 'chat') - Ingest typed text to Hub for transcript persistence - Use session.generate_reply() to inject typed text into conversation - Agent responds to typed messages by speaking aloud - Improve sendChatMessage() error handling with toast notifications
1 parent 6693bdf commit 70edf0f

File tree

2 files changed

+76
-6
lines changed

2 files changed

+76
-6
lines changed

apps/agent_worker/worker.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,58 @@ def on_conversation_item_added(event: ConversationItemAddedEvent) -> None:
247247

248248
session.on("conversation_item_added", on_conversation_item_added)
249249

250+
# Handle typed chat messages from data channel
251+
import json as _json_dc
252+
253+
@ctx.room.on("data_received")
254+
def on_data_received(data: rtc.DataPacket) -> None:
255+
"""Handle typed chat messages from participants.
256+
257+
When a user types a message in the chat UI, it's sent via LiveKit's
258+
data channel. We parse it and inject it into the conversation as if
259+
the user had spoken it.
260+
"""
261+
try:
262+
payload = data.data.decode("utf-8")
263+
msg = _json_dc.loads(payload)
264+
265+
# Only handle chat messages
266+
if msg.get("type") != "chat":
267+
return
268+
269+
text = msg.get("text", "").strip()
270+
if not text:
271+
return
272+
273+
sender = data.participant.identity if data.participant else "user"
274+
logger.info(f"Received typed chat from {sender}: {text[:50]}...")
275+
276+
# Ingest to Hub for persistence
277+
hub_ingest_transcript(
278+
space_id=space_id,
279+
text=text,
280+
role="user",
281+
participant_identity=sender,
282+
)
283+
284+
# Inject the typed message into the agent's conversation
285+
# Use generate_reply with the user's text as instructions
286+
asyncio.create_task(_handle_typed_message(text, sender))
287+
288+
except Exception as e:
289+
logger.warning(f"Failed to process data channel message: {e}")
290+
291+
async def _handle_typed_message(text: str, sender: str) -> None:
292+
"""Process a typed chat message and generate a response."""
293+
try:
294+
# Use generate_reply to have the agent respond to the typed text
295+
# The agent will speak the response aloud
296+
await session.generate_reply(
297+
instructions=f"The user '{sender}' typed this message (not spoken): {text}\n\nRespond naturally as if they had said it aloud."
298+
)
299+
except Exception as e:
300+
logger.warning(f"Failed to generate reply for typed message: {e}")
301+
250302
await session.start(
251303
room=ctx.room,
252304
agent=ForgeAssistant(),

functions/hub_api/templates/livekit_test.html

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -960,12 +960,30 @@ <h4 class="card-title"><i class="fas fa-closed-captioning"></i> Live Transcript<
960960

961961
function sendChatMessage() {
962962
var text = (chatInputEl.value || '').trim();
963-
if (!text || !room) return;
964-
var msg = { type: 'chat', text: text, ts: Date.now() };
965-
var data = new TextEncoder().encode(JSON.stringify(msg));
966-
room.localParticipant.publishData(data, { reliable: true });
967-
addChatMessage(myIdentity, text, msg.ts);
968-
chatInputEl.value = '';
963+
if (!text) {
964+
return;
965+
}
966+
if (!room) {
967+
Marvain.showToast('warning', 'Not Connected', 'Please join a room first');
968+
log('Cannot send message: not connected to a room');
969+
return;
970+
}
971+
if (room.state !== 'connected') {
972+
Marvain.showToast('warning', 'Not Connected', 'Room state: ' + room.state);
973+
log('Cannot send message: room state is ' + room.state);
974+
return;
975+
}
976+
try {
977+
var msg = { type: 'chat', text: text, ts: Date.now() };
978+
var data = new TextEncoder().encode(JSON.stringify(msg));
979+
room.localParticipant.publishData(data, { reliable: true });
980+
addChatMessage(myIdentity, text, msg.ts);
981+
chatInputEl.value = '';
982+
log('Sent chat message: ' + text.substring(0, 30) + (text.length > 30 ? '...' : ''));
983+
} catch (e) {
984+
Marvain.showToast('error', 'Send Failed', e.message);
985+
log('Failed to send message: ' + e.message);
986+
}
969987
}
970988

971989
async function join() {

0 commit comments

Comments
 (0)