voice-mode 2.34.2__py3-none-any.whl → 4.0.1__py3-none-any.whl

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.
Files changed (156) hide show
  1. voice_mode/__version__.py +1 -1
  2. voice_mode/cli.py +5 -0
  3. voice_mode/cli_commands/transcribe.py +141 -0
  4. voice_mode/config.py +139 -37
  5. voice_mode/frontend/.next/BUILD_ID +1 -0
  6. voice_mode/frontend/.next/app-build-manifest.json +28 -0
  7. voice_mode/frontend/.next/app-path-routes-manifest.json +1 -0
  8. voice_mode/frontend/.next/build-manifest.json +32 -0
  9. voice_mode/frontend/.next/export-marker.json +1 -0
  10. voice_mode/frontend/.next/images-manifest.json +1 -0
  11. voice_mode/frontend/.next/next-minimal-server.js.nft.json +1 -0
  12. voice_mode/frontend/.next/next-server.js.nft.json +1 -0
  13. voice_mode/frontend/.next/package.json +1 -0
  14. voice_mode/frontend/.next/prerender-manifest.json +1 -0
  15. voice_mode/frontend/.next/react-loadable-manifest.json +1 -0
  16. voice_mode/frontend/.next/required-server-files.json +1 -0
  17. voice_mode/frontend/.next/routes-manifest.json +1 -0
  18. voice_mode/frontend/.next/server/app/_not-found/page.js +1 -0
  19. voice_mode/frontend/.next/server/app/_not-found/page.js.nft.json +1 -0
  20. voice_mode/frontend/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  21. voice_mode/frontend/.next/server/app/_not-found.html +1 -0
  22. voice_mode/frontend/.next/server/app/_not-found.meta +6 -0
  23. voice_mode/frontend/.next/server/app/_not-found.rsc +9 -0
  24. voice_mode/frontend/.next/server/app/api/connection-details/route.js +12 -0
  25. voice_mode/frontend/.next/server/app/api/connection-details/route.js.nft.json +1 -0
  26. voice_mode/frontend/.next/server/app/favicon.ico/route.js +12 -0
  27. voice_mode/frontend/.next/server/app/favicon.ico/route.js.nft.json +1 -0
  28. voice_mode/frontend/.next/server/app/favicon.ico.body +0 -0
  29. voice_mode/frontend/.next/server/app/favicon.ico.meta +1 -0
  30. voice_mode/frontend/.next/server/app/index.html +1 -0
  31. voice_mode/frontend/.next/server/app/index.meta +5 -0
  32. voice_mode/frontend/.next/server/app/index.rsc +7 -0
  33. voice_mode/frontend/.next/server/app/page.js +11 -0
  34. voice_mode/frontend/.next/server/app/page.js.nft.json +1 -0
  35. voice_mode/frontend/.next/server/app/page_client-reference-manifest.js +1 -0
  36. voice_mode/frontend/.next/server/app-paths-manifest.json +6 -0
  37. voice_mode/frontend/.next/server/chunks/463.js +1 -0
  38. voice_mode/frontend/.next/server/chunks/682.js +6 -0
  39. voice_mode/frontend/.next/server/chunks/948.js +2 -0
  40. voice_mode/frontend/.next/server/chunks/994.js +2 -0
  41. voice_mode/frontend/.next/server/chunks/font-manifest.json +1 -0
  42. voice_mode/frontend/.next/server/font-manifest.json +1 -0
  43. voice_mode/frontend/.next/server/functions-config-manifest.json +1 -0
  44. voice_mode/frontend/.next/server/interception-route-rewrite-manifest.js +1 -0
  45. voice_mode/frontend/.next/server/middleware-build-manifest.js +1 -0
  46. voice_mode/frontend/.next/server/middleware-manifest.json +6 -0
  47. voice_mode/frontend/.next/server/middleware-react-loadable-manifest.js +1 -0
  48. voice_mode/frontend/.next/server/next-font-manifest.js +1 -0
  49. voice_mode/frontend/.next/server/next-font-manifest.json +1 -0
  50. voice_mode/frontend/.next/server/pages/404.html +1 -0
  51. voice_mode/frontend/.next/server/pages/500.html +1 -0
  52. voice_mode/frontend/.next/server/pages/_app.js +1 -0
  53. voice_mode/frontend/.next/server/pages/_app.js.nft.json +1 -0
  54. voice_mode/frontend/.next/server/pages/_document.js +1 -0
  55. voice_mode/frontend/.next/server/pages/_document.js.nft.json +1 -0
  56. voice_mode/frontend/.next/server/pages/_error.js +1 -0
  57. voice_mode/frontend/.next/server/pages/_error.js.nft.json +1 -0
  58. voice_mode/frontend/.next/server/pages-manifest.json +1 -0
  59. voice_mode/frontend/.next/server/server-reference-manifest.js +1 -0
  60. voice_mode/frontend/.next/server/server-reference-manifest.json +1 -0
  61. voice_mode/frontend/.next/server/webpack-runtime.js +1 -0
  62. voice_mode/frontend/.next/standalone/.next/BUILD_ID +1 -0
  63. voice_mode/frontend/.next/standalone/.next/app-build-manifest.json +28 -0
  64. voice_mode/frontend/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  65. voice_mode/frontend/.next/standalone/.next/build-manifest.json +32 -0
  66. voice_mode/frontend/.next/standalone/.next/package.json +1 -0
  67. voice_mode/frontend/.next/standalone/.next/prerender-manifest.json +1 -0
  68. voice_mode/frontend/.next/standalone/.next/react-loadable-manifest.json +1 -0
  69. voice_mode/frontend/.next/standalone/.next/required-server-files.json +1 -0
  70. voice_mode/frontend/.next/standalone/.next/routes-manifest.json +1 -0
  71. voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page.js +1 -0
  72. voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -0
  73. voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  74. voice_mode/frontend/.next/standalone/.next/server/app/_not-found.html +1 -0
  75. voice_mode/frontend/.next/standalone/.next/server/app/_not-found.meta +6 -0
  76. voice_mode/frontend/.next/standalone/.next/server/app/_not-found.rsc +9 -0
  77. voice_mode/frontend/.next/standalone/.next/server/app/api/connection-details/route.js +12 -0
  78. voice_mode/frontend/.next/standalone/.next/server/app/api/connection-details/route.js.nft.json +1 -0
  79. voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico/route.js +12 -0
  80. voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico/route.js.nft.json +1 -0
  81. voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico.body +0 -0
  82. voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico.meta +1 -0
  83. voice_mode/frontend/.next/standalone/.next/server/app/index.html +1 -0
  84. voice_mode/frontend/.next/standalone/.next/server/app/index.meta +5 -0
  85. voice_mode/frontend/.next/standalone/.next/server/app/index.rsc +7 -0
  86. voice_mode/frontend/.next/standalone/.next/server/app/page.js +11 -0
  87. voice_mode/frontend/.next/standalone/.next/server/app/page.js.nft.json +1 -0
  88. voice_mode/frontend/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -0
  89. voice_mode/frontend/.next/standalone/.next/server/app-paths-manifest.json +6 -0
  90. voice_mode/frontend/.next/standalone/.next/server/chunks/463.js +1 -0
  91. voice_mode/frontend/.next/standalone/.next/server/chunks/682.js +6 -0
  92. voice_mode/frontend/.next/standalone/.next/server/chunks/948.js +2 -0
  93. voice_mode/frontend/.next/standalone/.next/server/chunks/994.js +2 -0
  94. voice_mode/frontend/.next/standalone/.next/server/font-manifest.json +1 -0
  95. voice_mode/frontend/.next/standalone/.next/server/middleware-build-manifest.js +1 -0
  96. voice_mode/frontend/.next/standalone/.next/server/middleware-manifest.json +6 -0
  97. voice_mode/frontend/.next/standalone/.next/server/middleware-react-loadable-manifest.js +1 -0
  98. voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.js +1 -0
  99. voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.json +1 -0
  100. voice_mode/frontend/.next/standalone/.next/server/pages/404.html +1 -0
  101. voice_mode/frontend/.next/standalone/.next/server/pages/500.html +1 -0
  102. voice_mode/frontend/.next/standalone/.next/server/pages/_app.js +1 -0
  103. voice_mode/frontend/.next/standalone/.next/server/pages/_app.js.nft.json +1 -0
  104. voice_mode/frontend/.next/standalone/.next/server/pages/_document.js +1 -0
  105. voice_mode/frontend/.next/standalone/.next/server/pages/_document.js.nft.json +1 -0
  106. voice_mode/frontend/.next/standalone/.next/server/pages/_error.js +1 -0
  107. voice_mode/frontend/.next/standalone/.next/server/pages/_error.js.nft.json +1 -0
  108. voice_mode/frontend/.next/standalone/.next/server/pages-manifest.json +1 -0
  109. voice_mode/frontend/.next/standalone/.next/server/server-reference-manifest.js +1 -0
  110. voice_mode/frontend/.next/standalone/.next/server/server-reference-manifest.json +1 -0
  111. voice_mode/frontend/.next/standalone/.next/server/webpack-runtime.js +1 -0
  112. voice_mode/frontend/.next/standalone/package.json +40 -0
  113. voice_mode/frontend/.next/standalone/server.js +38 -0
  114. voice_mode/frontend/.next/static/c5TIe90lGzrESrqJkkXQa/_buildManifest.js +1 -0
  115. voice_mode/frontend/.next/static/c5TIe90lGzrESrqJkkXQa/_ssgManifest.js +1 -0
  116. voice_mode/frontend/.next/static/chunks/117-40bc79a2b97edb21.js +2 -0
  117. voice_mode/frontend/.next/static/chunks/144d3bae-2d5f122b82426d88.js +1 -0
  118. voice_mode/frontend/.next/static/chunks/471-bd4b96a33883dfa2.js +3 -0
  119. voice_mode/frontend/.next/static/chunks/app/_not-found/page-5011050e402ab9c8.js +1 -0
  120. voice_mode/frontend/.next/static/chunks/app/layout-0074dd8ab91cdbe0.js +1 -0
  121. voice_mode/frontend/.next/static/chunks/app/page-ae5f3aa9d9ba5993.js +1 -0
  122. voice_mode/frontend/.next/static/chunks/fd9d1056-af324d327b243cf1.js +1 -0
  123. voice_mode/frontend/.next/static/chunks/framework-f66176bb897dc684.js +1 -0
  124. voice_mode/frontend/.next/static/chunks/main-3163eca598b76a9f.js +1 -0
  125. voice_mode/frontend/.next/static/chunks/main-app-233f6c633f73ae84.js +1 -0
  126. voice_mode/frontend/.next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
  127. voice_mode/frontend/.next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
  128. voice_mode/frontend/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  129. voice_mode/frontend/.next/static/chunks/webpack-0ea9b80f19935b70.js +1 -0
  130. voice_mode/frontend/.next/static/css/a2f49a47752b5010.css +3 -0
  131. voice_mode/frontend/.next/static/media/01099be941da1820-s.woff2 +0 -0
  132. voice_mode/frontend/.next/static/media/39883d31a7792467-s.p.woff2 +0 -0
  133. voice_mode/frontend/.next/static/media/6368404d2e8d66fe-s.woff2 +0 -0
  134. voice_mode/frontend/.next/trace +43 -0
  135. voice_mode/frontend/.next/types/app/api/connection-details/route.ts +343 -0
  136. voice_mode/frontend/.next/types/app/layout.ts +79 -0
  137. voice_mode/frontend/.next/types/app/page.ts +79 -0
  138. voice_mode/frontend/.next/types/package.json +1 -0
  139. voice_mode/frontend/package-lock.json +154 -1
  140. voice_mode/providers.py +7 -8
  141. voice_mode/resources/configuration.py +2 -2
  142. voice_mode/tools/configuration_management.py +106 -5
  143. voice_mode/tools/converse.py +98 -0
  144. voice_mode/tools/service.py +1 -7
  145. voice_mode/tools/transcription/__init__.py +14 -0
  146. voice_mode/tools/transcription/backends.py +287 -0
  147. voice_mode/tools/transcription/core.py +136 -0
  148. voice_mode/tools/transcription/formats.py +144 -0
  149. voice_mode/tools/transcription/types.py +52 -0
  150. voice_mode/utils/services/kokoro_helpers.py +16 -3
  151. {voice_mode-2.34.2.dist-info → voice_mode-4.0.1.dist-info}/METADATA +5 -2
  152. voice_mode-4.0.1.dist-info/RECORD +255 -0
  153. voice_mode/voice_preferences.py +0 -125
  154. voice_mode-2.34.2.dist-info/RECORD +0 -116
  155. {voice_mode-2.34.2.dist-info → voice_mode-4.0.1.dist-info}/WHEEL +0 -0
  156. {voice_mode-2.34.2.dist-info → voice_mode-4.0.1.dist-info}/entry_points.txt +0 -0
