@sutrata/editor 0.1.1 → 0.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.
Files changed (268) hide show
  1. package/README.md +87 -0
  2. package/dist/ai/VoiceConfirmDialog.d.ts +1 -0
  3. package/dist/ai/VoiceConfirmDialog.js +6 -6
  4. package/dist/ai/VoiceToolbar.d.ts +1 -0
  5. package/dist/ai/VoiceToolbar.js +22 -16
  6. package/dist/ai/clean-response.d.ts +7 -0
  7. package/dist/ai/clean-response.js +58 -0
  8. package/dist/ai/format-transcript.d.ts +3 -0
  9. package/dist/ai/format-transcript.js +7 -0
  10. package/dist/ai/prompts.d.ts +8 -1
  11. package/dist/ai/prompts.js +34 -7
  12. package/dist/autocomplete/suggestions.js +1 -1
  13. package/dist/context/DocumentContext.d.ts +28 -4
  14. package/dist/context/DocumentContext.js +67 -37
  15. package/dist/editor/EditorView.js +32 -4
  16. package/dist/editor/SelectionMenu.js +5 -3
  17. package/dist/editor/external-decorations-plugin.d.ts +23 -0
  18. package/dist/editor/external-decorations-plugin.js +151 -0
  19. package/dist/editor/omitted-scene-plugin.d.ts +9 -0
  20. package/dist/editor/omitted-scene-plugin.js +44 -0
  21. package/dist/editor/plugins.js +4 -0
  22. package/dist/editor/private-metadata-plugin.d.ts +10 -0
  23. package/dist/editor/private-metadata-plugin.js +60 -0
  24. package/dist/editor/prosemirror-to-sutra.js +179 -74
  25. package/dist/editor/scene-heading-view.js +2 -0
  26. package/dist/editor/schema.js +69 -12
  27. package/dist/editor/sutra-to-prosemirror.d.ts +2 -0
  28. package/dist/editor/sutra-to-prosemirror.js +32 -34
  29. package/dist/extensions/ai-provider.d.ts +44 -0
  30. package/dist/extensions/ai-provider.js +27 -0
  31. package/dist/extensions/collab.d.ts +21 -0
  32. package/dist/extensions/collab.js +6 -0
  33. package/dist/extensions/command-registry.d.ts +49 -0
  34. package/dist/extensions/command-registry.js +28 -0
  35. package/dist/extensions/decorations.d.ts +48 -0
  36. package/dist/extensions/decorations.js +6 -0
  37. package/dist/extensions/export-registry.d.ts +59 -0
  38. package/dist/extensions/export-registry.js +10 -0
  39. package/dist/extensions/panel-registry.d.ts +30 -0
  40. package/dist/extensions/panel-registry.js +16 -0
  41. package/dist/extensions/registries.d.ts +15 -0
  42. package/dist/extensions/registries.js +12 -0
  43. package/dist/extensions/registry-store.d.ts +26 -0
  44. package/dist/extensions/registry-store.js +35 -0
  45. package/dist/extensions/session.d.ts +29 -0
  46. package/dist/extensions/session.js +20 -0
  47. package/dist/extensions/speech-provider.d.ts +35 -0
  48. package/dist/extensions/speech-provider.js +17 -0
  49. package/dist/file/builtin-exporters.d.ts +9 -0
  50. package/dist/file/builtin-exporters.js +72 -0
  51. package/dist/file/docx-exporter.js +7 -4
  52. package/dist/file/docx-importer.d.ts +9 -0
  53. package/dist/file/docx-importer.js +47 -19
  54. package/dist/file/scene-number.d.ts +5 -0
  55. package/dist/file/scene-number.js +8 -0
  56. package/dist/file/workflow-reports.js +13 -7
  57. package/dist/findreplace/FindReplace.js +5 -2
  58. package/dist/import/AIImportDialog.d.ts +11 -0
  59. package/dist/import/AIImportDialog.js +112 -0
  60. package/dist/import/ai-import.d.ts +51 -0
  61. package/dist/import/ai-import.js +200 -0
  62. package/dist/import/apply-import.d.ts +7 -0
  63. package/dist/import/apply-import.js +35 -0
  64. package/dist/import/extract.d.ts +17 -0
  65. package/dist/import/extract.js +18 -0
  66. package/dist/index.d.ts +17 -2
  67. package/dist/index.js +7 -0
  68. package/dist/locales/bn.json +8 -0
  69. package/dist/locales/en.json +8 -0
  70. package/dist/locales/gu.json +8 -0
  71. package/dist/locales/hi.json +8 -0
  72. package/dist/locales/kn.json +8 -0
  73. package/dist/locales/ml.json +8 -0
  74. package/dist/locales/mr.json +8 -0
  75. package/dist/locales/or.json +8 -0
  76. package/dist/locales/pa.json +8 -0
  77. package/dist/locales/si.json +8 -0
  78. package/dist/locales/ta.json +8 -0
  79. package/dist/locales/te.json +8 -0
  80. package/dist/navigator/SceneNavigator.js +125 -131
  81. package/dist/navigator/StatisticsDialog.js +5 -1
  82. package/dist/navigator/scene-edits.d.ts +18 -0
  83. package/dist/navigator/scene-edits.js +143 -0
  84. package/dist/navigator/scene-list.js +4 -4
  85. package/dist/navigator/scene-numbering.d.ts +37 -0
  86. package/dist/navigator/scene-numbering.js +88 -0
  87. package/dist/settings/SettingsDialog.js +8 -120
  88. package/dist/shell/AppBar.js +21 -2
  89. package/dist/shell/AppShell.js +18 -4
  90. package/dist/shell/ElementToolbar.d.ts +5 -0
  91. package/dist/shell/ElementToolbar.js +35 -25
  92. package/dist/shell/LanguageStatus.js +6 -0
  93. package/dist/shell/StatusBar.js +11 -6
  94. package/dist/shell/builtin-commands.d.ts +10 -0
  95. package/dist/shell/builtin-commands.js +43 -0
  96. package/dist/shell/icons.d.ts +6 -0
  97. package/dist/shell/icons.js +6 -0
  98. package/dist/shell/use-commands.d.ts +7 -0
  99. package/dist/shell/use-commands.js +24 -0
  100. package/dist/shell/use-panel-context.d.ts +3 -0
  101. package/dist/shell/use-panel-context.js +39 -0
  102. package/dist/source/SourceView.js +18 -1
  103. package/dist/styles/screenplay.css +113 -1
  104. package/dist/titlepage/ExportDialog.d.ts +5 -0
  105. package/dist/titlepage/ExportDialog.js +59 -96
  106. package/package.json +32 -5
  107. package/dist/ai/OnboardingDialog.d.ts +0 -6
  108. package/dist/ai/OnboardingDialog.js +0 -115
  109. package/dist/ai/ai-client.d.ts +0 -71
  110. package/dist/ai/ai-client.js +0 -494
  111. package/dist/ai/generated-providers.json +0 -3696
  112. package/dist/ai/voice-service.d.ts +0 -15
  113. package/dist/ai/voice-service.js +0 -81
  114. package/dist/editor/character-view.d.ts +0 -35
  115. package/dist/editor/character-view.js +0 -112
  116. package/dist/editor/transliterate-input.d.ts +0 -10
  117. package/dist/editor/transliterate-input.js +0 -88
  118. package/dist/extensions/types.d.ts +0 -73
  119. package/dist/extensions/types.js +0 -12
  120. package/scripts/copy-assets.mjs +0 -16
  121. package/scripts/generate-providers.mjs +0 -127
  122. package/src/ai/OnboardingDialog.tsx +0 -390
  123. package/src/ai/VoiceConfirmDialog.tsx +0 -137
  124. package/src/ai/VoiceToolbar.tsx +0 -321
  125. package/src/ai/ai-client.ts +0 -596
  126. package/src/ai/commands.ts +0 -164
  127. package/src/ai/generated-providers.json +0 -3696
  128. package/src/ai/prompts.ts +0 -62
  129. package/src/ai/voice-service.ts +0 -96
  130. package/src/autocomplete/AutocompletePlugin.ts +0 -21
  131. package/src/autocomplete/suggestions.ts +0 -147
  132. package/src/context/DocumentContext.tsx +0 -648
  133. package/src/desktop-api.d.ts +0 -40
  134. package/src/editor/EditorView.tsx +0 -82
  135. package/src/editor/SelectionMenu.tsx +0 -152
  136. package/src/editor/attr-pencil-plugin.ts +0 -172
  137. package/src/editor/character-decoration-plugin.ts +0 -104
  138. package/src/editor/editor-bus.ts +0 -51
  139. package/src/editor/find-highlight-plugin.ts +0 -68
  140. package/src/editor/frontmatter-field-view.ts +0 -52
  141. package/src/editor/frontmatter-field.ts +0 -115
  142. package/src/editor/grapheme-cursor.ts +0 -99
  143. package/src/editor/ime-plugin.ts +0 -85
  144. package/src/editor/insert-helper.ts +0 -43
  145. package/src/editor/keymap.ts +0 -124
  146. package/src/editor/language-class-plugin.ts +0 -91
  147. package/src/editor/plugins.ts +0 -26
  148. package/src/editor/prosemirror-to-sutra.ts +0 -192
  149. package/src/editor/scene-heading-view.ts +0 -147
  150. package/src/editor/schema.ts +0 -156
  151. package/src/editor/sutra-to-prosemirror.ts +0 -156
  152. package/src/extensions/storage-adapter.ts +0 -65
  153. package/src/extensions/types.ts +0 -60
  154. package/src/file/docx-exporter.ts +0 -585
  155. package/src/file/docx-importer.ts +0 -313
  156. package/src/file/print-paginator.ts +0 -301
  157. package/src/file/templates.ts +0 -73
  158. package/src/file/workflow-reports.ts +0 -820
  159. package/src/findreplace/FindReplace.tsx +0 -242
  160. package/src/findreplace/find-engine.ts +0 -97
  161. package/src/findreplace/scroll-to-match.ts +0 -89
  162. package/src/fonts/tofu-detect.ts +0 -53
  163. package/src/i18n/LanguageContext.tsx +0 -207
  164. package/src/i18n/language-detect.ts +0 -73
  165. package/src/i18n/useTranslation.tsx +0 -74
  166. package/src/index.ts +0 -27
  167. package/src/locales/bn.json +0 -143
  168. package/src/locales/en.json +0 -143
  169. package/src/locales/gu.json +0 -143
  170. package/src/locales/hi.json +0 -143
  171. package/src/locales/kn.json +0 -143
  172. package/src/locales/ml.json +0 -143
  173. package/src/locales/mr.json +0 -143
  174. package/src/locales/or.json +0 -143
  175. package/src/locales/pa.json +0 -143
  176. package/src/locales/si.json +0 -143
  177. package/src/locales/ta.json +0 -143
  178. package/src/locales/te.json +0 -143
  179. package/src/navigator/SceneNavigator.tsx +0 -844
  180. package/src/navigator/StatisticsDialog.tsx +0 -249
  181. package/src/navigator/scene-list.ts +0 -84
  182. package/src/settings/SettingsDialog.tsx +0 -303
  183. package/src/shell/AppBar.tsx +0 -354
  184. package/src/shell/AppShell.tsx +0 -71
  185. package/src/shell/ConfirmDialog.tsx +0 -71
  186. package/src/shell/ElementToolbar.tsx +0 -175
  187. package/src/shell/LanguageStatus.tsx +0 -101
  188. package/src/shell/StatusBar.tsx +0 -135
  189. package/src/shell/SutrataLogo.tsx +0 -89
  190. package/src/shell/Toast.tsx +0 -46
  191. package/src/shell/ToolbarButton.tsx +0 -30
  192. package/src/shell/VersionHistory.tsx +0 -212
  193. package/src/shell/Watermark.tsx +0 -9
  194. package/src/shell/fonts/fonts.css +0 -1133
  195. package/src/shell/icons.tsx +0 -335
  196. package/src/shell/toolbar-actions.ts +0 -59
  197. package/src/source/SourceView.tsx +0 -111
  198. package/src/source/find-highlight-source.ts +0 -57
  199. package/src/source/sutra-lang.ts +0 -121
  200. package/src/spellcheck/dictionaries.ts +0 -21
  201. package/src/spellcheck/hunspell.ts +0 -110
  202. package/src/spellcheck/spellcheck-plugin.ts +0 -83
  203. package/src/styles/StyleDialog.tsx +0 -148
  204. package/src/styles/builtin-styles.ts +0 -139
  205. package/src/styles/css-adapter.ts +0 -76
  206. package/src/styles/docx-adapter.ts +0 -109
  207. package/src/styles/embed-fonts.ts +0 -81
  208. package/src/styles/pagination.ts +0 -27
  209. package/src/styles/print-adapter.ts +0 -288
  210. package/src/styles/registry.ts +0 -20
  211. package/src/styles/screenplay.css +0 -3006
  212. package/src/styles/script-ranges.ts +0 -17
  213. package/src/styles/types.ts +0 -104
  214. package/src/styles/validate.ts +0 -114
  215. package/src/templates/templates.ts +0 -194
  216. package/src/titlepage/ExportDialog.tsx +0 -494
  217. package/src/titlepage/TitlePageDialog.tsx +0 -312
  218. package/src/titlepage/frontmatter-form.ts +0 -280
  219. package/src/titlepage/languages.ts +0 -37
  220. package/src/types.ts +0 -7
  221. package/tests/AppBar.test.tsx +0 -35
  222. package/tests/AppShell.test.tsx +0 -22
  223. package/tests/ElementToolbar.test.tsx +0 -36
  224. package/tests/LanguageStatus.test.tsx +0 -129
  225. package/tests/SelectionMenu.test.tsx +0 -71
  226. package/tests/ToolbarButton.test.tsx +0 -24
  227. package/tests/ai-onboarding.test.ts +0 -102
  228. package/tests/ai-status.test.tsx +0 -72
  229. package/tests/ai-structured.test.ts +0 -94
  230. package/tests/autocomplete/AutocompletePlugin.test.ts +0 -20
  231. package/tests/autocomplete/suggestions.test.ts +0 -106
  232. package/tests/document-context.test.tsx +0 -565
  233. package/tests/docx-importer.test.ts +0 -160
  234. package/tests/editor/character-decoration-plugin.test.ts +0 -101
  235. package/tests/editor/character-extension.test.ts +0 -44
  236. package/tests/editor/grapheme-cursor.test.ts +0 -118
  237. package/tests/editor/ime-plugin.test.ts +0 -72
  238. package/tests/editor/inline-note-bridge.test.ts +0 -32
  239. package/tests/editor/language-class-plugin.test.ts +0 -265
  240. package/tests/editor/title-page.test.ts +0 -124
  241. package/tests/embed-fonts.test.ts +0 -17
  242. package/tests/exporters.test.ts +0 -365
  243. package/tests/findreplace/FindReplace.test.tsx +0 -391
  244. package/tests/findreplace/find-engine.test.ts +0 -86
  245. package/tests/findreplace/scroll-to-match.test.ts +0 -50
  246. package/tests/fonts/tofu-detect.test.ts +0 -122
  247. package/tests/i18n/LanguageContext.test.tsx +0 -146
  248. package/tests/i18n/language-detect.test.ts +0 -54
  249. package/tests/i18n/locale-parity.test.ts +0 -25
  250. package/tests/i18n/useTranslation.test.tsx +0 -45
  251. package/tests/keymap.test.ts +0 -18
  252. package/tests/navigator/SceneNavigator.test.tsx +0 -177
  253. package/tests/navigator/StatisticsDialog.test.tsx +0 -63
  254. package/tests/navigator/scene-list.test.ts +0 -71
  255. package/tests/print-paginator.test.ts +0 -82
  256. package/tests/shell/VersionHistory.test.tsx +0 -129
  257. package/tests/spellcheck/hunspell.test.ts +0 -82
  258. package/tests/spellcheck/spellcheck-plugin.test.ts +0 -56
  259. package/tests/styles.test.ts +0 -315
  260. package/tests/templates/templates.test.ts +0 -83
  261. package/tests/titlepage/TitlePageDialog.test.tsx +0 -90
  262. package/tests/titlepage/apply-title-page.test.ts +0 -61
  263. package/tests/titlepage/frontmatter-form.test.ts +0 -115
  264. package/tests/toolbar-actions.test.ts +0 -93
  265. package/tests/voice-commands.test.ts +0 -58
  266. package/tsconfig.json +0 -17
  267. package/vitest.config.ts +0 -11
  268. package/vitest.setup.ts +0 -65
