agentvibes 5.1.4 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agentvibes/config.json +23 -13
- package/.claude/commands/agent-vibes/verbosity.md +98 -89
- package/.claude/config/audio-effects.cfg +4 -1
- package/.claude/hooks/bmad-speak.sh +2 -2
- package/.claude/hooks/piper-download-voices.sh +233 -225
- package/.claude/hooks/piper-installer.sh +1 -1
- package/.claude/hooks/piper-voice-manager.sh +125 -0
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +97 -90
- package/.claude/hooks/play-tts-enhanced.sh +1 -1
- package/.claude/hooks/play-tts-piper.sh +16 -5
- package/.claude/hooks/play-tts-ssh-remote.sh +168 -167
- package/.claude/hooks/play-tts.sh +21 -9
- package/.claude/hooks/session-start-tts.sh +4 -1
- package/.claude/hooks/stop-tts.sh +1 -1
- package/.claude/hooks/verbosity-manager.sh +185 -178
- package/.claude/hooks-windows/download-extra-voices.ps1 +243 -185
- package/.claude/hooks-windows/play-tts-piper.ps1 +7 -2
- package/.claude/hooks-windows/play-tts.ps1 +7 -1
- package/.claude/hooks-windows/session-start-tts.ps1 +2 -1
- package/.claude/hooks-windows/verbosity-manager.ps1 +126 -119
- package/README.md +10 -2
- package/RELEASE_NOTES.md +36 -0
- package/bin/agentvibes-voice-browser.js +1939 -1840
- package/mcp-server/server.py +52 -9
- package/package.json +1 -1
- package/src/console/tabs/receiver-tab.js +1527 -1483
- package/src/console/tabs/settings-tab.js +2 -2
- package/src/console/tabs/setup-tab.js +53 -8
- package/src/console/tabs/voices-tab.js +130 -13
- package/src/i18n/en.js +202 -202
- package/src/services/verbosity-service.js +159 -157
- package/templates/agentvibes-receiver.sh +3 -2
|
@@ -64,7 +64,7 @@ const FOOTER_TEXT =
|
|
|
64
64
|
'[↑↓] Navigate [Enter] Edit [Esc] Tab Bar';
|
|
65
65
|
|
|
66
66
|
const MUSIC_DEFAULTS = Object.freeze({ enabled: false, track: 'agentvibes_soft_flamenco_loop.mp3', volume: 20 });
|
|
67
|
-
const VERBOSITY_LABELS = Object.freeze({ high: 'High', medium: 'Medium', low: 'Low', minimal: 'Minimal', custom: 'Custom' });
|
|
67
|
+
const VERBOSITY_LABELS = Object.freeze({ high: 'High', medium: 'Medium', low: 'Low', caveman: 'Caveman', minimal: 'Minimal', custom: 'Custom' });
|
|
68
68
|
|
|
69
69
|
// ---------------------------------------------------------------------------
|
|
70
70
|
// Exported format helpers (pure functions — used by tests and UI)
|
|
@@ -740,7 +740,7 @@ export function createSettingsTab(screen, services) {
|
|
|
740
740
|
function _editVerbosity() {
|
|
741
741
|
navigationService?.openModal();
|
|
742
742
|
|
|
743
|
-
const levels = ['high', 'medium', 'low'];
|
|
743
|
+
const levels = ['high', 'medium', 'low', 'caveman'];
|
|
744
744
|
const modal = blessed.list({
|
|
745
745
|
parent: screen,
|
|
746
746
|
top: 'center',
|
|
@@ -38,7 +38,7 @@ import { openReverbPicker, REVERB_PRESETS } from '../widgets/reverb-picker.js';
|
|
|
38
38
|
import { openTrackPicker, openVolumeInput } from '../widgets/track-picker.js';
|
|
39
39
|
import { formatTrackName } from '../widgets/format-utils.js';
|
|
40
40
|
import { destroyList } from '../widgets/destroy-list.js';
|
|
41
|
-
import { scanInstalledVoices, getVoiceMeta, genderIconTag, PIPER_VOICES_DIR, SAMPLE_PHRASES, parseMultiSpeaker, getFavorites, toggleFavorite } from './voices-tab.js';
|
|
41
|
+
import { scanInstalledVoices, getVoiceMeta, genderIconTag, PIPER_VOICES_DIR, SAMPLE_PHRASES, parseMultiSpeaker, getFavorites, getThumbsDown, toggleFavorite, toggleThumbsUp, toggleThumbsDown } from './voices-tab.js';
|
|
42
42
|
import { attachBtnBlink } from './agents-tab.js';
|
|
43
43
|
import { buildAudioEnv, detectWavPlayer } from '../audio-env.js';
|
|
44
44
|
import { spawn } from 'node:child_process';
|
|
@@ -889,7 +889,7 @@ export function createSetupTab(screen, services) {
|
|
|
889
889
|
const hooksSubdir = process.platform === 'win32' ? 'hooks-windows' : 'hooks';
|
|
890
890
|
const isWin = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
|
|
891
891
|
// Don't include pretext — play-tts already prepends it from the config
|
|
892
|
-
const sampleText = '
|
|
892
|
+
const sampleText = 'This is how your audio settings sound right now.';
|
|
893
893
|
|
|
894
894
|
let cmd, args;
|
|
895
895
|
if (isWin) {
|
|
@@ -1226,18 +1226,20 @@ export function createSetupTab(screen, services) {
|
|
|
1226
1226
|
|
|
1227
1227
|
blessed.text({
|
|
1228
1228
|
parent: vpModal, bottom: 2, left: 2, right: 2, tags: true,
|
|
1229
|
-
content: '{white-fg}[↑↓] Nav [PgUp/PgDn] Page [
|
|
1229
|
+
content: '{white-fg}[↑↓] Nav [PgUp/PgDn] Page [a-z] Jump [Enter] Select [Space] Preview [+] 👍 [-] 👎 [Esc] Cancel{/white-fg}',
|
|
1230
1230
|
style: { bg: COLORS.contentBg },
|
|
1231
1231
|
});
|
|
1232
1232
|
|
|
1233
1233
|
function _buildVoiceItems(voices) {
|
|
1234
1234
|
const favs = getFavorites(configService);
|
|
1235
|
+
const td = getThumbsDown(configService);
|
|
1235
1236
|
return voices.map(v => {
|
|
1236
1237
|
const isActive = v === draft.voice;
|
|
1237
1238
|
const isPrev = v === _previewVoiceId;
|
|
1238
|
-
const
|
|
1239
|
+
const isUp = favs.includes(v);
|
|
1240
|
+
const isDown = td.includes(v);
|
|
1239
1241
|
const dot = isPrev ? '♪' : (isActive ? '●' : ' ');
|
|
1240
|
-
const star =
|
|
1242
|
+
const star = isUp ? '{green-fg}👍{/green-fg}' : (isDown ? '{red-fg}👎{/red-fg}' : ' ');
|
|
1241
1243
|
const meta = getVoiceMeta(v);
|
|
1242
1244
|
const name = meta.displayName.length > COL_N
|
|
1243
1245
|
? meta.displayName.slice(0, COL_N - 1) + '…'
|
|
@@ -1266,13 +1268,52 @@ export function createSetupTab(screen, services) {
|
|
|
1266
1268
|
if (_previewVoiceId === voiceId) { _killVP(); vpPreviewLine.setContent(''); _refreshVP(); return; }
|
|
1267
1269
|
_killVP();
|
|
1268
1270
|
|
|
1271
|
+
const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
|
|
1272
|
+
|
|
1273
|
+
// Route through remote provider if active
|
|
1274
|
+
const _remoteProviders = ['ssh-remote', 'agentvibes-receiver'];
|
|
1275
|
+
let _activeProvider = '';
|
|
1276
|
+
try {
|
|
1277
|
+
const _projectRoot = path.resolve(__dirname, '..', '..');
|
|
1278
|
+
const _provPaths = [
|
|
1279
|
+
path.join(_projectRoot, '.claude', 'tts-provider.txt'),
|
|
1280
|
+
path.join(os.homedir(), '.claude', 'tts-provider.txt'),
|
|
1281
|
+
];
|
|
1282
|
+
for (const p of _provPaths) {
|
|
1283
|
+
if (fs.existsSync(p)) { _activeProvider = fs.readFileSync(p, 'utf8').trim(); break; }
|
|
1284
|
+
}
|
|
1285
|
+
} catch {}
|
|
1286
|
+
|
|
1287
|
+
if (_remoteProviders.includes(_activeProvider)) {
|
|
1288
|
+
const _projectRoot = path.resolve(__dirname, '..', '..');
|
|
1289
|
+
let rProc;
|
|
1290
|
+
if (_isWin) {
|
|
1291
|
+
const _playTts = path.join(_projectRoot, '.claude', 'hooks-windows', 'play-tts.ps1');
|
|
1292
|
+
rProc = spawn('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', _playTts, phrase, voiceId], {
|
|
1293
|
+
stdio: 'ignore', detached: false, windowsHide: true, env: _spawnEnv,
|
|
1294
|
+
});
|
|
1295
|
+
} else {
|
|
1296
|
+
const _playTts = path.join(_projectRoot, '.claude', 'hooks', 'play-tts.sh');
|
|
1297
|
+
rProc = spawn('bash', [_playTts, phrase, voiceId], {
|
|
1298
|
+
stdio: 'ignore', detached: true, env: _spawnEnv,
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
_previewProc = rProc;
|
|
1302
|
+
_previewVoiceId = voiceId;
|
|
1303
|
+
if (!_vpClosed) { vpPreviewLine.setContent(`{cyan-fg}♪ Playing (remote): ${voiceId}{/cyan-fg}`); screen.render(); }
|
|
1304
|
+
rProc.on('exit', () => {
|
|
1305
|
+
if (_previewVoiceId === voiceId) { _previewVoiceId = null; _previewProc = null; if (!_vpClosed) { vpPreviewLine.setContent(''); _refreshVP(); } }
|
|
1306
|
+
});
|
|
1307
|
+
rProc.on('error', () => { _previewProc = null; _previewVoiceId = null; });
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1269
1311
|
const _ms = parseMultiSpeaker(voiceId);
|
|
1270
1312
|
const voicePath = path.resolve(PIPER_VOICES_DIR, _ms.model + '.onnx');
|
|
1271
1313
|
const safeBase = path.resolve(PIPER_VOICES_DIR);
|
|
1272
1314
|
if (!voicePath.startsWith(safeBase + path.sep) && voicePath !== safeBase) return;
|
|
1273
1315
|
|
|
1274
1316
|
const tempWav = _secureTempWav('vp');
|
|
1275
|
-
const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
|
|
1276
1317
|
|
|
1277
1318
|
let _piperBin = 'piper';
|
|
1278
1319
|
if (_isWin) {
|
|
@@ -1331,9 +1372,13 @@ export function createSetupTab(screen, services) {
|
|
|
1331
1372
|
const sel = _allVoices[vpList.selected];
|
|
1332
1373
|
if (sel) _previewVoice(sel);
|
|
1333
1374
|
});
|
|
1334
|
-
vpList.key(['*'], () => {
|
|
1375
|
+
vpList.key(['*', '+'], () => {
|
|
1376
|
+
const sel = _allVoices[vpList.selected];
|
|
1377
|
+
if (sel) { toggleThumbsUp(configService, sel); _refreshVP(); }
|
|
1378
|
+
});
|
|
1379
|
+
vpList.key(['-'], () => {
|
|
1335
1380
|
const sel = _allVoices[vpList.selected];
|
|
1336
|
-
if (sel) {
|
|
1381
|
+
if (sel) { toggleThumbsDown(configService, sel); _refreshVP(); }
|
|
1337
1382
|
});
|
|
1338
1383
|
vpList.key(['escape', 'q'], _closeVP);
|
|
1339
1384
|
|
|
@@ -471,29 +471,85 @@ export function scanInstalledVoices() {
|
|
|
471
471
|
}
|
|
472
472
|
|
|
473
473
|
/**
|
|
474
|
-
* Get favorites array from config.
|
|
474
|
+
* Get favorites (thumbs-up) array from config.
|
|
475
475
|
* @param {object} configService
|
|
476
476
|
* @returns {string[]}
|
|
477
477
|
*/
|
|
478
478
|
export function getFavorites(configService) {
|
|
479
|
-
const
|
|
479
|
+
const cfg = configService.getConfig();
|
|
480
|
+
// Prefer thumbsUp if present, fall back to legacy favorites
|
|
481
|
+
const favs = cfg.thumbsUp ?? cfg.favorites;
|
|
480
482
|
return Array.isArray(favs) ? favs : [];
|
|
481
483
|
}
|
|
482
484
|
|
|
483
485
|
/**
|
|
484
|
-
*
|
|
486
|
+
* Get thumbs-down array from config.
|
|
487
|
+
* @param {object} configService
|
|
488
|
+
* @returns {string[]}
|
|
489
|
+
*/
|
|
490
|
+
export function getThumbsDown(configService) {
|
|
491
|
+
const td = configService.getConfig().thumbsDown;
|
|
492
|
+
return Array.isArray(td) ? td : [];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Toggle thumbs-up on a voice. Clears thumbs-down if set.
|
|
485
497
|
* @param {object} configService
|
|
486
498
|
* @param {string} voiceId
|
|
499
|
+
* @returns {'added'|'removed'}
|
|
487
500
|
*/
|
|
488
|
-
export function
|
|
501
|
+
export function toggleThumbsUp(configService, voiceId) {
|
|
489
502
|
const favs = getFavorites(configService);
|
|
490
503
|
const idx = favs.indexOf(voiceId);
|
|
504
|
+
let result;
|
|
491
505
|
if (idx >= 0) {
|
|
492
506
|
favs.splice(idx, 1);
|
|
507
|
+
result = 'removed';
|
|
493
508
|
} else {
|
|
494
509
|
favs.push(voiceId);
|
|
510
|
+
// Remove from thumbs-down
|
|
511
|
+
const td = getThumbsDown(configService);
|
|
512
|
+
const tdIdx = td.indexOf(voiceId);
|
|
513
|
+
if (tdIdx >= 0) { td.splice(tdIdx, 1); configService.set('thumbsDown', td); }
|
|
514
|
+
result = 'added';
|
|
495
515
|
}
|
|
496
|
-
configService.set('
|
|
516
|
+
configService.set('thumbsUp', favs);
|
|
517
|
+
configService.set('favorites', favs); // backward compat
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Toggle thumbs-down on a voice. Clears thumbs-up if set.
|
|
523
|
+
* @param {object} configService
|
|
524
|
+
* @param {string} voiceId
|
|
525
|
+
* @returns {'added'|'removed'}
|
|
526
|
+
*/
|
|
527
|
+
export function toggleThumbsDown(configService, voiceId) {
|
|
528
|
+
const td = getThumbsDown(configService);
|
|
529
|
+
const idx = td.indexOf(voiceId);
|
|
530
|
+
let result;
|
|
531
|
+
if (idx >= 0) {
|
|
532
|
+
td.splice(idx, 1);
|
|
533
|
+
result = 'removed';
|
|
534
|
+
} else {
|
|
535
|
+
td.push(voiceId);
|
|
536
|
+
// Remove from thumbs-up / favorites
|
|
537
|
+
const favs = getFavorites(configService);
|
|
538
|
+
const fIdx = favs.indexOf(voiceId);
|
|
539
|
+
if (fIdx >= 0) { favs.splice(fIdx, 1); configService.set('thumbsUp', favs); configService.set('favorites', favs); }
|
|
540
|
+
result = 'added';
|
|
541
|
+
}
|
|
542
|
+
configService.set('thumbsDown', td);
|
|
543
|
+
return result;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Toggle a voice in the favorites list (legacy compat — calls toggleThumbsUp).
|
|
548
|
+
* @param {object} configService
|
|
549
|
+
* @param {string} voiceId
|
|
550
|
+
*/
|
|
551
|
+
export function toggleFavorite(configService, voiceId) {
|
|
552
|
+
toggleThumbsUp(configService, voiceId);
|
|
497
553
|
}
|
|
498
554
|
|
|
499
555
|
// ---------------------------------------------------------------------------
|
|
@@ -708,7 +764,7 @@ export function createVoicesTab(screen, services) {
|
|
|
708
764
|
|
|
709
765
|
// -------------------------------------------------------------------------
|
|
710
766
|
// Hint text shown in previewLine when the list has focus and nothing is playing
|
|
711
|
-
const HINT_TEXT = '{white-fg}[Space] preview [Enter] select
|
|
767
|
+
const HINT_TEXT = '{white-fg}[Space] preview [Enter] select [+] thumbs up [-] thumbs down{/white-fg}';
|
|
712
768
|
let _listFocused = false;
|
|
713
769
|
|
|
714
770
|
// Inline selection hint appended to the currently highlighted voice row.
|
|
@@ -811,6 +867,55 @@ export function createVoicesTab(screen, services) {
|
|
|
811
867
|
_killPlayingProcess();
|
|
812
868
|
_playingVoiceId = null;
|
|
813
869
|
|
|
870
|
+
// Check if we should route through remote provider (ssh-remote / agentvibes-receiver)
|
|
871
|
+
const projectRoot = path.resolve(__dirname, '..', '..');
|
|
872
|
+
const remoteProviders = ['ssh-remote', 'agentvibes-receiver'];
|
|
873
|
+
let activeProvider = '';
|
|
874
|
+
try {
|
|
875
|
+
const providerPaths = [
|
|
876
|
+
path.join(projectRoot, '.claude', 'tts-provider.txt'),
|
|
877
|
+
path.join(os.homedir(), '.claude', 'tts-provider.txt'),
|
|
878
|
+
];
|
|
879
|
+
for (const p of providerPaths) {
|
|
880
|
+
if (fs.existsSync(p)) { activeProvider = fs.readFileSync(p, 'utf8').trim(); break; }
|
|
881
|
+
}
|
|
882
|
+
} catch {}
|
|
883
|
+
|
|
884
|
+
if (remoteProviders.includes(activeProvider)) {
|
|
885
|
+
const isWindows = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
|
|
886
|
+
const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
|
|
887
|
+
let proc;
|
|
888
|
+
if (isWindows) {
|
|
889
|
+
const playTts = path.join(projectRoot, '.claude', 'hooks-windows', 'play-tts.ps1');
|
|
890
|
+
proc = spawn('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', playTts, phrase, voiceId], {
|
|
891
|
+
stdio: 'ignore', detached: false, windowsHide: true, env: _spawnEnv,
|
|
892
|
+
});
|
|
893
|
+
} else {
|
|
894
|
+
const playTts = path.join(projectRoot, '.claude', 'hooks', 'play-tts.sh');
|
|
895
|
+
proc = spawn('bash', [playTts, phrase, voiceId], {
|
|
896
|
+
stdio: 'ignore', detached: true, env: _spawnEnv,
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
_playingProcess = proc;
|
|
900
|
+
_playingVoiceId = voiceId;
|
|
901
|
+
previewLine.setContent(`{${COLORS.activeFg}-fg}♪ Playing (remote): ${voiceId}{/${COLORS.activeFg}-fg}`);
|
|
902
|
+
screen.render();
|
|
903
|
+
proc.on('exit', () => {
|
|
904
|
+
if (_playingVoiceId === voiceId) {
|
|
905
|
+
_playingVoiceId = null; _playingProcess = null;
|
|
906
|
+
previewLine.setContent(_listFocused ? HINT_TEXT : '');
|
|
907
|
+
refreshDisplay();
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
proc.on('error', () => {
|
|
911
|
+
_playingVoiceId = null; _playingProcess = null;
|
|
912
|
+
previewLine.setContent(`{red-fg}Remote preview failed{/red-fg}`);
|
|
913
|
+
screen.render();
|
|
914
|
+
setTimeout(() => { previewLine.setContent(_listFocused ? HINT_TEXT : ''); screen.render(); }, 4000);
|
|
915
|
+
});
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
|
|
814
919
|
// Resolve model path (may be multi-speaker)
|
|
815
920
|
const ms = parseMultiSpeaker(voiceId);
|
|
816
921
|
const voicePath = path.resolve(PIPER_VOICES_DIR, ms.model + '.onnx');
|
|
@@ -1435,13 +1540,14 @@ export function createVoicesTab(screen, services) {
|
|
|
1435
1540
|
return _installedSet.has(voiceId);
|
|
1436
1541
|
}
|
|
1437
1542
|
|
|
1438
|
-
function _buildListItems(voices, active, favorites) {
|
|
1543
|
+
function _buildListItems(voices, active, favorites, thumbsDown) {
|
|
1439
1544
|
return voices.map(v => {
|
|
1440
1545
|
const installed = _isInstalled(v);
|
|
1441
|
-
const
|
|
1546
|
+
const isUp = favorites.includes(v);
|
|
1547
|
+
const isDown = thumbsDown.includes(v);
|
|
1442
1548
|
const isActive = v === active;
|
|
1443
1549
|
const isPrev = v === _playingVoiceId;
|
|
1444
|
-
const star =
|
|
1550
|
+
const star = isUp ? '{green-fg}👍{/green-fg}' : (isDown ? '{red-fg}👎{/red-fg}' : ' ');
|
|
1445
1551
|
const dot = isPrev ? '♪' : (isActive ? '{green-fg}✓{/green-fg}' : ' ');
|
|
1446
1552
|
|
|
1447
1553
|
let displayName, gender, provider;
|
|
@@ -1531,8 +1637,9 @@ export function createVoicesTab(screen, services) {
|
|
|
1531
1637
|
|
|
1532
1638
|
const active = providerService.getActiveVoiceId();
|
|
1533
1639
|
const favorites = getFavorites(configService);
|
|
1640
|
+
const thumbsDown = getThumbsDown(configService);
|
|
1534
1641
|
const filtered = _getFilteredVoices();
|
|
1535
|
-
const items = _buildListItems(filtered, active, favorites);
|
|
1642
|
+
const items = _buildListItems(filtered, active, favorites, thumbsDown);
|
|
1536
1643
|
|
|
1537
1644
|
voiceList.setItems(items.length > 0 ? items : [' (no voices found — install piper first)']);
|
|
1538
1645
|
const maxIdx = Math.max(0, (items.length > 0 ? items.length : 1) - 1);
|
|
@@ -1620,12 +1727,22 @@ export function createVoicesTab(screen, services) {
|
|
|
1620
1727
|
if (typeof focusMainTabBar === 'function') { focusMainTabBar(); screen.render(); }
|
|
1621
1728
|
});
|
|
1622
1729
|
|
|
1623
|
-
// '
|
|
1624
|
-
voiceList.key(['*'], () => {
|
|
1730
|
+
// '*' or '+' in voiceList toggles thumbs-up
|
|
1731
|
+
voiceList.key(['*', '+'], () => {
|
|
1625
1732
|
const voices = _getFilteredVoices();
|
|
1626
1733
|
const selected = voices[voiceList.selected];
|
|
1627
1734
|
if (selected) {
|
|
1628
|
-
|
|
1735
|
+
toggleThumbsUp(configService, selected);
|
|
1736
|
+
refreshDisplay();
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
// '-' in voiceList toggles thumbs-down
|
|
1741
|
+
voiceList.key(['-'], () => {
|
|
1742
|
+
const voices = _getFilteredVoices();
|
|
1743
|
+
const selected = voices[voiceList.selected];
|
|
1744
|
+
if (selected) {
|
|
1745
|
+
toggleThumbsDown(configService, selected);
|
|
1629
1746
|
refreshDisplay();
|
|
1630
1747
|
}
|
|
1631
1748
|
});
|