@@ -21,6 +21,7 @@
21
21
  "@types/node": "^20.17.13",
22
22
  "@types/react": "^18.3.18",
23
23
  "@types/react-dom": "^18.3.5",
24
+ "autoprefixer": "^10.4.21",
24
25
  "eslint": "^8.57.1",
25
26
  "eslint-config-next": "14.2.29",
26
27
  "eslint-config-prettier": "9.1.0",
@@ -1254,6 +1255,44 @@
1254
1255
  "node": ">= 0.4"
1255
1256
  }
1256
1257
  },
1258
+ "node_modules/autoprefixer": {
1259
+ "version": "10.4.21",
1260
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
1261
+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
1262
+ "dev": true,
1263
+ "funding": [
1264
+ {
1265
+ "type": "opencollective",
1266
+ "url": "https://opencollective.com/postcss/"
1267
+ },
1268
+ {
1269
+ "type": "tidelift",
1270
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
1271
+ },
1272
+ {
1273
+ "type": "github",
1274
+ "url": "https://github.com/sponsors/ai"
1275
+ }
1276
+ ],
1277
+ "license": "MIT",
1278
+ "dependencies": {
1279
+ "browserslist": "^4.24.4",
1280
+ "caniuse-lite": "^1.0.30001702",
1281
+ "fraction.js": "^4.3.7",
1282
+ "normalize-range": "^0.1.2",
1283
+ "picocolors": "^1.1.1",
1284
+ "postcss-value-parser": "^4.2.0"
1285
+ },
1286
+ "bin": {
1287
+ "autoprefixer": "bin/autoprefixer"
1288
+ },
1289
+ "engines": {
1290
+ "node": "^10 || ^12 || >=14"
1291
+ },
1292
+ "peerDependencies": {
1293
+ "postcss": "^8.1.0"
1294
+ }
1295
+ },
1257
1296
  "node_modules/available-typed-arrays": {
1258
1297
  "version": "1.0.7",
1259
1298
  "dev": true,
@@ -1320,6 +1359,39 @@
1320
1359
  "node": ">=8"
1321
1360
  }
