Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/backend/base/langflow/api/v1/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ class ConfigResponse(BaseModel):
public_flow_cleanup_interval: int
public_flow_expiration: int
event_delivery: Literal["polling", "streaming", "direct"]
voice_mode_available: bool

@classmethod
def from_settings(cls, settings: Settings) -> "ConfigResponse":
Expand All @@ -431,6 +432,7 @@ def from_settings(cls, settings: Settings) -> "ConfigResponse":
public_flow_cleanup_interval=settings.public_flow_cleanup_interval,
public_flow_expiration=settings.public_flow_expiration,
event_delivery=settings.event_delivery,
voice_mode_available=settings.voice_mode_available,
)


Expand Down
10 changes: 10 additions & 0 deletions src/backend/base/langflow/services/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,16 @@ def update_settings(self, **kwargs) -> None:
logger.debug(f"Updated {key}")
logger.debug(f"{key}: {getattr(self, key)}")

@property
def voice_mode_available(self) -> bool:
"""Check if voice mode is available by testing webrtcvad import."""
try:
import webrtcvad # noqa: F401
except ImportError:
return False
else:
return True

@classmethod
@override
def settings_customise_sources( # type: ignore[misc]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface ConfigResponse {
webhook_polling_interval: number;
serialization_max_items_length: number;
event_delivery: EventDeliveryType;
voice_mode_available: boolean;
}

export const useGetConfig: useQueryFunctionType<undefined, ConfigResponse> = (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type React from "react";
import { useGetConfig } from "@/controllers/API/queries/config/use-get-config";
import {
ENABLE_IMAGE_ON_PLAYGROUND,
ENABLE_VOICE_ASSISTANT,
Expand Down Expand Up @@ -51,6 +52,9 @@ const InputWrapper: React.FC<InputWrapperProps> = ({
}) => {
const classNameFilePreview = `flex w-full items-center gap-2 py-2 overflow-auto custom-scroll`;

// Check if voice mode is available
const { data: config } = useGetConfig();

return (
<div className="flex w-full flex-col-reverse">
<div
Expand Down Expand Up @@ -95,7 +99,7 @@ const InputWrapper: React.FC<InputWrapperProps> = ({
)}
</div>
<div className="flex items-center gap-2">
{ENABLE_VOICE_ASSISTANT && (
{ENABLE_VOICE_ASSISTANT && config?.voice_mode_available && (
<VoiceButton toggleRecording={() => setShowAudioInput(true)} />
)}

Expand Down
72 changes: 72 additions & 0 deletions src/frontend/tests/core/features/voice-assistant.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ test(
"OPENAI_API_KEY required to run this test",
);

await page.route("**/api/v1/config", (route) => {
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
voice_mode_available: true,
}),
headers: {
"content-type": "application/json",
...route.request().headers(),
},
});
});

await awaitBootstrapTest(page);

await page.getByTestId("side_nav_options_all-templates").click();
Expand Down Expand Up @@ -58,3 +72,61 @@ test(
await expect(page.getByTestId("input-wrapper")).toBeVisible();
},
);

test(
"user should not be able to see voice button if voice mode is not available",
{ tag: ["@release", "@workspace", "@api"] },
async ({ page, request }) => {
await page.route("**/api/v1/config", (route) => {
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
voice_mode_available: false,
}),
headers: {
"content-type": "application/json",
...route.request().headers(),
},
});
});

await awaitBootstrapTest(page);

await page.getByTestId("side_nav_options_all-templates").click();
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.getByTestId("playground-btn-flow-io").click();

await expect(page.getByTestId("voice-button")).not.toBeVisible();
},
);

test(
"user should be able to see voice button if voice mode is available",
{ tag: ["@release", "@workspace", "@api"] },
async ({ page, request }) => {
await page.route("**/api/v1/config", (route) => {
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
voice_mode_available: true,
}),
headers: {
"content-type": "application/json",
...route.request().headers(),
},
});
});

await awaitBootstrapTest(page);

await page.getByTestId("side_nav_options_all-templates").click();
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.getByTestId("playground-btn-flow-io").click();

await expect(page.getByTestId("voice-button")).toBeVisible();

await page.getByTestId("voice-button").click();
},
);
Loading