@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.
- package/README.md +87 -0
- package/dist/ai/VoiceConfirmDialog.d.ts +1 -0
- package/dist/ai/VoiceConfirmDialog.js +6 -6
- package/dist/ai/VoiceToolbar.d.ts +1 -0
- package/dist/ai/VoiceToolbar.js +22 -16
- package/dist/ai/clean-response.d.ts +7 -0
- package/dist/ai/clean-response.js +58 -0
- package/dist/ai/format-transcript.d.ts +3 -0
- package/dist/ai/format-transcript.js +7 -0
- package/dist/ai/prompts.d.ts +8 -1
- package/dist/ai/prompts.js +34 -7
- package/dist/autocomplete/suggestions.js +1 -1
- package/dist/context/DocumentContext.d.ts +28 -4
- package/dist/context/DocumentContext.js +67 -37
- package/dist/editor/EditorView.js +32 -4
- package/dist/editor/SelectionMenu.js +5 -3
- package/dist/editor/external-decorations-plugin.d.ts +23 -0
- package/dist/editor/external-decorations-plugin.js +151 -0
- package/dist/editor/omitted-scene-plugin.d.ts +9 -0
- package/dist/editor/omitted-scene-plugin.js +44 -0
- package/dist/editor/plugins.js +4 -0
- package/dist/editor/private-metadata-plugin.d.ts +10 -0
- package/dist/editor/private-metadata-plugin.js +60 -0
- package/dist/editor/prosemirror-to-sutra.js +179 -74
- package/dist/editor/scene-heading-view.js +2 -0
- package/dist/editor/schema.js +69 -12
- package/dist/editor/sutra-to-prosemirror.d.ts +2 -0
- package/dist/editor/sutra-to-prosemirror.js +32 -34
- package/dist/extensions/ai-provider.d.ts +44 -0
- package/dist/extensions/ai-provider.js +27 -0
- package/dist/extensions/collab.d.ts +21 -0
- package/dist/extensions/collab.js +6 -0
- package/dist/extensions/command-registry.d.ts +49 -0
- package/dist/extensions/command-registry.js +28 -0
- package/dist/extensions/decorations.d.ts +48 -0
- package/dist/extensions/decorations.js +6 -0
- package/dist/extensions/export-registry.d.ts +59 -0
- package/dist/extensions/export-registry.js +10 -0
- package/dist/extensions/panel-registry.d.ts +30 -0
- package/dist/extensions/panel-registry.js +16 -0
- package/dist/extensions/registries.d.ts +15 -0
- package/dist/extensions/registries.js +12 -0
- package/dist/extensions/registry-store.d.ts +26 -0
- package/dist/extensions/registry-store.js +35 -0
- package/dist/extensions/session.d.ts +29 -0
- package/dist/extensions/session.js +20 -0
- package/dist/extensions/speech-provider.d.ts +35 -0
- package/dist/extensions/speech-provider.js +17 -0
- package/dist/file/builtin-exporters.d.ts +9 -0
- package/dist/file/builtin-exporters.js +72 -0
- package/dist/file/docx-exporter.js +7 -4
- package/dist/file/docx-importer.d.ts +9 -0
- package/dist/file/docx-importer.js +47 -19
- package/dist/file/scene-number.d.ts +5 -0
- package/dist/file/scene-number.js +8 -0
- package/dist/file/workflow-reports.js +13 -7
- package/dist/findreplace/FindReplace.js +5 -2
- package/dist/import/AIImportDialog.d.ts +11 -0
- package/dist/import/AIImportDialog.js +112 -0
- package/dist/import/ai-import.d.ts +51 -0
- package/dist/import/ai-import.js +200 -0
- package/dist/import/apply-import.d.ts +7 -0
- package/dist/import/apply-import.js +35 -0
- package/dist/import/extract.d.ts +17 -0
- package/dist/import/extract.js +18 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +7 -0
- package/dist/locales/bn.json +8 -0
- package/dist/locales/en.json +8 -0
- package/dist/locales/gu.json +8 -0
- package/dist/locales/hi.json +8 -0
- package/dist/locales/kn.json +8 -0
- package/dist/locales/ml.json +8 -0
- package/dist/locales/mr.json +8 -0
- package/dist/locales/or.json +8 -0
- package/dist/locales/pa.json +8 -0
- package/dist/locales/si.json +8 -0
- package/dist/locales/ta.json +8 -0
- package/dist/locales/te.json +8 -0
- package/dist/navigator/SceneNavigator.js +125 -131
- package/dist/navigator/StatisticsDialog.js +5 -1
- package/dist/navigator/scene-edits.d.ts +18 -0
- package/dist/navigator/scene-edits.js +143 -0
- package/dist/navigator/scene-list.js +4 -4
- package/dist/navigator/scene-numbering.d.ts +37 -0
- package/dist/navigator/scene-numbering.js +88 -0
- package/dist/settings/SettingsDialog.js +8 -120
- package/dist/shell/AppBar.js +21 -2
- package/dist/shell/AppShell.js +18 -4
- package/dist/shell/ElementToolbar.d.ts +5 -0
- package/dist/shell/ElementToolbar.js +35 -25
- package/dist/shell/LanguageStatus.js +6 -0
- package/dist/shell/StatusBar.js +11 -6
- package/dist/shell/builtin-commands.d.ts +10 -0
- package/dist/shell/builtin-commands.js +43 -0
- package/dist/shell/icons.d.ts +6 -0
- package/dist/shell/icons.js +6 -0
- package/dist/shell/use-commands.d.ts +7 -0
- package/dist/shell/use-commands.js +24 -0
- package/dist/shell/use-panel-context.d.ts +3 -0
- package/dist/shell/use-panel-context.js +39 -0
- package/dist/source/SourceView.js +18 -1
- package/dist/styles/screenplay.css +113 -1
- package/dist/titlepage/ExportDialog.d.ts +5 -0
- package/dist/titlepage/ExportDialog.js +59 -96
- package/package.json +32 -5
- package/dist/ai/OnboardingDialog.d.ts +0 -6
- package/dist/ai/OnboardingDialog.js +0 -115
- package/dist/ai/ai-client.d.ts +0 -71
- package/dist/ai/ai-client.js +0 -494
- package/dist/ai/generated-providers.json +0 -3696
- package/dist/ai/voice-service.d.ts +0 -15
- package/dist/ai/voice-service.js +0 -81
- package/dist/editor/character-view.d.ts +0 -35
- package/dist/editor/character-view.js +0 -112
- package/dist/editor/transliterate-input.d.ts +0 -10
- package/dist/editor/transliterate-input.js +0 -88
- package/dist/extensions/types.d.ts +0 -73
- package/dist/extensions/types.js +0 -12
- package/scripts/copy-assets.mjs +0 -16
- package/scripts/generate-providers.mjs +0 -127
- package/src/ai/OnboardingDialog.tsx +0 -390
- package/src/ai/VoiceConfirmDialog.tsx +0 -137
- package/src/ai/VoiceToolbar.tsx +0 -321
- package/src/ai/ai-client.ts +0 -596
- package/src/ai/commands.ts +0 -164
- package/src/ai/generated-providers.json +0 -3696
- package/src/ai/prompts.ts +0 -62
- package/src/ai/voice-service.ts +0 -96
- package/src/autocomplete/AutocompletePlugin.ts +0 -21
- package/src/autocomplete/suggestions.ts +0 -147
- package/src/context/DocumentContext.tsx +0 -648
- package/src/desktop-api.d.ts +0 -40
- package/src/editor/EditorView.tsx +0 -82
- package/src/editor/SelectionMenu.tsx +0 -152
- package/src/editor/attr-pencil-plugin.ts +0 -172
- package/src/editor/character-decoration-plugin.ts +0 -104
- package/src/editor/editor-bus.ts +0 -51
- package/src/editor/find-highlight-plugin.ts +0 -68
- package/src/editor/frontmatter-field-view.ts +0 -52
- package/src/editor/frontmatter-field.ts +0 -115
- package/src/editor/grapheme-cursor.ts +0 -99
- package/src/editor/ime-plugin.ts +0 -85
- package/src/editor/insert-helper.ts +0 -43
- package/src/editor/keymap.ts +0 -124
- package/src/editor/language-class-plugin.ts +0 -91
- package/src/editor/plugins.ts +0 -26
- package/src/editor/prosemirror-to-sutra.ts +0 -192
- package/src/editor/scene-heading-view.ts +0 -147
- package/src/editor/schema.ts +0 -156
- package/src/editor/sutra-to-prosemirror.ts +0 -156
- package/src/extensions/storage-adapter.ts +0 -65
- package/src/extensions/types.ts +0 -60
- package/src/file/docx-exporter.ts +0 -585
- package/src/file/docx-importer.ts +0 -313
- package/src/file/print-paginator.ts +0 -301
- package/src/file/templates.ts +0 -73
- package/src/file/workflow-reports.ts +0 -820
- package/src/findreplace/FindReplace.tsx +0 -242
- package/src/findreplace/find-engine.ts +0 -97
- package/src/findreplace/scroll-to-match.ts +0 -89
- package/src/fonts/tofu-detect.ts +0 -53
- package/src/i18n/LanguageContext.tsx +0 -207
- package/src/i18n/language-detect.ts +0 -73
- package/src/i18n/useTranslation.tsx +0 -74
- package/src/index.ts +0 -27
- package/src/locales/bn.json +0 -143
- package/src/locales/en.json +0 -143
- package/src/locales/gu.json +0 -143
- package/src/locales/hi.json +0 -143
- package/src/locales/kn.json +0 -143
- package/src/locales/ml.json +0 -143
- package/src/locales/mr.json +0 -143
- package/src/locales/or.json +0 -143
- package/src/locales/pa.json +0 -143
- package/src/locales/si.json +0 -143
- package/src/locales/ta.json +0 -143
- package/src/locales/te.json +0 -143
- package/src/navigator/SceneNavigator.tsx +0 -844
- package/src/navigator/StatisticsDialog.tsx +0 -249
- package/src/navigator/scene-list.ts +0 -84
- package/src/settings/SettingsDialog.tsx +0 -303
- package/src/shell/AppBar.tsx +0 -354
- package/src/shell/AppShell.tsx +0 -71
- package/src/shell/ConfirmDialog.tsx +0 -71
- package/src/shell/ElementToolbar.tsx +0 -175
- package/src/shell/LanguageStatus.tsx +0 -101
- package/src/shell/StatusBar.tsx +0 -135
- package/src/shell/SutrataLogo.tsx +0 -89
- package/src/shell/Toast.tsx +0 -46
- package/src/shell/ToolbarButton.tsx +0 -30
- package/src/shell/VersionHistory.tsx +0 -212
- package/src/shell/Watermark.tsx +0 -9
- package/src/shell/fonts/fonts.css +0 -1133
- package/src/shell/icons.tsx +0 -335
- package/src/shell/toolbar-actions.ts +0 -59
- package/src/source/SourceView.tsx +0 -111
- package/src/source/find-highlight-source.ts +0 -57
- package/src/source/sutra-lang.ts +0 -121
- package/src/spellcheck/dictionaries.ts +0 -21
- package/src/spellcheck/hunspell.ts +0 -110
- package/src/spellcheck/spellcheck-plugin.ts +0 -83
- package/src/styles/StyleDialog.tsx +0 -148
- package/src/styles/builtin-styles.ts +0 -139
- package/src/styles/css-adapter.ts +0 -76
- package/src/styles/docx-adapter.ts +0 -109
- package/src/styles/embed-fonts.ts +0 -81
- package/src/styles/pagination.ts +0 -27
- package/src/styles/print-adapter.ts +0 -288
- package/src/styles/registry.ts +0 -20
- package/src/styles/screenplay.css +0 -3006
- package/src/styles/script-ranges.ts +0 -17
- package/src/styles/types.ts +0 -104
- package/src/styles/validate.ts +0 -114
- package/src/templates/templates.ts +0 -194
- package/src/titlepage/ExportDialog.tsx +0 -494
- package/src/titlepage/TitlePageDialog.tsx +0 -312
- package/src/titlepage/frontmatter-form.ts +0 -280
- package/src/titlepage/languages.ts +0 -37
- package/src/types.ts +0 -7
- package/tests/AppBar.test.tsx +0 -35
- package/tests/AppShell.test.tsx +0 -22
- package/tests/ElementToolbar.test.tsx +0 -36
- package/tests/LanguageStatus.test.tsx +0 -129
- package/tests/SelectionMenu.test.tsx +0 -71
- package/tests/ToolbarButton.test.tsx +0 -24
- package/tests/ai-onboarding.test.ts +0 -102
- package/tests/ai-status.test.tsx +0 -72
- package/tests/ai-structured.test.ts +0 -94
- package/tests/autocomplete/AutocompletePlugin.test.ts +0 -20
- package/tests/autocomplete/suggestions.test.ts +0 -106
- package/tests/document-context.test.tsx +0 -565
- package/tests/docx-importer.test.ts +0 -160
- package/tests/editor/character-decoration-plugin.test.ts +0 -101
- package/tests/editor/character-extension.test.ts +0 -44
- package/tests/editor/grapheme-cursor.test.ts +0 -118
- package/tests/editor/ime-plugin.test.ts +0 -72
- package/tests/editor/inline-note-bridge.test.ts +0 -32
- package/tests/editor/language-class-plugin.test.ts +0 -265
- package/tests/editor/title-page.test.ts +0 -124
- package/tests/embed-fonts.test.ts +0 -17
- package/tests/exporters.test.ts +0 -365
- package/tests/findreplace/FindReplace.test.tsx +0 -391
- package/tests/findreplace/find-engine.test.ts +0 -86
- package/tests/findreplace/scroll-to-match.test.ts +0 -50
- package/tests/fonts/tofu-detect.test.ts +0 -122
- package/tests/i18n/LanguageContext.test.tsx +0 -146
- package/tests/i18n/language-detect.test.ts +0 -54
- package/tests/i18n/locale-parity.test.ts +0 -25
- package/tests/i18n/useTranslation.test.tsx +0 -45
- package/tests/keymap.test.ts +0 -18
- package/tests/navigator/SceneNavigator.test.tsx +0 -177
- package/tests/navigator/StatisticsDialog.test.tsx +0 -63
- package/tests/navigator/scene-list.test.ts +0 -71
- package/tests/print-paginator.test.ts +0 -82
- package/tests/shell/VersionHistory.test.tsx +0 -129
- package/tests/spellcheck/hunspell.test.ts +0 -82
- package/tests/spellcheck/spellcheck-plugin.test.ts +0 -56
- package/tests/styles.test.ts +0 -315
- package/tests/templates/templates.test.ts +0 -83
- package/tests/titlepage/TitlePageDialog.test.tsx +0 -90
- package/tests/titlepage/apply-title-page.test.ts +0 -61
- package/tests/titlepage/frontmatter-form.test.ts +0 -115
- package/tests/toolbar-actions.test.ts +0 -93
- package/tests/voice-commands.test.ts +0 -58
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -11
- package/vitest.setup.ts +0 -65
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @sutrata/editor
|
|
2
|
+
|
|
3
|
+
The React screenplay editor behind [Sutrata](https://github.com/sutrata/sutrata), for writing scripts in **Sutra**, a plain-text screenplay format for world languages. It's built on ProseMirror and handles Indic and other complex scripts properly:
|
|
4
|
+
- Text is stored in logical order.
|
|
5
|
+
- The cursor and selection move by grapheme cluster (user-perceived character).
|
|
6
|
+
- Find/replace matches after NFC normalization, so differently encoded forms of the same text still match.
|
|
7
|
+
|
|
8
|
+
This package contains the complete editor:
|
|
9
|
+
- WYSIWYG and source modes, with smart Enter that cycles through screenplay elements.
|
|
10
|
+
- A scene navigator, find/replace, a title-page form and screenplay styles.
|
|
11
|
+
- Statistics, autocomplete and spellcheck.
|
|
12
|
+
- Export to PDF, DOCX, Fountain and workflow reports, including romanized exports.
|
|
13
|
+
- Localized UI.
|
|
14
|
+
|
|
15
|
+
Sutra parsing and serialization come from [`@sutrata/parser`](https://www.npmjs.com/package/@sutrata/parser).
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @sutrata/editor react react-dom
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`react` and `react-dom` (^18.3) are peer dependencies.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
The editor doesn't read or write files itself. You pass it a `StorageAdapter`, and it saves, opens and keeps version history through that adapter. The same editor can therefore run on local files, in a desktop shell or against a server.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import React from 'react'
|
|
31
|
+
import { createRoot } from 'react-dom/client'
|
|
32
|
+
import {
|
|
33
|
+
TranslationProvider,
|
|
34
|
+
LanguageProvider,
|
|
35
|
+
DocumentProvider,
|
|
36
|
+
AppShell,
|
|
37
|
+
type StorageAdapter,
|
|
38
|
+
} from '@sutrata/editor'
|
|
39
|
+
import '@sutrata/editor/dist/shell/fonts/fonts.css'
|
|
40
|
+
import '@sutrata/editor/dist/styles/screenplay.css'
|
|
41
|
+
|
|
42
|
+
const storageAdapter: StorageAdapter = {
|
|
43
|
+
// documents and version history
|
|
44
|
+
saveDocument: async (path, content) => { /* ... */ },
|
|
45
|
+
loadDocument: async (path) => null,
|
|
46
|
+
listVersions: async (path) => [],
|
|
47
|
+
// file dialogs
|
|
48
|
+
openFile: async () => null, // { name, content, handle }
|
|
49
|
+
openDocx: async () => null, // { name, buffer }
|
|
50
|
+
openStyleJson: async () => null, // { name, text }
|
|
51
|
+
saveFile: async (name, content, handle) => null, // { savedName, handle }
|
|
52
|
+
// custom screenplay styles
|
|
53
|
+
saveStyle: async (style) => {},
|
|
54
|
+
loadAllStyles: async () => [],
|
|
55
|
+
deleteStyle: async (id) => {},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
createRoot(document.getElementById('root')!).render(
|
|
59
|
+
<TranslationProvider>
|
|
60
|
+
<LanguageProvider>
|
|
61
|
+
<DocumentProvider storageAdapter={storageAdapter}>
|
|
62
|
+
<AppShell />
|
|
63
|
+
</DocumentProvider>
|
|
64
|
+
</LanguageProvider>
|
|
65
|
+
</TranslationProvider>,
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`StorageAdapter` also has optional methods: `saveVersion`, `deleteAllVersions`, `persistFileHandle` and `restoreFileHandle`. For a complete implementation built on IndexedDB and the File System Access API, see [`local-storage-adapter.ts`](https://github.com/sutrata/sutrata/blob/main/packages/app/src/local-storage-adapter.ts) in the Sutrata app.
|
|
70
|
+
|
|
71
|
+
Inside the providers, `useDocument()` gives you the current Sutra text, its parsed AST and the editor state.
|
|
72
|
+
|
|
73
|
+
### Fonts
|
|
74
|
+
|
|
75
|
+
`fonts.css` declares Noto font faces for every supported script, loaded from `/fonts/*.woff2` on your own site. This package does not include the font files. Copy them from [`packages/app/public/fonts`](https://github.com/sutrata/sutrata/tree/main/packages/app/public/fonts) into your app's `/fonts/` path, or use your own `@font-face` rules. Without them, some scripts may display as missing-glyph boxes (tofu).
|
|
76
|
+
|
|
77
|
+
## Extension points
|
|
78
|
+
|
|
79
|
+
Beyond `StorageAdapter`, the package exports type definitions for more extension points: `AIProvider`, `SpeechProvider`, `SessionContext`, `PanelRegistry`, `CommandRegistry`, `ExportRegistry`, `CollabBinding` and `DecorationProvider`. **These are type-only previews and are not wired into the editor yet.** Their shapes may change before 1.0.
|
|
80
|
+
|
|
81
|
+
## Status
|
|
82
|
+
|
|
83
|
+
Pre-1.0. The API may change between minor versions.
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
[GPL-3.0-or-later](https://github.com/sutrata/sutrata/blob/main/packages/editor/LICENSE).
|
|
@@ -5,5 +5,6 @@ interface Props {
|
|
|
5
5
|
onConfirm: (formattedText: string) => void;
|
|
6
6
|
onCancel: () => void;
|
|
7
7
|
}
|
|
8
|
+
/** Rendered only where useAI() is non-null (VoiceToolbar, SelectionMenu's Format). */
|
|
8
9
|
export declare function VoiceConfirmDialog({ rawTranscript, langCode, onConfirm, onCancel }: Props): React.JSX.Element;
|
|
9
10
|
export {};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { useAI } from '../extensions/ai-provider';
|
|
4
|
+
import { formatTranscript as formatWithAI } from './format-transcript';
|
|
5
5
|
import { SparklesIcon } from '../shell/icons';
|
|
6
|
+
/** Rendered only where useAI() is non-null (VoiceToolbar, SelectionMenu's Format). */
|
|
6
7
|
export function VoiceConfirmDialog({ rawTranscript, langCode, onConfirm, onCancel }) {
|
|
8
|
+
const ai = useAI();
|
|
7
9
|
const [formattedText, setFormattedText] = useState('');
|
|
8
10
|
const [isLoading, setIsLoading] = useState(true);
|
|
9
11
|
const [error, setError] = useState('');
|
|
@@ -15,9 +17,7 @@ export function VoiceConfirmDialog({ rawTranscript, langCode, onConfirm, onCance
|
|
|
15
17
|
setIsLoading(true);
|
|
16
18
|
setError('');
|
|
17
19
|
try {
|
|
18
|
-
const
|
|
19
|
-
const prompt = `Raw Voice Transcript (${langCode}):\n"${rawTranscript}"`;
|
|
20
|
-
const result = await callAI(prompt, VOICE_FORMAT_SYSTEM_PROMPT, config, { clean: 'voice' });
|
|
20
|
+
const result = await formatWithAI(ai, rawTranscript, langCode);
|
|
21
21
|
setFormattedText(result.trim());
|
|
22
22
|
}
|
|
23
23
|
catch (e) {
|
|
@@ -33,5 +33,5 @@ export function VoiceConfirmDialog({ rawTranscript, langCode, onConfirm, onCance
|
|
|
33
33
|
localStorage.setItem('sutrata_always_auto_insert', alwaysAutoInsert ? 'true' : 'false');
|
|
34
34
|
onConfirm(formattedText);
|
|
35
35
|
};
|
|
36
|
-
return (_jsx("div", { className: "cs-dialog-overlay", onClick: onCancel, style: { zIndex: 310 }, children: _jsxs("div", { className: "cs-dialog cs-voice-confirm-dialog", role: "dialog", "aria-modal": "true", "aria-label": "
|
|
36
|
+
return (_jsx("div", { className: "cs-dialog-overlay", onClick: onCancel, style: { zIndex: 310 }, children: _jsxs("div", { className: "cs-dialog cs-voice-confirm-dialog", role: "dialog", "aria-modal": "true", "aria-label": "Review Formatting", onClick: e => e.stopPropagation(), children: [_jsxs("div", { className: "cs-dialog-header", children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx(SparklesIcon, { size: 16 }), _jsx("span", { className: "cs-dialog-title", children: "Review Formatting" })] }), _jsx("button", { type: "button", className: "cs-fr-close", onClick: onCancel, "aria-label": "Close", children: "\u00D7" })] }), _jsx("div", { className: "cs-voice-confirm-body", children: isLoading ? (_jsxs("div", { className: "cs-voice-loading-state", children: [_jsx("div", { className: "cs-spinner", style: { width: '28px', height: '28px', borderWidth: '2.5px' } }), _jsx("span", { style: { fontSize: '13px', color: 'var(--cs-ui-text-dark-secondary, #a8a69d)' }, children: "Formatting into Sutra screenplay format..." })] })) : (_jsxs(_Fragment, { children: [error && (_jsxs("div", { className: "cs-voice-error", role: "alert", children: ["AI formatting notice: ", error, " (showing raw input below)"] })), _jsxs("div", { className: "cs-voice-grid", children: [_jsxs("div", { className: "cs-voice-column", children: [_jsx("span", { className: "cs-voice-col-title", children: "Raw Content" }), _jsx("div", { className: "cs-voice-box-raw", tabIndex: 0, children: rawTranscript })] }), _jsxs("div", { className: "cs-voice-column", children: [_jsx("span", { className: "cs-voice-col-title", children: "Formatted Sutra" }), _jsx("textarea", { className: "cs-voice-box-formatted", value: formattedText, onChange: e => setFormattedText(e.target.value), rows: 8 })] })] }), _jsx("div", { className: "cs-voice-options", children: _jsxs("label", { className: "cs-voice-checkbox-label", children: [_jsx("input", { type: "checkbox", checked: alwaysAutoInsert, onChange: e => setAlwaysAutoInsert(e.target.checked) }), _jsx("span", { children: "Always auto-insert directly into screenplay (skip review)" })] }) }), _jsxs("div", { className: "cs-dialog-actions", style: { display: 'flex', justifyContent: 'flex-end', gap: '8px', marginTop: '12px' }, children: [_jsx("button", { type: "button", className: "cs-voice-btn-ghost", onClick: onCancel, children: "Discard" }), _jsx("button", { type: "button", className: "cs-voice-btn-primary", onClick: handleConfirm, children: "Insert Into Screenplay" })] })] })) })] }) }));
|
|
37
37
|
}
|
package/dist/ai/VoiceToolbar.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { useDocument } from '../context/DocumentContext';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { formatTranscript } from './format-transcript';
|
|
5
|
+
import { useAI } from '../extensions/ai-provider';
|
|
6
|
+
import { useSpeech } from '../extensions/speech-provider';
|
|
7
7
|
import { insertSutra } from '../editor/insert-helper';
|
|
8
8
|
import { VoiceConfirmDialog } from './VoiceConfirmDialog';
|
|
9
9
|
import { LOCALIZED_COMMANDS } from './commands';
|
|
@@ -20,8 +20,12 @@ const VOICE_LANGUAGES = [
|
|
|
20
20
|
{ code: 'gu-IN', label: 'ગુજરાતી (Gujarati)', base: 'gu' },
|
|
21
21
|
{ code: 'mr-IN', label: 'मराठी (Marathi)', base: 'mr' },
|
|
22
22
|
];
|
|
23
|
+
/** Rendered by AppShell only when useSpeech() (which implies useAI()) is non-null. */
|
|
23
24
|
export function VoiceToolbar() {
|
|
24
|
-
const { mode, setVoiceActive,
|
|
25
|
+
const { mode, setVoiceActive, showToast } = useDocument();
|
|
26
|
+
const ai = useAI();
|
|
27
|
+
const speech = useSpeech();
|
|
28
|
+
const sessionRef = useRef(null);
|
|
25
29
|
const { locale } = useTranslation();
|
|
26
30
|
const defaultLang = VOICE_LANGUAGES.find(l => l.base === locale)?.code || 'en-US';
|
|
27
31
|
const [langCode, setLangCode] = useState(defaultLang);
|
|
@@ -36,7 +40,7 @@ export function VoiceToolbar() {
|
|
|
36
40
|
setLangCode(matched);
|
|
37
41
|
}, [locale]);
|
|
38
42
|
const canvasRef = useRef(null);
|
|
39
|
-
const isSupported =
|
|
43
|
+
const isSupported = speech.isSupported();
|
|
40
44
|
// Audio waveform animation
|
|
41
45
|
useEffect(() => {
|
|
42
46
|
if (!isListening)
|
|
@@ -77,9 +81,7 @@ export function VoiceToolbar() {
|
|
|
77
81
|
if (alwaysAuto) {
|
|
78
82
|
setIsFormatting(true);
|
|
79
83
|
try {
|
|
80
|
-
const
|
|
81
|
-
const prompt = `Raw Voice Transcript (${langCode}):\n"${fullTranscript}"`;
|
|
82
|
-
const result = await callAI(prompt, VOICE_FORMAT_SYSTEM_PROMPT, config, { clean: 'voice' });
|
|
84
|
+
const result = await formatTranscript(ai, fullTranscript, langCode);
|
|
83
85
|
insertSutra(result.trim(), mode);
|
|
84
86
|
setVoiceActive(false);
|
|
85
87
|
showToast('Voice dictation inserted.', 'success');
|
|
@@ -97,16 +99,20 @@ export function VoiceToolbar() {
|
|
|
97
99
|
}
|
|
98
100
|
};
|
|
99
101
|
const handleStart = async () => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
if (!(await ai.isConfigured())) {
|
|
103
|
+
if (ai.openSetup) {
|
|
104
|
+
showToast('AI must be set up to format dictated text. Opening Setup...', 'info');
|
|
105
|
+
ai.openSetup();
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
showToast(`${ai.displayName} is not available right now.`, 'warn');
|
|
109
|
+
}
|
|
104
110
|
return;
|
|
105
111
|
}
|
|
106
112
|
setError('');
|
|
107
113
|
setInterimResult('');
|
|
108
114
|
setIsListening(true);
|
|
109
|
-
|
|
115
|
+
sessionRef.current = speech.listen(langCode, {
|
|
110
116
|
onResult: (text, isFinal) => {
|
|
111
117
|
if (isFinal) {
|
|
112
118
|
setTranscript(prev => (prev ? prev + ' ' + text : text));
|
|
@@ -131,7 +137,7 @@ export function VoiceToolbar() {
|
|
|
131
137
|
});
|
|
132
138
|
};
|
|
133
139
|
const handleStopListening = async () => {
|
|
134
|
-
|
|
140
|
+
sessionRef.current?.stop();
|
|
135
141
|
setIsListening(false);
|
|
136
142
|
const fullTranscript = (transcript + ' ' + interimResult).trim();
|
|
137
143
|
if (fullTranscript) {
|
|
@@ -147,7 +153,7 @@ export function VoiceToolbar() {
|
|
|
147
153
|
await performFormatting(fullTranscript);
|
|
148
154
|
};
|
|
149
155
|
const handleCancel = () => {
|
|
150
|
-
|
|
156
|
+
sessionRef.current?.abort();
|
|
151
157
|
setIsListening(false);
|
|
152
158
|
setVoiceActive(false);
|
|
153
159
|
};
|
|
@@ -169,7 +175,7 @@ export function VoiceToolbar() {
|
|
|
169
175
|
return (_jsxs("div", { className: "cs-voice-toolbar", role: "region", "aria-label": "Voice Dictation Toolbar", children: [_jsxs("div", { className: "cs-voice-toolbar-row", children: [_jsxs("div", { className: "cs-voice-toolbar-badge", children: [_jsx(MicIcon, { size: 14 }), _jsx("span", { children: "Voice Dictation" })] }), _jsx("select", { className: "cs-voice-select", value: langCode, onChange: e => setLangCode(e.target.value), disabled: isListening, "aria-label": "Voice Language", children: VOICE_LANGUAGES.map(l => (_jsx("option", { value: l.code, children: l.label }, l.code))) }), isListening ? (_jsxs("div", { className: "cs-voice-listening-group", children: [_jsx("button", { type: "button", className: "cs-voice-btn-danger", onClick: handleStopListening, children: "Stop & Format" }), _jsx("canvas", { ref: canvasRef, width: 110, height: 26, className: "cs-voice-waveform", role: "img", "aria-label": "Audio waveform visualization" })] })) : (_jsxs("div", { className: "cs-voice-actions-group", children: [_jsxs("button", { type: "button", className: "cs-voice-btn-primary", onClick: handleStart, disabled: !isSupported || isFormatting, children: [_jsx(MicIcon, { size: 14 }), _jsx("span", { children: isFormatting ? 'Formatting...' : 'Start Dictation' })] }), transcript && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "cs-voice-btn-secondary", onClick: handleFormatCaptured, disabled: isFormatting, children: "Re-format" }), _jsx("button", { type: "button", className: "cs-voice-btn-ghost", onClick: () => {
|
|
170
176
|
setTranscript('');
|
|
171
177
|
setError('');
|
|
172
|
-
}, children: "Clear" })] }))] })), _jsx("button", { type: "button", className: "cs-voice-btn-ghost", onClick: handleCancel, children: "Close" }), !isSupported && (
|
|
178
|
+
}, children: "Clear" })] }))] })), _jsx("button", { type: "button", className: "cs-voice-btn-ghost", onClick: handleCancel, children: "Close" }), !isSupported && (_jsxs("span", { className: "cs-voice-warning", children: [speech.displayName, " is not supported in this browser."] }))] }), (isListening || transcript || interimResult) && (isListening ? (_jsxs("div", { className: "cs-voice-transcript-box", children: [transcript, interimResult && _jsxs("span", { className: "cs-voice-interim", children: [" ", interimResult] }), !transcript && !interimResult && _jsx("span", { className: "cs-voice-placeholder", children: "Speak now to dictate..." })] })) : (_jsx("textarea", { className: "cs-voice-textarea", value: transcript, onChange: (e) => setTranscript(e.target.value), placeholder: "Edit captured text before formatting...", rows: 2 }))), error && (_jsx("div", { className: "cs-voice-error", role: "alert", children: error })), _jsxs("div", { className: "cs-voice-cheatsheet", children: [_jsx("span", { className: "cs-voice-cheatsheet-title", children: "Commands:" }), cheatsheet.map(c => {
|
|
173
179
|
if (c.type === 'literal')
|
|
174
180
|
return null;
|
|
175
181
|
return (_jsxs("span", { className: "cs-voice-tag", children: [_jsx("span", { className: "cs-voice-tag-type", children: c.type }), ": \u201C", c.keywords[0], "\u201D"] }, c.type));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip the chatter models wrap around Sutra output (code fences, list
|
|
3
|
+
* bullets, restated rules, "->" reasoning lines), keeping the blank lines
|
|
4
|
+
* that separate Sutra blocks. Applied to every AI formatting result before
|
|
5
|
+
* it is inserted, whichever AIProvider produced it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function cleanVoiceResponse(text: string): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip the chatter models wrap around Sutra output (code fences, list
|
|
3
|
+
* bullets, restated rules, "->" reasoning lines), keeping the blank lines
|
|
4
|
+
* that separate Sutra blocks. Applied to every AI formatting result before
|
|
5
|
+
* it is inserted, whichever AIProvider produced it.
|
|
6
|
+
*/
|
|
7
|
+
export function cleanVoiceResponse(text) {
|
|
8
|
+
let cleaned = text.trim();
|
|
9
|
+
// Strip markdown code block wrapper if present
|
|
10
|
+
cleaned = cleaned.replace(/^```[a-zA-Z0-9]*\n/, '').replace(/\n```$/, '');
|
|
11
|
+
const lines = cleaned.split('\n');
|
|
12
|
+
const filteredLines = lines.map(line => {
|
|
13
|
+
let l = line.trim();
|
|
14
|
+
// Strip leading list bullet (e.g. *, -, +, •) and spaces
|
|
15
|
+
l = l.replace(/^[*•\-+]\s*/, '').trim();
|
|
16
|
+
// Strip leading/trailing backticks if any
|
|
17
|
+
l = l.replace(/^`+/, '').replace(/`+$/, '').trim();
|
|
18
|
+
// Strip common explanatory trailing comments in parens
|
|
19
|
+
l = l.replace(/\s*\(Standard screenplay shorthand[^)]*\)/gi, '');
|
|
20
|
+
l = l.replace(/\s*\(Standard shorthand[^)]*\)/gi, '');
|
|
21
|
+
return l;
|
|
22
|
+
}).filter(line => {
|
|
23
|
+
const trimmed = line.toLowerCase();
|
|
24
|
+
// Keep blank lines: they separate Sutra blocks (a blank line ends dialogue).
|
|
25
|
+
if (!trimmed)
|
|
26
|
+
return true;
|
|
27
|
+
// Discard reasoning/mapping/explanation patterns
|
|
28
|
+
if (trimmed.includes('->') ||
|
|
29
|
+
trimmed.includes('=>') ||
|
|
30
|
+
trimmed.startsWith('input:') ||
|
|
31
|
+
trimmed.startsWith('task:') ||
|
|
32
|
+
trimmed.startsWith('rules:') ||
|
|
33
|
+
trimmed.startsWith('instructions:') ||
|
|
34
|
+
trimmed.startsWith('sutra rules:') ||
|
|
35
|
+
trimmed.startsWith('scene headings:') ||
|
|
36
|
+
trimmed.startsWith('scene id:') ||
|
|
37
|
+
trimmed.startsWith('characters:') ||
|
|
38
|
+
trimmed.startsWith('parentheticals:') ||
|
|
39
|
+
trimmed.startsWith('dialogue:') ||
|
|
40
|
+
trimmed.startsWith('transitions:') ||
|
|
41
|
+
trimmed.startsWith('lyrics:') ||
|
|
42
|
+
trimmed.startsWith('action:') ||
|
|
43
|
+
trimmed.startsWith('format:') ||
|
|
44
|
+
trimmed.startsWith('components:') ||
|
|
45
|
+
trimmed.startsWith('output only') ||
|
|
46
|
+
trimmed.startsWith('no markdown') ||
|
|
47
|
+
trimmed.includes('convert it into valid sutra') ||
|
|
48
|
+
trimmed.includes('sutra syntax rules') ||
|
|
49
|
+
trimmed.includes('clearly a scene heading') ||
|
|
50
|
+
trimmed.includes('screenplay shorthand') ||
|
|
51
|
+
trimmed.includes('append as')) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
// Collapse runs of blank lines left behind by discarded explanation lines.
|
|
57
|
+
return filteredLines.join('\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
58
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { VOICE_FORMAT_SYSTEM_PROMPT } from './prompts';
|
|
2
|
+
import { cleanVoiceResponse } from './clean-response';
|
|
3
|
+
/** Format a dictated (or selected) transcript as Sutra with the injected AI provider. */
|
|
4
|
+
export async function formatTranscript(ai, transcript, langCode) {
|
|
5
|
+
const prompt = `Raw Voice Transcript (${langCode}):\n"${transcript}"`;
|
|
6
|
+
return cleanVoiceResponse(await ai.complete({ system: VOICE_FORMAT_SYSTEM_PROMPT, prompt }));
|
|
7
|
+
}
|
package/dist/ai/prompts.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const VOICE_FORMAT_SYSTEM_PROMPT = "You are Sutrata's AI screenplay formatter.\nYour task is to take a raw voice dictation transcript (which may contain explicit formatting keywords like \"scene\", \"character\", \"dialogue\", \"parenthetical\", \"transition\") and convert it into valid Sutra screenplay format.\n\nSutra syntax rules:\n1. Scene Headings: Start with \"##\" (e.g., \"## INT. COFFEE SHOP - DAY\"). If a scene number/ID is specified (e.g., \"scene #2\", \"scene 2\", or \"scene 3A\"), append it as a Sutra attribute ID at the end of the scene heading, like \"{#2}\" or \"{#3A}\" (e.g., \"## EXT. SWIMMING POOL - DAY {#2}\").\n2.
|
|
1
|
+
export declare const VOICE_FORMAT_SYSTEM_PROMPT = "You are Sutrata's AI screenplay formatter.\nYour task is to take a raw voice dictation transcript (which may contain explicit formatting keywords like \"scene\", \"character\", \"dialogue\", \"parenthetical\", \"transition\") and convert it into valid Sutra screenplay format.\n\nSutra syntax rules (one element per block; separate blocks with a blank line):\n1. Scene Headings: Start with \"##\" (e.g., \"## INT. COFFEE SHOP - DAY\"). If a scene number/ID is specified (e.g., \"scene #2\", \"scene 2\", or \"scene 3A\"), append it as a Sutra attribute ID at the end of the scene heading, like \"{#2}\" or \"{#3A}\" (e.g., \"## EXT. SWIMMING POOL - DAY {#2}\").\n2. Scene Metadata: Lines directly under a scene heading starting with \"&\", as \"& key: value\" (e.g., \"& time: DAY\", \"& synopsis: Raj waits for Meera.\"). Only add metadata the speaker actually gave.\n3. Character Cues: Start with \"@\", alone on a line (e.g., \"@RAJ\", \"@\u092E\u0940\u0930\u093E\"). Keep the speaker's casing and script; never rely on ALL CAPS.\n4. Parentheticals: In parentheses, on a line by itself under the character cue (e.g., \"(whispering)\").\n5. Dialogue: The lines directly after the character cue or parenthetical, with no blank line in between. A blank line ends the dialogue block.\n6. Transitions: Start with \">>\" (e.g., \">> CUT TO:\").\n7. Lyrics: Prefixed with \"~ \" (e.g., \"~ Sing a song\").\n8. Action: Standard descriptive paragraph.\n\nCRITICAL INSTRUCTIONS:\n- Preserve Indic/Unicode characters and mixed-language code-switching exactly as transcribed. Do NOT translate non-English text to English.\n- Output ONLY the formatted Sutra text.\n- Do NOT include any explanations, steps, reasoning, bullet points, meta-commentary, or introductory/concluding text.\n- Do NOT output headings, drafts, prefixes, or suffixes.\n- Do NOT wrap in markdown code blocks like ```cine.\n- The output must contain nothing but the final formatted Sutra screenplay lines.\n";
|
|
2
2
|
/**
|
|
3
3
|
* Combined synopsis+duration prompt. Merged into one call (and one JSON
|
|
4
4
|
* response) instead of two separate requests to halve per-scene LLM cost.
|
|
@@ -23,3 +23,10 @@ export declare const SCENE_METADATA_SCHEMA: {
|
|
|
23
23
|
readonly required: readonly ["synopsis", "duration"];
|
|
24
24
|
readonly additionalProperties: false;
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* AI-assisted import (import/ai-import.ts): turns a chunk of a screenplay
|
|
28
|
+
* written elsewhere (Word, plain text, Markdown) into Sutra. The content
|
|
29
|
+
* check after each call compares words in and out, so the rules below about
|
|
30
|
+
* never adding, dropping or translating text are enforced, not just asked.
|
|
31
|
+
*/
|
|
32
|
+
export declare const IMPORT_FORMAT_SYSTEM_PROMPT = "You convert screenplay text into Sutra, a plain-text screenplay format. You receive one chunk of a longer script; format only that chunk.\n\nSutra syntax (separate blocks with one blank line):\n1. Scene heading: \"## \" + the heading text as written (e.g. \"## INT. COFFEE SHOP - DAY\", \"## \u0905\u0902\u0926\u0930. \u0930\u0947\u0932\u0935\u0947 \u0938\u094D\u091F\u0947\u0936\u0928 - \u0930\u093E\u0924\"). If the source numbers the scene, append the number as \"{#12}\" at the end of the heading line and drop it from the text.\n2. Action: a plain paragraph with no sigil.\n3. Character cue: \"@\" + the name exactly as written, alone on a line, optionally followed by an extension like \"(V.O.)\". Never change the name's case or script.\n4. Dialogue: the lines directly under the cue, no blank line in between.\n5. Parenthetical: \"(text)\" on its own line inside a dialogue block.\n6. Transition: \">> \" + text (e.g. \">> CUT TO:\").\n7. Centered text: \">> text <<\".\n8. Lyrics: \"~ \" at the start of every lyric line.\n9. Section/act heading: \"# \" + text.\n10. Emphasis already marked with *italic*, **bold** or _underline_ must be kept.\n\nRules:\n- Keep every word of the source, in its original language and script. Never translate, transliterate, summarise, correct, reorder or add text.\n- Do not invent scene headings, character names, metadata (\"& key: value\") or notes.\n- Page numbers, \"CONTINUED\", \"(MORE)\" and \"(CONT'D)\" page furniture may be dropped.\n- If a line's role is unclear, keep it as action.\n- Output only the Sutra text: no explanations, no code fences, no frontmatter.";
|
package/dist/ai/prompts.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export const VOICE_FORMAT_SYSTEM_PROMPT = `You are Sutrata's AI screenplay formatter.
|
|
2
2
|
Your task is to take a raw voice dictation transcript (which may contain explicit formatting keywords like "scene", "character", "dialogue", "parenthetical", "transition") and convert it into valid Sutra screenplay format.
|
|
3
3
|
|
|
4
|
-
Sutra syntax rules:
|
|
4
|
+
Sutra syntax rules (one element per block; separate blocks with a blank line):
|
|
5
5
|
1. Scene Headings: Start with "##" (e.g., "## INT. COFFEE SHOP - DAY"). If a scene number/ID is specified (e.g., "scene #2", "scene 2", or "scene 3A"), append it as a Sutra attribute ID at the end of the scene heading, like "{#2}" or "{#3A}" (e.g., "## EXT. SWIMMING POOL - DAY {#2}").
|
|
6
|
-
2.
|
|
7
|
-
3.
|
|
8
|
-
4.
|
|
9
|
-
5.
|
|
10
|
-
6.
|
|
11
|
-
7.
|
|
6
|
+
2. Scene Metadata: Lines directly under a scene heading starting with "&", as "& key: value" (e.g., "& time: DAY", "& synopsis: Raj waits for Meera."). Only add metadata the speaker actually gave.
|
|
7
|
+
3. Character Cues: Start with "@", alone on a line (e.g., "@RAJ", "@मीरा"). Keep the speaker's casing and script; never rely on ALL CAPS.
|
|
8
|
+
4. Parentheticals: In parentheses, on a line by itself under the character cue (e.g., "(whispering)").
|
|
9
|
+
5. Dialogue: The lines directly after the character cue or parenthetical, with no blank line in between. A blank line ends the dialogue block.
|
|
10
|
+
6. Transitions: Start with ">>" (e.g., ">> CUT TO:").
|
|
11
|
+
7. Lyrics: Prefixed with "~ " (e.g., "~ Sing a song").
|
|
12
|
+
8. Action: Standard descriptive paragraph.
|
|
12
13
|
|
|
13
14
|
CRITICAL INSTRUCTIONS:
|
|
14
15
|
- Preserve Indic/Unicode characters and mixed-language code-switching exactly as transcribed. Do NOT translate non-English text to English.
|
|
@@ -57,3 +58,29 @@ export const SCENE_METADATA_SCHEMA = {
|
|
|
57
58
|
required: ['synopsis', 'duration'],
|
|
58
59
|
additionalProperties: false,
|
|
59
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* AI-assisted import (import/ai-import.ts): turns a chunk of a screenplay
|
|
63
|
+
* written elsewhere (Word, plain text, Markdown) into Sutra. The content
|
|
64
|
+
* check after each call compares words in and out, so the rules below about
|
|
65
|
+
* never adding, dropping or translating text are enforced, not just asked.
|
|
66
|
+
*/
|
|
67
|
+
export const IMPORT_FORMAT_SYSTEM_PROMPT = `You convert screenplay text into Sutra, a plain-text screenplay format. You receive one chunk of a longer script; format only that chunk.
|
|
68
|
+
|
|
69
|
+
Sutra syntax (separate blocks with one blank line):
|
|
70
|
+
1. Scene heading: "## " + the heading text as written (e.g. "## INT. COFFEE SHOP - DAY", "## अंदर. रेलवे स्टेशन - रात"). If the source numbers the scene, append the number as "{#12}" at the end of the heading line and drop it from the text.
|
|
71
|
+
2. Action: a plain paragraph with no sigil.
|
|
72
|
+
3. Character cue: "@" + the name exactly as written, alone on a line, optionally followed by an extension like "(V.O.)". Never change the name's case or script.
|
|
73
|
+
4. Dialogue: the lines directly under the cue, no blank line in between.
|
|
74
|
+
5. Parenthetical: "(text)" on its own line inside a dialogue block.
|
|
75
|
+
6. Transition: ">> " + text (e.g. ">> CUT TO:").
|
|
76
|
+
7. Centered text: ">> text <<".
|
|
77
|
+
8. Lyrics: "~ " at the start of every lyric line.
|
|
78
|
+
9. Section/act heading: "# " + text.
|
|
79
|
+
10. Emphasis already marked with *italic*, **bold** or _underline_ must be kept.
|
|
80
|
+
|
|
81
|
+
Rules:
|
|
82
|
+
- Keep every word of the source, in its original language and script. Never translate, transliterate, summarise, correct, reorder or add text.
|
|
83
|
+
- Do not invent scene headings, character names, metadata ("& key: value") or notes.
|
|
84
|
+
- Page numbers, "CONTINUED", "(MORE)" and "(CONT'D)" page furniture may be dropped.
|
|
85
|
+
- If a line's role is unclear, keep it as action.
|
|
86
|
+
- Output only the Sutra text: no explanations, no code fences, no frontmatter.`;
|
|
@@ -3,7 +3,7 @@ const PREDEFINED_TRANSITIONS = [
|
|
|
3
3
|
'MATCH CUT TO:', 'WIPE TO:', 'IRIS IN:', 'IRIS OUT:',
|
|
4
4
|
];
|
|
5
5
|
const RESERVED_METADATA_KEYS = [
|
|
6
|
-
'status', 'tags', 'location', 'time', '
|
|
6
|
+
'status', 'tags', 'location', 'time', 'lang', 'mood',
|
|
7
7
|
'arc', 'note', 'locked',
|
|
8
8
|
];
|
|
9
9
|
/**
|
|
@@ -2,6 +2,14 @@ import React from 'react';
|
|
|
2
2
|
import type { DocumentNode } from '@sutrata/parser';
|
|
3
3
|
import type { EditorMode, VersionEntry } from '../types';
|
|
4
4
|
import type { StorageAdapter } from '../extensions/storage-adapter';
|
|
5
|
+
import type { AIProvider } from '../extensions/ai-provider';
|
|
6
|
+
import type { SpeechProvider } from '../extensions/speech-provider';
|
|
7
|
+
import type { SessionContext } from '../extensions/session';
|
|
8
|
+
import type { PanelRegistry, PanelContribution } from '../extensions/panel-registry';
|
|
9
|
+
import type { CommandRegistry, CommandContribution } from '../extensions/command-registry';
|
|
10
|
+
import type { ExportRegistry, ExportContribution } from '../extensions/export-registry';
|
|
11
|
+
import type { DecorationProvider } from '../extensions/decorations';
|
|
12
|
+
import type { CollabBinding } from '../extensions/collab';
|
|
5
13
|
import type { ScreenplayStyleDefinition } from '../styles/types';
|
|
6
14
|
import type { ConfirmOptions } from '../shell/ConfirmDialog';
|
|
7
15
|
import type { ToastItem } from '../shell/Toast';
|
|
@@ -17,7 +25,6 @@ interface DocumentContextValue {
|
|
|
17
25
|
navVisible: boolean;
|
|
18
26
|
exportVisible: boolean;
|
|
19
27
|
settingsVisible: boolean;
|
|
20
|
-
aiOnboardingVisible: boolean;
|
|
21
28
|
voiceActive: boolean;
|
|
22
29
|
metadataVisible: boolean;
|
|
23
30
|
findReplaceVisible: boolean;
|
|
@@ -37,7 +44,6 @@ interface DocumentContextValue {
|
|
|
37
44
|
setNavVisible: (v: boolean) => void;
|
|
38
45
|
setExportVisible: (v: boolean) => void;
|
|
39
46
|
setSettingsVisible: (v: boolean) => void;
|
|
40
|
-
setAIOnboardingVisible: (v: boolean) => void;
|
|
41
47
|
setVoiceActive: (v: boolean) => void;
|
|
42
48
|
setMetadataVisible: (v: boolean) => void;
|
|
43
49
|
setFindReplaceVisible: (visible: boolean) => void;
|
|
@@ -60,9 +66,27 @@ interface DocumentContextValue {
|
|
|
60
66
|
showToast: (message: string, type?: 'info' | 'success' | 'warn' | 'error') => void;
|
|
61
67
|
dismissToast: (id: string) => void;
|
|
62
68
|
}
|
|
63
|
-
export
|
|
69
|
+
export interface DocumentProviderProps {
|
|
64
70
|
children: React.ReactNode;
|
|
71
|
+
/** Required: where documents, versions and styles are stored. */
|
|
65
72
|
storageAdapter: StorageAdapter;
|
|
66
|
-
|
|
73
|
+
/** Optional LLM access. Without it, AI features are hidden. */
|
|
74
|
+
aiProvider?: AIProvider;
|
|
75
|
+
/** Optional speech-to-text. Voice dictation needs it and aiProvider. */
|
|
76
|
+
speechProvider?: SpeechProvider;
|
|
77
|
+
/** Optional user/permission/feature-flag context. Default: local user, `edit`. */
|
|
78
|
+
session?: SessionContext;
|
|
79
|
+
/** Optional embedder panels: a live registry, or a fixed list. */
|
|
80
|
+
panels?: PanelRegistry | PanelContribution[];
|
|
81
|
+
/** Optional embedder commands (toolbar/app-bar entries, shortcuts): a live registry, or a fixed list. */
|
|
82
|
+
commands?: CommandRegistry | CommandContribution[];
|
|
83
|
+
/** Optional embedder export formats and reports: a live registry, or a fixed list. */
|
|
84
|
+
exporters?: ExportRegistry | ExportContribution[];
|
|
85
|
+
/** Optional annotation sources (comments, breakdown, revisions). Pass a stable array. */
|
|
86
|
+
decorationProviders?: DecorationProvider[];
|
|
87
|
+
/** Optional real-time collaboration binding for the formatted editor. */
|
|
88
|
+
collabBinding?: CollabBinding;
|
|
89
|
+
}
|
|
90
|
+
export declare function DocumentProvider({ children, storageAdapter, aiProvider, speechProvider, session, panels, commands, exporters, decorationProviders, collabBinding, }: DocumentProviderProps): React.JSX.Element;
|
|
67
91
|
export declare function useDocument(): DocumentContextValue;
|
|
68
92
|
export {};
|