1322
1361
  },
1362
+ "node_modules/browserslist": {
1363
+ "version": "4.25.4",
1364
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz",
1365
+ "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==",
1366
+ "dev": true,
1367
+ "funding": [
1368
+ {
1369
+ "type": "opencollective",
1370
+ "url": "https://opencollective.com/browserslist"
1371
+ },
1372
+ {
1373
+ "type": "tidelift",
1374
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1375
+ },
1376
+ {
1377
+ "type": "github",
1378
+ "url": "https://github.com/sponsors/ai"
1379
+ }
1380
+ ],
1381
+ "license": "MIT",
1382
+ "dependencies": {
1383
+ "caniuse-lite": "^1.0.30001737",
1384
+ "electron-to-chromium": "^1.5.211",
1385
+ "node-releases": "^2.0.19",
1386
+ "update-browserslist-db": "^1.1.3"
1387
+ },
1388
+ "bin": {
1389
+ "browserslist": "cli.js"
1390
+ },
1391
+ "engines": {
1392
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1393
+ }
1394
+ },
1323
1395
  "node_modules/busboy": {
1324
1396
  "version": "1.6.0",
1325
1397
  "dev": true,
@@ -1417,7 +1489,9 @@
1417
1489
  }
1418
1490
  },
1419
1491
  "node_modules/caniuse-lite": {
1420
- "version": "1.0.30001726",
1492
+ "version": "1.0.30001739",
1493
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
1494
+ "integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==",
1421
1495
  "dev": true,
1422
1496
  "funding": [
1423
1497
  {
@@ -1699,6 +1773,13 @@
1699
1773
  "dev": true,
1700
1774
  "license": "MIT"
1701
1775
  },
1776
+ "node_modules/electron-to-chromium": {
1777
+ "version": "1.5.211",
1778
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz",
1779
+ "integrity": "sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==",
1780
+ "dev": true,
1781
+ "license": "ISC"
1782
+ },
1702
1783
  "node_modules/emoji-regex": {
1703
1784
  "version": "9.2.2",
1704
1785
  "dev": true,
@@ -1865,6 +1946,16 @@
1865
1946
  "url": "https://github.com/sponsors/ljharb"
1866
1947
  }
1867
1948
  },
1949
+ "node_modules/escalade": {
1950
+ "version": "3.2.0",
1951
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1952
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1953
+ "dev": true,
1954
+ "license": "MIT",
1955
+ "engines": {
1956
+ "node": ">=6"
1957
+ }
1958
+ },
1868
1959
  "node_modules/escape-string-regexp": {
1869
1960
  "version": "4.0.0",
1870
1961
  "dev": true,
@@ -2462,6 +2553,20 @@
2462
2553
  "url": "https://github.com/sponsors/isaacs"
2463
2554
  }
2464
2555
  },
