@joewinke/jatui 0.1.25 → 0.1.26

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 (242) hide show
  1. package/dist/actions/collapse.d.ts +80 -0
  2. package/dist/actions/collapse.js +134 -0
  3. package/dist/actions/railNav.d.ts +167 -0
  4. package/dist/actions/railNav.js +351 -0
  5. package/dist/components/AnnotationLayer.svelte +108 -0
  6. package/dist/components/AnnotationLayer.svelte.d.ts +12 -0
  7. package/dist/components/AnnotationPanel.svelte +319 -0
  8. package/dist/components/AnnotationPanel.svelte.d.ts +13 -0
  9. package/dist/components/AudioWaveform.svelte +701 -0
  10. package/dist/components/AudioWaveform.svelte.d.ts +19 -0
  11. package/dist/components/AvailabilityModal.svelte +177 -0
  12. package/dist/components/AvailabilityModal.svelte.d.ts +14 -0
  13. package/dist/components/AvatarUpload.svelte +234 -0
  14. package/dist/components/AvatarUpload.svelte.d.ts +23 -0
  15. package/dist/components/Badge.svelte +38 -0
  16. package/dist/components/Badge.svelte.d.ts +14 -0
  17. package/dist/components/BookingForm.svelte +278 -0
  18. package/dist/components/BookingForm.svelte.d.ts +37 -0
  19. package/dist/components/BurndownChart.svelte +778 -0
  20. package/dist/components/BurndownChart.svelte.d.ts +17 -0
  21. package/dist/components/Button.svelte +81 -0
  22. package/dist/components/Button.svelte.d.ts +21 -0
  23. package/dist/components/CalendarPicker.svelte +284 -0
  24. package/dist/components/CalendarPicker.svelte.d.ts +22 -0
  25. package/dist/components/Card.svelte +67 -0
  26. package/dist/components/Card.svelte.d.ts +17 -0
  27. package/dist/components/CharacterCounter.svelte +82 -0
  28. package/dist/components/CharacterCounter.svelte.d.ts +10 -0
  29. package/dist/components/ChipInput.svelte +609 -0
  30. package/dist/components/ChipInput.svelte.d.ts +55 -0
  31. package/dist/components/Collapse.svelte +91 -0
  32. package/dist/components/Collapse.svelte.d.ts +18 -0
  33. package/dist/components/ColorSelector.svelte +167 -0
  34. package/dist/components/ColorSelector.svelte.d.ts +9 -0
  35. package/dist/components/CommentThread.svelte +773 -0
  36. package/dist/components/CommentThread.svelte.d.ts +32 -0
  37. package/dist/components/ConfirmDialog.svelte +348 -0
  38. package/dist/components/ConfirmDialog.svelte.d.ts +3 -0
  39. package/dist/components/ConfirmModal.svelte +142 -0
  40. package/dist/components/ConfirmModal.svelte.d.ts +15 -0
  41. package/dist/components/ContextMenu.svelte +188 -0
  42. package/dist/components/ContextMenu.svelte.d.ts +31 -0
  43. package/dist/components/CountdownTimer.svelte +94 -0
  44. package/dist/components/CountdownTimer.svelte.d.ts +10 -0
  45. package/dist/components/DateRangePicker.svelte +194 -0
  46. package/dist/components/DateRangePicker.svelte.d.ts +10 -0
  47. package/dist/components/Drawer.svelte +143 -0
  48. package/dist/components/Drawer.svelte.d.ts +33 -0
  49. package/dist/components/EmojiPicker.svelte +159 -0
  50. package/dist/components/EmojiPicker.svelte.d.ts +8 -0
  51. package/dist/components/EntityPreviewCard.svelte +104 -0
  52. package/dist/components/EntityPreviewCard.svelte.d.ts +9 -0
  53. package/dist/components/FileDropzone.svelte +493 -0
  54. package/dist/components/FileDropzone.svelte.d.ts +40 -0
  55. package/dist/components/FilePicker.svelte +488 -0
  56. package/dist/components/FilePicker.svelte.d.ts +20 -0
  57. package/dist/components/FileThumbnail.svelte +80 -0
  58. package/dist/components/FileThumbnail.svelte.d.ts +12 -0
  59. package/dist/components/FilterDropdown.svelte +203 -0
  60. package/dist/components/FilterDropdown.svelte.d.ts +48 -0
  61. package/dist/components/FloatingActionBar.svelte +354 -0
  62. package/dist/components/FloatingActionBar.svelte.d.ts +51 -0
  63. package/dist/components/GPSTracker.svelte +202 -0
  64. package/dist/components/GPSTracker.svelte.d.ts +34 -0
  65. package/dist/components/HunkDiffView.svelte +348 -0
  66. package/dist/components/HunkDiffView.svelte.d.ts +9 -0
  67. package/dist/components/ImageLightbox.svelte +274 -0
  68. package/dist/components/ImageLightbox.svelte.d.ts +25 -0
  69. package/dist/components/ImageUpload.svelte +147 -0
  70. package/dist/components/ImageUpload.svelte.d.ts +10 -0
  71. package/dist/components/InlineEdit.svelte +291 -0
  72. package/dist/components/InlineEdit.svelte.d.ts +45 -0
  73. package/dist/components/InputDialog.svelte +327 -0
  74. package/dist/components/InputDialog.svelte.d.ts +3 -0
  75. package/dist/components/KeyboardShortcutsOverlay.svelte +296 -0
  76. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +42 -0
  77. package/dist/components/LazyImage.svelte +123 -0
  78. package/dist/components/LazyImage.svelte.d.ts +14 -0
  79. package/dist/components/LinkShortener.svelte +274 -0
  80. package/dist/components/LinkShortener.svelte.d.ts +27 -0
  81. package/dist/components/LoadingSpinner.svelte +106 -0
  82. package/dist/components/LoadingSpinner.svelte.d.ts +21 -0
  83. package/dist/components/LocationMap.svelte +186 -0
  84. package/dist/components/LocationMap.svelte.d.ts +11 -0
  85. package/dist/components/MapView.svelte +341 -0
  86. package/dist/components/MapView.svelte.d.ts +20 -0
  87. package/dist/components/MarkupEditor.svelte +485 -0
  88. package/dist/components/MarkupEditor.svelte.d.ts +10 -0
  89. package/dist/components/MarkupOverlay.svelte +55 -0
  90. package/dist/components/MarkupOverlay.svelte.d.ts +8 -0
  91. package/dist/components/MediaWorkbench.svelte +871 -0
  92. package/dist/components/MediaWorkbench.svelte.d.ts +9 -0
  93. package/dist/components/MilestoneCard.svelte +98 -0
  94. package/dist/components/MilestoneCard.svelte.d.ts +16 -0
  95. package/dist/components/MilestoneTimeline.svelte +91 -0
  96. package/dist/components/MilestoneTimeline.svelte.d.ts +15 -0
  97. package/dist/components/Modal.svelte +244 -0
  98. package/dist/components/Modal.svelte.d.ts +35 -0
  99. package/dist/components/PDFViewer.svelte +105 -0
  100. package/dist/components/PDFViewer.svelte.d.ts +9 -0
  101. package/dist/components/PdfThumbnail.svelte +63 -0
  102. package/dist/components/PdfThumbnail.svelte.d.ts +9 -0
  103. package/dist/components/PhoneInput.svelte +212 -0
  104. package/dist/components/PhoneInput.svelte.d.ts +25 -0
  105. package/dist/components/ResizableDivider.svelte +307 -0
  106. package/dist/components/ResizableDivider.svelte.d.ts +26 -0
  107. package/dist/components/ResizablePanel.svelte +305 -0
  108. package/dist/components/ResizablePanel.svelte.d.ts +18 -0
  109. package/dist/components/SearchDropdown.svelte +421 -0
  110. package/dist/components/SearchDropdown.svelte.d.ts +24 -0
  111. package/dist/components/SelectInput.svelte +237 -0
  112. package/dist/components/SelectInput.svelte.d.ts +26 -0
  113. package/dist/components/SidebarUserFooter.svelte +34 -0
  114. package/dist/components/SidebarUserFooter.svelte.d.ts +16 -0
  115. package/dist/components/SignaturePad.svelte +175 -0
  116. package/dist/components/SignaturePad.svelte.d.ts +17 -0
  117. package/dist/components/SmartImageEditor.svelte +720 -0
  118. package/dist/components/SmartImageEditor.svelte.d.ts +24 -0
  119. package/dist/components/SortDropdown.svelte +156 -0
  120. package/dist/components/SortDropdown.svelte.d.ts +40 -0
  121. package/dist/components/Sparkline.svelte +116 -0
  122. package/dist/components/Sparkline.svelte.d.ts +12 -0
  123. package/dist/components/SpeechForm.svelte +114 -0
  124. package/dist/components/SpeechForm.svelte.d.ts +17 -0
  125. package/dist/components/StatusBadge.svelte +157 -0
  126. package/dist/components/StatusBadge.svelte.d.ts +24 -0
  127. package/dist/components/TaskTypeIcon.svelte +54 -0
  128. package/dist/components/TaskTypeIcon.svelte.d.ts +17 -0
  129. package/dist/components/TextArea.svelte +162 -0
  130. package/dist/components/TextArea.svelte.d.ts +30 -0
  131. package/dist/components/TextInput.svelte +131 -0
  132. package/dist/components/TextInput.svelte.d.ts +29 -0
  133. package/dist/components/ThemeSelector.svelte +206 -0
  134. package/dist/components/ThemeSelector.svelte.d.ts +14 -0
  135. package/dist/components/TimeSlotPicker.svelte +162 -0
  136. package/dist/components/TimeSlotPicker.svelte.d.ts +18 -0
  137. package/dist/components/UserAvatar.svelte +72 -0
  138. package/dist/components/UserAvatar.svelte.d.ts +11 -0
  139. package/dist/components/VariablePicker.svelte +170 -0
  140. package/dist/components/VariablePicker.svelte.d.ts +40 -0
  141. package/dist/components/VoicePlayer.svelte +422 -0
  142. package/dist/components/VoicePlayer.svelte.d.ts +18 -0
  143. package/dist/components/linked-columns/LinkedColumns.svelte +526 -0
  144. package/dist/components/linked-columns/LinkedColumns.svelte.d.ts +130 -0
  145. package/dist/components/markup.d.ts +47 -0
  146. package/dist/components/markup.js +201 -0
  147. package/dist/components/messaging/Avatar.svelte +81 -0
  148. package/dist/components/messaging/Avatar.svelte.d.ts +11 -0
  149. package/dist/components/messaging/ChannelInfoModal.svelte +163 -0
  150. package/dist/components/messaging/ChannelInfoModal.svelte.d.ts +18 -0
  151. package/dist/components/messaging/ChannelList.svelte +107 -0
  152. package/dist/components/messaging/ChannelList.svelte.d.ts +11 -0
  153. package/dist/components/messaging/ChannelMemberAvatarStack.svelte +69 -0
  154. package/dist/components/messaging/ChannelMemberAvatarStack.svelte.d.ts +11 -0
  155. package/dist/components/messaging/ChannelMembersModal.svelte +182 -0
  156. package/dist/components/messaging/ChannelMembersModal.svelte.d.ts +16 -0
  157. package/dist/components/messaging/CreateChannelModal.svelte +190 -0
  158. package/dist/components/messaging/CreateChannelModal.svelte.d.ts +14 -0
  159. package/dist/components/messaging/DirectMessageList.svelte +145 -0
  160. package/dist/components/messaging/DirectMessageList.svelte.d.ts +12 -0
  161. package/dist/components/messaging/EmojiSelector.svelte +261 -0
  162. package/dist/components/messaging/EmojiSelector.svelte.d.ts +19 -0
  163. package/dist/components/messaging/MentionAutocomplete.svelte +193 -0
  164. package/dist/components/messaging/MentionAutocomplete.svelte.d.ts +17 -0
  165. package/dist/components/messaging/MessageAttachment.svelte +273 -0
  166. package/dist/components/messaging/MessageAttachment.svelte.d.ts +13 -0
  167. package/dist/components/messaging/MessageAttachmentUpload.svelte +243 -0
  168. package/dist/components/messaging/MessageAttachmentUpload.svelte.d.ts +19 -0
  169. package/dist/components/messaging/MessageInput.svelte +452 -0
  170. package/dist/components/messaging/MessageInput.svelte.d.ts +28 -0
  171. package/dist/components/messaging/MessageItem.svelte +341 -0
  172. package/dist/components/messaging/MessageItem.svelte.d.ts +41 -0
  173. package/dist/components/messaging/MessageThread.svelte +306 -0
  174. package/dist/components/messaging/MessageThread.svelte.d.ts +30 -0
  175. package/dist/components/messaging/NotificationSettingsModal.svelte +234 -0
  176. package/dist/components/messaging/NotificationSettingsModal.svelte.d.ts +13 -0
  177. package/dist/components/messaging/QuotedMessageDisplay.svelte +123 -0
  178. package/dist/components/messaging/QuotedMessageDisplay.svelte.d.ts +10 -0
  179. package/dist/components/messaging/StartDMModal.svelte +100 -0
  180. package/dist/components/messaging/StartDMModal.svelte.d.ts +14 -0
  181. package/dist/components/messaging/ThreadPanel.svelte +153 -0
  182. package/dist/components/messaging/ThreadPanel.svelte.d.ts +16 -0
  183. package/dist/components/pipeline/DESIGN.md +361 -0
  184. package/dist/components/pipeline/Pipeline.svelte +391 -0
  185. package/dist/components/pipeline/Pipeline.svelte.d.ts +28 -0
  186. package/dist/components/pipeline/PipelineCard.svelte +94 -0
  187. package/dist/components/pipeline/PipelineCard.svelte.d.ts +13 -0
  188. package/dist/components/pipeline/PipelineColumn.svelte +163 -0
  189. package/dist/components/pipeline/PipelineColumn.svelte.d.ts +30 -0
  190. package/dist/components/replay/ChapterTimeline.svelte +326 -0
  191. package/dist/components/replay/ChapterTimeline.svelte.d.ts +39 -0
  192. package/dist/components/session-nav/transcriptModel.d.ts +132 -0
  193. package/dist/components/session-nav/transcriptModel.js +285 -0
  194. package/dist/data/emojis.d.ts +18 -0
  195. package/dist/data/emojis.js +1196 -0
  196. package/dist/index.d.ts +142 -0
  197. package/dist/index.js +142 -0
  198. package/dist/stores/confirmDialog.svelte.d.ts +21 -0
  199. package/dist/stores/confirmDialog.svelte.js +31 -0
  200. package/dist/stores/inputDialog.svelte.d.ts +24 -0
  201. package/dist/stores/inputDialog.svelte.js +32 -0
  202. package/dist/styles/rail.css +63 -0
  203. package/dist/types/annotation.d.ts +32 -0
  204. package/dist/types/annotation.js +3 -0
  205. package/dist/types/booking.d.ts +115 -0
  206. package/dist/types/booking.js +24 -0
  207. package/dist/types/comments.d.ts +65 -0
  208. package/dist/types/comments.js +22 -0
  209. package/dist/types/entityPreview.d.ts +42 -0
  210. package/dist/types/entityPreview.js +10 -0
  211. package/dist/types/filePicker.d.ts +15 -0
  212. package/dist/types/filePicker.js +1 -0
  213. package/dist/types/googleMaps.d.ts +51 -0
  214. package/dist/types/maps.d.ts +43 -0
  215. package/dist/types/maps.js +4 -0
  216. package/dist/types/messaging.d.ts +282 -0
  217. package/dist/types/messaging.js +100 -0
  218. package/dist/types/milestone.d.ts +20 -0
  219. package/dist/types/milestone.js +7 -0
  220. package/dist/types/pipeline.d.ts +115 -0
  221. package/dist/types/pipeline.js +15 -0
  222. package/dist/types/smartImageEditor.d.ts +42 -0
  223. package/dist/types/smartImageEditor.js +1 -0
  224. package/dist/types/templateVars.d.ts +34 -0
  225. package/dist/types/templateVars.js +12 -0
  226. package/dist/utils/currency.d.ts +8 -0
  227. package/dist/utils/currency.js +17 -0
  228. package/dist/utils/daisyuiColors.d.ts +37 -0
  229. package/dist/utils/daisyuiColors.js +152 -0
  230. package/dist/utils/dateFormatters.d.ts +69 -0
  231. package/dist/utils/dateFormatters.js +199 -0
  232. package/dist/utils/googleMapsLoader.d.ts +19 -0
  233. package/dist/utils/googleMapsLoader.js +121 -0
  234. package/dist/utils/mentionParser.d.ts +67 -0
  235. package/dist/utils/mentionParser.js +132 -0
  236. package/dist/utils/phone.d.ts +13 -0
  237. package/dist/utils/phone.js +67 -0
  238. package/dist/utils/phoneFormat.d.ts +26 -0
  239. package/dist/utils/phoneFormat.js +69 -0
  240. package/dist/utils/taskUtils.d.ts +37 -0
  241. package/dist/utils/taskUtils.js +71 -0
  242. package/package.json +3 -2
