@sutrata/editor 0.1.0 → 0.1.2

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 (152) hide show
  1. package/README.md +87 -0
  2. package/dist/ai/generated-providers.json +1 -1
  3. package/package.json +36 -9
  4. package/scripts/copy-assets.mjs +0 -16
  5. package/scripts/generate-providers.mjs +0 -127
  6. package/src/ai/OnboardingDialog.tsx +0 -390
  7. package/src/ai/VoiceConfirmDialog.tsx +0 -137
  8. package/src/ai/VoiceToolbar.tsx +0 -321
  9. package/src/ai/ai-client.ts +0 -596
  10. package/src/ai/commands.ts +0 -164
  11. package/src/ai/generated-providers.json +0 -3696
  12. package/src/ai/prompts.ts +0 -62
  13. package/src/ai/voice-service.ts +0 -96
  14. package/src/autocomplete/AutocompletePlugin.ts +0 -21
  15. package/src/autocomplete/suggestions.ts +0 -147
  16. package/src/context/DocumentContext.tsx +0 -648
  17. package/src/desktop-api.d.ts +0 -40
  18. package/src/editor/EditorView.tsx +0 -82
  19. package/src/editor/SelectionMenu.tsx +0 -152
  20. package/src/editor/attr-pencil-plugin.ts +0 -172
  21. package/src/editor/character-decoration-plugin.ts +0 -104
  22. package/src/editor/editor-bus.ts +0 -51
  23. package/src/editor/find-highlight-plugin.ts +0 -68
  24. package/src/editor/frontmatter-field-view.ts +0 -52
  25. package/src/editor/frontmatter-field.ts +0 -115
  26. package/src/editor/grapheme-cursor.ts +0 -99
  27. package/src/editor/ime-plugin.ts +0 -85
  28. package/src/editor/insert-helper.ts +0 -43
  29. package/src/editor/keymap.ts +0 -124
  30. package/src/editor/language-class-plugin.ts +0 -91
  31. package/src/editor/plugins.ts +0 -26
  32. package/src/editor/prosemirror-to-sutra.ts +0 -192
  33. package/src/editor/scene-heading-view.ts +0 -147
  34. package/src/editor/schema.ts +0 -156
  35. package/src/editor/sutra-to-prosemirror.ts +0 -156
  36. package/src/extensions/storage-adapter.ts +0 -65
  37. package/src/extensions/types.ts +0 -60
  38. package/src/file/docx-exporter.ts +0 -585
  39. package/src/file/docx-importer.ts +0 -313
  40. package/src/file/print-paginator.ts +0 -301
  41. package/src/file/templates.ts +0 -73
  42. package/src/file/workflow-reports.ts +0 -820
  43. package/src/findreplace/FindReplace.tsx +0 -242
  44. package/src/findreplace/find-engine.ts +0 -97
  45. package/src/findreplace/scroll-to-match.ts +0 -89
  46. package/src/fonts/tofu-detect.ts +0 -53
  47. package/src/i18n/LanguageContext.tsx +0 -207
  48. package/src/i18n/language-detect.ts +0 -73
  49. package/src/i18n/useTranslation.tsx +0 -74
  50. package/src/index.ts +0 -27
  51. package/src/locales/bn.json +0 -143
  52. package/src/locales/en.json +0 -143
  53. package/src/locales/gu.json +0 -143
  54. package/src/locales/hi.json +0 -143
  55. package/src/locales/kn.json +0 -143
  56. package/src/locales/ml.json +0 -143
  57. package/src/locales/mr.json +0 -143
  58. package/src/locales/or.json +0 -143
  59. package/src/locales/pa.json +0 -143
  60. package/src/locales/si.json +0 -143
  61. package/src/locales/ta.json +0 -143
  62. package/src/locales/te.json +0 -143
  63. package/src/navigator/SceneNavigator.tsx +0 -844
  64. package/src/navigator/StatisticsDialog.tsx +0 -249
  65. package/src/navigator/scene-list.ts +0 -84
  66. package/src/settings/SettingsDialog.tsx +0 -303
  67. package/src/shell/AppBar.tsx +0 -354
  68. package/src/shell/AppShell.tsx +0 -71
  69. package/src/shell/ConfirmDialog.tsx +0 -71
  70. package/src/shell/ElementToolbar.tsx +0 -175
  71. package/src/shell/LanguageStatus.tsx +0 -101
  72. package/src/shell/StatusBar.tsx +0 -135
  73. package/src/shell/SutrataLogo.tsx +0 -89
  74. package/src/shell/Toast.tsx +0 -46
  75. package/src/shell/ToolbarButton.tsx +0 -30
  76. package/src/shell/VersionHistory.tsx +0 -212
  77. package/src/shell/Watermark.tsx +0 -9
  78. package/src/shell/fonts/fonts.css +0 -1133
  79. package/src/shell/icons.tsx +0 -335
  80. package/src/shell/toolbar-actions.ts +0 -59
  81. package/src/source/SourceView.tsx +0 -111
  82. package/src/source/find-highlight-source.ts +0 -57
  83. package/src/source/sutra-lang.ts +0 -121
  84. package/src/spellcheck/dictionaries.ts +0 -21
  85. package/src/spellcheck/hunspell.ts +0 -110
  86. package/src/spellcheck/spellcheck-plugin.ts +0 -83
  87. package/src/styles/StyleDialog.tsx +0 -148
  88. package/src/styles/builtin-styles.ts +0 -139
  89. package/src/styles/css-adapter.ts +0 -76
  90. package/src/styles/docx-adapter.ts +0 -109
  91. package/src/styles/embed-fonts.ts +0 -81
  92. package/src/styles/pagination.ts +0 -27
  93. package/src/styles/print-adapter.ts +0 -288
  94. package/src/styles/registry.ts +0 -20
  95. package/src/styles/screenplay.css +0 -3006
  96. package/src/styles/script-ranges.ts +0 -17
  97. package/src/styles/types.ts +0 -104
  98. package/src/styles/validate.ts +0 -114
  99. package/src/templates/templates.ts +0 -194
  100. package/src/titlepage/ExportDialog.tsx +0 -494
  101. package/src/titlepage/TitlePageDialog.tsx +0 -312
  102. package/src/titlepage/frontmatter-form.ts +0 -280
  103. package/src/titlepage/languages.ts +0 -37
  104. package/src/types.ts +0 -7
  105. package/tests/AppBar.test.tsx +0 -35
  106. package/tests/AppShell.test.tsx +0 -22
  107. package/tests/ElementToolbar.test.tsx +0 -36
  108. package/tests/LanguageStatus.test.tsx +0 -129
  109. package/tests/SelectionMenu.test.tsx +0 -71
  110. package/tests/ToolbarButton.test.tsx +0 -24
  111. package/tests/ai-onboarding.test.ts +0 -102
  112. package/tests/ai-status.test.tsx +0 -72
  113. package/tests/ai-structured.test.ts +0 -94
  114. package/tests/autocomplete/AutocompletePlugin.test.ts +0 -20
  115. package/tests/autocomplete/suggestions.test.ts +0 -106
  116. package/tests/document-context.test.tsx +0 -565
  117. package/tests/docx-importer.test.ts +0 -160
  118. package/tests/editor/character-decoration-plugin.test.ts +0 -101
  119. package/tests/editor/character-extension.test.ts +0 -44
  120. package/tests/editor/grapheme-cursor.test.ts +0 -118
  121. package/tests/editor/ime-plugin.test.ts +0 -72
  122. package/tests/editor/inline-note-bridge.test.ts +0 -32
  123. package/tests/editor/language-class-plugin.test.ts +0 -265
  124. package/tests/editor/title-page.test.ts +0 -124
  125. package/tests/embed-fonts.test.ts +0 -17
  126. package/tests/exporters.test.ts +0 -365
  127. package/tests/findreplace/FindReplace.test.tsx +0 -391
  128. package/tests/findreplace/find-engine.test.ts +0 -86
  129. package/tests/findreplace/scroll-to-match.test.ts +0 -50
  130. package/tests/fonts/tofu-detect.test.ts +0 -122
  131. package/tests/i18n/LanguageContext.test.tsx +0 -146
  132. package/tests/i18n/language-detect.test.ts +0 -54
  133. package/tests/i18n/locale-parity.test.ts +0 -25
  134. package/tests/i18n/useTranslation.test.tsx +0 -45
  135. package/tests/keymap.test.ts +0 -18
  136. package/tests/navigator/SceneNavigator.test.tsx +0 -177
  137. package/tests/navigator/StatisticsDialog.test.tsx +0 -63
  138. package/tests/navigator/scene-list.test.ts +0 -71
  139. package/tests/print-paginator.test.ts +0 -82
  140. package/tests/shell/VersionHistory.test.tsx +0 -129
  141. package/tests/spellcheck/hunspell.test.ts +0 -82
  142. package/tests/spellcheck/spellcheck-plugin.test.ts +0 -56
  143. package/tests/styles.test.ts +0 -315
  144. package/tests/templates/templates.test.ts +0 -83
  145. package/tests/titlepage/TitlePageDialog.test.tsx +0 -90
  146. package/tests/titlepage/apply-title-page.test.ts +0 -61
  147. package/tests/titlepage/frontmatter-form.test.ts +0 -115
  148. package/tests/toolbar-actions.test.ts +0 -93
  149. package/tests/voice-commands.test.ts +0 -58
  150. package/tsconfig.json +0 -17
  151. package/vitest.config.ts +0 -11
  152. 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
- }