Voice.svelte 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <script lang="ts">
  2. import { createEventDispatcher, onMount } from 'svelte';
  3. const dispatch = createEventDispatcher();
  4. export let saveSettings: Function;
  5. // Voice
  6. let conversationMode = false;
  7. let speechAutoSend = false;
  8. let responseAutoPlayback = false;
  9. let engines = ['', 'openai'];
  10. let engine = '';
  11. let voices = [];
  12. let speaker = '';
  13. const getOpenAIVoices = () => {
  14. voices = [
  15. { name: 'alloy' },
  16. { name: 'echo' },
  17. { name: 'fable' },
  18. { name: 'onyx' },
  19. { name: 'nova' },
  20. { name: 'shimmer' }
  21. ];
  22. };
  23. const getWebAPIVoices = () => {
  24. const getVoicesLoop = setInterval(async () => {
  25. voices = await speechSynthesis.getVoices();
  26. // do your loop
  27. if (voices.length > 0) {
  28. clearInterval(getVoicesLoop);
  29. }
  30. }, 100);
  31. };
  32. const toggleConversationMode = async () => {
  33. conversationMode = !conversationMode;
  34. if (conversationMode) {
  35. responseAutoPlayback = true;
  36. speechAutoSend = true;
  37. }
  38. saveSettings({
  39. conversationMode: conversationMode,
  40. responseAutoPlayback: responseAutoPlayback,
  41. speechAutoSend: speechAutoSend
  42. });
  43. };
  44. const toggleResponseAutoPlayback = async () => {
  45. responseAutoPlayback = !responseAutoPlayback;
  46. saveSettings({ responseAutoPlayback: responseAutoPlayback });
  47. };
  48. const toggleSpeechAutoSend = async () => {
  49. speechAutoSend = !speechAutoSend;
  50. saveSettings({ speechAutoSend: speechAutoSend });
  51. };
  52. onMount(async () => {
  53. let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
  54. conversationMode = settings.conversationMode ?? false;
  55. speechAutoSend = settings.speechAutoSend ?? false;
  56. responseAutoPlayback = settings.responseAutoPlayback ?? false;
  57. engine = settings?.speech?.engine ?? '';
  58. speaker = settings?.speech?.speaker ?? '';
  59. if (engine === 'openai') {
  60. getOpenAIVoices();
  61. } else {
  62. getWebAPIVoices();
  63. }
  64. });
  65. </script>
  66. <form
  67. class="flex flex-col h-full justify-between space-y-3 text-sm"
  68. on:submit|preventDefault={() => {
  69. saveSettings({
  70. speech: {
  71. engine: engine !== '' ? engine : undefined,
  72. speaker: speaker !== '' ? speaker : undefined
  73. }
  74. });
  75. dispatch('save');
  76. }}
  77. >
  78. <div class=" space-y-3">
  79. <div>
  80. <div class=" mb-1 text-sm font-medium">TTS Settings</div>
  81. <div class=" py-0.5 flex w-full justify-between">
  82. <div class=" self-center text-xs font-medium">Speech Engine</div>
  83. <div class="flex items-center relative">
  84. <select
  85. class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
  86. bind:value={engine}
  87. placeholder="Select a mode"
  88. on:change={(e) => {
  89. if (e.target.value === 'openai') {
  90. getOpenAIVoices();
  91. speaker = 'alloy';
  92. } else {
  93. getWebAPIVoices();
  94. speaker = '';
  95. }
  96. }}
  97. >
  98. <option value="">Default (Web API)</option>
  99. <option value="openai">Open AI</option>
  100. </select>
  101. </div>
  102. </div>
  103. <div class=" py-0.5 flex w-full justify-between">
  104. <div class=" self-center text-xs font-medium">Conversation Mode</div>
  105. <button
  106. class="p-1 px-3 text-xs flex rounded transition"
  107. on:click={() => {
  108. toggleConversationMode();
  109. }}
  110. type="button"
  111. >
  112. {#if conversationMode === true}
  113. <span class="ml-2 self-center">On</span>
  114. {:else}
  115. <span class="ml-2 self-center">Off</span>
  116. {/if}
  117. </button>
  118. </div>
  119. <div class=" py-0.5 flex w-full justify-between">
  120. <div class=" self-center text-xs font-medium">Auto-send input after 3 sec.</div>
  121. <button
  122. class="p-1 px-3 text-xs flex rounded transition"
  123. on:click={() => {
  124. toggleSpeechAutoSend();
  125. }}
  126. type="button"
  127. >
  128. {#if speechAutoSend === true}
  129. <span class="ml-2 self-center">On</span>
  130. {:else}
  131. <span class="ml-2 self-center">Off</span>
  132. {/if}
  133. </button>
  134. </div>
  135. <div class=" py-0.5 flex w-full justify-between">
  136. <div class=" self-center text-xs font-medium">Auto-playback response</div>
  137. <button
  138. class="p-1 px-3 text-xs flex rounded transition"
  139. on:click={() => {
  140. toggleResponseAutoPlayback();
  141. }}
  142. type="button"
  143. >
  144. {#if responseAutoPlayback === true}
  145. <span class="ml-2 self-center">On</span>
  146. {:else}
  147. <span class="ml-2 self-center">Off</span>
  148. {/if}
  149. </button>
  150. </div>
  151. </div>
  152. <hr class=" dark:border-gray-700" />
  153. {#if engine === ''}
  154. <div>
  155. <div class=" mb-2.5 text-sm font-medium">Set Voice</div>
  156. <div class="flex w-full">
  157. <div class="flex-1">
  158. <select
  159. class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
  160. bind:value={speaker}
  161. placeholder="Select a voice"
  162. >
  163. <option value="" selected>Default</option>
  164. {#each voices.filter((v) => v.localService === true) as voice}
  165. <option value={voice.name} class="bg-gray-100 dark:bg-gray-700">{voice.name}</option
  166. >
  167. {/each}
  168. </select>
  169. </div>
  170. </div>
  171. </div>
  172. {:else if engine === 'openai'}
  173. <div>
  174. <div class=" mb-2.5 text-sm font-medium">Set Voice</div>
  175. <div class="flex w-full">
  176. <div class="flex-1">
  177. <select
  178. class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
  179. bind:value={speaker}
  180. placeholder="Select a voice"
  181. >
  182. {#each voices as voice}
  183. <option value={voice.name} class="bg-gray-100 dark:bg-gray-700">{voice.name}</option
  184. >
  185. {/each}
  186. </select>
  187. </div>
  188. </div>
  189. </div>
  190. {/if}
  191. </div>
  192. <div class="flex justify-end pt-3 text-sm font-medium">
  193. <button
  194. class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
  195. type="submit"
  196. >
  197. Save
  198. </button>
  199. </div>
  200. </form>