@sutrata/editor 0.1.1 → 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 +30 -3
  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,390 +0,0 @@
1
- import React, { useState, useEffect, useRef } from 'react'
2
- import {
3
- getApiKey, setApiKey, deleteApiKey, getAIConfig, saveAIConfig,
4
- fetchProviders, testConnection, listModels, AIConfig, ProviderConfig
5
- } from './ai-client'
6
- import { SparklesIcon } from '../shell/icons'
7
-
8
- interface Props {
9
- onClose: () => void
10
- }
11
-
12
- export function OnboardingDialog({ onClose }: Props) {
13
- const [activeTab, setActiveTab] = useState<'intro' | 'keys' | 'custom'>('intro')
14
- const [providers, setProviders] = useState<ProviderConfig[]>([])
15
- const [providerId, setProviderId] = useState<string>('anthropic')
16
- const [keyInput, setKeyInput] = useState<string>('')
17
- const [showKey, setShowKey] = useState<boolean>(false)
18
- const [testStatus, setTestStatus] = useState<'idle' | 'testing' | 'success' | 'error'>('idle')
19
- const [testError, setTestError] = useState<string>('')
20
- const [config, setConfig] = useState<AIConfig>(getAIConfig)
21
-
22
- // Custom provider state
23
- const [customId, setCustomId] = useState('')
24
- const [customName, setCustomName] = useState('')
25
- const [customBaseUrl, setCustomBaseUrl] = useState('')
26
- const [customModels, setCustomModels] = useState('')
27
-
28
- const dialogRef = useRef<HTMLDivElement>(null)
29
-
30
- useEffect(() => {
31
- void fetchProviders(config).then(setProviders)
32
- // eslint-disable-next-line react-hooks/exhaustive-deps
33
- }, [config.customProviders])
34
-
35
- useEffect(() => {
36
- dialogRef.current?.focus()
37
- void loadKeyForProvider(providerId)
38
- }, [providerId])
39
-
40
- const loadKeyForProvider = async (pid: string) => {
41
- setTestStatus('idle')
42
- setTestError('')
43
- const key = await getApiKey(pid)
44
- setKeyInput(key || '')
45
- }
46
-
47
- const handleTestKey = async () => {
48
- if (!keyInput.trim()) {
49
- setTestStatus('error')
50
- setTestError('Please enter an API key to test.')
51
- return
52
- }
53
- setTestStatus('testing')
54
- setTestError('')
55
- try {
56
- const ok = await testConnection(providerId, keyInput.trim(), config)
57
- if (ok) {
58
- setTestStatus('success')
59
- } else {
60
- setTestStatus('error')
61
- setTestError('Connection failed: empty or invalid response from endpoint.')
62
- }
63
- } catch (e: any) {
64
- setTestStatus('error')
65
- setTestError(e.message || String(e))
66
- }
67
- }
68
-
69
- const handleSaveKey = async () => {
70
- const trimmed = keyInput.trim()
71
- if (trimmed) {
72
- await setApiKey(providerId, trimmed)
73
-
74
- const provider = providers.find(p => p.id === providerId)
75
- let preferredModel = provider?.models[0]?.id || ''
76
- if (provider) {
77
- const liveModels = await listModels(provider, trimmed)
78
- preferredModel = liveModels[0]?.id || preferredModel
79
- }
80
-
81
- const updated = { ...config, preferredProvider: providerId, preferredModel }
82
- setConfig(updated)
83
- saveAIConfig(updated)
84
- setTestStatus('success')
85
- } else {
86
- await deleteApiKey(providerId)
87
- setTestStatus('idle')
88
- }
89
- }
90
-
91
- const handleAddCustomProvider = () => {
92
- if (!customId || !customName || !customBaseUrl || !customModels) {
93
- alert('Please fill out all custom provider fields.')
94
- return
95
- }
96
- const modelsList = customModels.split(',').map(m => {
97
- const parts = m.trim().split(':')
98
- const id = parts[0]?.trim() || ''
99
- const name = parts[1]?.trim() || id
100
- return { id, name }
101
- }).filter(m => m.id)
102
-
103
- const newCustom = {
104
- id: customId.trim().toLowerCase(),
105
- name: customName.trim(),
106
- baseUrl: customBaseUrl.trim(),
107
- models: modelsList,
108
- }
109
-
110
- const updated = { ...config }
111
- const index = updated.customProviders.findIndex(p => p.id === newCustom.id)
112
- if (index >= 0) {
113
- updated.customProviders[index] = newCustom
114
- } else {
115
- updated.customProviders.push(newCustom)
116
- }
117
-
118
- setConfig(updated)
119
- saveAIConfig(updated)
120
- setCustomId('')
121
- setCustomName('')
122
- setCustomBaseUrl('')
123
- setCustomModels('')
124
- alert(`Custom provider "${newCustom.name}" added. You can now configure its key in the API Keys tab.`)
125
- }
126
-
127
- const handleFinish = () => {
128
- localStorage.setItem('sutrata_onboarding_completed', 'true')
129
- onClose()
130
- }
131
-
132
- return (
133
- <div className="cs-dialog-overlay" onClick={onClose} style={{ zIndex: 300 }}>
134
- <div
135
- ref={dialogRef}
136
- className="cs-dialog cs-onboarding-dialog"
137
- role="dialog"
138
- aria-modal="true"
139
- aria-label="Intelligence Setup"
140
- onClick={e => e.stopPropagation()}
141
- tabIndex={-1}
142
- >
143
- <div className="cs-dialog-header">
144
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
145
- <SparklesIcon size={16} />
146
- <span className="cs-dialog-title">Intelligence &amp; Dictation Setup</span>
147
- </div>
148
- <button
149
- type="button"
150
- className="cs-fr-close"
151
- onClick={onClose}
152
- aria-label="Close"
153
- >
154
- ×
155
- </button>
156
- </div>
157
-
158
- {/* Tab Selection */}
159
- <div className="cs-tabs-header">
160
- <button
161
- type="button"
162
- className={`cs-tab-btn ${activeTab === 'intro' ? 'active' : ''}`}
163
- onClick={() => setActiveTab('intro')}
164
- >
165
- Overview
166
- </button>
167
- <button
168
- type="button"
169
- className={`cs-tab-btn ${activeTab === 'keys' ? 'active' : ''}`}
170
- onClick={() => setActiveTab('keys')}
171
- >
172
- API Keys
173
- </button>
174
- <button
175
- type="button"
176
- className={`cs-tab-btn ${activeTab === 'custom' ? 'active' : ''}`}
177
- onClick={() => setActiveTab('custom')}
178
- >
179
- Custom Endpoints
180
- </button>
181
- </div>
182
-
183
- <div className="cs-onboarding-body">
184
- {activeTab === 'intro' && (
185
- <div className="cs-onboarding-section">
186
- <h3 className="cs-onboarding-h3">Client-Side AI Formatting &amp; Voice</h3>
187
- <p className="cs-onboarding-desc">
188
- Sutrata connects directly from your device to high-performance inference APIs.
189
- Your screenplays and audio transcripts are processed strictly with your own API credentials.
190
- </p>
191
-
192
- <div className="cs-onboarding-card">
193
- <div className="cs-onboarding-step">
194
- <div className="cs-onboarding-step-num">1</div>
195
- <div>
196
- <strong>Get a Free-Tier Key:</strong> Obtain a free API key from Google AI Studio (Gemini/Gemma) or Groq (Llama 3.3).
197
- </div>
198
- </div>
199
- <div className="cs-onboarding-step">
200
- <div className="cs-onboarding-step-num">2</div>
201
- <div>
202
- <strong>Enter Your Key:</strong> Save the key in the API Keys tab. It is encrypted in your local OS keyring or stored securely in your browser session.
203
- </div>
204
- </div>
205
- <div className="cs-onboarding-step">
206
- <div className="cs-onboarding-step-num">3</div>
207
- <div>
208
- <strong>Start Dictating:</strong> Speak naturally into Sutrata; the formatting engine parses characters, dialogue, and scenes automatically.
209
- </div>
210
- </div>
211
- </div>
212
-
213
- <div className="cs-dialog-actions" style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '16px' }}>
214
- <button
215
- type="button"
216
- className="cs-voice-btn-primary"
217
- onClick={() => setActiveTab('keys')}
218
- >
219
- Configure API Keys →
220
- </button>
221
- </div>
222
- </div>
223
- )}
224
-
225
- {activeTab === 'keys' && (
226
- <div className="cs-onboarding-section">
227
- <div className="cs-settings-row" style={{ marginBottom: '12px' }}>
228
- <label className="cs-settings-label" htmlFor="cs-ob-provider">
229
- Active Provider:
230
- </label>
231
- <select
232
- id="cs-ob-provider"
233
- className="cs-settings-select"
234
- value={providerId}
235
- onChange={e => setProviderId(e.target.value)}
236
- >
237
- {providers.map(p => (
238
- <option key={p.id} value={p.id}>{p.name}</option>
239
- ))}
240
- </select>
241
- </div>
242
-
243
- <div style={{ display: 'flex', flexDirection: 'column', gap: '6px', marginBottom: '16px' }}>
244
- <label className="cs-settings-label" htmlFor="cs-ob-key">
245
- API Key for {providerId}:
246
- </label>
247
- <div style={{ display: 'flex', gap: '8px' }}>
248
- <input
249
- id="cs-ob-key"
250
- type={showKey ? 'text' : 'password'}
251
- className="cs-voice-textarea"
252
- style={{ flex: 1, minHeight: '34px', padding: '6px 10px', height: '34px' }}
253
- value={keyInput}
254
- onChange={e => setKeyInput(e.target.value)}
255
- placeholder={`Enter ${providerId} API key...`}
256
- />
257
- <button
258
- type="button"
259
- className="cs-voice-btn-ghost"
260
- onClick={() => setShowKey(!showKey)}
261
- style={{ padding: '0 10px', height: '34px' }}
262
- >
263
- {showKey ? 'Hide' : 'Show'}
264
- </button>
265
- </div>
266
- </div>
267
-
268
- <div style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '16px' }}>
269
- <button
270
- type="button"
271
- className="cs-voice-btn-primary"
272
- onClick={handleSaveKey}
273
- >
274
- Save Key
275
- </button>
276
- <button
277
- type="button"
278
- className="cs-voice-btn-secondary"
279
- onClick={handleTestKey}
280
- disabled={testStatus === 'testing'}
281
- >
282
- {testStatus === 'testing' ? 'Testing...' : 'Test Connection'}
283
- </button>
284
- {testStatus === 'success' && (
285
- <span style={{ color: '#10b981', fontSize: '13px', fontWeight: 500 }}>✓ Verified connection</span>
286
- )}
287
- {testStatus === 'error' && (
288
- <span style={{ color: '#ef4444', fontSize: '12px' }}>{testError}</span>
289
- )}
290
- </div>
291
-
292
- <div className="cs-dialog-actions" style={{ display: 'flex', justifyContent: 'space-between', marginTop: '20px' }}>
293
- <button
294
- type="button"
295
- className="cs-voice-btn-ghost"
296
- onClick={() => setActiveTab('intro')}
297
- >
298
- ← Back
299
- </button>
300
- <button
301
- type="button"
302
- className="cs-voice-btn-primary"
303
- onClick={handleFinish}
304
- >
305
- Done &amp; Close
306
- </button>
307
- </div>
308
- </div>
309
- )}
310
-
311
- {activeTab === 'custom' && (
312
- <div className="cs-onboarding-section">
313
- <p className="cs-onboarding-desc">
314
- Connect to local OpenAI-compatible endpoints such as Ollama, vLLM, or LM Studio.
315
- </p>
316
-
317
- <div style={{ display: 'flex', flexDirection: 'column', gap: '10px', marginBottom: '16px' }}>
318
- <div>
319
- <label className="cs-settings-label" htmlFor="cs-ob-custom-id">Provider ID (e.g. ollama):</label>
320
- <input
321
- id="cs-ob-custom-id"
322
- type="text"
323
- className="cs-voice-textarea"
324
- style={{ width: '100%', height: '34px', minHeight: '34px', padding: '6px 10px' }}
325
- value={customId}
326
- onChange={e => setCustomId(e.target.value)}
327
- placeholder="ollama"
328
- />
329
- </div>
330
- <div>
331
- <label className="cs-settings-label" htmlFor="cs-ob-custom-name">Display Name:</label>
332
- <input
333
- id="cs-ob-custom-name"
334
- type="text"
335
- className="cs-voice-textarea"
336
- style={{ width: '100%', height: '34px', minHeight: '34px', padding: '6px 10px' }}
337
- value={customName}
338
- onChange={e => setCustomName(e.target.value)}
339
- placeholder="Local Ollama"
340
- />
341
- </div>
342
- <div>
343
- <label className="cs-settings-label" htmlFor="cs-ob-custom-url">Base URL:</label>
344
- <input
345
- id="cs-ob-custom-url"
346
- type="text"
347
- className="cs-voice-textarea"
348
- style={{ width: '100%', height: '34px', minHeight: '34px', padding: '6px 10px' }}
349
- value={customBaseUrl}
350
- onChange={e => setCustomBaseUrl(e.target.value)}
351
- placeholder="http://localhost:11434/v1"
352
- />
353
- </div>
354
- <div>
355
- <label className="cs-settings-label" htmlFor="cs-ob-custom-models">Models (id:name comma-separated):</label>
356
- <input
357
- id="cs-ob-custom-models"
358
- type="text"
359
- className="cs-voice-textarea"
360
- style={{ width: '100%', height: '34px', minHeight: '34px', padding: '6px 10px' }}
361
- value={customModels}
362
- onChange={e => setCustomModels(e.target.value)}
363
- placeholder="llama3:Llama 3, mistral:Mistral 7B"
364
- />
365
- </div>
366
- </div>
367
-
368
- <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '16px' }}>
369
- <button
370
- type="button"
371
- className="cs-voice-btn-secondary"
372
- onClick={handleAddCustomProvider}
373
- >
374
- Register Endpoint
375
- </button>
376
- <button
377
- type="button"
378
- className="cs-voice-btn-primary"
379
- onClick={() => setActiveTab('keys')}
380
- >
381
- Go to API Keys →
382
- </button>
383
- </div>
384
- </div>
385
- )}
386
- </div>
387
- </div>
388
- </div>
389
- )
390
- }
@@ -1,137 +0,0 @@
1
- import React, { useState, useEffect } from 'react'
2
- import { callAI, getAIConfig } from './ai-client'
3
- import { VOICE_FORMAT_SYSTEM_PROMPT } from './prompts'
4
- import { SparklesIcon } from '../shell/icons'
5
-
6
- interface Props {
7
- rawTranscript: string
8
- langCode: string
9
- onConfirm: (formattedText: string) => void
10
- onCancel: () => void
11
- }
12
-
13
- export function VoiceConfirmDialog({ rawTranscript, langCode, onConfirm, onCancel }: Props) {
14
- const [formattedText, setFormattedText] = useState<string>('')
15
- const [isLoading, setIsLoading] = useState<boolean>(true)
16
- const [error, setError] = useState<string>('')
17
- const [alwaysAutoInsert, setAlwaysAutoInsert] = useState<boolean>(
18
- localStorage.getItem('sutrata_always_auto_insert') === 'true'
19
- )
20
-
21
- useEffect(() => {
22
- void formatTranscript()
23
- }, [rawTranscript])
24
-
25
- const formatTranscript = async () => {
26
- setIsLoading(true)
27
- setError('')
28
- try {
29
- const config = getAIConfig()
30
- const prompt = `Raw Voice Transcript (${langCode}):\n"${rawTranscript}"`
31
- const result = await callAI(prompt, VOICE_FORMAT_SYSTEM_PROMPT, config, { clean: 'voice' })
32
- setFormattedText(result.trim())
33
- } catch (e: any) {
34
- console.error('AI formatting failed:', e)
35
- setError(e.message || String(e))
36
- setFormattedText(rawTranscript)
37
- } finally {
38
- setIsLoading(false)
39
- }
40
- }
41
-
42
- const handleConfirm = () => {
43
- localStorage.setItem('sutrata_always_auto_insert', alwaysAutoInsert ? 'true' : 'false')
44
- onConfirm(formattedText)
45
- }
46
-
47
- return (
48
- <div className="cs-dialog-overlay" onClick={onCancel} style={{ zIndex: 310 }}>
49
- <div
50
- className="cs-dialog cs-voice-confirm-dialog"
51
- role="dialog"
52
- aria-modal="true"
53
- aria-label="Confirm Voice Dictation Formatting"
54
- onClick={e => e.stopPropagation()}
55
- >
56
- <div className="cs-dialog-header">
57
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
58
- <SparklesIcon size={16} />
59
- <span className="cs-dialog-title">Review Voice Formatting</span>
60
- </div>
61
- <button
62
- type="button"
63
- className="cs-fr-close"
64
- onClick={onCancel}
65
- aria-label="Close"
66
- >
67
- ×
68
- </button>
69
- </div>
70
-
71
- <div className="cs-voice-confirm-body">
72
- {isLoading ? (
73
- <div className="cs-voice-loading-state">
74
- <div className="cs-spinner" style={{ width: '28px', height: '28px', borderWidth: '2.5px' }} />
75
- <span style={{ fontSize: '13px', color: 'var(--cs-ui-text-dark-secondary, #a8a69d)' }}>
76
- Formatting speech into Sutra screenplay format...
77
- </span>
78
- </div>
79
- ) : (
80
- <>
81
- {error && (
82
- <div className="cs-voice-error" role="alert">
83
- AI formatting notice: {error} (showing raw input below)
84
- </div>
85
- )}
86
-
87
- <div className="cs-voice-grid">
88
- <div className="cs-voice-column">
89
- <span className="cs-voice-col-title">Raw Dictation</span>
90
- <div className="cs-voice-box-raw">{rawTranscript}</div>
91
- </div>
92
-
93
- <div className="cs-voice-column">
94
- <span className="cs-voice-col-title">Formatted Sutra</span>
95
- <textarea
96
- className="cs-voice-box-formatted"
97
- value={formattedText}
98
- onChange={e => setFormattedText(e.target.value)}
99
- rows={8}
100
- />
101
- </div>
102
- </div>
103
-
104
- <div className="cs-voice-options">
105
- <label className="cs-voice-checkbox-label">
106
- <input
107
- type="checkbox"
108
- checked={alwaysAutoInsert}
109
- onChange={e => setAlwaysAutoInsert(e.target.checked)}
110
- />
111
- <span>Always auto-insert directly into screenplay (skip review)</span>
112
- </label>
113
- </div>
114
-
115
- <div className="cs-dialog-actions" style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px', marginTop: '12px' }}>
116
- <button
117
- type="button"
118
- className="cs-voice-btn-ghost"
119
- onClick={onCancel}
120
- >
121
- Discard
122
- </button>
123
- <button
124
- type="button"
125
- className="cs-voice-btn-primary"
126
- onClick={handleConfirm}
127
- >
128
- Insert Into Screenplay
129
- </button>
130
- </div>
131
- </>
132
- )}
133
- </div>
134
- </div>
135
- </div>
136
- )
137
- }