File size: 4,620 Bytes
f8b5d42 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
import { createContext, useContext, useEffect, useState } from "react";
import System from "@/models/system";
import Appearance from "@/models/appearance";
const ASSISTANT_MESSAGE_COMPLETE_EVENT = "ASSISTANT_MESSAGE_COMPLETE_EVENT";
const TTSProviderContext = createContext();
/**
* This component is used to provide the TTS provider context to the application.
*
* TODO: This context provider simply wraps around the System.keys() call to get the TTS provider settings.
* However, we use .keys() in a ton of places and it might make more sense to make a generalized hook that
* can be used anywhere we need to get _any_ setting from the System by just grabbing keys() and reusing it
* as a hook where needed.
*
* For now, since TTSButtons are rendered on every message, we can save a ton of requests by just using this
* hook where for now so we can recycle the TTS settings in the chat container.
*/
export function TTSProvider({ children }) {
const [settings, setSettings] = useState({});
const [provider, setProvider] = useState("native");
const [loading, setLoading] = useState(true);
useEffect(() => {
async function getSettings() {
const _settings = await System.keys();
setProvider(_settings?.TextToSpeechProvider ?? "native");
setSettings(_settings);
setLoading(false);
}
getSettings();
}, []);
return (
<TTSProviderContext.Provider
value={{
settings,
provider,
loading,
}}
>
{children}
</TTSProviderContext.Provider>
);
}
/**
* This hook is used to get the TTS provider settings easily without
* having to refetch the settings from the System.keys() call each component mount.
*
* @returns {{settings: {TTSPiperTTSVoiceModel: string|null}, provider: string, loading: boolean}} The TTS provider settings.
*/
export function useTTSProvider() {
const context = useContext(TTSProviderContext);
if (!context)
throw new Error("useTTSProvider must be used within a TTSProvider");
return context;
}
/**
* This function will emit the ASSISTANT_MESSAGE_COMPLETE_EVENT event.
*
* This event is used to notify the TTSProvider that a message has been fully generated and that the TTS response
* should be played if the user setting is enabled.
*
* @param {string} chatId - The chatId of the message that has been fully generated.
*/
export function emitAssistantMessageCompleteEvent(chatId) {
window.dispatchEvent(
new CustomEvent(ASSISTANT_MESSAGE_COMPLETE_EVENT, { detail: { chatId } })
);
}
/**
* This hook will establish a listener for the ASSISTANT_MESSAGE_COMPLETE_EVENT event.
* When the event is triggered, the hook will attempt to play the TTS response for the given chatId.
* It will attempt to play the TTS response for the given chatId until it is successful or the maximum number of attempts
* is reached.
*
* 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"
);
function handleAutoPlayTTSEvent(event) {
let autoPlayAttempts = 0;
const { chatId } = event.detail;
/**
* 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;
}
playBtn.click();
return true;
}
setTimeout(() => {
attemptToPlay();
}, 800);
}
// Only bother to listen for these events if the user has autoPlayAssistantTtsResponse
// setting enabled.
useEffect(() => {
if (autoPlayAssistantTtsResponse) {
window.addEventListener(
ASSISTANT_MESSAGE_COMPLETE_EVENT,
handleAutoPlayTTSEvent
);
return () => {
window.removeEventListener(
ASSISTANT_MESSAGE_COMPLETE_EVENT,
handleAutoPlayTTSEvent
);
};
} else {
console.log("Assistant TTS auto-play is disabled");
}
}, [autoPlayAssistantTtsResponse]);
}
|