Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 33 additions & 45 deletions frontend/src/components/contexts/TTSProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,57 +80,45 @@ export function emitAssistantMessageCompleteEvent(chatId) {
* This is accomplished by looking for a button with the data-auto-play-chat-id attribute that matches the chatId.
*/
export function useWatchForAutoPlayAssistantTTSResponse() {
const autoPlayAssistantTtsResponse = Appearance.get(
"autoPlayAssistantTtsResponse"
);
useEffect(() => {
function handleAutoPlayTTSEvent(event) {
// Read preference when the message completes so toggling in this session
// always matches localStorage without requiring a workspace remount.
if (!Appearance.get("autoPlayAssistantTtsResponse")) return;

function handleAutoPlayTTSEvent(event) {
let autoPlayAttempts = 0;
const { chatId } = event.detail;
const chatId = event?.detail?.chatId;
if (chatId == null || chatId === "") return;

/**
* Attempt to play the TTS response for the given chatId.
* This is a recursive function that will attempt to play the TTS response
* for the given chatId until it is successful or the maximum number of attempts
* is reached.
* @returns {boolean} true if the TTS response was played, false otherwise.
*/
function attemptToPlay() {
const playBtn = document.querySelector(
`[data-auto-play-chat-id="${chatId}"]`
);
if (!playBtn) {
autoPlayAttempts++;
if (autoPlayAttempts > 3) return false;
setTimeout(() => {
attemptToPlay();
}, 1000 * autoPlayAttempts);
return false;
let autoPlayAttempts = 0;

function attemptToPlay() {
const playBtn = document.querySelector(
`[data-auto-play-chat-id="${String(chatId)}"]`
);
if (!playBtn) {
autoPlayAttempts++;
if (autoPlayAttempts > 3) return false;
setTimeout(() => {
attemptToPlay();
}, 1000 * autoPlayAttempts);
return false;
}
playBtn.click();
return true;
}
playBtn.click();
return true;
setTimeout(() => {
attemptToPlay();
}, 800);
}
setTimeout(() => {
attemptToPlay();
}, 800);
}

// Only bother to listen for these events if the user has autoPlayAssistantTtsResponse
// setting enabled.
useEffect(() => {
if (autoPlayAssistantTtsResponse) {
window.addEventListener(
window.addEventListener(
ASSISTANT_MESSAGE_COMPLETE_EVENT,
handleAutoPlayTTSEvent
);
return () =>
window.removeEventListener(
ASSISTANT_MESSAGE_COMPLETE_EVENT,
handleAutoPlayTTSEvent
);
return () => {
window.removeEventListener(
ASSISTANT_MESSAGE_COMPLETE_EVENT,
handleAutoPlayTTSEvent
);
};
} else {
console.log("Assistant TTS auto-play is disabled");
}
}, [autoPlayAssistantTtsResponse]);
}, []);
}
15 changes: 14 additions & 1 deletion frontend/src/utils/chat/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { v4 } from "uuid";
import { safeJsonParse } from "../request";
import { API_BASE } from "../constants";
import { useEffect, useState } from "react";
import { emitAssistantMessageCompleteEvent } from "@/components/contexts/TTSProvider";
import { THREAD_RENAME_EVENT } from "@/components/Sidebar/ActiveWorkspaces/ThreadContainer";

export const AGENT_SESSION_START = "agentSessionStart";
Expand Down Expand Up @@ -73,7 +74,12 @@ export default function handleSocketResponse(socket, event, setChatHistory) {
// If we get this message we know the provider supports agentic streaming
socket.supportsAgentStreaming = true;

return setChatHistory((prev) => {
const ttsChatIdOnPersist =
data.content?.type === "chatId" && data.content.chatId
? data.content.chatId
: null;

setChatHistory((prev) => {
if (data.content.type === "removeStatusResponse")
return [...prev.filter((msg) => msg.uuid !== data.content.uuid)];

Expand Down Expand Up @@ -203,6 +209,13 @@ export default function handleSocketResponse(socket, event, setChatHistory) {
);
}
});

if (ttsChatIdOnPersist != null && ttsChatIdOnPersist !== "") {
queueMicrotask(() =>
emitAssistantMessageCompleteEvent(ttsChatIdOnPersist)
);
}
return;
}

if (data.type === "fileDownloadCard") {
Expand Down
21 changes: 18 additions & 3 deletions frontend/src/utils/chat/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export default function handleChat(
setWebsocket
) {
const {
uuid,
uuid: streamedUuid,
id: streamedId,
textResponse,
type,
sources = [],
Expand All @@ -23,6 +24,7 @@ export default function handleChat(
action = null,
metrics = {},
} = chatResult;
const uuid = streamedUuid ?? streamedId;

if (type === "abort" || type === "statusResponse") {
setLoadingResponse(false);
Expand Down Expand Up @@ -82,12 +84,15 @@ export default function handleChat(
chatId,
metrics,
});
emitAssistantMessageCompleteEvent(chatId);
if (chatId != null && chatId !== "") {
emitAssistantMessageCompleteEvent(chatId);
}
} else if (
type === "textResponseChunk" ||
type === "finalizeResponseStream"
) {
const chatIdx = _chatHistory.findIndex((chat) => chat.uuid === uuid);
let finalizedWithoutMatch = false;
if (chatIdx !== -1) {
const existingHistory = { ..._chatHistory[chatIdx] };
let updatedHistory;
Expand All @@ -106,7 +111,9 @@ export default function handleChat(

_chatHistory[chatIdx - 1] = { ..._chatHistory[chatIdx - 1], chatId }; // update prompt with chatID

emitAssistantMessageCompleteEvent(chatId);
if (chatId != null && chatId !== "") {
emitAssistantMessageCompleteEvent(chatId);
}
setLoadingResponse(false);
} else {
updatedHistory = {
Expand Down Expand Up @@ -135,8 +142,16 @@ export default function handleChat(
chatId,
metrics,
});
finalizedWithoutMatch = type === "finalizeResponseStream";
}
setChatHistory([..._chatHistory]);

if (finalizedWithoutMatch) {
setLoadingResponse(false);
if (chatId != null && chatId !== "") {
emitAssistantMessageCompleteEvent(chatId);
}
}
} else if (type === "agentInitWebsocketConnection") {
setWebsocket(chatResult.websocketUUID);
} else if (type === "stopGeneration") {
Expand Down