2556
+ "node_modules/fraction.js": {
2557
+ "version": "4.3.7",
2558
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
2559
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
2560
+ "dev": true,
2561
+ "license": "MIT",
2562
+ "engines": {
2563
+ "node": "*"
2564
+ },
2565
+ "funding": {
2566
+ "type": "patreon",
2567
+ "url": "https://github.com/sponsors/rawify"
2568
+ }
2569
+ },
2465
2570
  "node_modules/framer-motion": {
2466
2571
  "version": "11.18.2",
2467
2572
  "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
@@ -3693,6 +3798,13 @@
3693
3798
  "node": "^10 || ^12 || >=14"
3694
3799
  }
3695
3800
  },
3801
+ "node_modules/node-releases": {
3802
+ "version": "2.0.19",
3803
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
3804
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
3805
+ "dev": true,
3806
+ "license": "MIT"
3807
+ },
3696
3808
  "node_modules/normalize-path": {
3697
3809
  "version": "3.0.0",
3698
3810
  "dev": true,
@@ -3701,6 +3813,16 @@
3701
3813
  "node": ">=0.10.0"
3702
3814
  }
3703
3815
  },
3816
+ "node_modules/normalize-range": {
3817
+ "version": "0.1.2",
3818
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
3819
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
3820
+ "dev": true,
3821
+ "license": "MIT",
3822
+ "engines": {
3823
+ "node": ">=0.10.0"
3824
+ }
3825
+ },
3704
3826
  "node_modules/object-assign": {
3705
3827
  "version": "4.1.1",
3706
3828
  "dev": true,
@@ -5218,6 +5340,37 @@
5218
5340
  "@unrs/resolver-binding-win32-x64-msvc": "1.9.2"
5219
5341
  }
5220
5342
  },
