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
29 changes: 29 additions & 0 deletions web/oss/src/components/AgentChatSlice/assets/constants.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {afterEach, describe, expect, it, vi} from "vitest"

import {getEnv} from "@/oss/lib/helpers/dynamicEnv"

import {isAgentVoiceInputAvailable} from "./constants"

vi.mock("@/oss/lib/helpers/dynamicEnv", () => ({getEnv: vi.fn(() => "")}))

const mockedGetEnv = vi.mocked(getEnv)

describe("voice input gating", () => {
afterEach(() => {
mockedGetEnv.mockReset()
mockedGetEnv.mockReturnValue("")
})

it("stays off when neither the setting nor the env flag is on", () => {
expect(isAgentVoiceInputAvailable(false)).toBe(false)
})

it("turns on from the per-user setting alone", () => {
expect(isAgentVoiceInputAvailable(true)).toBe(true)
})

it("turns on from the env flag alone, so dev stacks keep working", () => {
mockedGetEnv.mockReturnValue("true")
expect(isAgentVoiceInputAvailable(false)).toBe(true)
})
})
7 changes: 7 additions & 0 deletions web/oss/src/components/AgentChatSlice/assets/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ export const isAgentChatSteerEnabled = (): boolean =>
export const isAgentVoiceInputEnabled = (): boolean =>
(getEnv("NEXT_PUBLIC_AGENT_VOICE_INPUT") || "").toLowerCase() === "true"

/**
* Voice input shows when the per-user experimental setting is on, or when the deployment forces it
* on with the env flag (so dev stacks that set it keep the UI without touching their settings).
*/
Comment thread
mmabrouk marked this conversation as resolved.
export const isAgentVoiceInputAvailable = (settingEnabled: boolean): boolean =>
settingEnabled || isAgentVoiceInputEnabled()

/**
* File uploads and attachments — the composer attach button + attachment preview, and every drive
* upload entry point (upload button, drop-to-upload in the Files drawer, drop-to-stage on a recents
Expand Down
11 changes: 8 additions & 3 deletions web/oss/src/components/AgentChatSlice/hooks/useVoiceComposer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {type RefObject, useState} from "react"

import {type RichChatInputHandle} from "@agenta/ui/rich-chat-input"
import {useAtomValue} from "jotai"

import {isAgentVoiceInputEnabled} from "../assets/constants"
import {agentVoiceInputEnabledAtom} from "@/oss/state/settings/featureFlags"

import {isAgentVoiceInputAvailable} from "../assets/constants"

import {useAudioRecorder} from "./useAudioRecorder"

Expand Down Expand Up @@ -36,8 +39,10 @@ export const useVoiceComposer = ({
* Decided when recording STARTS: the composer is covered by the recording bar and drops are
* blocked while capturing, so neither the text nor the tray can change in between.
*/
// Flagged off until the agent service accepts audio parts (`NEXT_PUBLIC_AGENT_VOICE_INPUT`).
const voiceEnabled = isAgentVoiceInputEnabled()
// Experimental: off until the person turns it on in Settings, or the deployment forces it on
// with `NEXT_PUBLIC_AGENT_VOICE_INPUT`.
const voiceSettingEnabled = useAtomValue(agentVoiceInputEnabledAtom)
const voiceEnabled = isAgentVoiceInputAvailable(voiceSettingEnabled)
const [voiceWillSend, setVoiceWillSend] = useState(false)
const voiceRecorder = useAudioRecorder((file) => {
if (voiceWillSend) onSendVoiceMessage(file)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {useAtom, useAtomValue, useSetAtom} from "jotai"

import {navSimplifiedOverrideAtom} from "@/oss/lib/onboarding/atoms"
import {advancedNavHiddenAtom} from "@/oss/state/onboarding/selectors"
import {playgroundInspectorEnabledAtom} from "@/oss/state/settings/featureFlags"
import {
agentVoiceInputEnabledAtom,
playgroundInspectorEnabledAtom,
} from "@/oss/state/settings/featureFlags"

interface FlagRowProps {
title: string
Expand Down Expand Up @@ -36,6 +39,7 @@ const FeatureFlags = () => {
const [playgroundInspectorEnabled, setPlaygroundInspectorEnabled] = useAtom(
playgroundInspectorEnabledAtom,
)
const [agentVoiceInputEnabled, setAgentVoiceInputEnabled] = useAtom(agentVoiceInputEnabledAtom)

return (
<section className="flex max-w-[640px] flex-col gap-8">
Expand All @@ -49,6 +53,12 @@ const FeatureFlags = () => {
enabled={!advancedNavHidden}
onChange={(enabled) => setNavSimplifiedOverride(!enabled)}
/>
<FlagRow
title="Voice input"
description="Dictate messages in the agent chat."
enabled={agentVoiceInputEnabled}
onChange={setAgentVoiceInputEnabled}
/>
</div>

<div className="flex flex-col gap-4">
Expand Down
18 changes: 18 additions & 0 deletions web/oss/src/state/settings/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ const playgroundInspectorAtomFamily = atomFamily((userId: string) =>
atomWithStorage<boolean>(`agenta:settings:${userId}:playground-inspector`, false),
)

const agentVoiceInputAtomFamily = atomFamily((userId: string) =>
atomWithStorage<boolean>(`agenta:settings:${userId}:agent-voice-input`, false),
)

/** Experimental switch for the agent chat composer's dictation + voice-message controls. */
export const agentVoiceInputEnabledAtom = atom(
(get) => {
const userId = get(onboardingStorageUserIdAtom)
if (!userId) return false
return get(agentVoiceInputAtomFamily(userId))
},
(get, set, next: boolean) => {
const userId = get(onboardingStorageUserIdAtom)
if (!userId) return
set(agentVoiceInputAtomFamily(userId), next)
},
)

export const playgroundInspectorEnabledAtom = atom(
(get) => {
const userId = get(onboardingStorageUserIdAtom)
Expand Down
Loading