@sutrata/editor 0.1.2 → 0.3.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/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 +34 -5
- package/dist/context/DocumentContext.js +152 -51
- package/dist/editor/EditorView.js +35 -5
- package/dist/editor/SelectionMenu.js +5 -3
- package/dist/editor/editor-bus.d.ts +3 -0
- package/dist/editor/editor-bus.js +13 -0
- package/dist/editor/external-change.d.ts +11 -0
- package/dist/editor/external-change.js +105 -0
- package/dist/editor/external-decorations-plugin.d.ts +23 -0
- package/dist/editor/external-decorations-plugin.js +151 -0
- package/dist/editor/frontmatter-field-view.js +3 -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 +13 -0
- package/dist/editor/private-metadata-plugin.js +100 -0
- package/dist/editor/prosemirror-to-sutra.js +179 -74
- package/dist/editor/scene-heading-view.js +2 -0
- package/dist/editor/schema.js +74 -13
- 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 +32 -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/extensions/storage-adapter.d.ts +36 -1
- 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 +18 -3
- 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 +25 -4
- 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 +39 -3
- package/dist/styles/screenplay.css +114 -1
- package/dist/titlepage/ExportDialog.d.ts +5 -0
- package/dist/titlepage/ExportDialog.js +59 -96
- package/dist/titlepage/frontmatter-form.js +1 -1
- package/package.json +4 -4
- 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
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
3
3
|
import { useDocument } from '../context/DocumentContext';
|
|
4
|
-
import {
|
|
4
|
+
import { parse, romanizeSutra } from '@sutrata/parser';
|
|
5
5
|
import { useTranslation } from '../i18n/useTranslation';
|
|
6
|
-
import { exportToDocx } from '../file/docx-exporter';
|
|
7
|
-
import { extractWorkflowData, exportOneLinerCsv, exportShotListCsv, exportCastReportCsv, exportOneLinerDocx, exportShotListDocx, exportCastReportDocx, openPrintPreview, openScreenplayPrintPreview, } from '../file/workflow-reports';
|
|
8
6
|
import { allStyles } from '../styles/registry';
|
|
7
|
+
import { useExportRegistry } from '../extensions/export-registry';
|
|
8
|
+
import { useRegistryList, withBuiltins } from '../extensions/registry-store';
|
|
9
|
+
import { builtinExporters } from '../file/builtin-exporters';
|
|
10
|
+
/**
|
|
11
|
+
* Renders every export format from one list: the editor's built-ins
|
|
12
|
+
* (file/builtin-exporters.ts) plus the embedder's ExportRegistry entries.
|
|
13
|
+
* Screenplay formats are cards; reports are grouped by `family` with chips.
|
|
14
|
+
*/
|
|
9
15
|
export function ExportDialog() {
|
|
10
16
|
const { ast, text, filePath, setExportVisible, resolvedStyle, customStyles, showToast } = useDocument();
|
|
11
17
|
const { t } = useTranslation();
|
|
18
|
+
const registered = useRegistryList(useExportRegistry());
|
|
19
|
+
const exporters = useMemo(() => withBuiltins(builtinExporters(t), registered), [t, registered]);
|
|
20
|
+
const screenplayFormats = exporters.filter(e => e.group === 'screenplay');
|
|
21
|
+
const reportFormats = exporters.filter(e => e.group === 'report');
|
|
22
|
+
const reportFamilies = useMemo(() => {
|
|
23
|
+
const families = new Map();
|
|
24
|
+
for (const e of reportFormats)
|
|
25
|
+
families.set(e.family ?? e.label, [...(families.get(e.family ?? e.label) ?? []), e]);
|
|
26
|
+
return [...families];
|
|
27
|
+
}, [reportFormats]);
|
|
12
28
|
const [tab, setTab] = useState('screenplay');
|
|
13
|
-
const [
|
|
29
|
+
const [formatId, setFormatId] = useState(screenplayFormats[0]?.id ?? '');
|
|
14
30
|
const [warnings, setWarnings] = useState([]);
|
|
15
31
|
const [isExporting, setIsExporting] = useState(false);
|
|
16
32
|
const [styleOverride, setStyleOverride] = useState('');
|
|
@@ -20,7 +36,11 @@ export function ExportDialog() {
|
|
|
20
36
|
const [skipNotes, setSkipNotes] = useState(false);
|
|
21
37
|
const hasIndicText = /[\u0900-\u0DFF]/.test(text);
|
|
22
38
|
const styleChoices = allStyles(customStyles);
|
|
23
|
-
const
|
|
39
|
+
const selected = exporters.find(e => e.id === formatId) ?? screenplayFormats[0];
|
|
40
|
+
const showStylePicker = tab === 'screenplay' && !!selected?.supports?.style;
|
|
41
|
+
const showSkipNotes = tab === 'screenplay' && !!selected?.supports?.skipNotes;
|
|
42
|
+
const showRomanize = tab === 'screenplay' && hasIndicText && !!selected?.supports?.romanize;
|
|
43
|
+
const showReportRomanize = hasIndicText && reportFormats.some(e => e.supports?.romanize);
|
|
24
44
|
useEffect(() => {
|
|
25
45
|
const onKey = (e) => {
|
|
26
46
|
if (e.key === 'Escape')
|
|
@@ -30,87 +50,37 @@ export function ExportDialog() {
|
|
|
30
50
|
return () => document.removeEventListener('keydown', onKey);
|
|
31
51
|
}, [setExportVisible]);
|
|
32
52
|
const baseName = (filePath ?? 'screenplay').replace(/\.[^.]+$/, '');
|
|
33
|
-
/** The
|
|
53
|
+
/** The context an exporter runs with: the document, or a romanized copy
|
|
34
54
|
* (Indic → ISO 15919, export only; the .sutra text is never changed). */
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
function exportContext(exporter) {
|
|
56
|
+
const romanizeScopeFor = !exporter.supports?.romanize || !hasIndicText ? null
|
|
57
|
+
: exporter.group === 'report' ? (romanizeReports ? 'all' : null)
|
|
58
|
+
: romanizeScope === 'off' ? null : romanizeScope;
|
|
59
|
+
const options = {
|
|
60
|
+
styleId: exporter.group === 'screenplay' ? styleOverride || undefined : undefined,
|
|
61
|
+
skipNotes: exporter.group === 'screenplay' && skipNotes,
|
|
62
|
+
romanized: romanizeScopeFor ? { scope: romanizeScopeFor, variant: romanizeVariant } : null,
|
|
63
|
+
};
|
|
64
|
+
if (!romanizeScopeFor)
|
|
65
|
+
return { text, ast, style: resolvedStyle, customStyles, options, baseName };
|
|
66
|
+
const romanized = romanizeSutra(text, {
|
|
67
|
+
scope: romanizeScopeFor, variant: romanizeVariant, ...(exporter.group === 'report' ? { notice: false } : {}),
|
|
68
|
+
});
|
|
69
|
+
return { text: romanized, ast: parse(romanized), style: resolvedStyle, customStyles, options, baseName };
|
|
47
70
|
}
|
|
48
71
|
async function doExport() {
|
|
72
|
+
if (!selected)
|
|
73
|
+
return;
|
|
49
74
|
setIsExporting(true);
|
|
50
75
|
try {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
else if (format === 'docx') {
|
|
60
|
-
setWarnings([]);
|
|
61
|
-
const { doc, suffix } = screenplayAst();
|
|
62
|
-
const blob = await exportToDocx(doc, styleOverride || undefined, customStyles, skipNotes);
|
|
63
|
-
downloadBlob(`${baseName}${suffix}.docx`, blob);
|
|
64
|
-
showToast('Exported Word document (.docx)', 'success');
|
|
65
|
-
}
|
|
66
|
-
else if (format === 'pdf') {
|
|
67
|
-
setWarnings([]);
|
|
68
|
-
const { doc, suffix } = screenplayAst();
|
|
69
|
-
const title = `${baseName.toUpperCase()} - SCREENPLAY${suffix ? ' (ROMANIZED)' : ''}`;
|
|
70
|
-
await openScreenplayPrintPreview(doc, title, styleOverride || undefined, customStyles, skipNotes);
|
|
71
|
-
showToast('Opened Screenplay Print Preview', 'info');
|
|
72
|
-
}
|
|
73
|
-
else if (format === 'oneliner-print') {
|
|
74
|
-
setWarnings([]);
|
|
75
|
-
await openPrintPreview(data.scenes, `${baseName.toUpperCase()} - ONE-LINER SCHEDULE${reportTitleSuffix}`);
|
|
76
|
-
showToast('Opened One-Liner Schedule', 'info');
|
|
77
|
-
}
|
|
78
|
-
else if (format === 'oneliner-docx') {
|
|
79
|
-
setWarnings([]);
|
|
80
|
-
const blob = await exportOneLinerDocx(data.scenes);
|
|
81
|
-
downloadBlob(`${baseName}_one_liner${reportSuffix}.docx`, blob);
|
|
82
|
-
showToast('Exported One-Liner DOCX', 'success');
|
|
83
|
-
}
|
|
84
|
-
else if (format === 'oneliner-csv') {
|
|
85
|
-
setWarnings([]);
|
|
86
|
-
const csv = exportOneLinerCsv(data.scenes);
|
|
87
|
-
download(`${baseName}_one_liner${reportSuffix}.csv`, csv, 'text/csv');
|
|
88
|
-
showToast('Exported One-Liner CSV', 'success');
|
|
89
|
-
}
|
|
90
|
-
else if (format === 'shotlist-docx') {
|
|
91
|
-
setWarnings([]);
|
|
92
|
-
const blob = await exportShotListDocx(data.scenes);
|
|
93
|
-
downloadBlob(`${baseName}_shot_list${reportSuffix}.docx`, blob);
|
|
94
|
-
showToast('Exported Shot List DOCX', 'success');
|
|
95
|
-
}
|
|
96
|
-
else if (format === 'shotlist-csv') {
|
|
97
|
-
setWarnings([]);
|
|
98
|
-
const csv = exportShotListCsv(data.scenes);
|
|
99
|
-
download(`${baseName}_shot_list${reportSuffix}.csv`, csv, 'text/csv');
|
|
100
|
-
showToast('Exported Shot List CSV', 'success');
|
|
101
|
-
}
|
|
102
|
-
else if (format === 'cast-docx') {
|
|
103
|
-
setWarnings([]);
|
|
104
|
-
const blob = await exportCastReportDocx(data.characters);
|
|
105
|
-
downloadBlob(`${baseName}_cast_report${reportSuffix}.docx`, blob);
|
|
106
|
-
showToast('Exported Cast Report DOCX', 'success');
|
|
107
|
-
}
|
|
108
|
-
else if (format === 'cast-csv') {
|
|
109
|
-
setWarnings([]);
|
|
110
|
-
const csv = exportCastReportCsv(data.characters);
|
|
111
|
-
download(`${baseName}_cast_report${reportSuffix}.csv`, csv, 'text/csv');
|
|
112
|
-
showToast('Exported Cast Report CSV', 'success');
|
|
113
|
-
}
|
|
76
|
+
const ctx = exportContext(selected);
|
|
77
|
+
// Called before any await: print previews must open inside the click.
|
|
78
|
+
const output = await selected.run(ctx);
|
|
79
|
+
const blob = output instanceof Blob ? output : output?.blob;
|
|
80
|
+
setWarnings(output && !(output instanceof Blob) ? output.warnings ?? [] : []);
|
|
81
|
+
if (blob)
|
|
82
|
+
downloadBlob(selected.fileName(ctx), blob);
|
|
83
|
+
showToast(selected.successMessage ?? `Exported ${selected.label}`, blob ? 'success' : 'info');
|
|
114
84
|
}
|
|
115
85
|
catch (err) {
|
|
116
86
|
console.error('Export failed', err);
|
|
@@ -121,11 +91,6 @@ export function ExportDialog() {
|
|
|
121
91
|
setIsExporting(false);
|
|
122
92
|
}
|
|
123
93
|
}
|
|
124
|
-
function download(filename, content, mimeType) {
|
|
125
|
-
const data = mimeType === 'text/csv' ? ['\uFEFF', content] : [content];
|
|
126
|
-
const blob = new Blob(data, { type: `${mimeType};charset=utf-8` });
|
|
127
|
-
downloadBlob(filename, blob);
|
|
128
|
-
}
|
|
129
94
|
function downloadBlob(filename, blob) {
|
|
130
95
|
const url = URL.createObjectURL(blob);
|
|
131
96
|
const a = document.createElement('a');
|
|
@@ -139,14 +104,12 @@ export function ExportDialog() {
|
|
|
139
104
|
setExportVisible(false);
|
|
140
105
|
}, children: _jsxs("div", { className: "cs-export-dialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "cs-export-title", style: { maxHeight: '90vh', maxWidth: '540px', width: '92%', display: 'flex', flexDirection: 'column' }, children: [_jsxs("div", { className: "cs-panel-header", children: [_jsx("span", { id: "cs-export-title", className: "cs-panel-title", children: t('export.title') }), _jsx("button", { className: "cs-panel-close", onClick: () => setExportVisible(false), "aria-label": "Close", children: "\u00D7" })] }), _jsxs("div", { className: "cs-panel-body", style: { overflowY: 'auto', flex: 1, padding: '16px' }, children: [_jsxs("div", { className: "cs-export-segmented", role: "tablist", "aria-label": "Export category", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": tab === 'screenplay', className: `cs-export-seg-btn${tab === 'screenplay' ? ' cs-export-seg-btn-active' : ''}`, onClick: () => {
|
|
141
106
|
setTab('screenplay');
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
}, children: "Screenplay Formats" }), _jsx("button", { type: "button", role: "tab", "aria-selected": tab === 'reports', className: `cs-export-seg-btn${tab === 'reports' ? ' cs-export-seg-btn-active' : ''}`, onClick: () => {
|
|
107
|
+
if (selected?.group !== 'screenplay')
|
|
108
|
+
setFormatId(screenplayFormats[0]?.id ?? '');
|
|
109
|
+
}, children: "Screenplay Formats" }), reportFormats.length > 0 && _jsx("button", { type: "button", role: "tab", "aria-selected": tab === 'reports', className: `cs-export-seg-btn${tab === 'reports' ? ' cs-export-seg-btn-active' : ''}`, onClick: () => {
|
|
145
110
|
setTab('reports');
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
}, children: "Production Reports" })] }), tab === 'screenplay' ? (_jsxs("div", { children: [
|
|
149
|
-
|
|
150
|
-
setFormat('docx'); }, children: [_jsx("input", { type: "radio", name: "format", value: "docx", checked: format === 'docx', onChange: () => setFormat('docx'), style: { marginTop: '2px' } }), _jsxs("div", { children: [_jsx("strong", { style: { fontSize: '13px', display: 'block', color: 'var(--cs-ui-text-light-primary, #1c1b18)' }, children: "Microsoft Word (.docx)" }), _jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: t('export.docxDesc') })] })] }), _jsxs("div", { className: `cs-export-format-card${format === 'fountain' ? ' cs-export-format-card-selected' : ''}`, onClick: () => setFormat('fountain'), role: "radio", "aria-checked": format === 'fountain', tabIndex: 0, onKeyDown: e => { if (e.key === 'Enter' || e.key === ' ')
|
|
151
|
-
setFormat('fountain'); }, children: [_jsx("input", { type: "radio", name: "format", value: "fountain", checked: format === 'fountain', onChange: () => setFormat('fountain'), style: { marginTop: '2px' } }), _jsxs("div", { children: [_jsx("strong", { style: { fontSize: '13px', display: 'block', color: 'var(--cs-ui-text-light-primary, #1c1b18)' }, children: "Fountain (.fountain)" }), _jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: t('export.fountainDesc') })] })] })] }), showStylePicker && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '14px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-style-select", style: { fontSize: '12px' }, children: t('export.style') }), _jsxs("select", { id: "cs-export-style-select", className: "cs-settings-select", value: styleOverride, onChange: e => setStyleOverride(e.target.value), children: [_jsxs("option", { value: "", children: [t('export.styleDefault'), " (", resolvedStyle.name, ")"] }), styleChoices.map(s => (_jsx("option", { value: s.id, children: s.name }, s.id)))] })] })), showStylePicker && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("input", { id: "cs-export-skip-notes", type: "checkbox", checked: skipNotes, onChange: e => setSkipNotes(e.target.checked) }), _jsx("label", { htmlFor: "cs-export-skip-notes", className: "cs-settings-label", style: { fontSize: '12px', margin: 0 }, children: "Skip notes ([[ ]])" })] })), showStylePicker && hasIndicText && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', flexWrap: 'wrap', gap: '8px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-romanize-select", style: { fontSize: '12px' }, children: "Romanized copy (ISO 15919)" }), _jsxs("select", { id: "cs-export-romanize-select", className: "cs-settings-select", value: romanizeScope, onChange: e => setRomanizeScope(e.target.value), children: [_jsx("option", { value: "off", children: "Off (native script)" }), _jsx("option", { value: "all", children: "All text" }), _jsx("option", { value: "dialogue", children: "Dialogue only" })] }), romanizeScope !== 'off' && (_jsxs("select", { "aria-label": "Romanization variant", className: "cs-settings-select", value: romanizeVariant, onChange: e => setRomanizeVariant(e.target.value), children: [_jsx("option", { value: "strict", children: "Strict ISO 15919" }), _jsx("option", { value: "colloquial", children: "Casual (no diacritics)" })] })), romanizeScope !== 'off' && (_jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)', flexBasis: '100%' }, children: "Your script is not changed. Page numbers in the romanized copy differ from the original." }))] }))] })) : (_jsxs("div", { children: [_jsxs("div", { className: "cs-export-report-box", children: [_jsxs("div", { className: "cs-export-report-header", children: [_jsx("span", { className: "cs-export-report-title", children: "One-Liner Schedule" }), _jsxs("div", { className: "cs-export-chips", children: [_jsx("button", { type: "button", className: `cs-export-chip${format === 'oneliner-print' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('oneliner-print'), children: "Print View" }), _jsx("button", { type: "button", className: `cs-export-chip${format === 'oneliner-docx' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('oneliner-docx'), children: "Word (.docx)" }), _jsx("button", { type: "button", className: `cs-export-chip${format === 'oneliner-csv' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('oneliner-csv'), children: "Spreadsheet (.csv)" })] })] }), _jsx("div", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: "A scene-by-scene production stripboard breakdown with locations, cast, and timings." })] }), _jsxs("div", { className: "cs-export-report-box", children: [_jsxs("div", { className: "cs-export-report-header", children: [_jsx("span", { className: "cs-export-report-title", children: "Shot List" }), _jsxs("div", { className: "cs-export-chips", children: [_jsx("button", { type: "button", className: `cs-export-chip${format === 'shotlist-docx' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('shotlist-docx'), children: "Word (.docx)" }), _jsx("button", { type: "button", className: `cs-export-chip${format === 'shotlist-csv' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('shotlist-csv'), children: "Spreadsheet (.csv)" })] })] }), _jsx("div", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: "Camera shot specifications extracted from scene headings and production notes." })] }), _jsxs("div", { className: "cs-export-report-box", children: [_jsxs("div", { className: "cs-export-report-header", children: [_jsx("span", { className: "cs-export-report-title", children: "Cast Breakdown" }), _jsxs("div", { className: "cs-export-chips", children: [_jsx("button", { type: "button", className: `cs-export-chip${format === 'cast-docx' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('cast-docx'), children: "Word (.docx)" }), _jsx("button", { type: "button", className: `cs-export-chip${format === 'cast-csv' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('cast-csv'), children: "Spreadsheet (.csv)" })] })] }), _jsx("div", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: "Character occurrences, dialogue counts, and assigned actors list." })] }), hasIndicText && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', flexWrap: 'wrap', gap: '8px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-romanize-reports", style: { fontSize: '12px' }, children: "Romanized copy (ISO 15919)" }), _jsxs("select", { id: "cs-export-romanize-reports", className: "cs-settings-select", value: romanizeReports ? 'on' : 'off', onChange: e => setRomanizeReports(e.target.value === 'on'), children: [_jsx("option", { value: "off", children: "Off (native script)" }), _jsx("option", { value: "on", children: "Romanize all text" })] }), romanizeReports && (_jsxs("select", { "aria-label": "Romanization variant", className: "cs-settings-select", value: romanizeVariant, onChange: e => setRomanizeVariant(e.target.value), children: [_jsx("option", { value: "strict", children: "Strict ISO 15919" }), _jsx("option", { value: "colloquial", children: "Casual (no diacritics)" })] }))] }))] })), warnings.length > 0 && (_jsxs("div", { className: "cs-export-warnings", style: { background: '#fffbeb', border: '1px solid #fcd34d', padding: '10px', borderRadius: '4px', marginTop: '14px' }, children: [_jsx("div", { className: "cs-export-warn-title", style: { fontWeight: 600, color: '#b45309', fontSize: '12px', marginBottom: '4px' }, children: t('export.warnings') }), warnings.map((w, i) => (_jsxs("div", { className: "cs-export-warn-item", style: { fontSize: '11px', color: '#59574f' }, children: ["\u2022 ", w] }, i)))] }))] }), _jsxs("div", { className: "cs-export-actions", style: { borderTop: '1px solid var(--cs-ui-border-light-subtle, #eeece4)', padding: '12px 16px', display: 'flex', justifyContent: 'flex-end', gap: '8px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)' }, children: [_jsx("button", { type: "button", className: "cs-confirm-btn-cancel", onClick: () => setExportVisible(false), children: t('export.cancel') }), _jsx("button", { type: "button", className: "cs-confirm-btn-primary", onClick: doExport, disabled: isExporting, children: isExporting ? 'Exporting...' : t('export.download') })] })] }) }));
|
|
111
|
+
if (selected?.group !== 'report')
|
|
112
|
+
setFormatId(reportFormats[0]?.id ?? '');
|
|
113
|
+
}, children: "Production Reports" })] }), tab === 'screenplay' ? (_jsxs("div", { children: [_jsx("div", { className: "cs-export-card-grid", children: screenplayFormats.map(f => (_jsxs("div", { className: `cs-export-format-card${selected?.id === f.id ? ' cs-export-format-card-selected' : ''}`, onClick: () => setFormatId(f.id), role: "radio", "aria-checked": selected?.id === f.id, tabIndex: 0, onKeyDown: e => { if (e.key === 'Enter' || e.key === ' ')
|
|
114
|
+
setFormatId(f.id); }, children: [_jsx("input", { type: "radio", name: "format", value: f.id, checked: selected?.id === f.id, onChange: () => setFormatId(f.id), style: { marginTop: '2px' } }), _jsxs("div", { children: [_jsx("strong", { style: { fontSize: '13px', display: 'block', color: 'var(--cs-ui-text-light-primary, #1c1b18)' }, children: f.label }), f.description && (_jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: f.description }))] })] }, f.id))) }), showStylePicker && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '14px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-style-select", style: { fontSize: '12px' }, children: t('export.style') }), _jsxs("select", { id: "cs-export-style-select", className: "cs-settings-select", value: styleOverride, onChange: e => setStyleOverride(e.target.value), children: [_jsxs("option", { value: "", children: [t('export.styleDefault'), " (", resolvedStyle.name, ")"] }), styleChoices.map(s => (_jsx("option", { value: s.id, children: s.name }, s.id)))] })] })), showSkipNotes && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("input", { id: "cs-export-skip-notes", type: "checkbox", checked: skipNotes, onChange: e => setSkipNotes(e.target.checked) }), _jsx("label", { htmlFor: "cs-export-skip-notes", className: "cs-settings-label", style: { fontSize: '12px', margin: 0 }, children: "Skip notes ([[ ]])" })] })), showRomanize && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', flexWrap: 'wrap', gap: '8px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-romanize-select", style: { fontSize: '12px' }, children: "Romanized copy (ISO 15919)" }), _jsxs("select", { id: "cs-export-romanize-select", className: "cs-settings-select", value: romanizeScope, onChange: e => setRomanizeScope(e.target.value), children: [_jsx("option", { value: "off", children: "Off (native script)" }), _jsx("option", { value: "all", children: "All text" }), _jsx("option", { value: "dialogue", children: "Dialogue only" })] }), romanizeScope !== 'off' && (_jsxs("select", { "aria-label": "Romanization variant", className: "cs-settings-select", value: romanizeVariant, onChange: e => setRomanizeVariant(e.target.value), children: [_jsx("option", { value: "strict", children: "Strict ISO 15919" }), _jsx("option", { value: "colloquial", children: "Casual (no diacritics)" })] })), romanizeScope !== 'off' && (_jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)', flexBasis: '100%' }, children: "Your script is not changed. Page numbers in the romanized copy differ from the original." }))] }))] })) : (_jsxs("div", { children: [reportFamilies.map(([family, formats]) => (_jsxs("div", { className: "cs-export-report-box", children: [_jsxs("div", { className: "cs-export-report-header", children: [_jsx("span", { className: "cs-export-report-title", children: family }), _jsx("div", { className: "cs-export-chips", children: formats.map(f => (_jsx("button", { type: "button", className: `cs-export-chip${selected?.id === f.id ? ' cs-export-chip-active' : ''}`, onClick: () => setFormatId(f.id), children: f.formatLabel ?? f.label }, f.id))) })] }), formats[0].description && (_jsx("div", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: formats[0].description }))] }, family))), showReportRomanize && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', flexWrap: 'wrap', gap: '8px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-romanize-reports", style: { fontSize: '12px' }, children: "Romanized copy (ISO 15919)" }), _jsxs("select", { id: "cs-export-romanize-reports", className: "cs-settings-select", value: romanizeReports ? 'on' : 'off', onChange: e => setRomanizeReports(e.target.value === 'on'), children: [_jsx("option", { value: "off", children: "Off (native script)" }), _jsx("option", { value: "on", children: "Romanize all text" })] }), romanizeReports && (_jsxs("select", { "aria-label": "Romanization variant", className: "cs-settings-select", value: romanizeVariant, onChange: e => setRomanizeVariant(e.target.value), children: [_jsx("option", { value: "strict", children: "Strict ISO 15919" }), _jsx("option", { value: "colloquial", children: "Casual (no diacritics)" })] }))] }))] })), warnings.length > 0 && (_jsxs("div", { className: "cs-export-warnings", style: { background: '#fffbeb', border: '1px solid #fcd34d', padding: '10px', borderRadius: '4px', marginTop: '14px' }, children: [_jsx("div", { className: "cs-export-warn-title", style: { fontWeight: 600, color: '#b45309', fontSize: '12px', marginBottom: '4px' }, children: t('export.warnings') }), warnings.map((w, i) => (_jsxs("div", { className: "cs-export-warn-item", style: { fontSize: '11px', color: '#59574f' }, children: ["\u2022 ", w] }, i)))] }))] }), _jsxs("div", { className: "cs-export-actions", style: { borderTop: '1px solid var(--cs-ui-border-light-subtle, #eeece4)', padding: '12px 16px', display: 'flex', justifyContent: 'flex-end', gap: '8px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)' }, children: [_jsx("button", { type: "button", className: "cs-confirm-btn-cancel", onClick: () => setExportVisible(false), children: t('export.cancel') }), _jsx("button", { type: "button", className: "cs-confirm-btn-primary", onClick: doExport, disabled: isExporting || !selected, children: isExporting ? 'Exporting...' : t('export.download') })] })] }) }));
|
|
152
115
|
}
|
|
@@ -87,7 +87,7 @@ export function readTitlePage(data) {
|
|
|
87
87
|
f.watermark = str(data['watermark']);
|
|
88
88
|
const managed = new Set(MANAGED_KEYS);
|
|
89
89
|
for (const [key, value] of Object.entries(data)) {
|
|
90
|
-
if (managed.has(key) || HIDDEN_KEYS.has(key))
|
|
90
|
+
if (managed.has(key) || HIDDEN_KEYS.has(key) || key.startsWith('x-'))
|
|
91
91
|
continue;
|
|
92
92
|
if (typeof value === 'string' && !value.includes('\n'))
|
|
93
93
|
f.custom.push({ key, value });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sutrata/editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "React + ProseMirror screenplay editor for Sutra, with first-class support for Indic and other complex scripts.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"keywords": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"prosemirror-state": "^1.4.3",
|
|
47
47
|
"prosemirror-transform": "^1.9.0",
|
|
48
48
|
"prosemirror-view": "^1.33.0",
|
|
49
|
-
"@sutrata/parser": "0.
|
|
49
|
+
"@sutrata/parser": "0.3.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "^18.3.0",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"@types/react": "^18.3.0",
|
|
60
60
|
"@types/react-dom": "^18.3.0",
|
|
61
61
|
"@vitejs/plugin-react": "^4.3.0",
|
|
62
|
+
"fast-check": "^4.10.2",
|
|
62
63
|
"jsdom": "^29.1.1",
|
|
63
64
|
"react": "^18.3.0",
|
|
64
65
|
"react-dom": "^18.3.0",
|
|
@@ -66,8 +67,7 @@
|
|
|
66
67
|
"vitest": "^1.6.0"
|
|
67
68
|
},
|
|
68
69
|
"scripts": {
|
|
69
|
-
"build": "node
|
|
70
|
-
"generate:providers": "node scripts/generate-providers.mjs",
|
|
70
|
+
"build": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\" && tsc && node scripts/copy-assets.mjs",
|
|
71
71
|
"test": "vitest run",
|
|
72
72
|
"test:watch": "vitest"
|
|
73
73
|
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useRef } from 'react';
|
|
3
|
-
import { getApiKey, setApiKey, deleteApiKey, getAIConfig, saveAIConfig, fetchProviders, testConnection, listModels } from './ai-client';
|
|
4
|
-
import { SparklesIcon } from '../shell/icons';
|
|
5
|
-
export function OnboardingDialog({ onClose }) {
|
|
6
|
-
const [activeTab, setActiveTab] = useState('intro');
|
|
7
|
-
const [providers, setProviders] = useState([]);
|
|
8
|
-
const [providerId, setProviderId] = useState('anthropic');
|
|
9
|
-
const [keyInput, setKeyInput] = useState('');
|
|
10
|
-
const [showKey, setShowKey] = useState(false);
|
|
11
|
-
const [testStatus, setTestStatus] = useState('idle');
|
|
12
|
-
const [testError, setTestError] = useState('');
|
|
13
|
-
const [config, setConfig] = useState(getAIConfig);
|
|
14
|
-
// Custom provider state
|
|
15
|
-
const [customId, setCustomId] = useState('');
|
|
16
|
-
const [customName, setCustomName] = useState('');
|
|
17
|
-
const [customBaseUrl, setCustomBaseUrl] = useState('');
|
|
18
|
-
const [customModels, setCustomModels] = useState('');
|
|
19
|
-
const dialogRef = useRef(null);
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
void fetchProviders(config).then(setProviders);
|
|
22
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
23
|
-
}, [config.customProviders]);
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
dialogRef.current?.focus();
|
|
26
|
-
void loadKeyForProvider(providerId);
|
|
27
|
-
}, [providerId]);
|
|
28
|
-
const loadKeyForProvider = async (pid) => {
|
|
29
|
-
setTestStatus('idle');
|
|
30
|
-
setTestError('');
|
|
31
|
-
const key = await getApiKey(pid);
|
|
32
|
-
setKeyInput(key || '');
|
|
33
|
-
};
|
|
34
|
-
const handleTestKey = async () => {
|
|
35
|
-
if (!keyInput.trim()) {
|
|
36
|
-
setTestStatus('error');
|
|
37
|
-
setTestError('Please enter an API key to test.');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
setTestStatus('testing');
|
|
41
|
-
setTestError('');
|
|
42
|
-
try {
|
|
43
|
-
const ok = await testConnection(providerId, keyInput.trim(), config);
|
|
44
|
-
if (ok) {
|
|
45
|
-
setTestStatus('success');
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
setTestStatus('error');
|
|
49
|
-
setTestError('Connection failed: empty or invalid response from endpoint.');
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch (e) {
|
|
53
|
-
setTestStatus('error');
|
|
54
|
-
setTestError(e.message || String(e));
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
const handleSaveKey = async () => {
|
|
58
|
-
const trimmed = keyInput.trim();
|
|
59
|
-
if (trimmed) {
|
|
60
|
-
await setApiKey(providerId, trimmed);
|
|
61
|
-
const provider = providers.find(p => p.id === providerId);
|
|
62
|
-
let preferredModel = provider?.models[0]?.id || '';
|
|
63
|
-
if (provider) {
|
|
64
|
-
const liveModels = await listModels(provider, trimmed);
|
|
65
|
-
preferredModel = liveModels[0]?.id || preferredModel;
|
|
66
|
-
}
|
|
67
|
-
const updated = { ...config, preferredProvider: providerId, preferredModel };
|
|
68
|
-
setConfig(updated);
|
|
69
|
-
saveAIConfig(updated);
|
|
70
|
-
setTestStatus('success');
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
await deleteApiKey(providerId);
|
|
74
|
-
setTestStatus('idle');
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
const handleAddCustomProvider = () => {
|
|
78
|
-
if (!customId || !customName || !customBaseUrl || !customModels) {
|
|
79
|
-
alert('Please fill out all custom provider fields.');
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const modelsList = customModels.split(',').map(m => {
|
|
83
|
-
const parts = m.trim().split(':');
|
|
84
|
-
const id = parts[0]?.trim() || '';
|
|
85
|
-
const name = parts[1]?.trim() || id;
|
|
86
|
-
return { id, name };
|
|
87
|
-
}).filter(m => m.id);
|
|
88
|
-
const newCustom = {
|
|
89
|
-
id: customId.trim().toLowerCase(),
|
|
90
|
-
name: customName.trim(),
|
|
91
|
-
baseUrl: customBaseUrl.trim(),
|
|
92
|
-
models: modelsList,
|
|
93
|
-
};
|
|
94
|
-
const updated = { ...config };
|
|
95
|
-
const index = updated.customProviders.findIndex(p => p.id === newCustom.id);
|
|
96
|
-
if (index >= 0) {
|
|
97
|
-
updated.customProviders[index] = newCustom;
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
updated.customProviders.push(newCustom);
|
|
101
|
-
}
|
|
102
|
-
setConfig(updated);
|
|
103
|
-
saveAIConfig(updated);
|
|
104
|
-
setCustomId('');
|
|
105
|
-
setCustomName('');
|
|
106
|
-
setCustomBaseUrl('');
|
|
107
|
-
setCustomModels('');
|
|
108
|
-
alert(`Custom provider "${newCustom.name}" added. You can now configure its key in the API Keys tab.`);
|
|
109
|
-
};
|
|
110
|
-
const handleFinish = () => {
|
|
111
|
-
localStorage.setItem('sutrata_onboarding_completed', 'true');
|
|
112
|
-
onClose();
|
|
113
|
-
};
|
|
114
|
-
return (_jsx("div", { className: "cs-dialog-overlay", onClick: onClose, style: { zIndex: 300 }, children: _jsxs("div", { ref: dialogRef, className: "cs-dialog cs-onboarding-dialog", role: "dialog", "aria-modal": "true", "aria-label": "Intelligence Setup", onClick: e => e.stopPropagation(), tabIndex: -1, 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: "Intelligence & Dictation Setup" })] }), _jsx("button", { type: "button", className: "cs-fr-close", onClick: onClose, "aria-label": "Close", children: "\u00D7" })] }), _jsxs("div", { className: "cs-tabs-header", children: [_jsx("button", { type: "button", className: `cs-tab-btn ${activeTab === 'intro' ? 'active' : ''}`, onClick: () => setActiveTab('intro'), children: "Overview" }), _jsx("button", { type: "button", className: `cs-tab-btn ${activeTab === 'keys' ? 'active' : ''}`, onClick: () => setActiveTab('keys'), children: "API Keys" }), _jsx("button", { type: "button", className: `cs-tab-btn ${activeTab === 'custom' ? 'active' : ''}`, onClick: () => setActiveTab('custom'), children: "Custom Endpoints" })] }), _jsxs("div", { className: "cs-onboarding-body", children: [activeTab === 'intro' && (_jsxs("div", { className: "cs-onboarding-section", children: [_jsx("h3", { className: "cs-onboarding-h3", children: "Client-Side AI Formatting & Voice" }), _jsx("p", { className: "cs-onboarding-desc", children: "Sutrata connects directly from your device to high-performance inference APIs. Your screenplays and audio transcripts are processed strictly with your own API credentials." }), _jsxs("div", { className: "cs-onboarding-card", children: [_jsxs("div", { className: "cs-onboarding-step", children: [_jsx("div", { className: "cs-onboarding-step-num", children: "1" }), _jsxs("div", { children: [_jsx("strong", { children: "Get a Free-Tier Key:" }), " Obtain a free API key from Google AI Studio (Gemini/Gemma) or Groq (Llama 3.3)."] })] }), _jsxs("div", { className: "cs-onboarding-step", children: [_jsx("div", { className: "cs-onboarding-step-num", children: "2" }), _jsxs("div", { children: [_jsx("strong", { children: "Enter Your Key:" }), " Save the key in the API Keys tab. It is encrypted in your local OS keyring or stored securely in your browser session."] })] }), _jsxs("div", { className: "cs-onboarding-step", children: [_jsx("div", { className: "cs-onboarding-step-num", children: "3" }), _jsxs("div", { children: [_jsx("strong", { children: "Start Dictating:" }), " Speak naturally into Sutrata; the formatting engine parses characters, dialogue, and scenes automatically."] })] })] }), _jsx("div", { className: "cs-dialog-actions", style: { display: 'flex', justifyContent: 'flex-end', marginTop: '16px' }, children: _jsx("button", { type: "button", className: "cs-voice-btn-primary", onClick: () => setActiveTab('keys'), children: "Configure API Keys \u2192" }) })] })), activeTab === 'keys' && (_jsxs("div", { className: "cs-onboarding-section", children: [_jsxs("div", { className: "cs-settings-row", style: { marginBottom: '12px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-ob-provider", children: "Active Provider:" }), _jsx("select", { id: "cs-ob-provider", className: "cs-settings-select", value: providerId, onChange: e => setProviderId(e.target.value), children: providers.map(p => (_jsx("option", { value: p.id, children: p.name }, p.id))) })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '6px', marginBottom: '16px' }, children: [_jsxs("label", { className: "cs-settings-label", htmlFor: "cs-ob-key", children: ["API Key for ", providerId, ":"] }), _jsxs("div", { style: { display: 'flex', gap: '8px' }, children: [_jsx("input", { id: "cs-ob-key", type: showKey ? 'text' : 'password', className: "cs-voice-textarea", style: { flex: 1, minHeight: '34px', padding: '6px 10px', height: '34px' }, value: keyInput, onChange: e => setKeyInput(e.target.value), placeholder: `Enter ${providerId} API key...` }), _jsx("button", { type: "button", className: "cs-voice-btn-ghost", onClick: () => setShowKey(!showKey), style: { padding: '0 10px', height: '34px' }, children: showKey ? 'Hide' : 'Show' })] })] }), _jsxs("div", { style: { display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '16px' }, children: [_jsx("button", { type: "button", className: "cs-voice-btn-primary", onClick: handleSaveKey, children: "Save Key" }), _jsx("button", { type: "button", className: "cs-voice-btn-secondary", onClick: handleTestKey, disabled: testStatus === 'testing', children: testStatus === 'testing' ? 'Testing...' : 'Test Connection' }), testStatus === 'success' && (_jsx("span", { style: { color: '#10b981', fontSize: '13px', fontWeight: 500 }, children: "\u2713 Verified connection" })), testStatus === 'error' && (_jsx("span", { style: { color: '#ef4444', fontSize: '12px' }, children: testError }))] }), _jsxs("div", { className: "cs-dialog-actions", style: { display: 'flex', justifyContent: 'space-between', marginTop: '20px' }, children: [_jsx("button", { type: "button", className: "cs-voice-btn-ghost", onClick: () => setActiveTab('intro'), children: "\u2190 Back" }), _jsx("button", { type: "button", className: "cs-voice-btn-primary", onClick: handleFinish, children: "Done & Close" })] })] })), activeTab === 'custom' && (_jsxs("div", { className: "cs-onboarding-section", children: [_jsx("p", { className: "cs-onboarding-desc", children: "Connect to local OpenAI-compatible endpoints such as Ollama, vLLM, or LM Studio." }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px', marginBottom: '16px' }, children: [_jsxs("div", { children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-ob-custom-id", children: "Provider ID (e.g. ollama):" }), _jsx("input", { id: "cs-ob-custom-id", type: "text", className: "cs-voice-textarea", style: { width: '100%', height: '34px', minHeight: '34px', padding: '6px 10px' }, value: customId, onChange: e => setCustomId(e.target.value), placeholder: "ollama" })] }), _jsxs("div", { children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-ob-custom-name", children: "Display Name:" }), _jsx("input", { id: "cs-ob-custom-name", type: "text", className: "cs-voice-textarea", style: { width: '100%', height: '34px', minHeight: '34px', padding: '6px 10px' }, value: customName, onChange: e => setCustomName(e.target.value), placeholder: "Local Ollama" })] }), _jsxs("div", { children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-ob-custom-url", children: "Base URL:" }), _jsx("input", { id: "cs-ob-custom-url", type: "text", className: "cs-voice-textarea", style: { width: '100%', height: '34px', minHeight: '34px', padding: '6px 10px' }, value: customBaseUrl, onChange: e => setCustomBaseUrl(e.target.value), placeholder: "http://localhost:11434/v1" })] }), _jsxs("div", { children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-ob-custom-models", children: "Models (id:name comma-separated):" }), _jsx("input", { id: "cs-ob-custom-models", type: "text", className: "cs-voice-textarea", style: { width: '100%', height: '34px', minHeight: '34px', padding: '6px 10px' }, value: customModels, onChange: e => setCustomModels(e.target.value), placeholder: "llama3:Llama 3, mistral:Mistral 7B" })] })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', marginTop: '16px' }, children: [_jsx("button", { type: "button", className: "cs-voice-btn-secondary", onClick: handleAddCustomProvider, children: "Register Endpoint" }), _jsx("button", { type: "button", className: "cs-voice-btn-primary", onClick: () => setActiveTab('keys'), children: "Go to API Keys \u2192" })] })] }))] })] }) }));
|
|
115
|
-
}
|
package/dist/ai/ai-client.d.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
export interface AIConfig {
|
|
2
|
-
preferredProvider: string;
|
|
3
|
-
preferredModel: string;
|
|
4
|
-
customProviders: CustomProvider[];
|
|
5
|
-
}
|
|
6
|
-
export interface CustomProvider {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
baseUrl: string;
|
|
10
|
-
models: {
|
|
11
|
-
id: string;
|
|
12
|
-
name: string;
|
|
13
|
-
}[];
|
|
14
|
-
}
|
|
15
|
-
export interface ModelConfig {
|
|
16
|
-
id: string;
|
|
17
|
-
name: string;
|
|
18
|
-
}
|
|
19
|
-
export interface ProviderConfig {
|
|
20
|
-
id: string;
|
|
21
|
-
name: string;
|
|
22
|
-
baseUrl: string;
|
|
23
|
-
models: ModelConfig[];
|
|
24
|
-
}
|
|
25
|
-
/** Opaque JSON Schema object — forwarded into provider request bodies and/or
|
|
26
|
-
* embedded in the prompt text; see callAIStructured. */
|
|
27
|
-
export type JsonSchema = Record<string, unknown>;
|
|
28
|
-
export declare function getApiKey(provider: string): Promise<string | null>;
|
|
29
|
-
export declare function setApiKey(provider: string, key: string): Promise<void>;
|
|
30
|
-
export declare function deleteApiKey(provider: string): Promise<void>;
|
|
31
|
-
export declare function getAIConfig(): AIConfig;
|
|
32
|
-
export declare function saveAIConfig(config: AIConfig): void;
|
|
33
|
-
export declare function fetchProviders(config?: AIConfig): Promise<ProviderConfig[]>;
|
|
34
|
-
/**
|
|
35
|
-
* Parses a structured AI response into T, tolerating a model that wraps the
|
|
36
|
-
* JSON in a markdown fence or adds stray text around it despite being asked
|
|
37
|
-
* not to (some providers don't strictly honor response_format hints).
|
|
38
|
-
*/
|
|
39
|
-
export declare function extractJson<T>(raw: string): T;
|
|
40
|
-
export declare function cleanVoiceResponse(text: string): string;
|
|
41
|
-
export declare function subscribeToAICalls(callback: (calls: {
|
|
42
|
-
providerName: string;
|
|
43
|
-
model: string;
|
|
44
|
-
}[]) => void): () => void;
|
|
45
|
-
export declare function formatActiveCalls(calls: {
|
|
46
|
-
providerName: string;
|
|
47
|
-
model: string;
|
|
48
|
-
}[]): string;
|
|
49
|
-
export declare function callAI(prompt: string, systemPrompt: string, config: AIConfig, options?: {
|
|
50
|
-
clean?: 'voice';
|
|
51
|
-
}): Promise<string>;
|
|
52
|
-
/**
|
|
53
|
-
* Like callAI, but requests and validates a JSON response matching `schema`
|
|
54
|
-
* instead of returning free text — the explicit contract that replaced
|
|
55
|
-
* sniffing the system prompt for keywords like "duration"/"synopsis".
|
|
56
|
-
*/
|
|
57
|
-
export declare function callAIStructured<T = unknown>(prompt: string, systemPrompt: string, config: AIConfig, schema: JsonSchema): Promise<T>;
|
|
58
|
-
/** Any provider (from the fetched + custom list) with a stored API key. */
|
|
59
|
-
export declare function hasConfiguredApiKey(config: AIConfig): Promise<boolean>;
|
|
60
|
-
/**
|
|
61
|
-
* Fetches the provider's live model list so Settings/Onboarding show
|
|
62
|
-
* current models instead of a potentially stale static one. Never throws —
|
|
63
|
-
* falls back to `provider.models` on any error (missing endpoint, bad key,
|
|
64
|
-
* network failure), since this is a progressive enhancement.
|
|
65
|
-
*/
|
|
66
|
-
export declare function listModels(provider: ProviderConfig, apiKey: string): Promise<ModelConfig[]>;
|
|
67
|
-
export declare function testConnection(providerId: string, apiKey: string, config: AIConfig): Promise<boolean>;
|
|
68
|
-
export declare function mockActiveCallsList(calls: {
|
|
69
|
-
providerName: string;
|
|
70
|
-
model: string;
|
|
71
|
-
}[]): void;
|