@@ -1,321 +0,0 @@
1
- import React, { useState, useEffect, useRef } from 'react'
2
- import { useDocument } from '../context/DocumentContext'
3
- import { voiceServiceInstance } from './voice-service'
4
- import { getAIConfig, callAI, hasConfiguredApiKey } from './ai-client'
5
- import { VOICE_FORMAT_SYSTEM_PROMPT } from './prompts'
6
- import { insertSutra } from '../editor/insert-helper'
7
- import { VoiceConfirmDialog } from './VoiceConfirmDialog'
8
- import { LOCALIZED_COMMANDS } from './commands'
9
- import { useTranslation } from '../i18n/useTranslation'
10
- import { MicIcon } from '../shell/icons'
11
-
12
- const VOICE_LANGUAGES = [
13
- { code: 'en-US', label: 'English (US)', base: 'en' },
14
- { code: 'hi-IN', label: 'हिन्दी (Hindi)', base: 'hi' },
15
- { code: 'ta-IN', label: 'தமிழ் (Tamil)', base: 'ta' },
16
- { code: 'te-IN', label: 'తెలుగు (Telugu)', base: 'te' },
17
- { code: 'ml-IN', label: 'മലയാളം (Malayalam)', base: 'ml' },
18
- { code: 'kn-IN', label: 'ಕನ್ನಡ (Kannada)', base: 'kn' },
19
- { code: 'bn-IN', label: 'বাংলা (Bengali)', base: 'bn' },
20
- { code: 'gu-IN', label: 'ગુજરાતી (Gujarati)', base: 'gu' },
21
- { code: 'mr-IN', label: 'मराठी (Marathi)', base: 'mr' },
22
- ]
23
-
24
- export function VoiceToolbar() {
25
- const { mode, setVoiceActive, setAIOnboardingVisible, showToast } = useDocument()
26
- const { locale } = useTranslation()
27
- const defaultLang = VOICE_LANGUAGES.find(l => l.base === locale)?.code || 'en-US'
28
- const [langCode, setLangCode] = useState<string>(defaultLang)
29
- const [isListening, setIsListening] = useState<boolean>(false)
30
- const [transcript, setTranscript] = useState<string>('')
31
- const [interimResult, setInterimResult] = useState<string>('')
32
- const [error, setError] = useState<string>('')
33
- const [showConfirm, setShowConfirm] = useState<boolean>(false)
34
- const [isFormatting, setIsFormatting] = useState<boolean>(false)
35
-
36
- useEffect(() => {
37
- const matched = VOICE_LANGUAGES.find(l => l.base === locale)?.code || 'en-US'
38
- setLangCode(matched)
39
- }, [locale])
40
-
41
- const canvasRef = useRef<HTMLCanvasElement>(null)
42
- const isSupported = voiceServiceInstance.isSupported()
43
-
44
- // Audio waveform animation
45
- useEffect(() => {
46
- if (!isListening) return
47
- const canvas = canvasRef.current
48
- if (!canvas) return
49
- const ctx = canvas.getContext('2d')
50
- if (!ctx) return
51
-
52
- let animationId: number
53
- let phase = 0
54
-
55
- const render = () => {
56
- ctx.clearRect(0, 0, canvas.width, canvas.height)
57
- ctx.strokeStyle = '#d97706'
58
- ctx.lineWidth = 2
59
- ctx.lineCap = 'round'
60
- ctx.beginPath()
61
-
62
- const width = canvas.width
63
- const height = canvas.height
64
- const midY = height / 2
65
-
66
- ctx.moveTo(0, midY)
67
- for (let x = 0; x < width; x++) {
68
- const angle = (x / width) * Math.PI * 4 + phase
69
- const amp = Math.sin(phase * 1.8) * (height / 2.6) * Math.sin((x / width) * Math.PI)
70
- const y = midY + Math.sin(angle) * amp
71
- ctx.lineTo(x, y)
72
- }
73
- ctx.stroke()
74
-
75
- phase += 0.08
76
- animationId = requestAnimationFrame(render)
77
- }
78
-
79
- render()
80
- return () => cancelAnimationFrame(animationId)
81
- }, [isListening])
82
-
83
- const performFormatting = async (fullTranscript: string) => {
84
- const alwaysAuto = localStorage.getItem('sutrata_always_auto_insert') === 'true'
85
- if (alwaysAuto) {
86
- setIsFormatting(true)
87
- try {
88
- const config = getAIConfig()
89
- const prompt = `Raw Voice Transcript (${langCode}):\n"${fullTranscript}"`
90
- const result = await callAI(prompt, VOICE_FORMAT_SYSTEM_PROMPT, config, { clean: 'voice' })
91
- insertSutra(result.trim(), mode)
92
- setVoiceActive(false)
93
- showToast('Voice dictation inserted.', 'success')
94
- } catch (e: any) {
95
- showToast(`Auto-insert failed: ${e.message || String(e)}. Opening confirmation dialog.`, 'warn')
96
- setShowConfirm(true)
97
- } finally {
98
- setIsFormatting(false)
99
- }
100
- } else {
101
- setShowConfirm(true)
102
- }
103
- }
104
-
105
- const handleStart = async () => {
106
- const hasKey = await hasConfiguredApiKey(getAIConfig())
107
- if (!hasKey) {
108
- showToast('AI keys are required to format dictated text. Opening Setup...', 'info')
109
- setAIOnboardingVisible(true)
110
- return
111
- }
112
-
113
- setError('')
114
- setInterimResult('')
115
- setIsListening(true)
116
-
117
- voiceServiceInstance.start(langCode, {
118
- onResult: (text, isFinal) => {
119
- if (isFinal) {
120
- setTranscript(prev => (prev ? prev + ' ' + text : text))
121
- setInterimResult('')
122
- } else {
123
- setInterimResult(text)
124
- }
125
- },
126
- onError: (err) => {
127
- if (err === 'network') {
128
- setError('Web Speech API requires an active network connection to transcribe speech.')
129
- } else {
130
- setError(err)
131
- }
132
- setIsListening(false)
133
- },
134
- onEnd: () => {
135
- setIsListening(false)
136
- },
137
- })
138
- }
139
-
140
- const handleStopListening = async () => {
141
- voiceServiceInstance.stop()
142
- setIsListening(false)
143
-
144
- const fullTranscript = (transcript + ' ' + interimResult).trim()
145
- if (fullTranscript) {
146
- setTranscript(fullTranscript)
147
- await performFormatting(fullTranscript)
148
- }
149
- setInterimResult('')
150
- }
151
-
152
- const handleFormatCaptured = async () => {
153
- const fullTranscript = transcript.trim()
154
- if (!fullTranscript) return
155
- await performFormatting(fullTranscript)
156
- }
157
-
158
- const handleCancel = () => {
159
- voiceServiceInstance.abort()
160
- setIsListening(false)
161
- setVoiceActive(false)
162
- }
163
-
164
- const handleConfirmResult = (formattedText: string) => {
165
- insertSutra(formattedText, mode)
166
- setShowConfirm(false)
167
- setVoiceActive(false)
168
- showToast('Screenplay formatted and inserted.', 'success')
169
- }
170
-
171
- const activeBaseLang = VOICE_LANGUAGES.find(l => l.code === langCode)?.base || 'en'
172
- const cheatsheet = LOCALIZED_COMMANDS[activeBaseLang] || LOCALIZED_COMMANDS.en!
173
-
174
- if (showConfirm) {
175
- const fullTranscript = (transcript + ' ' + interimResult).trim()
176
- return (
177
- <VoiceConfirmDialog
178
- rawTranscript={fullTranscript}
179
- langCode={langCode}
180
- onConfirm={handleConfirmResult}
181
- onCancel={() => {
182
- setShowConfirm(false)
183
- setVoiceActive(false)
184
- }}
185
- />
186
- )
187
- }
188
-
189
- return (
190
- <div className="cs-voice-toolbar" role="region" aria-label="Voice Dictation Toolbar">
191
- <div className="cs-voice-toolbar-row">
192
- <div className="cs-voice-toolbar-badge">
193
- <MicIcon size={14} />
194
- <span>Voice Dictation</span>
195
- </div>
196
-
197
- <select
198
- className="cs-voice-select"
199
- value={langCode}
200
- onChange={e => setLangCode(e.target.value)}
201
- disabled={isListening}
202
- aria-label="Voice Language"
203
- >
204
- {VOICE_LANGUAGES.map(l => (
205
- <option key={l.code} value={l.code}>{l.label}</option>
206
- ))}
207
- </select>
208
-
209
- {isListening ? (
210
- <div className="cs-voice-listening-group">
211
- <button
212
- type="button"
213
- className="cs-voice-btn-danger"
214
- onClick={handleStopListening}
215
- >
216
- Stop &amp; Format
217
- </button>
218
- <canvas
219
- ref={canvasRef}
220
- width={110}
221
- height={26}
222
- className="cs-voice-waveform"
223
- role="img"
224
- aria-label="Audio waveform visualization"
225
- />
226
- </div>
227
- ) : (
228
- <div className="cs-voice-actions-group">
229
- <button
230
- type="button"
231
- className="cs-voice-btn-primary"
232
- onClick={handleStart}
233
- disabled={!isSupported || isFormatting}
234
- >
235
- <MicIcon size={14} />
236
- <span>{isFormatting ? 'Formatting...' : 'Start Dictation'}</span>
237
- </button>
238
-
239
- {transcript && (
240
- <>
241
- <button
242
- type="button"
243
- className="cs-voice-btn-secondary"
244
- onClick={handleFormatCaptured}
245
- disabled={isFormatting}
246
- >
247
- Re-format
248
- </button>
249
- <button
250
- type="button"
251
- className="cs-voice-btn-ghost"
252
- onClick={() => {
253
- setTranscript('')
254
- setError('')
255
- }}
256
- >
257
- Clear
258
- </button>
259
- </>
260
- )}
261
- </div>
262
- )}
263
-
264
- <button
265
- type="button"
266
- className="cs-voice-btn-ghost"
267
- onClick={handleCancel}
268
- >
269
- Close
270
- </button>
271
-
272
- {!isSupported && (
273
- <span className="cs-voice-warning">
274
- Web Speech API is not supported in this browser.
275
- </span>
276
- )}
277
- </div>
278
-
279
- {/* Real-time transcript display / editable area */}
280
- {(isListening || transcript || interimResult) && (
281
- isListening ? (
282
- <div className="cs-voice-transcript-box">
283
- {transcript}
284
- {interimResult && <span className="cs-voice-interim"> {interimResult}</span>}
285
- {!transcript && !interimResult && <span className="cs-voice-placeholder">Speak now to dictate...</span>}
286
- </div>
287
- ) : (
288
- <textarea
289
- className="cs-voice-textarea"
290
- value={transcript}
291
- onChange={(e) => setTranscript(e.target.value)}
292
- placeholder="Edit captured text before formatting..."
293
- rows={2}
294
- />
295
- )
296
- )}
297
-
298
- {error && (
299
- <div className="cs-voice-error" role="alert">
300
- {error}
301
- </div>
302
- )}
303
-
304
- {/* Localized formatting cheatsheet */}
305
- <div className="cs-voice-cheatsheet">
306
- <span className="cs-voice-cheatsheet-title">Commands:</span>
307
- {cheatsheet.map(c => {
308
- if (c.type === 'literal') return null
309
- return (
310
- <span key={c.type} className="cs-voice-tag">
311
- <span className="cs-voice-tag-type">{c.type}</span>: &ldquo;{c.keywords[0]}&rdquo;
312
- </span>
313
- )
314
- })}
315
- <span className="cs-voice-tag cs-voice-tag-bypass">
316
- bypass format: &ldquo;literal [text]&rdquo;
317
- </span>
318
- </div>
319
- </div>
320
- )
321
- }