@@ -0,0 +1,422 @@
1
+ <script lang="ts">
2
+ /**
3
+ * VoicePlayer — TTS playback widget with voice selection, volume, speed, and provider switching.
4
+ *
5
+ * Requires backend endpoints:
6
+ * - voicesUrl (GET) — returns { voices: [{ voice_id, name, labels?, preview_url? }] }
7
+ * - ttsUrl (POST) — accepts { text, voiceId, provider, speed } and returns audio/mpeg blob
8
+ */
9
+
10
+ let {
11
+ text,
12
+ voiceId,
13
+ voiceName,
14
+ onVoiceChange,
15
+ expandDirection = "left",
16
+ autoPlay = false,
17
+ onPlaybackComplete,
18
+ onError,
19
+ // External control
20
+ messageId,
21
+ onRegisterController,
22
+ onStateChange,
23
+ // API configuration
24
+ voicesUrl = "/api/voices",
25
+ ttsUrl = "/api/tts",
26
+ } = $props<{
27
+ text?: string
28
+ voiceId?: string
29
+ voiceName?: string
30
+ onVoiceChange?: (voiceId: string, voiceName: string, provider: string) => void
31
+ expandDirection?: "left" | "right"
32
+ autoPlay?: boolean
33
+ onPlaybackComplete?: () => void
34
+ onError?: (message: string) => void
35
+ messageId?: string
36
+ onRegisterController?: (messageId: string, controller: any) => void
37
+ onStateChange?: (playing: boolean, loading: boolean) => void
38
+ voicesUrl?: string
39
+ ttsUrl?: string
40
+ }>()
41
+
42
+ let audioEl = $state<HTMLAudioElement | null>(null)
43
+ let isPlaying = $state(false)
44
+ let isPaused = $state(false)
45
+ let isLoadingAudio = $state(false)
46
+ let audioUrl = $state<string | null>(null)
47
+
48
+ let showControls = $state(false)
49
+ let showSettings = $state(false)
50
+
51
+ let provider = $state<"openai" | "elevenlabs">("openai")
52
+
53
+ let voices = $state<any[]>([])
54
+ let voicesLoading = $state(false)
55
+ let showVoices = $state(false)
56
+ // svelte-ignore state_referenced_locally
57
+ let selectedVoiceId = $state<string | undefined>(voiceId || "alloy")
58
+
59
+ let speed = $state(1.0)
60
+
61
+ let autoPlayAttempted = $state(false)
62
+ let manuallyPaused = $state(false)
63
+
64
+ const controller = {
65
+ play: () => playTextAudio(),
66
+ pause: () => pauseTextAudio(),
67
+ isPlaying: () => isPlaying,
68
+ isLoading: () => isLoadingAudio
69
+ }
70
+
71
+ function reportError(msg: string) {
72
+ if (onError) onError(msg)
73
+ else console.error(msg)
74
+ }
75
+
76
+ $effect(() => {
77
+ if (voiceId && voiceId !== selectedVoiceId) {
78
+ selectedVoiceId = voiceId
79
+ }
80
+ })
81
+
82
+ async function fetchVoices() {
83
+ voicesLoading = true
84
+ try {
85
+ const res = await fetch(`${voicesUrl}?provider=${provider}`)
86
+ if (!res.ok) throw new Error("Failed to fetch voices")
87
+ const data = await res.json()
88
+ voices = data.voices || []
89
+ } catch (e: any) {
90
+ reportError("Failed to load voices: " + (e.message || e))
91
+ } finally {
92
+ voicesLoading = false
93
+ }
94
+ }
95
+
96
+ function switchProvider(newProvider: "openai" | "elevenlabs") {
97
+ provider = newProvider
98
+ selectedVoiceId = newProvider === "openai" ? "alloy" : undefined
99
+ if (voices.length === 0 || provider !== newProvider) {
100
+ fetchVoices()
101
+ }
102
+ showSettings = false
103
+ }
104
+
105
+ async function playTextAudio() {
106
+ try {
107
+ manuallyPaused = false
108
+
109
+ if (audioEl && isPaused) {
110
+ audioEl.play()
111
+ isPlaying = true
112
+ isPaused = false
113
+ onStateChange?.(true, false)
114
+ return
115
+ }
116
+ if (isPlaying || isLoadingAudio) return
117
+
118
+ if (!text || !text.trim()) {
119
+ reportError("No text to play")
120
+ return
121
+ }
122
+
123
+ if (audioEl) {
124
+ audioEl.pause()
125
+ audioEl = null
126
+ audioUrl && URL.revokeObjectURL(audioUrl)
127
+ audioUrl = null
128
+ }
129
+
130
+ isLoadingAudio = true
131
+ onStateChange?.(false, true)
132
+
133
+ const res = await fetch(ttsUrl, {
134
+ method: "POST",
135
+ headers: { "Content-Type": "application/json" },
136
+ body: JSON.stringify({ text, voiceId: selectedVoiceId, provider, speed }),
137
+ })
138
+
139
+ if (!res.ok) {
140
+ isLoadingAudio = false
141
+ onStateChange?.(false, false)
142
+ const err = await res.json().catch(() => ({}))
143
+ reportError("TTS error: " + (err.error || res.statusText))
144
+ return
145
+ }
146
+
147
+ const audioData = await res.arrayBuffer()
148
+ audioUrl = URL.createObjectURL(new Blob([audioData], { type: "audio/mpeg" }))
149
+ audioEl = new Audio(audioUrl)
150
+
151
+ audioEl.onended = () => {
152
+ isPlaying = false
153
+ isPaused = false
154
+ isLoadingAudio = false
155
+ onStateChange?.(false, false)
156
+ audioEl = null
157
+ audioUrl && URL.revokeObjectURL(audioUrl)
158
+ audioUrl = null
159
+ onPlaybackComplete?.()
160
+ }
161
+ audioEl.onpause = () => {
162
+ if (audioEl && !audioEl.ended) {
163
+ isPlaying = false
164
+ isPaused = true
165
+ onStateChange?.(false, false)
166
+ }
167
+ }
168
+ audioEl.onplay = () => {
169
+ isPlaying = true
170
+ isPaused = false
171
+ isLoadingAudio = false
172
+ onStateChange?.(true, false)
173
+ }
174
+
175
+ await audioEl.play()
176
+ } catch (e: any) {
177
+ reportError("Failed to play audio: " + (e.message || e))
178
+ isPlaying = false
179
+ isPaused = false
180
+ isLoadingAudio = false
181
+ onStateChange?.(false, false)
182
+ audioEl = null
183
+ if (audioUrl) {
184
+ URL.revokeObjectURL(audioUrl)
185
+ audioUrl = null
186
+ }
187
+ }
188
+ }
189
+
190
+ function pauseTextAudio() {
191
+ if (audioEl && isPlaying) {
192
+ manuallyPaused = true
193
+ audioEl.pause()
194
+ }
195
+ }
196
+
197
+ function toggleVoices() {
198
+ showVoices = !showVoices
199
+ if (showVoices && voices.length === 0 && !voicesLoading) {
200
+ fetchVoices()
201
+ }
202
+ }
203
+
204
+ function toggleSettings() {
205
+ showSettings = !showSettings
206
+ }
207
+
208
+ function selectVoice(id: string, name: string) {
209
+ selectedVoiceId = id
210
+ showVoices = false
211
+ if (onVoiceChange) onVoiceChange(id, name, provider)
212
+ }
213
+
214
+ $effect(() => {
215
+ if (messageId && onRegisterController && autoPlay) {
216
+ onRegisterController(messageId, controller)
217
+ }
218
+ })
219
+
220
+ $effect(() => {
221
+ if (text) {
222
+ autoPlayAttempted = false
223
+ manuallyPaused = false
224
+ }
225
+ })
226
+
227
+ $effect(() => {
228
+ if (autoPlay && text?.trim() && !isPlaying && !isLoadingAudio && !autoPlayAttempted && !manuallyPaused) {
229
+ autoPlayAttempted = true
230
+ setTimeout(() => playTextAudio(), 100)
231
+ }
232
+ })
233
+ </script>
234
+
235
+ {#if text && text.trim()}
236
+ <div class="flex items-center gap-1 justify-end">
237
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
238
+ <div
239
+ class="relative"
240
+ onmouseenter={() => (showControls = true)}
241
+ onmouseleave={() => (showControls = false)}
242
+ onfocus={() => (showControls = true)}
243
+ onblur={() => (showControls = false)}
244
+ >
245
+ {#if !showControls}
246
+ <button
247
+ class="badge badge-ghost p-2 rounded-full flex items-center justify-center"
248
+ aria-label="Audio controls"
249
+ onclick={() => (showControls = !showControls)}
250
+ style="min-width: 32px; min-height: 32px;"
251
+ >
252
+ <!-- Volume2 icon -->
253
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/></svg>
254
+ </button>
255
+ {/if}
256
+ {#if showControls}
257
+ <div
258
+ class="absolute flex items-center gap-2 bg-base-200 border border-base-300 rounded-full z-50 transition-all {expandDirection === 'right' ? 'right-0' : 'left-0'}"
259
+ style="min-width: 180px;"
260
+ >
261
+ <!-- Play/Pause -->
262
+ <button
263
+ class="btn btn-xs btn-ghost flex items-center justify-center"
264
+ title={isLoadingAudio ? "Loading audio" : isPlaying && !isPaused ? "Pause" : isPaused ? "Resume" : "Play audio"}
265
+ aria-label={isLoadingAudio ? "Loading audio" : isPlaying && !isPaused ? "Pause" : isPaused ? "Resume" : "Play audio"}
266
+ onclick={isLoadingAudio ? undefined : isPlaying && !isPaused ? pauseTextAudio : playTextAudio}
267
+ disabled={isLoadingAudio}
268
+ tabindex="-1"
269
+ style="min-width: 28px; min-height: 28px;"
270
+ >
271
+ {#if isLoadingAudio}
272
+ <span class="loading loading-dots loading-xs"></span>
273
+ {:else if isPlaying && !isPaused}
274
+ <!-- PauseCircle -->
275
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="10" x2="10" y1="15" y2="9"/><line x1="14" x2="14" y1="15" y2="9"/></svg>
276
+ {:else}
277
+ <!-- PlayCircle -->
278
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/></svg>
279
+ {/if}
280
+ </button>
281
+
282
+ <!-- Volume slider -->
283
+ <input
284
+ type="range"
285
+ min="0"
286
+ max="1"
287
+ step="0.01"
288
+ value={audioEl ? audioEl.volume : 1}
289
+ oninput={(e) => {
290
+ const target = e.target as HTMLInputElement
291
+ if (audioEl && target) audioEl.volume = +target.value
292
+ }}
293
+ aria-label="Volume"
294
+ class="range range-xs w-16"
295
+ tabindex="-1"
296
+ style="min-width: 48px;"
297
+ />
298
+
299
+ <!-- Settings -->
300
+ <button
301
+ class="btn btn-xs btn-ghost flex items-center justify-center"
302
+ title="TTS Settings"
303
+ aria-label="TTS Settings"
304
+ onclick={(e) => { e.stopPropagation(); toggleSettings() }}
305
+ tabindex="-1"
306
+ style="min-width: 28px; min-height: 28px;"
307
+ >
308
+ <!-- Settings icon -->
309
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
310
+ </button>
311
+
312
+ <!-- Voice Dropdown -->
313
+ <div class="relative">
314
+ <button
315
+ class="btn btn-xs btn-ghost flex items-center justify-between border border-base-300 rounded-full px-2 py-1 bg-base-200 hover:bg-base-300 transition-colors w-24"
316
+ title="Select voice"
317
+ aria-label="Select voice"
318
+ onclick={toggleVoices}
319
+ disabled={voicesLoading}
320
+ style="min-width: 64px;"
321
+ tabindex="-1"
322
+ >
323
+ <span class="text-xs font-semibold truncate">
324
+ {voicesLoading ? "..." : voiceName ? voiceName : "Voice"}
325
+ </span>
326
+ <span class="flex-1"></span>
327
+ <!-- ChevronDown -->
328
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-70"><path d="m6 9 6 6 6-6"/></svg>
329
+ </button>
330
+
331
+ <!-- Settings panel -->
332
+ {#if showSettings}
333
+ <div
334
+ class="absolute right-0 top-full z-50 bg-base-200 border border-base-300 rounded-lg p-3 w-64"
335
+ style="margin-top: 0.25rem;"
336
+ >
337
+ <div class="mb-3">
338
+ <h3 class="font-semibold mb-2">TTS Provider</h3>
339
+ <div class="flex gap-2">
340
+ <button class={`btn btn-xs ${provider === "openai" ? "btn-primary" : "btn-ghost"}`} onclick={() => switchProvider("openai")}>OpenAI</button>
341
+ <button class={`btn btn-xs ${provider === "elevenlabs" ? "btn-primary" : "btn-ghost"}`} onclick={() => switchProvider("elevenlabs")}>ElevenLabs</button>
342
+ </div>
343
+ </div>
344
+
345
+ {#if provider === "openai"}
346
+ <div>
347
+ <h3 class="font-semibold mb-1">Speed</h3>
348
+ <div class="flex items-center gap-2">
349
+ <input
350
+ type="range"
351
+ min="0.25"
352
+ max="4.0"
353
+ step="0.05"
354
+ value={speed}
355
+ oninput={(e) => { speed = +(e.target as HTMLInputElement).value }}
356
+ class="range range-xs flex-1"
357
+ />
358
+ <span class="text-xs w-8 text-right">{speed.toFixed(2)}x</span>
359
+ </div>
360
+ </div>
361
+ {/if}
362
+ </div>
363
+ {/if}
364
+
365
+ <!-- Voice selection dropdown -->
366
+ {#if showVoices}
367
+ <div
368
+ class="absolute right-0 top-full z-50 bg-base-200 border border-base-300 rounded-lg p-2 max-h-64 overflow-y-auto w-64"
369
+ style="margin-top: 0.25rem;"
370
+ >
371
+ {#if voicesLoading}
372
+ <div class="flex items-center gap-2 p-2">
373
+ <span class="loading loading-dots loading-xs"></span>
374
+ <span>Loading voices...</span>
375
+ </div>
376
+ {:else if voices.length === 0}
377
+ <div class="p-2 text-xs text-base-content/60">No voices found</div>
378
+ {:else}
379
+ <ul>
380
+ {#each voices as voice}
381
+ <button
382
+ class="w-full text-left p-2 hover:bg-base-300 rounded-lg cursor-pointer transition-colors"
383
+ onclick={() => selectVoice(voice.voice_id, voice.name)}
384
+ >
385
+ <div class="mb-1">
386
+ <span class="font-semibold text-base-content truncate block">{voice.name}</span>
387
+ {#if voice.labels}
388
+ <span class="text-xs text-base-content/70 truncate block">
389
+ {[voice.labels.accent, voice.labels.gender, voice.labels.age, voice.labels.description, voice.labels.use_case]
390
+ .filter(Boolean)
391
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
392
+ .join(", ")}
393
+ </span>
394
+ {/if}
395
+ </div>
396
+ <div class="flex items-center gap-1">
397
+ {#if voice.preview_url}
398
+ <audio
399
+ src={voice.preview_url}
400
+ controls
401
+ style="height:1.5em;"
402
+ preload="none"
403
+ onclick={(e) => e.stopPropagation()}
404
+ aria-label="Preview {voice.name} voice"
405
+ ></audio>
406
+ {/if}
407
+ {#if selectedVoiceId === voice.voice_id}
408
+ <span class="badge badge-primary badge-xs ml-2">Selected</span>
409
+ {/if}
410
+ </div>
411
+ </button>
412
+ {/each}
413
+ </ul>
414
+ {/if}
415
+ </div>
416
+ {/if}
417
+ </div>
418
+ </div>
419
+ {/if}
420
+ </div>
421
+ </div>
422
+ {/if}
@@ -0,0 +1,18 @@
1
+ type $$ComponentProps = {
2
+ text?: string;
3
+ voiceId?: string;
4
+ voiceName?: string;
5
+ onVoiceChange?: (voiceId: string, voiceName: string, provider: string) => void;
6
+ expandDirection?: "left" | "right";
7
+ autoPlay?: boolean;
8
+ onPlaybackComplete?: () => void;
9
+ onError?: (message: string) => void;
10
+ messageId?: string;
11
+ onRegisterController?: (messageId: string, controller: any) => void;
12
+ onStateChange?: (playing: boolean, loading: boolean) => void;
13
+ voicesUrl?: string;
14
+ ttsUrl?: string;
15
+ };
16
+ declare const VoicePlayer: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type VoicePlayer = ReturnType<typeof VoicePlayer>;
18
+ export default VoicePlayer;