5343
+ "node_modules/update-browserslist-db": {
5344
+ "version": "1.1.3",
5345
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
5346
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
5347
+ "dev": true,
5348
+ "funding": [
5349
+ {
5350
+ "type": "opencollective",
5351
+ "url": "https://opencollective.com/browserslist"
5352
+ },
5353
+ {
5354
+ "type": "tidelift",
5355
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
5356
+ },
5357
+ {
5358
+ "type": "github",
5359
+ "url": "https://github.com/sponsors/ai"
5360
+ }
5361
+ ],
5362
+ "license": "MIT",
5363
+ "dependencies": {
5364
+ "escalade": "^3.2.0",
5365
+ "picocolors": "^1.1.1"
5366
+ },
5367
+ "bin": {
5368
+ "update-browserslist-db": "cli.js"
5369
+ },
5370
+ "peerDependencies": {
5371
+ "browserslist": ">= 4.21.0"
5372
+ }
5373
+ },
5221
5374
  "node_modules/uri-js": {
5222
5375
  "version": "4.4.1",
5223
5376
  "dev": true,
voice_mode/providers.py CHANGED
@@ -9,9 +9,8 @@ import logging
9
9
  from typing import Dict, Optional, List, Any, Tuple
10
10
  from openai import AsyncOpenAI
11
11
 
12
- from .config import TTS_VOICES, TTS_MODELS, TTS_BASE_URLS, OPENAI_API_KEY
12
+ from .config import TTS_VOICES, TTS_MODELS, TTS_BASE_URLS, OPENAI_API_KEY, get_voice_preferences
13
13
  from .provider_discovery import provider_registry, EndpointInfo
14
- from .voice_preferences import get_preferred_voices
15
14
 
16
15
  logger = logging.getLogger("voice-mode")
17
16
 
@@ -68,14 +67,14 @@ async def get_tts_client_and_voice(
68
67
  return client, selected_voice, selected_model, endpoint_info
69
68
 
70
69
  # Voice-first selection algorithm
71
- # Get user preferences and prepend to system defaults
72
- user_preferences = get_preferred_voices()
73
- combined_voice_list = user_preferences + [v for v in TTS_VOICES if v not in user_preferences]
70
+ # Get user preferences from configuration
71
+ voice_preferences = get_voice_preferences()
72
+ combined_voice_list = voice_preferences
74
73
 
75
74
  logger.info(f"TTS Provider Selection (voice-first)")
76
- if user_preferences:
77
- logger.info(f" User voice preferences: {user_preferences}")
78
- logger.info(f" Combined voice list: {combined_voice_list}")
75
+ if voice_preferences:
76
+ logger.info(f" Voice preferences: {voice_preferences}")
77
+ logger.info(f" Voice list: {combined_voice_list}")
79
78
  logger.info(f" Preferred models: {TTS_MODELS}")
80
79
  logger.info(f" Available endpoints: {TTS_BASE_URLS}")
81
80
 
@@ -267,7 +267,7 @@ async def environment_variables() -> str:
267
267
  ("VOICEMODE_AUTO_START_KOKORO", "Auto-start Kokoro service (true/false)"),
268
268
  ("VOICEMODE_TTS_BASE_URLS", "Comma-separated list of TTS endpoints"),
269
269
  ("VOICEMODE_STT_BASE_URLS", "Comma-separated list of STT endpoints"),
270
- ("VOICEMODE_TTS_VOICES", "Comma-separated list of preferred voices"),
270
+ ("VOICEMODE_VOICES", "Comma-separated list of preferred voices"),
271
271
  ("VOICEMODE_TTS_MODELS", "Comma-separated list of preferred models"),
272
272
  # Audio Settings
273
273
  ("VOICEMODE_AUDIO_FORMAT", "Audio format for recording (pcm/mp3/wav/flac/aac/opus)"),
@@ -358,7 +358,7 @@ async def environment_template() -> str:
358
358
  f"export VOICEMODE_AUTO_START_KOKORO=\"{str(AUTO_START_KOKORO).lower()}\"",
359
359
  f"export VOICEMODE_TTS_BASE_URLS=\"{','.join(TTS_BASE_URLS)}\"",
360
360
  f"export VOICEMODE_STT_BASE_URLS=\"{','.join(STT_BASE_URLS)}\"",
361
- f"export VOICEMODE_TTS_VOICES=\"{','.join(TTS_VOICES)}\"",
361
+ f"export VOICEMODE_VOICES=\"{','.join(TTS_VOICES)}\"",
362
362
  f"export VOICEMODE_TTS_MODELS=\"{','.join(TTS_MODELS)}\"",
363
363
  "",
364
364
  "# Audio Settings",
@@ -5,7 +5,7 @@ import re
5
5
  from pathlib import Path
6
6
  from typing import Dict, Optional, List
7
7
  from voice_mode.server import mcp
8
- from voice_mode.config import BASE_DIR
8
+ from voice_mode.config import BASE_DIR, reload_configuration, find_voicemode_env_files
9
9
  import logging
10
10
 
11
11
  logger = logging.getLogger("voice-mode")
@@ -109,7 +109,7 @@ async def update_config(key: str, value: str) -> str:
109
109
  """Update a configuration value in the voicemode.env file.
110
110
 
111
111
  Args:
112
- key: The configuration key to update (e.g., 'VOICEMODE_TTS_VOICES')
112
+ key: The configuration key to update (e.g., 'VOICEMODE_VOICES')
113
113
  value: The new value for the configuration
114
114
 
115
115
  Returns:
@@ -175,7 +175,7 @@ async def list_config_keys() -> str:
175
175
  ("Provider Configuration", [
176
176
  ("VOICEMODE_TTS_BASE_URLS", "Comma-separated list of TTS endpoints"),
177
177
  ("VOICEMODE_STT_BASE_URLS", "Comma-separated list of STT endpoints"),
178
- ("VOICEMODE_TTS_VOICES", "Comma-separated list of preferred voices"),
178
+ ("VOICEMODE_VOICES", "Comma-separated list of preferred voices"),
179
179
  ("VOICEMODE_TTS_MODELS", "Comma-separated list of preferred models"),
180
180
  ("VOICEMODE_PREFER_LOCAL", "Prefer local providers over cloud (true/false)"),
181
181
  ("VOICEMODE_ALWAYS_TRY_LOCAL", "Always attempt local providers (true/false)"),
@@ -211,6 +211,107 @@ async def list_config_keys() -> str:
211
211
  lines.append(f" {description}")
212
212
  lines.append("")
213
213
 
214
- lines.append("💡 Usage: update_config(key='VOICEMODE_TTS_VOICES', value='af_sky,nova')")
214
+ lines.append("💡 Usage: update_config(key='VOICEMODE_VOICES', value='af_sky,nova')")
215
215
 
216
- return "\n".join(lines)
216
+ return "\n".join(lines)
217
+
218
+
219
+ @mcp.tool()
220
+ async def config_reload() -> str:
221
+ """Reload configuration from .voicemode.env files and clear all caches.
222
+
223
+ This tool reloads configuration from:
224
+ 1. Global ~/.voicemode/voicemode.env file
225
+ 2. Project-specific .voicemode.env files (searched up directory tree)
226
+ 3. Environment variables (highest priority)
227
+
228
+ Returns:
229
+ Status message showing which files were loaded and any changes
230
+ """
231
+ try:
232
+ # Get config files before reload
233
+ old_files = find_voicemode_env_files()
234
+
235
+ # Reload configuration
236
+ reload_configuration()
237
+
238
+ # Get config files after reload
239
+ new_files = find_voicemode_env_files()
240
+
241
+ lines = ["✅ Configuration reloaded successfully!", ""]
242
+
243
+ if new_files:
244
+ lines.append("📁 Configuration files loaded (in order):")
245
+ for i, config_file in enumerate(new_files, 1):
246
+ lines.append(f" {i}. {config_file}")
247
+ else:
248
+ lines.append("📁 No configuration files found - using defaults")
249
+
250
+ lines.append("")
251
+ lines.append("🔄 All caches have been cleared")
252
+ lines.append("📊 Voice preferences and provider settings updated")
253
+
254
+ logger.info(f"Configuration reloaded from {len(new_files)} files")
255
+
256
+ return "\n".join(lines)
257
+
258
+ except Exception as e:
259
+ logger.error(f"Failed to reload configuration: {e}")
260
+ return f"❌ Failed to reload configuration: {str(e)}"
261
+
262
+
263
+ @mcp.tool()
264
+ async def show_config_files() -> str:
265
+ """Show which .voicemode.env files are being used for configuration.
266
+
267
+ This shows the current configuration file discovery and loading order:
268
+ - Global configuration from ~/.voicemode/voicemode.env
269
+ - Project-specific configuration (searched up directory tree)
270
+ - Current working directory for context
271
+
272
+ Returns:
273
+ Formatted list of configuration files and their status
274
+ """
275
+ try:
276
+ config_files = find_voicemode_env_files()
277
+
278
+ lines = ["📋 Voice Mode Configuration Files", "=" * 40, ""]
279
+ lines.append(f"🗂️ Current directory: {Path.cwd()}")
280
+ lines.append("")
281
+
282
+ if config_files:
283
+ lines.append("📁 Configuration files (loading order):")
284
+ lines.append("")
285
+
286
+ for i, config_file in enumerate(config_files, 1):
287
+ status = "✅ EXISTS" if config_file.exists() else "❌ MISSING"
288
+ file_type = ""
289
+
290
+ if config_file.name == "voicemode.env" and config_file.parent.name == ".voicemode":
291
+ if config_file.parent == Path.home() / ".voicemode":
292
+ file_type = " (Global)"
293
+ else:
294
+ file_type = " (Project - in .voicemode dir)"
295
+ elif config_file.name == ".voicemode.env":
296
+ if config_file.parent == Path.cwd():
297
+ file_type = " (Project - current dir)"
298
+ else:
299
+ file_type = " (Project - parent dir)"
300
+
301
+ lines.append(f" {i}. {config_file}{file_type}")
302
+ lines.append(f" {status}")
303
+ lines.append("")
304
+ else:
305
+ lines.append("❌ No configuration files found")
306
+ lines.append("")
307
+ lines.append("💡 Tip: Create ~/.voicemode/voicemode.env for global configuration")
308
+ lines.append("💡 Tip: Create .voicemode.env in project directories for project-specific settings")
309
+
310
+ lines.append("")
311
+ lines.append("🔄 Use reload_config() to reload after making changes")
312
+
313
+ return "\n".join(lines)
314
+
315
+ except Exception as e:
316
+ logger.error(f"Failed to show config files: {e}")
317
+ return f"❌ Failed to show config files: {str(e)}"
@@ -875,6 +875,45 @@ def record_audio(duration: float) -> np.ndarray:
875
875
  logger.error(f"Recording failed: {e}")
876
876
  logger.error(f"Audio config when error occurred - Sample rate: {SAMPLE_RATE}, Channels: {CHANNELS}")
877
877
 
878
+ # Check if this is a device error that might be recoverable
879
+ error_str = str(e).lower()
880
+ if any(err in error_str for err in ['device unavailable', 'device disconnected',
881
+ 'invalid device', 'unanticipated host error',
882
+ 'portaudio error']):
883
+ logger.info("Audio device error detected - attempting to reinitialize audio system")
884
+
885
+ # Try to reinitialize sounddevice
886
+ try:
887
+ # Get current default device info before reinit
888
+ try:
889
+ old_device = sd.query_devices(kind='input')
890
+ old_device_name = old_device.get('name', 'Unknown')
891
+ except:
892
+ old_device_name = 'Previous device'
893
+
894
+ sd._terminate()
895
+ sd._initialize()
896
+
897
+ # Get new default device info
898
+ try:
899
+ new_device = sd.query_devices(kind='input')
900
+ new_device_name = new_device.get('name', 'Unknown')
901
+ logger.info(f"Audio system reinitialized - switched from '{old_device_name}' to '{new_device_name}'")
902
+ except:
903
+ logger.info("Audio system reinitialized - retrying with new default device")
904
+
905
+ # Wait a moment for the system to stabilize
906
+ import time as time_module
907
+ time_module.sleep(0.5)
908
+
909
+ # Try recording again with the new device (recursive call)
910
+ logger.info("Retrying recording with new audio device...")
911
+ return record_audio(duration)
912
+
913
+ except Exception as reinit_error:
914
+ logger.error(f"Failed to reinitialize audio: {reinit_error}")
915
+ # Fall through to normal error handling
916
+
878
917
  # Import here to avoid circular imports
879
918
  from voice_mode.utils.audio_diagnostics import get_audio_error_help
880
919
 
@@ -989,6 +1028,14 @@ def record_audio_with_silence_detection(max_duration: float, disable_silence_det
989
1028
  """Callback for continuous audio stream"""
990
1029
  if status:
991
1030
  logger.warning(f"Audio stream status: {status}")
1031
+ # Check for device-related errors
1032
+ status_str = str(status).lower()
1033
+ if any(err in status_str for err in ['device unavailable', 'device disconnected',
1034
+ 'invalid device', 'unanticipated host error',
1035
+ 'stream is stopped', 'portaudio error']):
1036
+ # Signal that we should stop recording due to device error
1037
+ audio_queue.put(None) # Sentinel value to indicate error
1038
+ return
992
1039
  # Put the audio data in the queue for processing
993
1040
  audio_queue.put(indata.copy())
994
1041
 
@@ -1007,6 +1054,12 @@ def record_audio_with_silence_detection(max_duration: float, disable_silence_det
1007
1054
  # Get audio chunk from queue with timeout
1008
1055
  chunk = audio_queue.get(timeout=0.1)
1009
1056
 
1057
+ # Check for error sentinel
1058
+ if chunk is None:
1059
+ logger.error("Audio device error detected - stopping recording")
1060
+ # Raise an exception to trigger recovery logic
1061
+ raise sd.PortAudioError("Audio device disconnected or unavailable")
1062
+
1010
1063
  # Flatten for consistency
1011
1064
  chunk_flat = chunk.flatten()
1012
1065
  chunks.append(chunk_flat)
@@ -1109,6 +1162,45 @@ def record_audio_with_silence_detection(max_duration: float, disable_silence_det
1109
1162
  # Import here to avoid circular imports
1110
1163
  from voice_mode.utils.audio_diagnostics import get_audio_error_help
1111
1164
 
1165
+ # Check if this is a device error that might be recoverable
1166
+ error_str = str(e).lower()
1167
+ if any(err in error_str for err in ['device unavailable', 'device disconnected',
1168
+ 'invalid device', 'unanticipated host error',
1169
+ 'portaudio error']):
1170
+ logger.info("Audio device error detected - attempting to reinitialize audio system")
1171
+
1172
+ # Try to reinitialize sounddevice
1173
+ try:
1174
+ # Get current default device info before reinit
1175
+ try:
1176
+ old_device = sd.query_devices(kind='input')
1177
+ old_device_name = old_device.get('name', 'Unknown')
1178
+ except:
1179
+ old_device_name = 'Previous device'
1180
+
1181
+ sd._terminate()
1182
+ sd._initialize()
1183
+
1184
+ # Get new default device info
1185
+ try:
1186
+ new_device = sd.query_devices(kind='input')
1187
+ new_device_name = new_device.get('name', 'Unknown')
1188
+ logger.info(f"Audio system reinitialized - switched from '{old_device_name}' to '{new_device_name}'")
1189
+ except:
1190
+ logger.info("Audio system reinitialized - retrying with new default device")
1191
+
1192
+ # Wait a moment for the system to stabilize
1193
+ import time as time_module
1194
+ time_module.sleep(0.5)
1195
+
1196
+ # Try recording again with the new device (recursive call in sync context)
1197
+ logger.info("Retrying recording with new audio device...")
1198
+ return record_audio_with_silence_detection(max_duration, disable_silence_detection, min_duration, vad_aggressiveness)
1199
+
1200
+ except Exception as reinit_error:
1201
+ logger.error(f"Failed to reinitialize audio: {reinit_error}")
1202
+ # Fall through to normal error handling
1203
+
1112
1204
  # Get helpful error message
1113
1205
  help_message = get_audio_error_help(e)
1114
1206
  logger.error(f"\n{help_message}")
@@ -1555,6 +1647,12 @@ async def converse(
1555
1647
  # Run startup initialization if needed
1556
1648
  await startup_initialization()
1557
1649
 
1650
+ # Refresh audio device cache to pick up any device changes (AirPods, etc.)
1651
+ # This takes ~1ms and ensures we use the current default device
1652
+ import sounddevice as sd
1653
+ sd._terminate()
1654
+ sd._initialize()
1655
+
1558
1656
  # Get event logger and start session
1559
1657
  event_logger = get_event_logger()
1560
1658
  session_id = None
@@ -73,14 +73,12 @@ def get_service_config_vars(service_name: str) -> Dict[str, Any]:
73
73
  # Try GPU scripts first
74
74
  possible_scripts = [
75
75
  Path(kokoro_dir) / "start-gpu.sh",
76
- Path(kokoro_dir) / "start.sh", # fallback
77
76
  Path(kokoro_dir) / "start-cpu.sh" # last resort
78
77
  ]
79
78
  else:
80
79
  # No GPU, prefer CPU script
81
80
  possible_scripts = [
82
81
  Path(kokoro_dir) / "start-cpu.sh",
83
- Path(kokoro_dir) / "start.sh", # fallback
84
82
  Path(kokoro_dir) / "start-gpu.sh" # might work with CPU fallback
85
83
  ]
86
84
 
@@ -409,14 +407,12 @@ async def start_service(service_name: str) -> str:
409
407
  # Try GPU scripts first
410
408
  possible_scripts = [
411
409
  Path(kokoro_dir) / "start-gpu.sh",
412
- Path(kokoro_dir) / "start.sh", # fallback
413
410
  Path(kokoro_dir) / "start-cpu.sh" # last resort
414
411
  ]
415
412
  else:
416
413
  # No GPU, prefer CPU script
417
414
  possible_scripts = [
418
415
  Path(kokoro_dir) / "start-cpu.sh",
419
- Path(kokoro_dir) / "start.sh", # fallback
420
416
  Path(kokoro_dir) / "start-gpu.sh" # might work with CPU fallback
421
417
  ]
422
418
 
@@ -688,14 +684,12 @@ async def enable_service(service_name: str) -> str:
688
684
  # Try GPU scripts first
689
685
  possible_scripts = [
690
686
  Path(kokoro_dir) / "start-gpu.sh",
691
- Path(kokoro_dir) / "start.sh", # fallback
692
687
  Path(kokoro_dir) / "start-cpu.sh" # last resort
693
688
  ]
694
689
  else:
695
690
  # No GPU, prefer CPU script
696
691
  possible_scripts = [
697
692
  Path(kokoro_dir) / "start-cpu.sh",
698
- Path(kokoro_dir) / "start.sh", # fallback
699
693
  Path(kokoro_dir) / "start-gpu.sh" # might work with CPU fallback
700
694
  ]
701
695
 
@@ -1100,4 +1094,4 @@ async def uninstall_service(service_name: str) -> Dict[str, Any]:
1100
1094
 
1101
1095
  except Exception as e:
1102
1096
  logger.error(f"Error uninstalling service {service_name}: {e}")
1103
- return {"success": False, "error": str(e)}
1097
+ return {"success": False, "error": str(e)}
@@ -0,0 +1,14 @@
1
+ """Audio transcription with word-level timestamps."""
2
+
3
+ from .types import TranscriptionBackend, OutputFormat, TranscriptionResult, WordData, SegmentData
4
+ from .core import transcribe_audio, transcribe_audio_sync
5
+
6
+ __all__ = [
7
+ 'transcribe_audio',
8
+ 'transcribe_audio_sync',
9
+ 'TranscriptionBackend',
10
+ 'OutputFormat',
11
+ 'TranscriptionResult',
12
+ 'WordData',
13
+ 'SegmentData',
14
+ ]