@mieweb/ui 0.6.1-dev.169 → 0.6.1-dev.170
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/brands/index.cjs +18 -18
- package/dist/brands/index.js +3 -3
- package/dist/chunk-3Q2VWPML.cjs +1802 -0
- package/dist/chunk-3Q2VWPML.cjs.map +1 -0
- package/dist/{chunk-6W3HLFAJ.cjs → chunk-3YRDZBW6.cjs} +3 -3
- package/dist/{chunk-6W3HLFAJ.cjs.map → chunk-3YRDZBW6.cjs.map} +1 -1
- package/dist/{chunk-UVQEHZRF.js → chunk-6VGXX6L6.js} +2 -2
- package/dist/{chunk-UVQEHZRF.js.map → chunk-6VGXX6L6.js.map} +1 -1
- package/dist/chunk-CH26CHPH.js +1776 -0
- package/dist/chunk-CH26CHPH.js.map +1 -0
- package/dist/{chunk-TXRQQMG5.js → chunk-CYMZ2QS5.js} +2 -2
- package/dist/{chunk-TXRQQMG5.js.map → chunk-CYMZ2QS5.js.map} +1 -1
- package/dist/chunk-FGP64Z24.js +131 -0
- package/dist/chunk-FGP64Z24.js.map +1 -0
- package/dist/chunk-FIWKHLWY.js +197 -0
- package/dist/chunk-FIWKHLWY.js.map +1 -0
- package/dist/chunk-GLXTVCHN.cjs +4 -0
- package/dist/chunk-GLXTVCHN.cjs.map +1 -0
- package/dist/chunk-J7KWQJOB.js +14 -0
- package/dist/chunk-J7KWQJOB.js.map +1 -0
- package/dist/chunk-JYZW2PZS.cjs +166 -0
- package/dist/chunk-JYZW2PZS.cjs.map +1 -0
- package/dist/{chunk-XVF472GT.js → chunk-NSLR3B7K.js} +2 -2
- package/dist/{chunk-XVF472GT.js.map → chunk-NSLR3B7K.js.map} +1 -1
- package/dist/chunk-NTYGCBCL.js +543 -0
- package/dist/chunk-NTYGCBCL.js.map +1 -0
- package/dist/chunk-RISZWWEQ.js +3 -0
- package/dist/chunk-RISZWWEQ.js.map +1 -0
- package/dist/chunk-S7JQ3FV5.cjs +16 -0
- package/dist/chunk-S7JQ3FV5.cjs.map +1 -0
- package/dist/{chunk-JWTCEWQ4.cjs → chunk-SNEQXSC5.cjs} +2 -2
- package/dist/{chunk-JWTCEWQ4.cjs.map → chunk-SNEQXSC5.cjs.map} +1 -1
- package/dist/chunk-SQHMGT4W.js +164 -0
- package/dist/chunk-SQHMGT4W.js.map +1 -0
- package/dist/chunk-UO3KEUYK.cjs +220 -0
- package/dist/chunk-UO3KEUYK.cjs.map +1 -0
- package/dist/{chunk-FETRH4V5.js → chunk-X763OLF2.js} +3 -3
- package/dist/{chunk-FETRH4V5.js.map → chunk-X763OLF2.js.map} +1 -1
- package/dist/{chunk-DKGUOMBC.cjs → chunk-X7J45FU5.cjs} +2 -2
- package/dist/{chunk-DKGUOMBC.cjs.map → chunk-X7J45FU5.cjs.map} +1 -1
- package/dist/chunk-Y4ESBDQ5.cjs +552 -0
- package/dist/chunk-Y4ESBDQ5.cjs.map +1 -0
- package/dist/{chunk-MJ7YITLN.cjs → chunk-Y65SK5Y2.cjs} +2 -2
- package/dist/{chunk-MJ7YITLN.cjs.map → chunk-Y65SK5Y2.cjs.map} +1 -1
- package/dist/chunk-YD23ZGRU.cjs +155 -0
- package/dist/chunk-YD23ZGRU.cjs.map +1 -0
- package/dist/components/Markdown/index.cjs +10 -10
- package/dist/components/Markdown/index.js +2 -2
- package/dist/components/MediaEditor/index.cjs +42 -0
- package/dist/components/MediaEditor/index.cjs.map +1 -0
- package/dist/components/MediaEditor/index.d.cts +151 -0
- package/dist/components/MediaEditor/index.d.ts +151 -0
- package/dist/components/MediaEditor/index.js +17 -0
- package/dist/components/MediaEditor/index.js.map +1 -0
- package/dist/components/MediaPlayer/index.cjs +24 -0
- package/dist/components/MediaPlayer/index.cjs.map +1 -0
- package/dist/components/MediaPlayer/index.d.cts +71 -0
- package/dist/components/MediaPlayer/index.d.ts +71 -0
- package/dist/components/MediaPlayer/index.js +7 -0
- package/dist/components/MediaPlayer/index.js.map +1 -0
- package/dist/components/TranscriptView/index.cjs +21 -0
- package/dist/components/TranscriptView/index.cjs.map +1 -0
- package/dist/components/TranscriptView/index.d.cts +55 -0
- package/dist/components/TranscriptView/index.d.ts +55 -0
- package/dist/components/TranscriptView/index.js +4 -0
- package/dist/components/TranscriptView/index.js.map +1 -0
- package/dist/esheet.cjs +5 -5
- package/dist/esheet.js +2 -2
- package/dist/hooks/index.cjs +49 -11
- package/dist/hooks/index.d.cts +88 -1
- package/dist/hooks/index.d.ts +88 -1
- package/dist/hooks/index.js +3 -1
- package/dist/index.cjs +374 -247
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +87 -40
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/transcript-oaWqY3yr.d.cts +81 -0
- package/dist/transcript-oaWqY3yr.d.ts +81 -0
- package/dist/useTranscriptEdits-BUylQFg0.d.cts +168 -0
- package/dist/useTranscriptEdits-CWYnjbGP.d.ts +168 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1802 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkS7JQ3FV5_cjs = require('./chunk-S7JQ3FV5.cjs');
|
|
4
|
+
var chunkGOKC4CCZ_cjs = require('./chunk-GOKC4CCZ.cjs');
|
|
5
|
+
var chunkYD23ZGRU_cjs = require('./chunk-YD23ZGRU.cjs');
|
|
6
|
+
var chunkDFCZPVG4_cjs = require('./chunk-DFCZPVG4.cjs');
|
|
7
|
+
var chunkFRT6RQND_cjs = require('./chunk-FRT6RQND.cjs');
|
|
8
|
+
var chunkKJPOFUF5_cjs = require('./chunk-KJPOFUF5.cjs');
|
|
9
|
+
var chunkMSRU3XBH_cjs = require('./chunk-MSRU3XBH.cjs');
|
|
10
|
+
var chunkA2QVQF54_cjs = require('./chunk-A2QVQF54.cjs');
|
|
11
|
+
var chunkY4ESBDQ5_cjs = require('./chunk-Y4ESBDQ5.cjs');
|
|
12
|
+
var React6 = require('react');
|
|
13
|
+
var classVarianceAuthority = require('class-variance-authority');
|
|
14
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
15
|
+
|
|
16
|
+
function _interopNamespace(e) {
|
|
17
|
+
if (e && e.__esModule) return e;
|
|
18
|
+
var n = Object.create(null);
|
|
19
|
+
if (e) {
|
|
20
|
+
Object.keys(e).forEach(function (k) {
|
|
21
|
+
if (k !== 'default') {
|
|
22
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return e[k]; }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
n.default = e;
|
|
31
|
+
return Object.freeze(n);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var React6__namespace = /*#__PURE__*/_interopNamespace(React6);
|
|
35
|
+
|
|
36
|
+
var SILENCE_THRESHOLD_OPTIONS = [0.3, 0.4, 0.5, 0.75, 1, 1.5, 2];
|
|
37
|
+
function formatDuration(ms) {
|
|
38
|
+
const totalSeconds = Math.round(ms / 1e3);
|
|
39
|
+
if (totalSeconds < 90) {
|
|
40
|
+
return `${totalSeconds}s`;
|
|
41
|
+
}
|
|
42
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
43
|
+
const seconds = totalSeconds % 60;
|
|
44
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
45
|
+
}
|
|
46
|
+
var FillerWordsModal = ({
|
|
47
|
+
isOpen,
|
|
48
|
+
onClose,
|
|
49
|
+
onApply,
|
|
50
|
+
matchingCounts,
|
|
51
|
+
fillerDurations,
|
|
52
|
+
silenceCounts,
|
|
53
|
+
currentDurationMs
|
|
54
|
+
}) => {
|
|
55
|
+
const [searchTerm, setSearchTerm] = React6__namespace.useState("");
|
|
56
|
+
const [selectedFillers, setSelectedFillers] = React6__namespace.useState(
|
|
57
|
+
new Set(chunkY4ESBDQ5_cjs.DEFAULT_FILLER_WORDS)
|
|
58
|
+
);
|
|
59
|
+
const [customWord, setCustomWord] = React6__namespace.useState("");
|
|
60
|
+
const [removeSilence, setRemoveSilence] = React6__namespace.useState(false);
|
|
61
|
+
const [silenceThreshold, setSilenceThreshold] = React6__namespace.useState(0.4);
|
|
62
|
+
const filteredFillers = chunkY4ESBDQ5_cjs.DEFAULT_FILLER_WORDS.filter(
|
|
63
|
+
(word) => word.toLowerCase().includes(searchTerm.toLowerCase())
|
|
64
|
+
).sort((a, b) => {
|
|
65
|
+
const countA = matchingCounts.get(a) || 0;
|
|
66
|
+
const countB = matchingCounts.get(b) || 0;
|
|
67
|
+
if (countB !== countA) return countB - countA;
|
|
68
|
+
return a.localeCompare(b);
|
|
69
|
+
});
|
|
70
|
+
const customFillers = Array.from(selectedFillers).filter((word) => !chunkY4ESBDQ5_cjs.DEFAULT_FILLER_WORDS.includes(word)).filter((word) => word.toLowerCase().includes(searchTerm.toLowerCase())).sort((a, b) => {
|
|
71
|
+
const countA = matchingCounts.get(a) || 0;
|
|
72
|
+
const countB = matchingCounts.get(b) || 0;
|
|
73
|
+
if (countB !== countA) return countB - countA;
|
|
74
|
+
return a.localeCompare(b);
|
|
75
|
+
});
|
|
76
|
+
const toggleFiller = (word) => {
|
|
77
|
+
setSelectedFillers((prev) => {
|
|
78
|
+
const next = new Set(prev);
|
|
79
|
+
if (next.has(word)) {
|
|
80
|
+
next.delete(word);
|
|
81
|
+
} else {
|
|
82
|
+
next.add(word);
|
|
83
|
+
}
|
|
84
|
+
return next;
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
const handleAddCustom = () => {
|
|
88
|
+
const trimmed = customWord.trim().toLowerCase();
|
|
89
|
+
if (trimmed && !selectedFillers.has(trimmed)) {
|
|
90
|
+
setSelectedFillers((prev) => /* @__PURE__ */ new Set([...prev, trimmed]));
|
|
91
|
+
setCustomWord("");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const silenceData = removeSilence ? silenceCounts.find((s) => s.threshold === silenceThreshold) || {
|
|
95
|
+
count: 0,
|
|
96
|
+
durationMs: 0
|
|
97
|
+
} : { count: 0, durationMs: 0 };
|
|
98
|
+
const silenceRemovalCount = silenceData.count;
|
|
99
|
+
const silenceDurationMs = silenceData.durationMs;
|
|
100
|
+
const handleApply = () => {
|
|
101
|
+
onApply(
|
|
102
|
+
Array.from(selectedFillers),
|
|
103
|
+
removeSilence ? silenceThreshold : null
|
|
104
|
+
);
|
|
105
|
+
onClose();
|
|
106
|
+
};
|
|
107
|
+
const selectAll = () => {
|
|
108
|
+
setSelectedFillers(/* @__PURE__ */ new Set([...chunkY4ESBDQ5_cjs.DEFAULT_FILLER_WORDS, ...customFillers]));
|
|
109
|
+
};
|
|
110
|
+
const selectNone = () => {
|
|
111
|
+
setSelectedFillers(/* @__PURE__ */ new Set());
|
|
112
|
+
};
|
|
113
|
+
const fillerMatches = Array.from(selectedFillers).reduce(
|
|
114
|
+
(sum, word) => sum + (matchingCounts.get(word) || 0),
|
|
115
|
+
0
|
|
116
|
+
);
|
|
117
|
+
const totalMatches = fillerMatches + silenceRemovalCount;
|
|
118
|
+
const fillerTimeSavedMs = Array.from(selectedFillers).reduce(
|
|
119
|
+
(sum, word) => sum + (fillerDurations.get(word) || 0),
|
|
120
|
+
0
|
|
121
|
+
);
|
|
122
|
+
const totalTimeSavedMs = fillerTimeSavedMs + silenceDurationMs;
|
|
123
|
+
const newDurationMs = currentDurationMs - totalTimeSavedMs;
|
|
124
|
+
const renderFillerItem = (word, isCustom) => {
|
|
125
|
+
const count = matchingCounts.get(word) || 0;
|
|
126
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
127
|
+
"label",
|
|
128
|
+
{
|
|
129
|
+
className: "hover:bg-muted flex cursor-pointer items-center gap-2 rounded-md px-1.5 py-1 text-sm transition-colors",
|
|
130
|
+
children: [
|
|
131
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
132
|
+
chunkDFCZPVG4_cjs.Checkbox,
|
|
133
|
+
{
|
|
134
|
+
size: "sm",
|
|
135
|
+
checked: selectedFillers.has(word),
|
|
136
|
+
onChange: () => toggleFiller(word),
|
|
137
|
+
"aria-label": `Remove "${word}"`
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground min-w-0 flex-1 truncate", children: word }),
|
|
141
|
+
count > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
142
|
+
"(",
|
|
143
|
+
count,
|
|
144
|
+
")"
|
|
145
|
+
] }),
|
|
146
|
+
isCustom && /* @__PURE__ */ jsxRuntime.jsx(
|
|
147
|
+
"button",
|
|
148
|
+
{
|
|
149
|
+
type: "button",
|
|
150
|
+
className: "text-muted-foreground hover:bg-destructive/10 hover:text-destructive focus-visible:ring-ring rounded px-1 transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
|
151
|
+
onClick: (e) => {
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
setSelectedFillers((prev) => {
|
|
154
|
+
const next = new Set(prev);
|
|
155
|
+
next.delete(word);
|
|
156
|
+
return next;
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
"aria-label": `Remove ${word}`,
|
|
160
|
+
children: "\xD7"
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
word
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
169
|
+
chunkKJPOFUF5_cjs.Modal,
|
|
170
|
+
{
|
|
171
|
+
open: isOpen,
|
|
172
|
+
onOpenChange: (open) => {
|
|
173
|
+
if (!open) onClose();
|
|
174
|
+
},
|
|
175
|
+
size: "lg",
|
|
176
|
+
children: [
|
|
177
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkKJPOFUF5_cjs.ModalHeader, { children: [
|
|
178
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkKJPOFUF5_cjs.ModalTitle, { children: "Remove Filler Words" }),
|
|
179
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkKJPOFUF5_cjs.ModalClose, { "aria-label": "Close modal" })
|
|
180
|
+
] }),
|
|
181
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkKJPOFUF5_cjs.ModalBody, { className: "flex flex-col gap-3", children: [
|
|
182
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
183
|
+
chunkMSRU3XBH_cjs.Input,
|
|
184
|
+
{
|
|
185
|
+
size: "sm",
|
|
186
|
+
type: "text",
|
|
187
|
+
placeholder: "Search filler words...",
|
|
188
|
+
value: searchTerm,
|
|
189
|
+
onChange: (e) => setSearchTerm(e.target.value),
|
|
190
|
+
"aria-label": "Search filler words"
|
|
191
|
+
}
|
|
192
|
+
),
|
|
193
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
194
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkA2QVQF54_cjs.Button, { variant: "ghost", size: "sm", onClick: selectAll, children: "Select All" }),
|
|
195
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkA2QVQF54_cjs.Button, { variant: "ghost", size: "sm", onClick: selectNone, children: "Select None" })
|
|
196
|
+
] }),
|
|
197
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border bg-muted/40 flex flex-wrap items-center gap-2 rounded-lg border px-3 py-2", children: [
|
|
198
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
199
|
+
chunkDFCZPVG4_cjs.Checkbox,
|
|
200
|
+
{
|
|
201
|
+
size: "sm",
|
|
202
|
+
label: "Remove silences greater than",
|
|
203
|
+
checked: removeSilence,
|
|
204
|
+
onChange: (e) => setRemoveSilence(e.target.checked)
|
|
205
|
+
}
|
|
206
|
+
),
|
|
207
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-24", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
208
|
+
chunkFRT6RQND_cjs.Select,
|
|
209
|
+
{
|
|
210
|
+
size: "sm",
|
|
211
|
+
options: SILENCE_THRESHOLD_OPTIONS.map((t) => ({
|
|
212
|
+
value: String(t),
|
|
213
|
+
label: `${t}s`
|
|
214
|
+
})),
|
|
215
|
+
value: String(silenceThreshold),
|
|
216
|
+
onValueChange: (value) => setSilenceThreshold(parseFloat(value)),
|
|
217
|
+
disabled: !removeSilence,
|
|
218
|
+
"aria-label": "Silence threshold in seconds"
|
|
219
|
+
}
|
|
220
|
+
) }),
|
|
221
|
+
removeSilence && silenceRemovalCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
222
|
+
"(",
|
|
223
|
+
silenceRemovalCount,
|
|
224
|
+
" silences)"
|
|
225
|
+
] })
|
|
226
|
+
] }),
|
|
227
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border grid max-h-56 grid-cols-2 gap-x-3 gap-y-0.5 overflow-y-auto rounded-lg border p-2 sm:grid-cols-3", children: [
|
|
228
|
+
filteredFillers.map((word) => renderFillerItem(word, false)),
|
|
229
|
+
customFillers.map((word) => renderFillerItem(word, true))
|
|
230
|
+
] }),
|
|
231
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
232
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
233
|
+
chunkMSRU3XBH_cjs.Input,
|
|
234
|
+
{
|
|
235
|
+
size: "sm",
|
|
236
|
+
type: "text",
|
|
237
|
+
placeholder: "Add custom filler word...",
|
|
238
|
+
value: customWord,
|
|
239
|
+
onChange: (e) => setCustomWord(e.target.value),
|
|
240
|
+
onKeyDown: (e) => {
|
|
241
|
+
if (e.key === "Enter") {
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
handleAddCustom();
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
"aria-label": "Add custom filler word"
|
|
247
|
+
}
|
|
248
|
+
),
|
|
249
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
250
|
+
chunkA2QVQF54_cjs.Button,
|
|
251
|
+
{
|
|
252
|
+
variant: "secondary",
|
|
253
|
+
size: "sm",
|
|
254
|
+
onClick: handleAddCustom,
|
|
255
|
+
disabled: !customWord.trim(),
|
|
256
|
+
children: "Add"
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
] })
|
|
260
|
+
] }),
|
|
261
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkKJPOFUF5_cjs.ModalFooter, { className: "justify-between", children: [
|
|
262
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground text-sm", children: [
|
|
263
|
+
totalMatches,
|
|
264
|
+
" items \u2022 saves ",
|
|
265
|
+
formatDuration(totalTimeSavedMs),
|
|
266
|
+
" \u2192",
|
|
267
|
+
" ",
|
|
268
|
+
formatDuration(newDurationMs)
|
|
269
|
+
] }),
|
|
270
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
271
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkA2QVQF54_cjs.Button, { variant: "secondary", size: "sm", onClick: onClose, children: "Cancel" }),
|
|
272
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
273
|
+
chunkA2QVQF54_cjs.Button,
|
|
274
|
+
{
|
|
275
|
+
variant: "primary",
|
|
276
|
+
size: "sm",
|
|
277
|
+
onClick: handleApply,
|
|
278
|
+
disabled: totalMatches === 0,
|
|
279
|
+
children: [
|
|
280
|
+
"Mark as Deleted (",
|
|
281
|
+
totalMatches,
|
|
282
|
+
")"
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
] })
|
|
287
|
+
] })
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
FillerWordsModal.displayName = "FillerWordsModal";
|
|
293
|
+
var WordEditorModal = ({
|
|
294
|
+
isOpen,
|
|
295
|
+
editableWord,
|
|
296
|
+
onClose,
|
|
297
|
+
onSave,
|
|
298
|
+
onSplitSilence,
|
|
299
|
+
onDelete
|
|
300
|
+
}) => {
|
|
301
|
+
const [inputValue, setInputValue] = React6__namespace.useState("");
|
|
302
|
+
const [error, setError] = React6__namespace.useState(null);
|
|
303
|
+
const inputRef = React6__namespace.useRef(null);
|
|
304
|
+
const isSilence = editableWord?.word.wordType === "silence";
|
|
305
|
+
const durationMs = editableWord ? editableWord.word.endMs - editableWord.word.startMs : 0;
|
|
306
|
+
const durationSec = durationMs / 1e3;
|
|
307
|
+
React6__namespace.useEffect(() => {
|
|
308
|
+
if (isOpen && editableWord) {
|
|
309
|
+
if (isSilence) {
|
|
310
|
+
setInputValue("");
|
|
311
|
+
} else {
|
|
312
|
+
setInputValue(editableWord.word.text);
|
|
313
|
+
}
|
|
314
|
+
setError(null);
|
|
315
|
+
setTimeout(() => inputRef.current?.focus(), 50);
|
|
316
|
+
}
|
|
317
|
+
}, [isOpen, editableWord, isSilence]);
|
|
318
|
+
const parseSilenceSplit = (input) => {
|
|
319
|
+
const trimmed = input.trim();
|
|
320
|
+
if (!trimmed) return null;
|
|
321
|
+
const parts = trimmed.split(/\s+/);
|
|
322
|
+
const durations = [];
|
|
323
|
+
let total = 0;
|
|
324
|
+
for (const part of parts) {
|
|
325
|
+
const num = parseFloat(part);
|
|
326
|
+
if (isNaN(num) || num <= 0) {
|
|
327
|
+
return {
|
|
328
|
+
durations: [],
|
|
329
|
+
remainder: 0,
|
|
330
|
+
error: `Invalid number: "${part}"`
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
durations.push(num);
|
|
334
|
+
total += num;
|
|
335
|
+
}
|
|
336
|
+
if (total >= durationSec) {
|
|
337
|
+
return {
|
|
338
|
+
durations: [],
|
|
339
|
+
remainder: 0,
|
|
340
|
+
error: `Total (${total.toFixed(1)}s) exceeds silence duration (${durationSec.toFixed(1)}s)`
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const remainder = durationSec - total;
|
|
344
|
+
return { durations, remainder };
|
|
345
|
+
};
|
|
346
|
+
const handleApply = () => {
|
|
347
|
+
if (isSilence) {
|
|
348
|
+
const parsed = parseSilenceSplit(inputValue);
|
|
349
|
+
if (parsed?.error) {
|
|
350
|
+
setError(parsed.error);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (parsed && parsed.durations.length > 0) {
|
|
354
|
+
const allDurations = [...parsed.durations, parsed.remainder];
|
|
355
|
+
onSplitSilence(allDurations);
|
|
356
|
+
onClose();
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
const trimmed = inputValue.trim();
|
|
360
|
+
if (trimmed && trimmed !== editableWord?.word.text) {
|
|
361
|
+
onSave(trimmed);
|
|
362
|
+
}
|
|
363
|
+
onClose();
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const handleKeyDown = (e) => {
|
|
367
|
+
if (e.key === "Enter") {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
handleApply();
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
const preview = isSilence ? parseSilenceSplit(inputValue) : null;
|
|
373
|
+
const applyDisabled = isSilence ? !inputValue.trim() || !!preview?.error || !preview || preview.durations.length === 0 : !inputValue.trim();
|
|
374
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
375
|
+
chunkKJPOFUF5_cjs.Modal,
|
|
376
|
+
{
|
|
377
|
+
open: isOpen && editableWord !== null,
|
|
378
|
+
onOpenChange: (open) => {
|
|
379
|
+
if (!open) onClose();
|
|
380
|
+
},
|
|
381
|
+
size: "md",
|
|
382
|
+
children: [
|
|
383
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkKJPOFUF5_cjs.ModalHeader, { children: [
|
|
384
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkKJPOFUF5_cjs.ModalTitle, { children: isSilence ? "Split Silence" : "Edit Word" }),
|
|
385
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkKJPOFUF5_cjs.ModalClose, { "aria-label": "Close editor" })
|
|
386
|
+
] }),
|
|
387
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkKJPOFUF5_cjs.ModalBody, { className: "flex flex-col gap-2", children: isSilence ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
388
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-foreground m-0 text-sm", children: [
|
|
389
|
+
"Silence duration:",
|
|
390
|
+
" ",
|
|
391
|
+
/* @__PURE__ */ jsxRuntime.jsxs("strong", { className: "font-semibold", children: [
|
|
392
|
+
durationSec.toFixed(1),
|
|
393
|
+
"s"
|
|
394
|
+
] })
|
|
395
|
+
] }),
|
|
396
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground m-0 text-xs", children: 'Enter space-separated durations to split (e.g., "1 1 4 1")' }),
|
|
397
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
398
|
+
chunkMSRU3XBH_cjs.Input,
|
|
399
|
+
{
|
|
400
|
+
ref: inputRef,
|
|
401
|
+
type: "text",
|
|
402
|
+
value: inputValue,
|
|
403
|
+
onChange: (e) => {
|
|
404
|
+
setInputValue(e.target.value);
|
|
405
|
+
setError(null);
|
|
406
|
+
},
|
|
407
|
+
onKeyDown: handleKeyDown,
|
|
408
|
+
placeholder: "1 2 3...",
|
|
409
|
+
"aria-label": "Split durations"
|
|
410
|
+
}
|
|
411
|
+
),
|
|
412
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive m-0 text-sm", role: "alert", children: error }),
|
|
413
|
+
preview && !preview.error && preview.durations.length > 0 && !error && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground m-0 text-sm", children: [
|
|
414
|
+
"Preview:",
|
|
415
|
+
" ",
|
|
416
|
+
[...preview.durations, preview.remainder].map((d) => `[${d.toFixed(1)}s]`).join(" ")
|
|
417
|
+
] }),
|
|
418
|
+
preview?.error && !error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive m-0 text-sm", role: "alert", children: preview.error })
|
|
419
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
420
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground m-0 text-sm", children: "Edit the word text:" }),
|
|
421
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
422
|
+
chunkMSRU3XBH_cjs.Input,
|
|
423
|
+
{
|
|
424
|
+
ref: inputRef,
|
|
425
|
+
type: "text",
|
|
426
|
+
value: inputValue,
|
|
427
|
+
onChange: (e) => setInputValue(e.target.value),
|
|
428
|
+
onKeyDown: handleKeyDown,
|
|
429
|
+
"aria-label": "Word text"
|
|
430
|
+
}
|
|
431
|
+
)
|
|
432
|
+
] }) }),
|
|
433
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkKJPOFUF5_cjs.ModalFooter, { className: "justify-between", children: [
|
|
434
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
435
|
+
chunkA2QVQF54_cjs.Button,
|
|
436
|
+
{
|
|
437
|
+
variant: editableWord?.deleted ? "secondary" : "danger",
|
|
438
|
+
size: "sm",
|
|
439
|
+
onClick: onDelete,
|
|
440
|
+
children: editableWord?.deleted ? "Restore" : "Delete"
|
|
441
|
+
}
|
|
442
|
+
),
|
|
443
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
444
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkA2QVQF54_cjs.Button, { variant: "secondary", size: "sm", onClick: onClose, children: "Cancel" }),
|
|
445
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
446
|
+
chunkA2QVQF54_cjs.Button,
|
|
447
|
+
{
|
|
448
|
+
variant: "primary",
|
|
449
|
+
size: "sm",
|
|
450
|
+
onClick: handleApply,
|
|
451
|
+
disabled: applyDisabled,
|
|
452
|
+
children: isSilence ? "Split" : "Save"
|
|
453
|
+
}
|
|
454
|
+
)
|
|
455
|
+
] })
|
|
456
|
+
] })
|
|
457
|
+
]
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
};
|
|
461
|
+
WordEditorModal.displayName = "WordEditorModal";
|
|
462
|
+
var SilenceSettingsModal = ({
|
|
463
|
+
isOpen,
|
|
464
|
+
onClose,
|
|
465
|
+
minSilenceMs,
|
|
466
|
+
nlSilenceMs,
|
|
467
|
+
onApply
|
|
468
|
+
}) => {
|
|
469
|
+
const [threshold, setThreshold] = React6__namespace.useState(minSilenceMs / 1e3);
|
|
470
|
+
const [nlThreshold, setNlThreshold] = React6__namespace.useState(nlSilenceMs / 1e3);
|
|
471
|
+
React6__namespace.useEffect(() => {
|
|
472
|
+
if (isOpen) {
|
|
473
|
+
setThreshold(minSilenceMs / 1e3);
|
|
474
|
+
setNlThreshold(nlSilenceMs / 1e3);
|
|
475
|
+
}
|
|
476
|
+
}, [isOpen, minSilenceMs, nlSilenceMs]);
|
|
477
|
+
const handleApply = () => {
|
|
478
|
+
const finalNlThreshold = Math.max(nlThreshold, threshold);
|
|
479
|
+
onApply(Math.round(threshold * 1e3), Math.round(finalNlThreshold * 1e3));
|
|
480
|
+
onClose();
|
|
481
|
+
};
|
|
482
|
+
const handleThresholdChange = (value) => {
|
|
483
|
+
setThreshold(value);
|
|
484
|
+
if (nlThreshold < value) {
|
|
485
|
+
setNlThreshold(value);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
const formatSeconds = (v) => `${v.toFixed(1)}s`;
|
|
489
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
490
|
+
chunkKJPOFUF5_cjs.Modal,
|
|
491
|
+
{
|
|
492
|
+
open: isOpen,
|
|
493
|
+
onOpenChange: (open) => {
|
|
494
|
+
if (!open) onClose();
|
|
495
|
+
},
|
|
496
|
+
size: "md",
|
|
497
|
+
children: [
|
|
498
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkKJPOFUF5_cjs.ModalHeader, { children: [
|
|
499
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkKJPOFUF5_cjs.ModalTitle, { children: "Silence Settings" }),
|
|
500
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkKJPOFUF5_cjs.ModalClose, { "aria-label": "Close modal" })
|
|
501
|
+
] }),
|
|
502
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkKJPOFUF5_cjs.ModalBody, { className: "flex flex-col gap-6", children: [
|
|
503
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
504
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
505
|
+
chunkGOKC4CCZ_cjs.Slider,
|
|
506
|
+
{
|
|
507
|
+
label: "Minimum silence duration",
|
|
508
|
+
min: 0.1,
|
|
509
|
+
max: 2,
|
|
510
|
+
step: 0.1,
|
|
511
|
+
value: threshold,
|
|
512
|
+
onValueChange: handleThresholdChange,
|
|
513
|
+
showValue: true,
|
|
514
|
+
formatValue: formatSeconds,
|
|
515
|
+
"aria-label": "Minimum silence duration"
|
|
516
|
+
}
|
|
517
|
+
),
|
|
518
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground m-0 text-xs", children: "Gaps shorter than this will not be shown as silences." })
|
|
519
|
+
] }),
|
|
520
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
521
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
522
|
+
chunkGOKC4CCZ_cjs.Slider,
|
|
523
|
+
{
|
|
524
|
+
label: "Paragraph break threshold",
|
|
525
|
+
min: threshold,
|
|
526
|
+
max: 5,
|
|
527
|
+
step: 0.1,
|
|
528
|
+
value: nlThreshold,
|
|
529
|
+
onValueChange: setNlThreshold,
|
|
530
|
+
showValue: true,
|
|
531
|
+
formatValue: formatSeconds,
|
|
532
|
+
"aria-label": "Paragraph break threshold"
|
|
533
|
+
}
|
|
534
|
+
),
|
|
535
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground m-0 text-xs", children: "Silences longer than this will create paragraph breaks." })
|
|
536
|
+
] })
|
|
537
|
+
] }),
|
|
538
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkKJPOFUF5_cjs.ModalFooter, { children: [
|
|
539
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkA2QVQF54_cjs.Button, { variant: "secondary", size: "sm", onClick: onClose, children: "Cancel" }),
|
|
540
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkA2QVQF54_cjs.Button, { variant: "primary", size: "sm", onClick: handleApply, children: "Apply" })
|
|
541
|
+
] })
|
|
542
|
+
]
|
|
543
|
+
}
|
|
544
|
+
);
|
|
545
|
+
};
|
|
546
|
+
SilenceSettingsModal.displayName = "SilenceSettingsModal";
|
|
547
|
+
var SpeedMarkerMenu = ({
|
|
548
|
+
isOpen,
|
|
549
|
+
position,
|
|
550
|
+
currentMarker,
|
|
551
|
+
defaultSpeed,
|
|
552
|
+
onSetSpeed,
|
|
553
|
+
onRemoveMarker,
|
|
554
|
+
onClose
|
|
555
|
+
}) => {
|
|
556
|
+
const menuRef = React6__namespace.useRef(null);
|
|
557
|
+
React6__namespace.useEffect(() => {
|
|
558
|
+
const handleKeyDown = (e) => {
|
|
559
|
+
if (e.key === "Escape") onClose();
|
|
560
|
+
};
|
|
561
|
+
const handleClickOutside = (e) => {
|
|
562
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
563
|
+
onClose();
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
if (isOpen) {
|
|
567
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
568
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
569
|
+
return () => {
|
|
570
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
571
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}, [isOpen, onClose]);
|
|
575
|
+
if (!isOpen || !position) return null;
|
|
576
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
577
|
+
"div",
|
|
578
|
+
{
|
|
579
|
+
ref: menuRef,
|
|
580
|
+
className: "border-border bg-card text-card-foreground fixed z-50 min-w-36 overflow-hidden rounded-lg border shadow-lg",
|
|
581
|
+
style: { left: position.x, top: position.y },
|
|
582
|
+
role: "menu",
|
|
583
|
+
"aria-label": "Set speed marker",
|
|
584
|
+
children: [
|
|
585
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-border text-muted-foreground border-b px-3 py-1.5 text-xs font-semibold", children: "Set Speed Marker" }),
|
|
586
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col p-1", children: chunkS7JQ3FV5_cjs.PLAYBACK_SPEEDS.map((speed) => {
|
|
587
|
+
const isActive = currentMarker?.speed === speed;
|
|
588
|
+
const isDefault = speed === defaultSpeed && !currentMarker;
|
|
589
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
590
|
+
"button",
|
|
591
|
+
{
|
|
592
|
+
type: "button",
|
|
593
|
+
className: `focus-visible:ring-ring flex items-center justify-between gap-2 rounded-md px-2.5 py-1 text-left text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none ${isActive ? "bg-primary-600 font-medium text-white" : "text-foreground hover:bg-muted"}`,
|
|
594
|
+
onClick: () => {
|
|
595
|
+
onSetSpeed(speed);
|
|
596
|
+
onClose();
|
|
597
|
+
},
|
|
598
|
+
role: "menuitem",
|
|
599
|
+
children: [
|
|
600
|
+
speed,
|
|
601
|
+
"x",
|
|
602
|
+
isDefault && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: "default" })
|
|
603
|
+
]
|
|
604
|
+
},
|
|
605
|
+
speed
|
|
606
|
+
);
|
|
607
|
+
}) }),
|
|
608
|
+
currentMarker && /* @__PURE__ */ jsxRuntime.jsx(
|
|
609
|
+
"button",
|
|
610
|
+
{
|
|
611
|
+
type: "button",
|
|
612
|
+
className: "border-border text-destructive hover:bg-destructive/10 focus-visible:ring-ring w-full border-t px-2.5 py-1.5 text-left text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
|
613
|
+
onClick: () => {
|
|
614
|
+
onRemoveMarker();
|
|
615
|
+
onClose();
|
|
616
|
+
},
|
|
617
|
+
role: "menuitem",
|
|
618
|
+
children: "Remove Marker"
|
|
619
|
+
}
|
|
620
|
+
)
|
|
621
|
+
]
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
};
|
|
625
|
+
SpeedMarkerMenu.displayName = "SpeedMarkerMenu";
|
|
626
|
+
var yamlPromise = null;
|
|
627
|
+
function loadYaml() {
|
|
628
|
+
yamlPromise ??= import(
|
|
629
|
+
/* @vite-ignore */
|
|
630
|
+
'js-yaml'
|
|
631
|
+
);
|
|
632
|
+
return yamlPromise;
|
|
633
|
+
}
|
|
634
|
+
async function parseScript(text, format) {
|
|
635
|
+
let data;
|
|
636
|
+
if (format === "json") {
|
|
637
|
+
data = JSON.parse(text);
|
|
638
|
+
} else {
|
|
639
|
+
const yaml = await loadYaml();
|
|
640
|
+
data = yaml.load(text);
|
|
641
|
+
}
|
|
642
|
+
if (!Array.isArray(data)) {
|
|
643
|
+
throw new Error("Script must be a list of word entries.");
|
|
644
|
+
}
|
|
645
|
+
return data.map((raw, i) => {
|
|
646
|
+
const ew = raw;
|
|
647
|
+
const w = ew.word;
|
|
648
|
+
if (!w || typeof w.text !== "string" || typeof w.startMs !== "number" || typeof w.endMs !== "number") {
|
|
649
|
+
throw new Error(
|
|
650
|
+
`Entry ${i + 1}: each entry needs word.text, word.startMs and word.endMs.`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
const word = {
|
|
654
|
+
text: w.text,
|
|
655
|
+
startMs: w.startMs,
|
|
656
|
+
endMs: w.endMs,
|
|
657
|
+
...typeof w.wordType === "string" ? { wordType: w.wordType } : {},
|
|
658
|
+
...typeof w.speakerId === "string" ? { speakerId: w.speakerId } : {}
|
|
659
|
+
};
|
|
660
|
+
return {
|
|
661
|
+
originalIndex: typeof ew.originalIndex === "number" ? ew.originalIndex : -1,
|
|
662
|
+
deleted: Boolean(ew.deleted),
|
|
663
|
+
...ew.inserted !== void 0 ? { inserted: Boolean(ew.inserted) } : {},
|
|
664
|
+
word
|
|
665
|
+
};
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
var ScriptPanel = ({
|
|
669
|
+
transcript,
|
|
670
|
+
editedWords,
|
|
671
|
+
onApply,
|
|
672
|
+
onClose,
|
|
673
|
+
className
|
|
674
|
+
}) => {
|
|
675
|
+
const [source, setSource] = React6__namespace.useState("editor");
|
|
676
|
+
const [format, setFormat] = React6__namespace.useState("yaml");
|
|
677
|
+
const [text, setText] = React6__namespace.useState("");
|
|
678
|
+
const [dirty, setDirty] = React6__namespace.useState(false);
|
|
679
|
+
const [error, setError] = React6__namespace.useState(null);
|
|
680
|
+
const readOnly = source === "original";
|
|
681
|
+
React6__namespace.useEffect(() => {
|
|
682
|
+
if (dirty) return;
|
|
683
|
+
let cancelled = false;
|
|
684
|
+
const data = source === "original" ? transcript : editedWords;
|
|
685
|
+
(async () => {
|
|
686
|
+
const serialized = format === "json" ? JSON.stringify(data, null, 2) : (await loadYaml()).dump(data, { lineWidth: 120 });
|
|
687
|
+
if (!cancelled) {
|
|
688
|
+
setText(serialized);
|
|
689
|
+
setError(null);
|
|
690
|
+
}
|
|
691
|
+
})();
|
|
692
|
+
return () => {
|
|
693
|
+
cancelled = true;
|
|
694
|
+
};
|
|
695
|
+
}, [source, format, transcript, editedWords, dirty]);
|
|
696
|
+
const handleApply = async () => {
|
|
697
|
+
try {
|
|
698
|
+
const words = await parseScript(text, format);
|
|
699
|
+
onApply(words);
|
|
700
|
+
setDirty(false);
|
|
701
|
+
setError(null);
|
|
702
|
+
} catch (err) {
|
|
703
|
+
setError(
|
|
704
|
+
err instanceof Error ? err.message : "Could not parse the script."
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
const handleReset = () => {
|
|
709
|
+
setDirty(false);
|
|
710
|
+
setError(null);
|
|
711
|
+
};
|
|
712
|
+
const sourceButton = (value, label) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
713
|
+
"button",
|
|
714
|
+
{
|
|
715
|
+
type: "button",
|
|
716
|
+
onClick: () => {
|
|
717
|
+
setSource(value);
|
|
718
|
+
setDirty(false);
|
|
719
|
+
setError(null);
|
|
720
|
+
},
|
|
721
|
+
"aria-pressed": source === value,
|
|
722
|
+
className: `focus-visible:ring-ring rounded px-1.5 py-0.5 text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none ${source === value ? "bg-primary-800 font-medium text-white" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
|
|
723
|
+
children: label
|
|
724
|
+
}
|
|
725
|
+
);
|
|
726
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
727
|
+
"aside",
|
|
728
|
+
{
|
|
729
|
+
className: `border-border bg-card flex w-80 shrink-0 flex-col border-l lg:w-96 ${className ?? ""}`,
|
|
730
|
+
"aria-label": "Transcript script",
|
|
731
|
+
children: [
|
|
732
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border flex shrink-0 flex-wrap items-center gap-1.5 border-b px-3 py-2", children: [
|
|
733
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xs font-semibold", children: "Script" }),
|
|
734
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-border mx-1 h-4 w-px", "aria-hidden": "true" }),
|
|
735
|
+
sourceButton("editor", "Editor"),
|
|
736
|
+
sourceButton("original", "Original"),
|
|
737
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-border mx-1 h-4 w-px", "aria-hidden": "true" }),
|
|
738
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
739
|
+
"button",
|
|
740
|
+
{
|
|
741
|
+
type: "button",
|
|
742
|
+
onClick: () => {
|
|
743
|
+
setFormat(format === "yaml" ? "json" : "yaml");
|
|
744
|
+
setDirty(false);
|
|
745
|
+
setError(null);
|
|
746
|
+
},
|
|
747
|
+
className: "text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:ring-ring rounded px-1.5 py-0.5 text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
|
748
|
+
title: "Toggle YAML / JSON",
|
|
749
|
+
children: format.toUpperCase()
|
|
750
|
+
}
|
|
751
|
+
),
|
|
752
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground ml-auto text-[10px]", children: readOnly ? "read-only" : dirty ? "edited" : "live" }),
|
|
753
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
754
|
+
chunkA2QVQF54_cjs.Button,
|
|
755
|
+
{
|
|
756
|
+
variant: "ghost",
|
|
757
|
+
size: "sm",
|
|
758
|
+
onClick: onClose,
|
|
759
|
+
"aria-label": "Close script panel",
|
|
760
|
+
children: "\u2715"
|
|
761
|
+
}
|
|
762
|
+
)
|
|
763
|
+
] }),
|
|
764
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
765
|
+
"textarea",
|
|
766
|
+
{
|
|
767
|
+
value: text,
|
|
768
|
+
readOnly,
|
|
769
|
+
spellCheck: false,
|
|
770
|
+
onChange: (e) => {
|
|
771
|
+
setText(e.target.value);
|
|
772
|
+
setDirty(true);
|
|
773
|
+
},
|
|
774
|
+
className: "text-foreground min-h-0 flex-1 resize-none bg-transparent p-3 font-mono text-xs leading-relaxed focus:outline-none",
|
|
775
|
+
"aria-label": `Script source (${source}, ${format.toUpperCase()})`
|
|
776
|
+
}
|
|
777
|
+
),
|
|
778
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(
|
|
779
|
+
"p",
|
|
780
|
+
{
|
|
781
|
+
className: "border-border text-destructive m-0 shrink-0 border-t px-3 py-2 text-xs",
|
|
782
|
+
role: "alert",
|
|
783
|
+
children: error
|
|
784
|
+
}
|
|
785
|
+
),
|
|
786
|
+
!readOnly && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border flex shrink-0 items-center gap-2 border-t px-3 py-2", children: [
|
|
787
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkA2QVQF54_cjs.Button, { size: "sm", onClick: handleApply, disabled: !dirty, children: "Apply" }),
|
|
788
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
789
|
+
chunkA2QVQF54_cjs.Button,
|
|
790
|
+
{
|
|
791
|
+
variant: "ghost",
|
|
792
|
+
size: "sm",
|
|
793
|
+
onClick: handleReset,
|
|
794
|
+
disabled: !dirty,
|
|
795
|
+
title: "Throw away unapplied typing and re-sync from the editor",
|
|
796
|
+
children: "Discard"
|
|
797
|
+
}
|
|
798
|
+
),
|
|
799
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-[10px]", children: "Apply pushes into the editor \u2014 revert an applied change with Undo" })
|
|
800
|
+
] })
|
|
801
|
+
]
|
|
802
|
+
}
|
|
803
|
+
);
|
|
804
|
+
};
|
|
805
|
+
var mediaEditorVariants = classVarianceAuthority.cva(
|
|
806
|
+
// Root is always a row so the ScriptPanel can dock on the right; the
|
|
807
|
+
// media/transcript split direction lives on the inner wrapper below.
|
|
808
|
+
"flex h-full min-h-0 w-full overflow-hidden rounded-xl border border-border bg-card text-card-foreground",
|
|
809
|
+
{
|
|
810
|
+
variants: {
|
|
811
|
+
splitLayout: {
|
|
812
|
+
horizontal: "",
|
|
813
|
+
vertical: ""
|
|
814
|
+
}
|
|
815
|
+
},
|
|
816
|
+
defaultVariants: {
|
|
817
|
+
splitLayout: "horizontal"
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
);
|
|
821
|
+
function formatDuration2(ms) {
|
|
822
|
+
const totalSeconds = Math.round(ms / 1e3);
|
|
823
|
+
if (totalSeconds < 90) {
|
|
824
|
+
return `${totalSeconds}s`;
|
|
825
|
+
}
|
|
826
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
827
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
828
|
+
const seconds = totalSeconds % 60;
|
|
829
|
+
if (hours > 0) {
|
|
830
|
+
return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
831
|
+
}
|
|
832
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
833
|
+
}
|
|
834
|
+
function wordClassName(s) {
|
|
835
|
+
const parts = [
|
|
836
|
+
"relative cursor-pointer rounded-sm px-1 py-0.5 transition-colors duration-150"
|
|
837
|
+
];
|
|
838
|
+
if (s.isSilence) {
|
|
839
|
+
parts.push("text-xs font-medium italic tracking-wide");
|
|
840
|
+
}
|
|
841
|
+
if (s.isSilenceNewline) {
|
|
842
|
+
parts.push("mt-1.5 block w-fit");
|
|
843
|
+
}
|
|
844
|
+
if (s.isAnchor) {
|
|
845
|
+
parts.push("bg-warning/40 outline outline-2 outline-warning");
|
|
846
|
+
} else if (s.isActive) {
|
|
847
|
+
parts.push("bg-primary-800 font-medium text-white");
|
|
848
|
+
} else if (s.isSelected && s.isFocused) {
|
|
849
|
+
parts.push("bg-primary-500/40");
|
|
850
|
+
} else if (s.isDeleted) {
|
|
851
|
+
parts.push(
|
|
852
|
+
"bg-destructive/10 text-destructive line-through opacity-40 hover:bg-destructive/20 hover:opacity-70"
|
|
853
|
+
);
|
|
854
|
+
} else if (s.isInserted) {
|
|
855
|
+
parts.push("bg-success/15 text-success hover:bg-success/25");
|
|
856
|
+
} else if (s.isSilenceNewline) {
|
|
857
|
+
parts.push("text-primary-500 hover:bg-primary-500/15");
|
|
858
|
+
} else if (s.isSilence) {
|
|
859
|
+
parts.push(
|
|
860
|
+
"border border-dashed border-border bg-muted text-muted-foreground hover:bg-muted/70"
|
|
861
|
+
);
|
|
862
|
+
} else {
|
|
863
|
+
parts.push("hover:bg-primary-500/30");
|
|
864
|
+
}
|
|
865
|
+
if (s.isDeleted && (s.isActive || s.isSelected && s.isFocused || s.isAnchor)) {
|
|
866
|
+
parts.push("line-through opacity-60");
|
|
867
|
+
}
|
|
868
|
+
if (s.isCursor && s.isFocused && !s.isAnchor) {
|
|
869
|
+
if (s.isDeleted) {
|
|
870
|
+
parts.push("outline outline-2 outline-offset-1 outline-destructive");
|
|
871
|
+
} else if (s.isActive) {
|
|
872
|
+
parts.push("outline outline-2 outline-offset-1 outline-background");
|
|
873
|
+
} else {
|
|
874
|
+
parts.push("outline outline-2 outline-offset-1 outline-primary-500");
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (s.hasSpeedMarker) {
|
|
878
|
+
parts.push("ml-0.5 border-l-[3px] border-l-warning pl-1.5");
|
|
879
|
+
}
|
|
880
|
+
return parts.join(" ");
|
|
881
|
+
}
|
|
882
|
+
var Caret = ({ side }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
883
|
+
"span",
|
|
884
|
+
{
|
|
885
|
+
"aria-hidden": "true",
|
|
886
|
+
className: `bg-primary-500 absolute top-0.5 bottom-0.5 w-0.5 motion-safe:animate-pulse ${side === "before" ? "-left-px" : "right-0.5"}`
|
|
887
|
+
}
|
|
888
|
+
);
|
|
889
|
+
var MediaEditor = React6__namespace.forwardRef(
|
|
890
|
+
({
|
|
891
|
+
src,
|
|
892
|
+
kind,
|
|
893
|
+
transcript,
|
|
894
|
+
initialEditedWords,
|
|
895
|
+
initialUndoStack,
|
|
896
|
+
onEditorStateChange,
|
|
897
|
+
onHasEditsChange,
|
|
898
|
+
onCursorTimestampChange,
|
|
899
|
+
onEditedWordsRender,
|
|
900
|
+
playerRef: externalPlayerRef,
|
|
901
|
+
className,
|
|
902
|
+
splitLayout
|
|
903
|
+
}, ref) => {
|
|
904
|
+
const edits = chunkY4ESBDQ5_cjs.useTranscriptEdits({
|
|
905
|
+
transcript,
|
|
906
|
+
initialEditedWords,
|
|
907
|
+
initialUndoStack,
|
|
908
|
+
onChange: onEditorStateChange
|
|
909
|
+
});
|
|
910
|
+
const {
|
|
911
|
+
editedWords,
|
|
912
|
+
undoStack,
|
|
913
|
+
clipboard,
|
|
914
|
+
hasEdits,
|
|
915
|
+
undo,
|
|
916
|
+
toggleWordDeleted,
|
|
917
|
+
deleteRange,
|
|
918
|
+
cut,
|
|
919
|
+
copy,
|
|
920
|
+
paste,
|
|
921
|
+
setWordText,
|
|
922
|
+
splitSilence,
|
|
923
|
+
deleteWord,
|
|
924
|
+
replaceEditedWords,
|
|
925
|
+
removeFillers,
|
|
926
|
+
setSilenceThresholds,
|
|
927
|
+
silenceThresholds,
|
|
928
|
+
speedMarkers,
|
|
929
|
+
defaultSpeed,
|
|
930
|
+
setDefaultSpeed,
|
|
931
|
+
toggleSpeedMarker,
|
|
932
|
+
removeSpeedMarker,
|
|
933
|
+
getSpeedAtIndex,
|
|
934
|
+
getSpeedMarkerAtIndex,
|
|
935
|
+
playbackSegments,
|
|
936
|
+
stats,
|
|
937
|
+
fillerAnalysis
|
|
938
|
+
} = edits;
|
|
939
|
+
const [activeWordIndex, setActiveWordIndex] = React6__namespace.useState(
|
|
940
|
+
null
|
|
941
|
+
);
|
|
942
|
+
const [cursorIndex, setCursorIndex] = React6__namespace.useState(0);
|
|
943
|
+
const [cursorPosition, setCursorPosition] = React6__namespace.useState("before");
|
|
944
|
+
const [selectionAnchor, setSelectionAnchor] = React6__namespace.useState(
|
|
945
|
+
null
|
|
946
|
+
);
|
|
947
|
+
const [selection, setSelection] = React6__namespace.useState(
|
|
948
|
+
null
|
|
949
|
+
);
|
|
950
|
+
const [doubleClickAnchor, setDoubleClickAnchor] = React6__namespace.useState(null);
|
|
951
|
+
const [isPlayingSequence, setIsPlayingSequence] = React6__namespace.useState(false);
|
|
952
|
+
const [isFocused, setIsFocused] = React6__namespace.useState(false);
|
|
953
|
+
const [showFillerModal, setShowFillerModal] = React6__namespace.useState(false);
|
|
954
|
+
const [showSilenceModal, setShowSilenceModal] = React6__namespace.useState(false);
|
|
955
|
+
const [showScriptPanel, setShowScriptPanel] = React6__namespace.useState(false);
|
|
956
|
+
const [editorWordIndex, setEditorWordIndex] = React6__namespace.useState(
|
|
957
|
+
null
|
|
958
|
+
);
|
|
959
|
+
const [speedMenuWordIndex, setSpeedMenuWordIndex] = React6__namespace.useState(null);
|
|
960
|
+
const [speedMenuPosition, setSpeedMenuPosition] = React6__namespace.useState(null);
|
|
961
|
+
const playerRef = React6__namespace.useRef(null);
|
|
962
|
+
React6__namespace.useImperativeHandle(
|
|
963
|
+
externalPlayerRef,
|
|
964
|
+
() => playerRef.current,
|
|
965
|
+
[]
|
|
966
|
+
);
|
|
967
|
+
const contentRef = React6__namespace.useRef(null);
|
|
968
|
+
const userSetCursor = React6__namespace.useRef(null);
|
|
969
|
+
const isDragging = React6__namespace.useRef(false);
|
|
970
|
+
const longPressTimer = React6__namespace.useRef(
|
|
971
|
+
null
|
|
972
|
+
);
|
|
973
|
+
const longPressTriggered = React6__namespace.useRef(false);
|
|
974
|
+
const wordPlaybackStartMs = React6__namespace.useRef(null);
|
|
975
|
+
const wordPlaybackEndMs = React6__namespace.useRef(null);
|
|
976
|
+
const sequenceSegments = React6__namespace.useRef([]);
|
|
977
|
+
const currentSegmentIndex = React6__namespace.useRef(0);
|
|
978
|
+
React6__namespace.useEffect(() => {
|
|
979
|
+
onHasEditsChange?.(hasEdits);
|
|
980
|
+
}, [hasEdits, onHasEditsChange]);
|
|
981
|
+
React6__namespace.useEffect(() => {
|
|
982
|
+
onEditedWordsRender?.(editedWords);
|
|
983
|
+
}, [editedWords, onEditedWordsRender]);
|
|
984
|
+
React6__namespace.useEffect(() => {
|
|
985
|
+
if (onCursorTimestampChange) {
|
|
986
|
+
const word = editedWords[cursorIndex];
|
|
987
|
+
if (word && !word.deleted) {
|
|
988
|
+
onCursorTimestampChange(word.word.startMs);
|
|
989
|
+
} else {
|
|
990
|
+
onCursorTimestampChange(null);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}, [cursorIndex, editedWords, onCursorTimestampChange]);
|
|
994
|
+
const prevTranscriptRef = React6__namespace.useRef(transcript);
|
|
995
|
+
React6__namespace.useEffect(() => {
|
|
996
|
+
if (prevTranscriptRef.current === transcript) return;
|
|
997
|
+
prevTranscriptRef.current = transcript;
|
|
998
|
+
setCursorIndex(0);
|
|
999
|
+
setCursorPosition("before");
|
|
1000
|
+
setSelection(null);
|
|
1001
|
+
setSelectionAnchor(null);
|
|
1002
|
+
setDoubleClickAnchor(null);
|
|
1003
|
+
setActiveWordIndex(null);
|
|
1004
|
+
setIsPlayingSequence(false);
|
|
1005
|
+
sequenceSegments.current = [];
|
|
1006
|
+
currentSegmentIndex.current = 0;
|
|
1007
|
+
}, [transcript]);
|
|
1008
|
+
React6__namespace.useEffect(() => {
|
|
1009
|
+
if (userSetCursor.current !== null) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (activeWordIndex !== null && activeWordIndex >= 0) {
|
|
1013
|
+
setCursorIndex(activeWordIndex);
|
|
1014
|
+
setCursorPosition("before");
|
|
1015
|
+
setSelection(null);
|
|
1016
|
+
setSelectionAnchor(null);
|
|
1017
|
+
}
|
|
1018
|
+
}, [activeWordIndex]);
|
|
1019
|
+
React6__namespace.useEffect(() => {
|
|
1020
|
+
if (activeWordIndex === null) return;
|
|
1021
|
+
const el = contentRef.current?.querySelector(
|
|
1022
|
+
`[data-word-index="${activeWordIndex}"]`
|
|
1023
|
+
);
|
|
1024
|
+
el?.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
1025
|
+
}, [activeWordIndex]);
|
|
1026
|
+
React6__namespace.useEffect(() => {
|
|
1027
|
+
contentRef.current?.focus();
|
|
1028
|
+
}, [transcript]);
|
|
1029
|
+
React6__namespace.useEffect(() => {
|
|
1030
|
+
const player = playerRef.current;
|
|
1031
|
+
if (!player) return;
|
|
1032
|
+
const timeMs = player.getCurrentTimeMs();
|
|
1033
|
+
const currentWordIndex = editedWords.findIndex(
|
|
1034
|
+
(ew) => !ew.deleted && timeMs >= ew.word.startMs && timeMs < ew.word.endMs
|
|
1035
|
+
);
|
|
1036
|
+
const targetSpeed = currentWordIndex >= 0 ? getSpeedAtIndex(currentWordIndex) : defaultSpeed;
|
|
1037
|
+
player.setPlaybackRate(targetSpeed);
|
|
1038
|
+
}, [defaultSpeed, speedMarkers, editedWords, getSpeedAtIndex]);
|
|
1039
|
+
const handleTimeUpdate = (timeMs) => {
|
|
1040
|
+
const player = playerRef.current;
|
|
1041
|
+
if (!player) return;
|
|
1042
|
+
if (isPlayingSequence && sequenceSegments.current.length > 0) {
|
|
1043
|
+
const currentSeg = sequenceSegments.current[currentSegmentIndex.current];
|
|
1044
|
+
if (currentSeg && timeMs >= currentSeg.endMs) {
|
|
1045
|
+
const nextIndex = currentSegmentIndex.current + 1;
|
|
1046
|
+
if (nextIndex < sequenceSegments.current.length) {
|
|
1047
|
+
currentSegmentIndex.current = nextIndex;
|
|
1048
|
+
player.seekToMs(sequenceSegments.current[nextIndex].startMs);
|
|
1049
|
+
} else {
|
|
1050
|
+
player.pause();
|
|
1051
|
+
setIsPlayingSequence(false);
|
|
1052
|
+
sequenceSegments.current = [];
|
|
1053
|
+
currentSegmentIndex.current = 0;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
} else if (hasEdits && !player.isPaused()) {
|
|
1057
|
+
const inDeletedWord = editedWords.some(
|
|
1058
|
+
(ew) => ew.deleted && timeMs >= ew.word.startMs && timeMs < ew.word.endMs
|
|
1059
|
+
);
|
|
1060
|
+
if (inDeletedWord) {
|
|
1061
|
+
const nextNonDeleted = editedWords.find(
|
|
1062
|
+
(ew) => !ew.deleted && ew.word.startMs >= timeMs
|
|
1063
|
+
);
|
|
1064
|
+
if (nextNonDeleted) {
|
|
1065
|
+
player.seekToMs(nextNonDeleted.word.startMs);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
if (wordPlaybackEndMs.current !== null && timeMs >= wordPlaybackEndMs.current) {
|
|
1070
|
+
player.pause();
|
|
1071
|
+
if (wordPlaybackStartMs.current !== null) {
|
|
1072
|
+
player.seekToMs(wordPlaybackStartMs.current);
|
|
1073
|
+
wordPlaybackStartMs.current = null;
|
|
1074
|
+
}
|
|
1075
|
+
wordPlaybackEndMs.current = null;
|
|
1076
|
+
}
|
|
1077
|
+
let editedIndex = -1;
|
|
1078
|
+
if (isPlayingSequence && sequenceSegments.current.length > 0) {
|
|
1079
|
+
const currentSeg = sequenceSegments.current[currentSegmentIndex.current];
|
|
1080
|
+
if (currentSeg) {
|
|
1081
|
+
for (const idx of currentSeg.editedIndices) {
|
|
1082
|
+
const ew = editedWords[idx];
|
|
1083
|
+
if (ew && !ew.deleted && timeMs >= ew.word.startMs && timeMs < ew.word.endMs) {
|
|
1084
|
+
editedIndex = idx;
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (editedIndex === -1) {
|
|
1091
|
+
editedIndex = editedWords.findIndex(
|
|
1092
|
+
(ew) => !ew.deleted && timeMs >= ew.word.startMs && timeMs < ew.word.endMs
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
if (editedIndex !== activeWordIndex && editedIndex >= 0) {
|
|
1096
|
+
player.setPlaybackRate(getSpeedAtIndex(editedIndex));
|
|
1097
|
+
}
|
|
1098
|
+
setActiveWordIndex(editedIndex >= 0 ? editedIndex : null);
|
|
1099
|
+
userSetCursor.current = null;
|
|
1100
|
+
};
|
|
1101
|
+
const stopSequencePlayback = () => {
|
|
1102
|
+
setIsPlayingSequence(false);
|
|
1103
|
+
sequenceSegments.current = [];
|
|
1104
|
+
currentSegmentIndex.current = 0;
|
|
1105
|
+
};
|
|
1106
|
+
const selectionIndices = React6__namespace.useCallback(() => {
|
|
1107
|
+
if (selection) {
|
|
1108
|
+
const indices = [];
|
|
1109
|
+
for (let i = selection.start; i <= selection.end; i++) indices.push(i);
|
|
1110
|
+
return indices;
|
|
1111
|
+
}
|
|
1112
|
+
return [cursorIndex];
|
|
1113
|
+
}, [selection, cursorIndex]);
|
|
1114
|
+
const handleCut = React6__namespace.useCallback(() => {
|
|
1115
|
+
cut(selectionIndices());
|
|
1116
|
+
}, [cut, selectionIndices]);
|
|
1117
|
+
const handlePaste = React6__namespace.useCallback(() => {
|
|
1118
|
+
if (!clipboard) return;
|
|
1119
|
+
const pastedCount = clipboard.words.length;
|
|
1120
|
+
const insertIndex = paste(cursorIndex, cursorPosition !== "after");
|
|
1121
|
+
if (insertIndex !== null) {
|
|
1122
|
+
setCursorIndex(insertIndex + pastedCount - 1);
|
|
1123
|
+
setCursorPosition("after");
|
|
1124
|
+
setSelection(null);
|
|
1125
|
+
setSelectionAnchor(null);
|
|
1126
|
+
}
|
|
1127
|
+
}, [clipboard, paste, cursorIndex, cursorPosition]);
|
|
1128
|
+
const handleDeleteToggle = React6__namespace.useCallback(() => {
|
|
1129
|
+
if (selection) {
|
|
1130
|
+
const anchorIndex = selectionAnchor ?? selection.start;
|
|
1131
|
+
const indices = [anchorIndex];
|
|
1132
|
+
for (let i = selection.start; i <= selection.end; i++) {
|
|
1133
|
+
if (i !== anchorIndex) indices.push(i);
|
|
1134
|
+
}
|
|
1135
|
+
deleteRange(indices);
|
|
1136
|
+
} else {
|
|
1137
|
+
toggleWordDeleted(cursorIndex);
|
|
1138
|
+
}
|
|
1139
|
+
}, [
|
|
1140
|
+
selection,
|
|
1141
|
+
selectionAnchor,
|
|
1142
|
+
cursorIndex,
|
|
1143
|
+
deleteRange,
|
|
1144
|
+
toggleWordDeleted
|
|
1145
|
+
]);
|
|
1146
|
+
const handleCopy = React6__namespace.useCallback(
|
|
1147
|
+
(e) => {
|
|
1148
|
+
const indices = selectionIndices();
|
|
1149
|
+
const selectedWords = indices.map((i) => editedWords[i]).filter((ew) => ew !== void 0 && !ew.deleted);
|
|
1150
|
+
if (selectedWords.length === 0) return;
|
|
1151
|
+
const selectedText = selectedWords.filter((ew) => ew.word.wordType !== "silence").map((ew) => ew.word.text).join(" ");
|
|
1152
|
+
e.preventDefault();
|
|
1153
|
+
e.clipboardData.setData("text/plain", selectedText);
|
|
1154
|
+
copy(indices);
|
|
1155
|
+
},
|
|
1156
|
+
[selectionIndices, editedWords, copy]
|
|
1157
|
+
);
|
|
1158
|
+
const openWordEditor = React6__namespace.useCallback((index) => {
|
|
1159
|
+
setEditorWordIndex(index);
|
|
1160
|
+
}, []);
|
|
1161
|
+
const closeWordEditor = React6__namespace.useCallback(() => {
|
|
1162
|
+
setEditorWordIndex(null);
|
|
1163
|
+
setTimeout(() => contentRef.current?.focus(), 0);
|
|
1164
|
+
}, []);
|
|
1165
|
+
const handleEditorSave = React6__namespace.useCallback(
|
|
1166
|
+
(newText) => {
|
|
1167
|
+
if (editorWordIndex === null) return;
|
|
1168
|
+
setWordText(editorWordIndex, newText);
|
|
1169
|
+
},
|
|
1170
|
+
[editorWordIndex, setWordText]
|
|
1171
|
+
);
|
|
1172
|
+
const handleEditorSplitSilence = React6__namespace.useCallback(
|
|
1173
|
+
(durations) => {
|
|
1174
|
+
if (editorWordIndex === null) return;
|
|
1175
|
+
splitSilence(editorWordIndex, durations);
|
|
1176
|
+
},
|
|
1177
|
+
[editorWordIndex, splitSilence]
|
|
1178
|
+
);
|
|
1179
|
+
const handleEditorDelete = React6__namespace.useCallback(() => {
|
|
1180
|
+
if (editorWordIndex !== null) {
|
|
1181
|
+
deleteWord(editorWordIndex);
|
|
1182
|
+
closeWordEditor();
|
|
1183
|
+
}
|
|
1184
|
+
}, [editorWordIndex, deleteWord, closeWordEditor]);
|
|
1185
|
+
const handleWordClick = (word, index, e) => {
|
|
1186
|
+
setCursorIndex(index);
|
|
1187
|
+
setCursorPosition("before");
|
|
1188
|
+
userSetCursor.current = index;
|
|
1189
|
+
if (e.shiftKey && selectionAnchor !== null) {
|
|
1190
|
+
setSelection({
|
|
1191
|
+
start: Math.min(selectionAnchor, index),
|
|
1192
|
+
end: Math.max(selectionAnchor, index)
|
|
1193
|
+
});
|
|
1194
|
+
} else if (doubleClickAnchor !== null && doubleClickAnchor !== index && !isDragging.current) {
|
|
1195
|
+
const start = Math.min(doubleClickAnchor, index);
|
|
1196
|
+
const end = Math.max(doubleClickAnchor, index);
|
|
1197
|
+
setSelection({ start, end });
|
|
1198
|
+
setSelectionAnchor(doubleClickAnchor);
|
|
1199
|
+
setDoubleClickAnchor(null);
|
|
1200
|
+
} else if (doubleClickAnchor === index) ; else if (!isDragging.current) {
|
|
1201
|
+
setSelectionAnchor(index);
|
|
1202
|
+
setSelection(null);
|
|
1203
|
+
const player = playerRef.current;
|
|
1204
|
+
if (player) {
|
|
1205
|
+
player.pause();
|
|
1206
|
+
player.seekToMs(word.startMs);
|
|
1207
|
+
wordPlaybackStartMs.current = word.startMs;
|
|
1208
|
+
wordPlaybackEndMs.current = word.endMs;
|
|
1209
|
+
player.play();
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
const handleWordDoubleClick = (index, e) => {
|
|
1214
|
+
e.preventDefault();
|
|
1215
|
+
if (longPressTriggered.current) return;
|
|
1216
|
+
if (doubleClickAnchor === index) {
|
|
1217
|
+
setDoubleClickAnchor(null);
|
|
1218
|
+
} else {
|
|
1219
|
+
setDoubleClickAnchor(index);
|
|
1220
|
+
setSelection(null);
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
const handleWordMouseDown = (index, e) => {
|
|
1224
|
+
if (e.button !== 0) return;
|
|
1225
|
+
longPressTriggered.current = false;
|
|
1226
|
+
longPressTimer.current = setTimeout(() => {
|
|
1227
|
+
longPressTriggered.current = true;
|
|
1228
|
+
openWordEditor(index);
|
|
1229
|
+
}, 500);
|
|
1230
|
+
isDragging.current = true;
|
|
1231
|
+
setSelectionAnchor(index);
|
|
1232
|
+
setCursorIndex(index);
|
|
1233
|
+
setCursorPosition("before");
|
|
1234
|
+
setSelection(null);
|
|
1235
|
+
};
|
|
1236
|
+
const handleWordMouseEnter = (index) => {
|
|
1237
|
+
if (longPressTimer.current) {
|
|
1238
|
+
clearTimeout(longPressTimer.current);
|
|
1239
|
+
longPressTimer.current = null;
|
|
1240
|
+
}
|
|
1241
|
+
if (!isDragging.current || selectionAnchor === null) return;
|
|
1242
|
+
setSelection({
|
|
1243
|
+
start: Math.min(selectionAnchor, index),
|
|
1244
|
+
end: Math.max(selectionAnchor, index)
|
|
1245
|
+
});
|
|
1246
|
+
setCursorIndex(index);
|
|
1247
|
+
};
|
|
1248
|
+
const handleMouseUp = () => {
|
|
1249
|
+
if (longPressTimer.current) {
|
|
1250
|
+
clearTimeout(longPressTimer.current);
|
|
1251
|
+
longPressTimer.current = null;
|
|
1252
|
+
}
|
|
1253
|
+
if (isDragging.current) {
|
|
1254
|
+
isDragging.current = false;
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
const handleWordContextMenu = (index, e) => {
|
|
1258
|
+
e.preventDefault();
|
|
1259
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
1260
|
+
const menuHeight = 280;
|
|
1261
|
+
const menuWidth = 150;
|
|
1262
|
+
let x = rect.left;
|
|
1263
|
+
let y = rect.bottom + 4;
|
|
1264
|
+
if (x + menuWidth > window.innerWidth) {
|
|
1265
|
+
x = window.innerWidth - menuWidth - 8;
|
|
1266
|
+
}
|
|
1267
|
+
if (y + menuHeight > window.innerHeight) {
|
|
1268
|
+
y = rect.top - menuHeight - 4;
|
|
1269
|
+
if (y < 0) {
|
|
1270
|
+
y = 8;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
setSpeedMenuPosition({ x, y });
|
|
1274
|
+
setSpeedMenuWordIndex(index);
|
|
1275
|
+
};
|
|
1276
|
+
const handleKeyDown = React6__namespace.useCallback(
|
|
1277
|
+
(e) => {
|
|
1278
|
+
const wordCount = editedWords.length;
|
|
1279
|
+
if (wordCount === 0) return;
|
|
1280
|
+
let newIndex = cursorIndex;
|
|
1281
|
+
let handled = false;
|
|
1282
|
+
if (e.key === "Delete" || e.key === "Backspace") {
|
|
1283
|
+
handleDeleteToggle();
|
|
1284
|
+
handled = true;
|
|
1285
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key === "x") {
|
|
1286
|
+
handleCut();
|
|
1287
|
+
handled = true;
|
|
1288
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key === "v" && clipboard) {
|
|
1289
|
+
handlePaste();
|
|
1290
|
+
handled = true;
|
|
1291
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key === "z" && !e.shiftKey) {
|
|
1292
|
+
undo();
|
|
1293
|
+
handled = true;
|
|
1294
|
+
} else if (e.key === "ArrowLeft") {
|
|
1295
|
+
if (cursorPosition === "after") {
|
|
1296
|
+
setCursorPosition("before");
|
|
1297
|
+
} else {
|
|
1298
|
+
newIndex = Math.max(0, cursorIndex - 1);
|
|
1299
|
+
}
|
|
1300
|
+
handled = true;
|
|
1301
|
+
} else if (e.key === "ArrowRight") {
|
|
1302
|
+
if (cursorIndex === wordCount - 1 && cursorPosition === "before") {
|
|
1303
|
+
setCursorPosition("after");
|
|
1304
|
+
} else if (cursorPosition !== "after") {
|
|
1305
|
+
newIndex = Math.min(wordCount - 1, cursorIndex + 1);
|
|
1306
|
+
}
|
|
1307
|
+
handled = true;
|
|
1308
|
+
} else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
1309
|
+
const currentElement = contentRef.current?.querySelector(
|
|
1310
|
+
`[data-word-index="${cursorIndex}"]`
|
|
1311
|
+
);
|
|
1312
|
+
if (currentElement) {
|
|
1313
|
+
const currentRect = currentElement.getBoundingClientRect();
|
|
1314
|
+
const currentCenterX = currentRect.left + currentRect.width / 2;
|
|
1315
|
+
const direction = e.key === "ArrowUp" ? -1 : 1;
|
|
1316
|
+
let bestMatch = cursorIndex;
|
|
1317
|
+
let bestDistance = Infinity;
|
|
1318
|
+
let targetRowTop = null;
|
|
1319
|
+
for (let i = cursorIndex + direction; i >= 0 && i < wordCount; i += direction) {
|
|
1320
|
+
const el = contentRef.current?.querySelector(
|
|
1321
|
+
`[data-word-index="${i}"]`
|
|
1322
|
+
);
|
|
1323
|
+
if (!el) continue;
|
|
1324
|
+
const rect = el.getBoundingClientRect();
|
|
1325
|
+
const isOnDifferentRow = direction === -1 ? rect.bottom <= currentRect.top : rect.top >= currentRect.bottom;
|
|
1326
|
+
if (isOnDifferentRow) {
|
|
1327
|
+
if (targetRowTop === null) {
|
|
1328
|
+
targetRowTop = rect.top;
|
|
1329
|
+
}
|
|
1330
|
+
const isSameTargetRow = Math.abs(rect.top - targetRowTop) < 5;
|
|
1331
|
+
if (isSameTargetRow) {
|
|
1332
|
+
const centerX = rect.left + rect.width / 2;
|
|
1333
|
+
const distance = Math.abs(centerX - currentCenterX);
|
|
1334
|
+
if (distance < bestDistance) {
|
|
1335
|
+
bestDistance = distance;
|
|
1336
|
+
bestMatch = i;
|
|
1337
|
+
}
|
|
1338
|
+
} else {
|
|
1339
|
+
break;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
newIndex = bestMatch;
|
|
1344
|
+
}
|
|
1345
|
+
handled = true;
|
|
1346
|
+
} else if (e.key === "Home") {
|
|
1347
|
+
newIndex = 0;
|
|
1348
|
+
handled = true;
|
|
1349
|
+
} else if (e.key === "End") {
|
|
1350
|
+
newIndex = wordCount - 1;
|
|
1351
|
+
handled = true;
|
|
1352
|
+
} else if (e.key === "Enter") {
|
|
1353
|
+
openWordEditor(cursorIndex);
|
|
1354
|
+
handled = true;
|
|
1355
|
+
} else if (e.key === " ") {
|
|
1356
|
+
const ew = editedWords[cursorIndex];
|
|
1357
|
+
const player = playerRef.current;
|
|
1358
|
+
if (player && ew && !ew.deleted) {
|
|
1359
|
+
if (player.isPaused()) {
|
|
1360
|
+
if (hasEdits) {
|
|
1361
|
+
const segments = playbackSegments;
|
|
1362
|
+
if (segments.length > 0) {
|
|
1363
|
+
let startSegmentIdx = 0;
|
|
1364
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1365
|
+
if (segments[i].editedIndices.includes(cursorIndex)) {
|
|
1366
|
+
startSegmentIdx = i;
|
|
1367
|
+
break;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
sequenceSegments.current = segments;
|
|
1371
|
+
currentSegmentIndex.current = startSegmentIdx;
|
|
1372
|
+
setIsPlayingSequence(true);
|
|
1373
|
+
player.seekToMs(ew.word.startMs);
|
|
1374
|
+
player.play();
|
|
1375
|
+
}
|
|
1376
|
+
} else {
|
|
1377
|
+
player.seekToMs(ew.word.startMs);
|
|
1378
|
+
player.play();
|
|
1379
|
+
}
|
|
1380
|
+
} else {
|
|
1381
|
+
player.pause();
|
|
1382
|
+
setIsPlayingSequence(false);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
handled = true;
|
|
1386
|
+
}
|
|
1387
|
+
if (handled) {
|
|
1388
|
+
e.preventDefault();
|
|
1389
|
+
e.stopPropagation();
|
|
1390
|
+
if (newIndex !== cursorIndex) {
|
|
1391
|
+
setCursorIndex(newIndex);
|
|
1392
|
+
setCursorPosition("before");
|
|
1393
|
+
}
|
|
1394
|
+
if (e.shiftKey && e.key !== "Delete" && e.key !== "Backspace") {
|
|
1395
|
+
const anchor = selectionAnchor ?? cursorIndex;
|
|
1396
|
+
if (selectionAnchor === null) {
|
|
1397
|
+
setSelectionAnchor(cursorIndex);
|
|
1398
|
+
}
|
|
1399
|
+
setSelection({
|
|
1400
|
+
start: Math.min(anchor, newIndex),
|
|
1401
|
+
end: Math.max(anchor, newIndex)
|
|
1402
|
+
});
|
|
1403
|
+
} else if (!e.metaKey && !e.ctrlKey) {
|
|
1404
|
+
setSelection(null);
|
|
1405
|
+
setSelectionAnchor(null);
|
|
1406
|
+
}
|
|
1407
|
+
const wordElement = contentRef.current?.querySelector(
|
|
1408
|
+
`[data-word-index="${newIndex}"]`
|
|
1409
|
+
);
|
|
1410
|
+
wordElement?.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
1411
|
+
}
|
|
1412
|
+
},
|
|
1413
|
+
[
|
|
1414
|
+
editedWords,
|
|
1415
|
+
cursorIndex,
|
|
1416
|
+
cursorPosition,
|
|
1417
|
+
selectionAnchor,
|
|
1418
|
+
clipboard,
|
|
1419
|
+
hasEdits,
|
|
1420
|
+
playbackSegments,
|
|
1421
|
+
handleDeleteToggle,
|
|
1422
|
+
handleCut,
|
|
1423
|
+
handlePaste,
|
|
1424
|
+
undo,
|
|
1425
|
+
openWordEditor
|
|
1426
|
+
]
|
|
1427
|
+
);
|
|
1428
|
+
const isWordSelected = (index) => {
|
|
1429
|
+
if (!selection) return false;
|
|
1430
|
+
return index >= selection.start && index <= selection.end;
|
|
1431
|
+
};
|
|
1432
|
+
const renderWord = (ew, index) => {
|
|
1433
|
+
const isActive = index === activeWordIndex;
|
|
1434
|
+
const isCursor = index === cursorIndex;
|
|
1435
|
+
const isSelected = isWordSelected(index);
|
|
1436
|
+
const isDeleted = ew.deleted;
|
|
1437
|
+
const isInserted = ew.inserted ?? false;
|
|
1438
|
+
const isSilence = ew.word.wordType === "silence" || ew.word.wordType === "silence-newline";
|
|
1439
|
+
const isSilenceNewline = ew.word.wordType === "silence-newline";
|
|
1440
|
+
const isAnchor = index === doubleClickAnchor;
|
|
1441
|
+
const showCursorBefore = isCursor && cursorPosition === "before";
|
|
1442
|
+
const showCursorAfter = isCursor && cursorPosition === "after" && index === editedWords.length - 1;
|
|
1443
|
+
const speedMarker = getSpeedMarkerAtIndex(index);
|
|
1444
|
+
const hasSpeedMarker = !!speedMarker;
|
|
1445
|
+
const effectiveSpeed = getSpeedAtIndex(index);
|
|
1446
|
+
let displayText = ew.word.text;
|
|
1447
|
+
if (isSilence && effectiveSpeed !== 1) {
|
|
1448
|
+
const originalDurationMs = ew.word.endMs - ew.word.startMs;
|
|
1449
|
+
const adjustedDurationSec = originalDurationMs / effectiveSpeed / 1e3;
|
|
1450
|
+
displayText = `[${adjustedDurationSec.toFixed(1)}s]`;
|
|
1451
|
+
}
|
|
1452
|
+
const title = `${ew.word.startMs}ms - ${ew.word.endMs}ms${ew.word.confidence ? ` (${Math.round(ew.word.confidence * 100)}%)` : ""}${isSilence ? ` [SILENCE: ${((ew.word.endMs - ew.word.startMs) / 1e3).toFixed(1)}s${effectiveSpeed !== 1 ? ` \u2192 ${((ew.word.endMs - ew.word.startMs) / effectiveSpeed / 1e3).toFixed(1)}s @ ${effectiveSpeed}x` : ""}]` : ""}${isDeleted ? " [DELETED]" : ""}${isInserted ? " [INSERTED]" : ""}${hasSpeedMarker ? ` [SPEED: ${speedMarker.speed}x]` : ""}${isAnchor ? " [ANCHOR - click another word to select range, double-click to clear]" : ""}${!isAnchor && !isDeleted ? " (right-click to set speed marker)" : ""}`;
|
|
1453
|
+
return (
|
|
1454
|
+
// Keyboard interaction is owned by the parent listbox via roving focus
|
|
1455
|
+
// (aria-activedescendant); options are pointer affordances only.
|
|
1456
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus
|
|
1457
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1458
|
+
"span",
|
|
1459
|
+
{
|
|
1460
|
+
id: `word-${index}`,
|
|
1461
|
+
"data-word-index": index,
|
|
1462
|
+
className: wordClassName({
|
|
1463
|
+
isActive,
|
|
1464
|
+
isCursor,
|
|
1465
|
+
isSelected,
|
|
1466
|
+
isDeleted,
|
|
1467
|
+
isInserted,
|
|
1468
|
+
isSilence,
|
|
1469
|
+
isSilenceNewline,
|
|
1470
|
+
isAnchor,
|
|
1471
|
+
isFocused,
|
|
1472
|
+
hasSpeedMarker
|
|
1473
|
+
}),
|
|
1474
|
+
onClick: (e) => handleWordClick(ew.word, index, e),
|
|
1475
|
+
onDoubleClick: (e) => handleWordDoubleClick(index, e),
|
|
1476
|
+
onMouseDown: (e) => handleWordMouseDown(index, e),
|
|
1477
|
+
onMouseEnter: () => handleWordMouseEnter(index),
|
|
1478
|
+
onContextMenu: (e) => handleWordContextMenu(index, e),
|
|
1479
|
+
role: "option",
|
|
1480
|
+
"aria-selected": isSelected || isCursor,
|
|
1481
|
+
title,
|
|
1482
|
+
children: [
|
|
1483
|
+
showCursorBefore && isFocused && /* @__PURE__ */ jsxRuntime.jsx(Caret, { side: "before" }),
|
|
1484
|
+
hasSpeedMarker && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1485
|
+
"span",
|
|
1486
|
+
{
|
|
1487
|
+
className: "border-warning/50 bg-warning/20 text-warning-foreground mr-1 inline-block rounded-sm border px-0.5 align-middle text-[9px] leading-snug font-semibold",
|
|
1488
|
+
title: `Speed: ${speedMarker.speed}x from here`,
|
|
1489
|
+
children: [
|
|
1490
|
+
speedMarker.speed,
|
|
1491
|
+
"x"
|
|
1492
|
+
]
|
|
1493
|
+
}
|
|
1494
|
+
),
|
|
1495
|
+
displayText,
|
|
1496
|
+
" ",
|
|
1497
|
+
showCursorAfter && isFocused && /* @__PURE__ */ jsxRuntime.jsx(Caret, { side: "after" })
|
|
1498
|
+
]
|
|
1499
|
+
},
|
|
1500
|
+
`${ew.originalIndex}-${index}`
|
|
1501
|
+
)
|
|
1502
|
+
);
|
|
1503
|
+
};
|
|
1504
|
+
const showEditBar = hasEdits || doubleClickAnchor !== null || selection !== null;
|
|
1505
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1506
|
+
"div",
|
|
1507
|
+
{
|
|
1508
|
+
ref,
|
|
1509
|
+
className: mediaEditorVariants({ splitLayout, className }),
|
|
1510
|
+
children: [
|
|
1511
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1512
|
+
"div",
|
|
1513
|
+
{
|
|
1514
|
+
className: `flex min-h-0 min-w-0 flex-1 ${splitLayout === "vertical" ? "flex-col md:flex-row" : "flex-col"}`,
|
|
1515
|
+
children: [
|
|
1516
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1517
|
+
"div",
|
|
1518
|
+
{
|
|
1519
|
+
className: `border-border bg-background min-h-0 shrink-0 ${splitLayout === "vertical" ? "md:w-1/2 md:border-r" : "max-h-[50%] border-b"}`,
|
|
1520
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1521
|
+
chunkYD23ZGRU_cjs.MediaPlayer,
|
|
1522
|
+
{
|
|
1523
|
+
ref: playerRef,
|
|
1524
|
+
src,
|
|
1525
|
+
kind,
|
|
1526
|
+
"aria-label": "Media player",
|
|
1527
|
+
onTimeUpdate: handleTimeUpdate,
|
|
1528
|
+
onEnded: stopSequencePlayback
|
|
1529
|
+
}
|
|
1530
|
+
)
|
|
1531
|
+
}
|
|
1532
|
+
),
|
|
1533
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1534
|
+
"div",
|
|
1535
|
+
{
|
|
1536
|
+
className: `flex min-h-0 flex-1 flex-col ${splitLayout === "vertical" ? "md:w-1/2" : ""}`,
|
|
1537
|
+
children: [
|
|
1538
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border flex shrink-0 flex-wrap items-center justify-between gap-2 border-b px-3 py-2", children: [
|
|
1539
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1540
|
+
hasEdits && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-warning/20 text-warning-foreground rounded px-1.5 py-0.5 text-xs font-semibold", children: "Edited" }),
|
|
1541
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1542
|
+
"label",
|
|
1543
|
+
{
|
|
1544
|
+
className: "text-muted-foreground text-xs",
|
|
1545
|
+
htmlFor: "media-editor-default-speed",
|
|
1546
|
+
children: "Speed:"
|
|
1547
|
+
}
|
|
1548
|
+
),
|
|
1549
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-20", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1550
|
+
chunkFRT6RQND_cjs.Select,
|
|
1551
|
+
{
|
|
1552
|
+
id: "media-editor-default-speed",
|
|
1553
|
+
size: "sm",
|
|
1554
|
+
options: chunkS7JQ3FV5_cjs.PLAYBACK_SPEEDS.map((speed) => ({
|
|
1555
|
+
value: String(speed),
|
|
1556
|
+
label: `${speed}x`
|
|
1557
|
+
})),
|
|
1558
|
+
value: String(defaultSpeed),
|
|
1559
|
+
onValueChange: (value) => setDefaultSpeed(parseFloat(value)),
|
|
1560
|
+
"aria-label": "Default playback speed"
|
|
1561
|
+
}
|
|
1562
|
+
) }),
|
|
1563
|
+
speedMarkers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1564
|
+
"span",
|
|
1565
|
+
{
|
|
1566
|
+
className: "text-muted-foreground text-xs",
|
|
1567
|
+
title: `${speedMarkers.length} speed marker(s) set. Right-click on a word to add/remove speed markers.`,
|
|
1568
|
+
children: [
|
|
1569
|
+
speedMarkers.length,
|
|
1570
|
+
" marker",
|
|
1571
|
+
speedMarkers.length !== 1 ? "s" : ""
|
|
1572
|
+
]
|
|
1573
|
+
}
|
|
1574
|
+
),
|
|
1575
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1576
|
+
chunkA2QVQF54_cjs.Button,
|
|
1577
|
+
{
|
|
1578
|
+
variant: "ghost",
|
|
1579
|
+
size: "sm",
|
|
1580
|
+
onClick: () => setShowFillerModal(true),
|
|
1581
|
+
"aria-label": "Remove filler words",
|
|
1582
|
+
title: "Remove filler words",
|
|
1583
|
+
children: "\u2702\uFE0F"
|
|
1584
|
+
}
|
|
1585
|
+
),
|
|
1586
|
+
undoStack.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1587
|
+
chunkA2QVQF54_cjs.Button,
|
|
1588
|
+
{
|
|
1589
|
+
variant: "ghost",
|
|
1590
|
+
size: "sm",
|
|
1591
|
+
onClick: undo,
|
|
1592
|
+
"aria-label": `Undo (${undoStack.length} available)`,
|
|
1593
|
+
title: "Undo (\u2318Z)",
|
|
1594
|
+
children: [
|
|
1595
|
+
"Undo (",
|
|
1596
|
+
undoStack.length,
|
|
1597
|
+
")"
|
|
1598
|
+
]
|
|
1599
|
+
}
|
|
1600
|
+
)
|
|
1601
|
+
] }),
|
|
1602
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-muted-foreground flex items-center gap-2 text-xs", children: [
|
|
1603
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1604
|
+
chunkA2QVQF54_cjs.Button,
|
|
1605
|
+
{
|
|
1606
|
+
variant: "ghost",
|
|
1607
|
+
size: "sm",
|
|
1608
|
+
onClick: () => setShowScriptPanel((v) => !v),
|
|
1609
|
+
"aria-pressed": showScriptPanel,
|
|
1610
|
+
"aria-label": "Show script",
|
|
1611
|
+
title: "Show the transcript script (editable YAML/JSON)",
|
|
1612
|
+
children: "Script"
|
|
1613
|
+
}
|
|
1614
|
+
),
|
|
1615
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1616
|
+
stats.activeWordCount,
|
|
1617
|
+
" words"
|
|
1618
|
+
] }),
|
|
1619
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1620
|
+
"button",
|
|
1621
|
+
{
|
|
1622
|
+
type: "button",
|
|
1623
|
+
className: "hover:bg-muted hover:text-foreground focus-visible:ring-ring rounded px-1 underline decoration-dotted transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
|
1624
|
+
onClick: () => setShowSilenceModal(true),
|
|
1625
|
+
title: stats.activeSilenceCount > 0 ? `Click to configure silence detection (currently \u2265${(silenceThresholds.minSilenceMs / 1e3).toFixed(1)}s)` : `No silences detected (threshold: ${(silenceThresholds.minSilenceMs / 1e3).toFixed(1)}s). Click to configure.`,
|
|
1626
|
+
"aria-label": "Configure silence detection",
|
|
1627
|
+
children: [
|
|
1628
|
+
stats.activeSilenceCount,
|
|
1629
|
+
" silences"
|
|
1630
|
+
]
|
|
1631
|
+
}
|
|
1632
|
+
),
|
|
1633
|
+
(stats.deletedWordCount > 0 || stats.deletedSilenceCount > 0) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-destructive", children: [
|
|
1634
|
+
stats.deletedWordCount + stats.deletedSilenceCount,
|
|
1635
|
+
" deleted"
|
|
1636
|
+
] }),
|
|
1637
|
+
transcript.speakers && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1638
|
+
transcript.speakers.length,
|
|
1639
|
+
" speakers"
|
|
1640
|
+
] }),
|
|
1641
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatDuration2(stats.editedDurationMs) })
|
|
1642
|
+
] })
|
|
1643
|
+
] }),
|
|
1644
|
+
showEditBar && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border bg-muted/40 flex shrink-0 items-center gap-1 border-b px-3 py-1", children: [
|
|
1645
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1646
|
+
chunkA2QVQF54_cjs.Button,
|
|
1647
|
+
{
|
|
1648
|
+
variant: "ghost",
|
|
1649
|
+
size: "sm",
|
|
1650
|
+
onClick: () => openWordEditor(cursorIndex),
|
|
1651
|
+
title: "Edit selected word (Enter)",
|
|
1652
|
+
children: [
|
|
1653
|
+
"Edit ",
|
|
1654
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-[10px] opacity-60", children: "\u21B5" })
|
|
1655
|
+
]
|
|
1656
|
+
}
|
|
1657
|
+
),
|
|
1658
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1659
|
+
chunkA2QVQF54_cjs.Button,
|
|
1660
|
+
{
|
|
1661
|
+
variant: "ghost",
|
|
1662
|
+
size: "sm",
|
|
1663
|
+
onClick: handleDeleteToggle,
|
|
1664
|
+
title: "Delete selected word (Del)",
|
|
1665
|
+
children: [
|
|
1666
|
+
"Del ",
|
|
1667
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-[10px] opacity-60", children: "\u232B" })
|
|
1668
|
+
]
|
|
1669
|
+
}
|
|
1670
|
+
),
|
|
1671
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1672
|
+
chunkA2QVQF54_cjs.Button,
|
|
1673
|
+
{
|
|
1674
|
+
variant: "ghost",
|
|
1675
|
+
size: "sm",
|
|
1676
|
+
onClick: handleCut,
|
|
1677
|
+
title: "Cut selection (\u2318X)",
|
|
1678
|
+
children: [
|
|
1679
|
+
"Cut ",
|
|
1680
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-[10px] opacity-60", children: "\u2318X" })
|
|
1681
|
+
]
|
|
1682
|
+
}
|
|
1683
|
+
),
|
|
1684
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1685
|
+
chunkA2QVQF54_cjs.Button,
|
|
1686
|
+
{
|
|
1687
|
+
variant: "ghost",
|
|
1688
|
+
size: "sm",
|
|
1689
|
+
onClick: handlePaste,
|
|
1690
|
+
disabled: !clipboard,
|
|
1691
|
+
title: "Paste (\u2318V)",
|
|
1692
|
+
children: [
|
|
1693
|
+
"Paste ",
|
|
1694
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-[10px] opacity-60", children: "\u2318V" })
|
|
1695
|
+
]
|
|
1696
|
+
}
|
|
1697
|
+
)
|
|
1698
|
+
] }),
|
|
1699
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1700
|
+
"div",
|
|
1701
|
+
{
|
|
1702
|
+
ref: contentRef,
|
|
1703
|
+
className: `focus-visible:ring-ring min-h-0 flex-1 overflow-y-auto p-3 text-sm leading-relaxed select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-inset ${isFocused ? "bg-muted/20" : ""}`,
|
|
1704
|
+
tabIndex: 0,
|
|
1705
|
+
onKeyDown: handleKeyDown,
|
|
1706
|
+
onCopy: handleCopy,
|
|
1707
|
+
onFocus: () => setIsFocused(true),
|
|
1708
|
+
onBlur: () => setIsFocused(false),
|
|
1709
|
+
onMouseUp: handleMouseUp,
|
|
1710
|
+
onMouseLeave: handleMouseUp,
|
|
1711
|
+
role: "listbox",
|
|
1712
|
+
"aria-label": "Transcript words",
|
|
1713
|
+
"aria-activedescendant": `word-${cursorIndex}`,
|
|
1714
|
+
children: editedWords.map(renderWord)
|
|
1715
|
+
}
|
|
1716
|
+
)
|
|
1717
|
+
]
|
|
1718
|
+
}
|
|
1719
|
+
)
|
|
1720
|
+
]
|
|
1721
|
+
}
|
|
1722
|
+
),
|
|
1723
|
+
showScriptPanel && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1724
|
+
ScriptPanel,
|
|
1725
|
+
{
|
|
1726
|
+
transcript,
|
|
1727
|
+
editedWords,
|
|
1728
|
+
onApply: replaceEditedWords,
|
|
1729
|
+
onClose: () => setShowScriptPanel(false)
|
|
1730
|
+
}
|
|
1731
|
+
),
|
|
1732
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1733
|
+
FillerWordsModal,
|
|
1734
|
+
{
|
|
1735
|
+
isOpen: showFillerModal,
|
|
1736
|
+
onClose: () => setShowFillerModal(false),
|
|
1737
|
+
onApply: removeFillers,
|
|
1738
|
+
matchingCounts: fillerAnalysis.matchCounts,
|
|
1739
|
+
fillerDurations: fillerAnalysis.durations,
|
|
1740
|
+
silenceCounts: fillerAnalysis.silenceCounts,
|
|
1741
|
+
currentDurationMs: stats.editedDurationMs
|
|
1742
|
+
}
|
|
1743
|
+
),
|
|
1744
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1745
|
+
SilenceSettingsModal,
|
|
1746
|
+
{
|
|
1747
|
+
isOpen: showSilenceModal,
|
|
1748
|
+
onClose: () => setShowSilenceModal(false),
|
|
1749
|
+
minSilenceMs: silenceThresholds.minSilenceMs,
|
|
1750
|
+
nlSilenceMs: silenceThresholds.nlSilenceMs,
|
|
1751
|
+
onApply: setSilenceThresholds
|
|
1752
|
+
}
|
|
1753
|
+
),
|
|
1754
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1755
|
+
WordEditorModal,
|
|
1756
|
+
{
|
|
1757
|
+
isOpen: editorWordIndex !== null,
|
|
1758
|
+
editableWord: editorWordIndex !== null ? editedWords[editorWordIndex] ?? null : null,
|
|
1759
|
+
onClose: closeWordEditor,
|
|
1760
|
+
onSave: handleEditorSave,
|
|
1761
|
+
onSplitSilence: handleEditorSplitSilence,
|
|
1762
|
+
onDelete: handleEditorDelete
|
|
1763
|
+
}
|
|
1764
|
+
),
|
|
1765
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1766
|
+
SpeedMarkerMenu,
|
|
1767
|
+
{
|
|
1768
|
+
isOpen: speedMenuWordIndex !== null,
|
|
1769
|
+
position: speedMenuPosition,
|
|
1770
|
+
wordIndex: speedMenuWordIndex,
|
|
1771
|
+
currentMarker: speedMenuWordIndex !== null ? getSpeedMarkerAtIndex(speedMenuWordIndex) : void 0,
|
|
1772
|
+
defaultSpeed,
|
|
1773
|
+
onSetSpeed: (speed) => {
|
|
1774
|
+
if (speedMenuWordIndex !== null) {
|
|
1775
|
+
toggleSpeedMarker(speedMenuWordIndex, speed);
|
|
1776
|
+
}
|
|
1777
|
+
},
|
|
1778
|
+
onRemoveMarker: () => {
|
|
1779
|
+
if (speedMenuWordIndex !== null) {
|
|
1780
|
+
removeSpeedMarker(speedMenuWordIndex);
|
|
1781
|
+
}
|
|
1782
|
+
},
|
|
1783
|
+
onClose: () => {
|
|
1784
|
+
setSpeedMenuWordIndex(null);
|
|
1785
|
+
setSpeedMenuPosition(null);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
)
|
|
1789
|
+
]
|
|
1790
|
+
}
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
);
|
|
1794
|
+
MediaEditor.displayName = "MediaEditor";
|
|
1795
|
+
|
|
1796
|
+
exports.FillerWordsModal = FillerWordsModal;
|
|
1797
|
+
exports.MediaEditor = MediaEditor;
|
|
1798
|
+
exports.SilenceSettingsModal = SilenceSettingsModal;
|
|
1799
|
+
exports.SpeedMarkerMenu = SpeedMarkerMenu;
|
|
1800
|
+
exports.WordEditorModal = WordEditorModal;
|
|
1801
|
+
//# sourceMappingURL=chunk-3Q2VWPML.cjs.map
|
|
1802
|
+
//# sourceMappingURL=chunk-3Q2VWPML.cjs.map
|