mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-14 13:53:02 -07:00
feat(voice): add privacy and compliance UX warning for Gemini Live backend (#26454)
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { createMockSettings } from '../../test-utils/settings.js';
|
||||
import { VoiceModelDialog } from './VoiceModelDialog.js';
|
||||
import { act } from 'react';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async () => {
|
||||
const actual = await vi.importActual('@google/gemini-cli-core');
|
||||
return {
|
||||
...actual,
|
||||
isBinaryAvailable: vi.fn().mockReturnValue(true),
|
||||
WhisperModelManager: vi.fn().mockImplementation(() => ({
|
||||
isModelInstalled: vi.fn().mockReturnValue(false),
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
downloadModel: vi.fn(),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
describe('VoiceModelDialog', () => {
|
||||
it('should display a privacy warning when Gemini Live API (Cloud) is selected', async () => {
|
||||
const onClose = vi.fn();
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<VoiceModelDialog onClose={onClose} />,
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
|
||||
const frame = lastFrame();
|
||||
expect(frame).toContain('Gemini Live API (Cloud)');
|
||||
expect(frame).toContain('When using the Gemini Live backend');
|
||||
});
|
||||
|
||||
it('should NOT display a privacy warning when Whisper (Local) is highlighted', async () => {
|
||||
const onClose = vi.fn();
|
||||
const { lastFrame, waitUntilReady, stdin } = await renderWithProviders(
|
||||
<VoiceModelDialog onClose={onClose} />,
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
|
||||
// Verify warning is present for default (Gemini Live)
|
||||
expect(lastFrame()).toContain('When using the Gemini Live backend');
|
||||
|
||||
// Arrow Down to highlight Whisper
|
||||
await act(async () => {
|
||||
stdin.write('\u001b[B');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const frame = lastFrame();
|
||||
expect(frame).toContain('Whisper (Local)');
|
||||
expect(frame).not.toContain('When using the Gemini Live backend');
|
||||
});
|
||||
});
|
||||
|
||||
it('should update settings and close dialog when a backend is selected', async () => {
|
||||
const onClose = vi.fn();
|
||||
const settings = createMockSettings();
|
||||
const setValueSpy = vi.spyOn(settings, 'setValue');
|
||||
|
||||
const { waitUntilReady, stdin } = await renderWithProviders(
|
||||
<VoiceModelDialog onClose={onClose} />,
|
||||
{ settings },
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
|
||||
// Select Gemini Live (it's already highlighted, just press Enter)
|
||||
await act(async () => {
|
||||
stdin.write('\r');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(setValueSpy).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
'experimental.voice.backend',
|
||||
'gemini-live',
|
||||
);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
type WhisperModelProgress,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { CliSpinner } from './CliSpinner.js';
|
||||
import { WarningMessage } from './messages/WarningMessage.js';
|
||||
|
||||
interface VoiceModelDialogProps {
|
||||
onClose: () => void;
|
||||
@@ -68,6 +69,9 @@ export function VoiceModelDialog({
|
||||
const currentWhisperModel =
|
||||
settings.merged.experimental.voice?.whisperModel ?? 'ggml-base.en.bin';
|
||||
|
||||
const [highlightedBackend, setHighlightedBackend] =
|
||||
useState<string>(currentBackend);
|
||||
|
||||
const handleKeypress = useCallback(
|
||||
(key: Key) => {
|
||||
if (key.name === 'escape') {
|
||||
@@ -101,6 +105,10 @@ export function VoiceModelDialog({
|
||||
[setSetting, onClose],
|
||||
);
|
||||
|
||||
const handleBackendHighlight = useCallback((value: string) => {
|
||||
setHighlightedBackend(value);
|
||||
}, []);
|
||||
|
||||
const handleWhisperModelSelect = useCallback(
|
||||
async (modelName: string) => {
|
||||
if (modelManager.isModelInstalled(modelName)) {
|
||||
@@ -203,14 +211,22 @@ export function VoiceModelDialog({
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<Box marginTop={1}>
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
{view === 'backend' ? (
|
||||
<DescriptiveRadioButtonSelect
|
||||
items={backendOptions}
|
||||
onSelect={handleBackendSelect}
|
||||
initialIndex={currentBackend === 'whisper' ? 1 : 0}
|
||||
showNumbers={true}
|
||||
/>
|
||||
<>
|
||||
<DescriptiveRadioButtonSelect
|
||||
items={backendOptions}
|
||||
onSelect={handleBackendSelect}
|
||||
onHighlight={handleBackendHighlight}
|
||||
initialIndex={currentBackend === 'whisper' ? 1 : 0}
|
||||
showNumbers={true}
|
||||
/>
|
||||
{highlightedBackend === 'gemini-live' && (
|
||||
<Box marginTop={1}>
|
||||
<WarningMessage text="When using the Gemini Live backend, voice recordings are sent to Google Cloud for transcription. Enterprise users should verify this aligns with their data privacy and compliance requirements." />
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<DescriptiveRadioButtonSelect
|
||||
items={whisperOptions}
|
||||
|
||||
Reference in New Issue
Block a user