From 9096293e52212e070a7740d6a7ad9870f4aad990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Est=C3=A9vez?= Date: Tue, 13 May 2025 16:39:55 -0400 Subject: [PATCH] fix: voice_mode break fix (#8014) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔧 (use-get-messages-polling.ts): refactor enqueuePolling method to simplify logic and improve readability 🔧 (use-get-messages-polling.ts): refactor startNextPolling method to remove unnecessary code and improve efficiency 🔧 (use-get-messages-polling.ts): refactor removeFromQueue method to simplify and improve maintainability 🔧 (use-get-messages-polling.ts): refactor useGetMessagesPollingMutation to handle stopping polling and removing from queue 🔧 (use-get-transactions.ts): add early return to getTransactionsFn to handle empty id case 🔧 (use-get-voice-list.ts): refactor useGetVoiceList function to handle empty elevenlabsApiKey case and improve readability 🔧 (audio-settings-dialog.tsx): update useGetVoiceList call to pass elevenLabsApiKey as argument * queues should hold payloads not strings * ✨ (use-get-global-variables.ts): add setGlobalVariablesEntities function to store global variables entities for use in the application ✨ (audio-settings-dialog.tsx): introduce debounced function to update ElevenLabs API key in global variables entities 📝 (globalVariables.ts): add setGlobalVariablesEntities function to the global variables store for managing entities 📝 (index.ts): define GlobalVariable type for global variables entities in the store * ✨ (frontend): add support for extracting flowId from URL query parameters to allow direct linking to specific flow logs. --------- Co-authored-by: cristhianzl --- .../base/langflow/api/v1/voice_mode.py | 8 ++-- .../messages/use-get-messages-polling.ts | 41 ++++++++----------- .../transactions/use-get-transactions.ts | 3 ++ .../variables/use-get-global-variables.ts | 4 ++ .../API/queries/voice/use-get-voice-list.ts | 16 +++----- .../audio-settings/audio-settings-dialog.tsx | 33 +++++++++++++-- .../src/modals/flowLogsModal/index.tsx | 5 ++- .../globalVariablesStore/globalVariables.ts | 4 ++ .../types/zustand/globalVariables/index.ts | 4 ++ 9 files changed, 74 insertions(+), 44 deletions(-) diff --git a/src/backend/base/langflow/api/v1/voice_mode.py b/src/backend/base/langflow/api/v1/voice_mode.py index af6752c1e..a96d45138 100644 --- a/src/backend/base/langflow/api/v1/voice_mode.py +++ b/src/backend/base/langflow/api/v1/voice_mode.py @@ -1143,8 +1143,8 @@ async def flow_tts_websocket( try: await client_websocket.accept() - openai_send_q: asyncio.Queue[str] = asyncio.Queue() - client_send_q: asyncio.Queue[str] = asyncio.Queue() + openai_send_q: asyncio.Queue[dict] = asyncio.Queue() + client_send_q: asyncio.Queue[dict] = asyncio.Queue() log_event = create_event_logger() @@ -1171,13 +1171,13 @@ async def flow_tts_websocket( def openai_send(payload): log_event(payload, LF_TO_OPENAI) logger.trace(f"Sending text {LF_TO_OPENAI}: {payload['type']}") - openai_send_q.put_nowait(json.dumps(payload)) + openai_send_q.put_nowait(payload) logger.trace("JSON sent.") def client_send(payload): log_event(payload, LF_TO_CLIENT) logger.trace(f"Sending JSON {LF_TO_CLIENT}: {payload['type']}") - client_send_q.put_nowait(json.dumps(payload)) + client_send_q.put_nowait(payload) logger.trace("JSON sent.") async def close(): diff --git a/src/frontend/src/controllers/API/queries/messages/use-get-messages-polling.ts b/src/frontend/src/controllers/API/queries/messages/use-get-messages-polling.ts index 49984771b..6fca4ef41 100644 --- a/src/frontend/src/controllers/API/queries/messages/use-get-messages-polling.ts +++ b/src/frontend/src/controllers/API/queries/messages/use-get-messages-polling.ts @@ -34,20 +34,12 @@ const MessagesPollingManager = { activePolls: new Map(), enqueuePolling(id: string, pollingItem: PollingItem) { - if (!this.pollingQueue.has(id)) { - this.pollingQueue.set(id, []); - } - this.pollingQueue.set( - id, - (this.pollingQueue.get(id) || []).filter( - (item) => item.timestamp !== pollingItem.timestamp, - ), - ); - this.pollingQueue.get(id)?.push(pollingItem); + this.stopAll(); - if (!this.activePolls.has(id)) { - this.startNextPolling(id); - } + this.pollingQueue.clear(); + this.pollingQueue.set(id, [pollingItem]); + + this.startNextPolling(id); }, startNextPolling(id: string) { @@ -67,12 +59,7 @@ const MessagesPollingManager = { if (activePoll) { clearInterval(activePoll.interval); this.activePolls.delete(id); - const queue = this.pollingQueue.get(id) || []; - this.pollingQueue.set( - id, - queue.filter((item) => item.timestamp !== activePoll.timestamp), - ); - this.startNextPolling(id); + this.pollingQueue.delete(id); } }, @@ -83,11 +70,7 @@ const MessagesPollingManager = { }, removeFromQueue(id: string, timestamp: number) { - const queue = this.pollingQueue.get(id) || []; - this.pollingQueue.set( - id, - queue.filter((item) => item.timestamp !== timestamp), - ); + this.pollingQueue.delete(id); }, }; @@ -146,6 +129,10 @@ export const useGetMessagesPollingMutation = ( return Promise.reject("Request already in progress"); } + if (MessagesPollingManager.activePolls.has(requestId)) { + MessagesPollingManager.stopPoll(requestId); + } + if ( requestIdRef.current === requestId && MessagesPollingManager.activePolls.has(requestId) @@ -189,11 +176,15 @@ export const useGetMessagesPollingMutation = ( return () => { if (requestIdRef.current) { MessagesPollingManager.stopPoll(requestIdRef.current); + MessagesPollingManager.removeFromQueue( + requestIdRef.current, + Date.now(), + ); + requestIdRef.current = null; } }; }, []); - // Cast the mutation to the correct type const mutation = mutate( ["useGetMessagesMutation"], (payload: MessagesQueryParams) => diff --git a/src/frontend/src/controllers/API/queries/transactions/use-get-transactions.ts b/src/frontend/src/controllers/API/queries/transactions/use-get-transactions.ts index dbb2860a5..041f633f1 100644 --- a/src/frontend/src/controllers/API/queries/transactions/use-get-transactions.ts +++ b/src/frontend/src/controllers/API/queries/transactions/use-get-transactions.ts @@ -46,7 +46,10 @@ export const useGetTransactionsQuery: useQueryFunctionType< }; const getTransactionsFn = async () => { + if (!id) return { pagination: {}, rows: [], columns: [] }; + const config = {}; + config["params"] = { flow_id: id }; if (params) { config["params"] = { ...config["params"], ...params }; diff --git a/src/frontend/src/controllers/API/queries/variables/use-get-global-variables.ts b/src/frontend/src/controllers/API/queries/variables/use-get-global-variables.ts index 431cdec85..d939e2970 100644 --- a/src/frontend/src/controllers/API/queries/variables/use-get-global-variables.ts +++ b/src/frontend/src/controllers/API/queries/variables/use-get-global-variables.ts @@ -20,6 +20,9 @@ export const useGetGlobalVariables: useQueryFunctionType< const setUnavailableFields = useGlobalVariablesStore( (state) => state.setUnavailableFields, ); + const setGlobalVariablesEntities = useGlobalVariablesStore( + (state) => state.setGlobalVariablesEntities, + ); const isAuthenticated = useAuthStore((state) => state.isAuthenticated); @@ -28,6 +31,7 @@ export const useGetGlobalVariables: useQueryFunctionType< const res = await api.get(`${getURL("VARIABLES")}/`); setGlobalVariablesEntries(res.data.map((entry) => entry.name)); setUnavailableFields(getUnavailableFields(res.data)); + setGlobalVariablesEntities(res.data); return res.data; }; diff --git a/src/frontend/src/controllers/API/queries/voice/use-get-voice-list.ts b/src/frontend/src/controllers/API/queries/voice/use-get-voice-list.ts index 4074af173..4e9d72aac 100644 --- a/src/frontend/src/controllers/API/queries/voice/use-get-voice-list.ts +++ b/src/frontend/src/controllers/API/queries/voice/use-get-voice-list.ts @@ -4,22 +4,18 @@ import { api } from "../../api"; import { getURL } from "../../helpers/constants"; import { UseRequestProcessor } from "../../services/request-processor"; -export const useGetVoiceList: useQueryFunctionType = ( - options, -) => { +export const useGetVoiceList = (elevenlabsApiKey: string, options?: any) => { const { query } = UseRequestProcessor(); const setVoices = useVoiceStore((state) => state.setVoices); const voices = useVoiceStore((state) => state.voices); - const getVoiceListFn = async (): Promise< - { - name: string; - voice_id: string; - }[] - > => { + const getVoiceListFn = async () => { if (voices.length > 0) { return voices; } + if (!elevenlabsApiKey) { + return []; + } const res = await api.get(`${getURL("VOICE")}/elevenlabs/voice_ids`); const data = res.data; @@ -41,7 +37,7 @@ export const useGetVoiceList: useQueryFunctionType = ( }; const queryResult = query( - ["useGetVoiceList"], + ["useGetVoiceList", elevenlabsApiKey], getVoiceListFn, defaultOptions, ); diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/voice-assistant/components/audio-settings/audio-settings-dialog.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/voice-assistant/components/audio-settings/audio-settings-dialog.tsx index ecaf4cc33..d9bb94929 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/voice-assistant/components/audio-settings/audio-settings-dialog.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/voice-assistant/components/audio-settings/audio-settings-dialog.tsx @@ -9,7 +9,9 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Separator } from "@/components/ui/separator"; +import { usePatchGlobalVariables } from "@/controllers/API/queries/variables"; import { useGetVoiceList } from "@/controllers/API/queries/voice/use-get-voice-list"; +import { useDebounce } from "@/hooks/use-debounce"; import GeneralDeleteConfirmationModal from "@/shared/components/delete-confirmation-modal"; import GeneralGlobalVariableModal from "@/shared/components/global-variable-modal"; import { useGlobalVariablesStore } from "@/stores/globalVariablesStore/globalVariables"; @@ -69,6 +71,10 @@ const SettingsVoiceModal = ({ (state) => state.globalVariablesEntries, ); + const globalVariablesEntities = useGlobalVariablesStore( + (state) => state.globalVariablesEntities, + ); + const openaiVoices = useVoiceStore((state) => state.openaiVoices); const [allVoices, setAllVoices] = useState< { @@ -83,7 +89,7 @@ const SettingsVoiceModal = ({ data: voiceList, isFetched, refetch, - } = useGetVoiceList({ + } = useGetVoiceList(elevenLabsApiKey, { enabled: shouldFetchVoices, refetchOnMount: shouldFetchVoices, refetchOnWindowFocus: shouldFetchVoices, @@ -158,6 +164,8 @@ const SettingsVoiceModal = ({ return globalVariables?.map((variable) => variable).includes(variable); }; + const { mutate: updateVariable } = usePatchGlobalVariables(); + const handleSetMicrophone = (deviceId: string) => { setSelectedMicrophone(deviceId); localStorage.setItem("lf_selected_microphone", deviceId); @@ -227,6 +235,25 @@ const SettingsVoiceModal = ({ const showAddOpenAIKeyButton = !hasOpenAIAPIKey || isEditingOpenAIKey; const showAllSettings = hasOpenAIAPIKey && !isEditingOpenAIKey; + const debouncedSetElevenLabsApiKey = useDebounce((value: string) => { + const globalVariable = globalVariablesEntities?.find( + (variable) => variable.name === "ELEVENLABS_API_KEY", + ); + + if (globalVariable) { + updateVariable({ + name: "ELEVENLABS_API_KEY", + value: value, + id: globalVariable.id, + }); + } + }, 2000); + + const handleSetElevenLabsApiKey = (value: string) => { + setElevenLabsApiKey(value); + debouncedSetElevenLabsApiKey(value); + }; + return ( <> @@ -372,9 +399,7 @@ const SettingsVoiceModal = ({ /> )} value={elevenLabsApiKey} - onChange={(value) => { - setElevenLabsApiKey(value); - }} + onChange={handleSetElevenLabsApiKey} selectedOption={ checkIfGlobalVariableExists(elevenLabsApiKey) ? elevenLabsApiKey diff --git a/src/frontend/src/modals/flowLogsModal/index.tsx b/src/frontend/src/modals/flowLogsModal/index.tsx index db6000169..4b0c1d074 100644 --- a/src/frontend/src/modals/flowLogsModal/index.tsx +++ b/src/frontend/src/modals/flowLogsModal/index.tsx @@ -7,6 +7,7 @@ import { FlowSettingsPropsType } from "@/types/components"; import { convertUTCToLocalTimezone } from "@/utils/utils"; import { ColDef, ColGroupDef } from "ag-grid-community"; import { useCallback, useEffect, useState } from "react"; +import { useSearchParams } from "react-router-dom"; import BaseModal from "../baseModal"; export default function FlowLogsModal({ @@ -19,9 +20,11 @@ export default function FlowLogsModal({ const [pageSize, setPageSize] = useState(20); const [columns, setColumns] = useState>([]); const [rows, setRows] = useState([]); + const [searchParams] = useSearchParams(); + const flowIdFromUrl = searchParams.get("id"); const { data, isLoading, refetch } = useGetTransactionsQuery({ - id: currentFlowId, + id: currentFlowId ?? flowIdFromUrl, params: { page: pageIndex, size: pageSize, diff --git a/src/frontend/src/stores/globalVariablesStore/globalVariables.ts b/src/frontend/src/stores/globalVariablesStore/globalVariables.ts index 8096a6b9b..ab1bc0d54 100644 --- a/src/frontend/src/stores/globalVariablesStore/globalVariables.ts +++ b/src/frontend/src/stores/globalVariablesStore/globalVariables.ts @@ -11,5 +11,9 @@ export const useGlobalVariablesStore = create( setGlobalVariablesEntries: (entries) => { set({ globalVariablesEntries: entries }); }, + setGlobalVariablesEntities: (entities) => { + set({ globalVariablesEntities: entities }); + }, + globalVariablesEntities: undefined, }), ); diff --git a/src/frontend/src/types/zustand/globalVariables/index.ts b/src/frontend/src/types/zustand/globalVariables/index.ts index 81e10993e..1ab99aaf1 100644 --- a/src/frontend/src/types/zustand/globalVariables/index.ts +++ b/src/frontend/src/types/zustand/globalVariables/index.ts @@ -1,6 +1,10 @@ +import { GlobalVariable } from "@/types/global_variables"; + export type GlobalVariablesStore = { globalVariablesEntries: Array | undefined; setGlobalVariablesEntries: (entries: Array) => void; unavailableFields: { [name: string]: string }; setUnavailableFields: (fields: { [name: string]: string }) => void; + globalVariablesEntities: Array | undefined; + setGlobalVariablesEntities: (entities: GlobalVariable[]) => void; };