@mieweb/ui 0.11.0-dev.7 → 0.11.0-dev.9
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 +12 -12
- package/dist/brands/index.js +3 -3
- package/dist/{chunk-WGXJNCRG.cjs → chunk-2GBVJHWN.cjs} +47 -28
- package/dist/{chunk-WGXJNCRG.cjs.map → chunk-2GBVJHWN.cjs.map} +1 -1
- package/dist/chunk-3TUULOTW.cjs +333 -0
- package/dist/chunk-3TUULOTW.cjs.map +1 -0
- package/dist/{chunk-OODVC2UD.cjs → chunk-AWSSWNJY.cjs} +2 -2
- package/dist/{chunk-OODVC2UD.cjs.map → chunk-AWSSWNJY.cjs.map} +1 -1
- package/dist/{chunk-GJPCWR4C.js → chunk-DAAJ3B2T.js} +47 -29
- package/dist/chunk-DAAJ3B2T.js.map +1 -0
- package/dist/{chunk-QJ2L5RU6.js → chunk-DSQCYX7L.js} +2 -2
- package/dist/{chunk-QJ2L5RU6.js.map → chunk-DSQCYX7L.js.map} +1 -1
- package/dist/{chunk-JTSKPDPH.cjs → chunk-HIYQ3M5V.cjs} +4 -3
- package/dist/chunk-HIYQ3M5V.cjs.map +1 -0
- package/dist/chunk-IZY4CMOO.js +715 -0
- package/dist/chunk-IZY4CMOO.js.map +1 -0
- package/dist/{chunk-753ENNB6.js → chunk-J3QEMZCH.js} +2 -2
- package/dist/{chunk-753ENNB6.js.map → chunk-J3QEMZCH.js.map} +1 -1
- package/dist/{chunk-VNWJTCBQ.js → chunk-MQOYDQTF.js} +4 -3
- package/dist/chunk-MQOYDQTF.js.map +1 -0
- package/dist/{chunk-NKG2UNXE.js → chunk-OZKJYLN6.js} +3 -3
- package/dist/{chunk-NKG2UNXE.js.map → chunk-OZKJYLN6.js.map} +1 -1
- package/dist/{chunk-C66JR43M.cjs → chunk-Q6QLGCL7.cjs} +9 -9
- package/dist/{chunk-C66JR43M.cjs.map → chunk-Q6QLGCL7.cjs.map} +1 -1
- package/dist/{chunk-NAH65KPK.cjs → chunk-TOJ56HKQ.cjs} +2 -2
- package/dist/{chunk-NAH65KPK.cjs.map → chunk-TOJ56HKQ.cjs.map} +1 -1
- package/dist/chunk-TRZLN32I.cjs +740 -0
- package/dist/chunk-TRZLN32I.cjs.map +1 -0
- package/dist/chunk-XQIQX5OB.js +309 -0
- package/dist/chunk-XQIQX5OB.js.map +1 -0
- package/dist/components/DictationButton/index.cjs +23 -0
- package/dist/components/DictationButton/index.cjs.map +1 -0
- package/dist/components/DictationButton/index.d.cts +67 -0
- package/dist/components/DictationButton/index.d.ts +67 -0
- package/dist/components/DictationButton/index.js +6 -0
- package/dist/components/DictationButton/index.js.map +1 -0
- package/dist/components/Markdown/index.cjs +10 -10
- package/dist/components/Markdown/index.js +2 -2
- package/dist/components/RecordButton/index.cjs +4 -4
- package/dist/components/RecordButton/index.js +1 -1
- package/dist/components/SuperChat/index.cjs +3 -3
- package/dist/components/SuperChat/index.d.cts +2 -2
- package/dist/components/SuperChat/index.d.ts +2 -2
- package/dist/components/SuperChat/index.js +1 -1
- package/dist/components/SuperChat/plugins/index.cjs +3 -3
- package/dist/components/SuperChat/plugins/index.d.cts +2 -2
- package/dist/components/SuperChat/plugins/index.d.ts +2 -2
- package/dist/components/SuperChat/plugins/index.js +2 -2
- package/dist/index.cjs +363 -899
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -1
- package/dist/index.d.ts +102 -1
- package/dist/index.js +124 -730
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/{types-Cx1FHLq2.d.cts → types-DSWbrVkM.d.cts} +1 -1
- package/dist/{types-Cx1FHLq2.d.ts → types-DSWbrVkM.d.ts} +1 -1
- package/dist/whisperTranscribe-AV752TZI.cjs +81 -0
- package/dist/whisperTranscribe-AV752TZI.cjs.map +1 -0
- package/dist/whisperTranscribe-UBNUWXUQ.js +4 -0
- package/dist/whisperTranscribe-UBNUWXUQ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-GJPCWR4C.js.map +0 -1
- package/dist/chunk-JTSKPDPH.cjs.map +0 -1
- package/dist/chunk-VNWJTCBQ.js.map +0 -1
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkHIYQ3M5V_cjs = require('./chunk-HIYQ3M5V.cjs');
|
|
4
|
+
var chunkS5RVSXDV_cjs = require('./chunk-S5RVSXDV.cjs');
|
|
5
|
+
var React = require('react');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
function _interopNamespace(e) {
|
|
9
|
+
if (e && e.__esModule) return e;
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
27
|
+
|
|
28
|
+
var onDeviceTranscribe = async (audio, signal) => {
|
|
29
|
+
const { transcribeBlob } = await import('./whisperTranscribe-AV752TZI.cjs');
|
|
30
|
+
if (signal.aborted) throw new Error("Dictation cancelled");
|
|
31
|
+
return transcribeBlob(audio);
|
|
32
|
+
};
|
|
33
|
+
function toError(value) {
|
|
34
|
+
if (value instanceof Error) return value;
|
|
35
|
+
if (value && typeof value === "object" && "name" in value && "message" in value)
|
|
36
|
+
return value;
|
|
37
|
+
return new Error(String(value));
|
|
38
|
+
}
|
|
39
|
+
function liveAudio(stream) {
|
|
40
|
+
return stream?.getAudioTracks().some((track) => track.readyState === "live") ? stream : null;
|
|
41
|
+
}
|
|
42
|
+
function useDictation({
|
|
43
|
+
onText,
|
|
44
|
+
transcribe,
|
|
45
|
+
onError,
|
|
46
|
+
maxDurationSeconds = 300,
|
|
47
|
+
getStream
|
|
48
|
+
}) {
|
|
49
|
+
const [status, setStatus] = React__namespace.useState("idle");
|
|
50
|
+
const [error, setError] = React__namespace.useState(null);
|
|
51
|
+
const [elapsedMs, setElapsedMs] = React__namespace.useState(0);
|
|
52
|
+
const callbacksRef = React__namespace.useRef({ onText, onError, transcribe, getStream });
|
|
53
|
+
callbacksRef.current = { onText, onError, transcribe, getStream };
|
|
54
|
+
const maxMsRef = React__namespace.useRef(maxDurationSeconds * 1e3);
|
|
55
|
+
maxMsRef.current = maxDurationSeconds * 1e3;
|
|
56
|
+
const sessionRef = React__namespace.useRef(0);
|
|
57
|
+
const busyRef = React__namespace.useRef(false);
|
|
58
|
+
const mountedRef = React__namespace.useRef(true);
|
|
59
|
+
const streamRef = React__namespace.useRef(null);
|
|
60
|
+
const recorderRef = React__namespace.useRef(null);
|
|
61
|
+
const timerRef = React__namespace.useRef(null);
|
|
62
|
+
const abortRef = React__namespace.useRef(null);
|
|
63
|
+
const releaseMic = React__namespace.useCallback(() => {
|
|
64
|
+
if (timerRef.current) clearInterval(timerRef.current);
|
|
65
|
+
timerRef.current = null;
|
|
66
|
+
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
67
|
+
streamRef.current = null;
|
|
68
|
+
}, []);
|
|
69
|
+
const fail = React__namespace.useCallback(
|
|
70
|
+
(err) => {
|
|
71
|
+
releaseMic();
|
|
72
|
+
recorderRef.current = null;
|
|
73
|
+
busyRef.current = false;
|
|
74
|
+
const e = toError(err);
|
|
75
|
+
if (mountedRef.current) {
|
|
76
|
+
setError(e);
|
|
77
|
+
setStatus("error");
|
|
78
|
+
}
|
|
79
|
+
callbacksRef.current.onError?.(e);
|
|
80
|
+
},
|
|
81
|
+
[releaseMic]
|
|
82
|
+
);
|
|
83
|
+
const finish = React__namespace.useCallback(
|
|
84
|
+
async (session, chunks, mimeType) => {
|
|
85
|
+
if (session !== sessionRef.current) return;
|
|
86
|
+
releaseMic();
|
|
87
|
+
recorderRef.current = null;
|
|
88
|
+
const audio = new Blob(chunks, { type: mimeType });
|
|
89
|
+
if (audio.size === 0) {
|
|
90
|
+
busyRef.current = false;
|
|
91
|
+
if (mountedRef.current) setStatus("idle");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
abortRef.current = controller;
|
|
96
|
+
if (mountedRef.current) setStatus("transcribing");
|
|
97
|
+
try {
|
|
98
|
+
const run = callbacksRef.current.transcribe ?? onDeviceTranscribe;
|
|
99
|
+
const text = (await run(audio, controller.signal)).trim();
|
|
100
|
+
if (session !== sessionRef.current || controller.signal.aborted) return;
|
|
101
|
+
busyRef.current = false;
|
|
102
|
+
abortRef.current = null;
|
|
103
|
+
if (mountedRef.current) setStatus("idle");
|
|
104
|
+
if (text) callbacksRef.current.onText(text);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (session !== sessionRef.current || controller.signal.aborted) return;
|
|
107
|
+
abortRef.current = null;
|
|
108
|
+
fail(err);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
[releaseMic, fail]
|
|
112
|
+
);
|
|
113
|
+
const cancel = React__namespace.useCallback(() => {
|
|
114
|
+
sessionRef.current += 1;
|
|
115
|
+
abortRef.current?.abort();
|
|
116
|
+
abortRef.current = null;
|
|
117
|
+
const recorder = recorderRef.current;
|
|
118
|
+
recorderRef.current = null;
|
|
119
|
+
if (recorder && recorder.state !== "inactive") recorder.stop();
|
|
120
|
+
releaseMic();
|
|
121
|
+
busyRef.current = false;
|
|
122
|
+
if (mountedRef.current) {
|
|
123
|
+
setStatus("idle");
|
|
124
|
+
setError(null);
|
|
125
|
+
setElapsedMs(0);
|
|
126
|
+
}
|
|
127
|
+
}, [releaseMic]);
|
|
128
|
+
const stop = React__namespace.useCallback(() => {
|
|
129
|
+
const recorder = recorderRef.current;
|
|
130
|
+
if (recorder) {
|
|
131
|
+
if (recorder.state !== "inactive") recorder.stop();
|
|
132
|
+
} else if (busyRef.current && !abortRef.current) {
|
|
133
|
+
cancel();
|
|
134
|
+
}
|
|
135
|
+
}, [cancel]);
|
|
136
|
+
const start = React__namespace.useCallback(async () => {
|
|
137
|
+
if (busyRef.current) return;
|
|
138
|
+
busyRef.current = true;
|
|
139
|
+
const session = ++sessionRef.current;
|
|
140
|
+
setStatus("idle");
|
|
141
|
+
setError(null);
|
|
142
|
+
setElapsedMs(0);
|
|
143
|
+
let shared;
|
|
144
|
+
try {
|
|
145
|
+
shared = liveAudio(callbacksRef.current.getStream?.());
|
|
146
|
+
} catch (err) {
|
|
147
|
+
fail(err);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (typeof MediaRecorder === "undefined" || !shared && (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia)) {
|
|
151
|
+
fail(new Error("Microphone is not available in this context"));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let stream;
|
|
155
|
+
if (shared) {
|
|
156
|
+
stream = shared;
|
|
157
|
+
} else {
|
|
158
|
+
try {
|
|
159
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
160
|
+
} catch (err) {
|
|
161
|
+
if (session === sessionRef.current) fail(err);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (session !== sessionRef.current) {
|
|
165
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
streamRef.current = stream;
|
|
169
|
+
}
|
|
170
|
+
let recorder;
|
|
171
|
+
try {
|
|
172
|
+
recorder = new MediaRecorder(stream);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
fail(err);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const chunks = [];
|
|
178
|
+
recorder.ondataavailable = (e) => {
|
|
179
|
+
if (e.data.size > 0) chunks.push(e.data);
|
|
180
|
+
};
|
|
181
|
+
recorder.onstop = () => {
|
|
182
|
+
void finish(session, chunks, recorder.mimeType || "audio/webm");
|
|
183
|
+
};
|
|
184
|
+
recorder.onerror = (e) => {
|
|
185
|
+
if (session !== sessionRef.current) return;
|
|
186
|
+
sessionRef.current += 1;
|
|
187
|
+
recorderRef.current = null;
|
|
188
|
+
fail(e.error ?? e);
|
|
189
|
+
};
|
|
190
|
+
if (!shared) {
|
|
191
|
+
stream.getAudioTracks().forEach((track) => {
|
|
192
|
+
track.addEventListener("ended", () => {
|
|
193
|
+
if (recorderRef.current === recorder && recorder.state !== "inactive")
|
|
194
|
+
recorder.stop();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
recorderRef.current = recorder;
|
|
199
|
+
try {
|
|
200
|
+
recorder.start();
|
|
201
|
+
} catch (err) {
|
|
202
|
+
recorderRef.current = null;
|
|
203
|
+
fail(err);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
setStatus("recording");
|
|
207
|
+
const startedAt = Date.now();
|
|
208
|
+
timerRef.current = setInterval(() => {
|
|
209
|
+
const elapsed = Date.now() - startedAt;
|
|
210
|
+
setElapsedMs(elapsed);
|
|
211
|
+
if (maxMsRef.current > 0 && elapsed >= maxMsRef.current) {
|
|
212
|
+
if (recorder.state !== "inactive") recorder.stop();
|
|
213
|
+
}
|
|
214
|
+
}, 250);
|
|
215
|
+
}, [fail, finish]);
|
|
216
|
+
React__namespace.useEffect(() => {
|
|
217
|
+
mountedRef.current = true;
|
|
218
|
+
return () => {
|
|
219
|
+
mountedRef.current = false;
|
|
220
|
+
cancel();
|
|
221
|
+
};
|
|
222
|
+
}, [cancel]);
|
|
223
|
+
return { status, error, elapsedMs, start, stop, cancel };
|
|
224
|
+
}
|
|
225
|
+
var defaultDictationLabels = {
|
|
226
|
+
start: "Start dictation",
|
|
227
|
+
stop: "Stop dictation",
|
|
228
|
+
listening: "Listening\u2026",
|
|
229
|
+
transcribing: "Transcribing\u2026",
|
|
230
|
+
error: "Dictation failed. Try again.",
|
|
231
|
+
micBlocked: "Microphone access is blocked."
|
|
232
|
+
};
|
|
233
|
+
var buttonState = {
|
|
234
|
+
idle: "idle",
|
|
235
|
+
recording: "recording",
|
|
236
|
+
transcribing: "processing",
|
|
237
|
+
error: "error"
|
|
238
|
+
};
|
|
239
|
+
var DictationButton = React__namespace.forwardRef(
|
|
240
|
+
({
|
|
241
|
+
onText,
|
|
242
|
+
transcribe,
|
|
243
|
+
onError,
|
|
244
|
+
maxDurationSeconds,
|
|
245
|
+
getStream,
|
|
246
|
+
disabled = false,
|
|
247
|
+
size = "sm",
|
|
248
|
+
variant = "ghost",
|
|
249
|
+
showDuration = false,
|
|
250
|
+
showStatus = false,
|
|
251
|
+
labels: labelOverrides,
|
|
252
|
+
className,
|
|
253
|
+
...rest
|
|
254
|
+
}, ref) => {
|
|
255
|
+
const labels = { ...defaultDictationLabels, ...labelOverrides };
|
|
256
|
+
const { status, error, elapsedMs, start, stop, cancel } = useDictation({
|
|
257
|
+
onText,
|
|
258
|
+
transcribe,
|
|
259
|
+
onError,
|
|
260
|
+
maxDurationSeconds,
|
|
261
|
+
getStream
|
|
262
|
+
});
|
|
263
|
+
React__namespace.useEffect(() => {
|
|
264
|
+
if (disabled) cancel();
|
|
265
|
+
}, [disabled, cancel]);
|
|
266
|
+
React__namespace.useEffect(() => {
|
|
267
|
+
if (status !== "recording") return;
|
|
268
|
+
const onEscape = (e) => {
|
|
269
|
+
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
cancel();
|
|
272
|
+
};
|
|
273
|
+
document.addEventListener("keydown", onEscape);
|
|
274
|
+
return () => document.removeEventListener("keydown", onEscape);
|
|
275
|
+
}, [status, cancel]);
|
|
276
|
+
const errorText = error?.name === "NotAllowedError" ? labels.micBlocked : labels.error;
|
|
277
|
+
const statusText = status === "recording" ? labels.listening : status === "transcribing" ? labels.transcribing : status === "error" ? errorText : "";
|
|
278
|
+
const ariaLabel = status === "recording" ? labels.stop : status === "transcribing" ? labels.transcribing : status === "error" ? errorText : labels.start;
|
|
279
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
280
|
+
"div",
|
|
281
|
+
{
|
|
282
|
+
"data-slot": "dictation-button",
|
|
283
|
+
className: chunkS5RVSXDV_cjs.cn("inline-flex items-center gap-2", className),
|
|
284
|
+
children: [
|
|
285
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
286
|
+
chunkHIYQ3M5V_cjs.RecordButton,
|
|
287
|
+
{
|
|
288
|
+
...rest,
|
|
289
|
+
ref,
|
|
290
|
+
state: disabled ? "disabled" : buttonState[status],
|
|
291
|
+
disabled,
|
|
292
|
+
size,
|
|
293
|
+
variant,
|
|
294
|
+
"aria-label": ariaLabel,
|
|
295
|
+
onClick: () => {
|
|
296
|
+
if (status === "recording") stop();
|
|
297
|
+
else void start();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
),
|
|
301
|
+
showDuration && status === "recording" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
302
|
+
"span",
|
|
303
|
+
{
|
|
304
|
+
"data-slot": "dictation-button-duration",
|
|
305
|
+
className: "text-destructive font-mono text-xs tabular-nums",
|
|
306
|
+
children: chunkHIYQ3M5V_cjs.formatDuration(elapsedMs / 1e3)
|
|
307
|
+
}
|
|
308
|
+
),
|
|
309
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
310
|
+
"span",
|
|
311
|
+
{
|
|
312
|
+
role: "status",
|
|
313
|
+
"data-slot": "dictation-button-status",
|
|
314
|
+
className: chunkS5RVSXDV_cjs.cn(
|
|
315
|
+
"text-xs",
|
|
316
|
+
status === "error" ? "text-destructive" : "text-muted-foreground",
|
|
317
|
+
!showStatus && "sr-only"
|
|
318
|
+
),
|
|
319
|
+
children: statusText
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
DictationButton.displayName = "DictationButton";
|
|
328
|
+
|
|
329
|
+
exports.DictationButton = DictationButton;
|
|
330
|
+
exports.defaultDictationLabels = defaultDictationLabels;
|
|
331
|
+
exports.useDictation = useDictation;
|
|
332
|
+
//# sourceMappingURL=chunk-3TUULOTW.cjs.map
|
|
333
|
+
//# sourceMappingURL=chunk-3TUULOTW.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/DictationButton/useDictation.ts","../src/components/DictationButton/DictationButton.tsx"],"names":["React","React2","jsxs","cn","jsx","RecordButton","formatDuration"],"mappings":"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","file":"chunk-3TUULOTW.cjs","sourcesContent":["import * as React from 'react';\n\nexport type DictationStatus = 'idle' | 'recording' | 'transcribing' | 'error';\n\n/** Turns recorded audio into text. Reject to report an error; honor `signal` to support cancel. */\nexport type DictationTranscribe = (\n audio: Blob,\n signal: AbortSignal\n) => Promise<string>;\n\nexport interface UseDictationOptions {\n /** Called with the final, trimmed transcript. Not called for empty results or after cancel. */\n onText: (text: string) => void;\n /** Transcription provider. Defaults to on-device Whisper (audio never leaves the browser). */\n transcribe?: DictationTranscribe;\n /** Called with the underlying error (permission denied, provider failure, …). */\n onError?: (error: Error) => void;\n /** Auto-stop after this many seconds. `0` disables the limit. Default 300. */\n maxDurationSeconds?: number;\n /**\n * Reuse a mic stream the host already holds (e.g. a wake-word listener) instead of opening one.\n * Its tracks are never stopped by the hook. Falls back to `getUserMedia` when it returns no live audio.\n */\n getStream?: () => MediaStream | null | undefined;\n}\n\nexport interface UseDictationResult {\n status: DictationStatus;\n error: Error | null;\n /** Milliseconds recorded so far in the current session. */\n elapsedMs: number;\n start: () => Promise<void>;\n /** Stop recording and transcribe. */\n stop: () => void;\n /** Discard the recording or in-flight transcription. `onText` will not fire. */\n cancel: () => void;\n}\n\nconst onDeviceTranscribe: DictationTranscribe = async (audio, signal) => {\n // Loaded lazily so hosts using a server provider never pull in Whisper.\n const { transcribeBlob } = await import('../AI/whisperTranscribe');\n // The shared worker cannot stop a running decode, so cancel only discards its result.\n if (signal.aborted) throw new Error('Dictation cancelled');\n return transcribeBlob(audio);\n};\n\nfunction toError(value: unknown): Error {\n if (value instanceof Error) return value;\n // DOMException (e.g. NotAllowedError) is not an Error subclass in every runtime; keep its `name`.\n if (\n value &&\n typeof value === 'object' &&\n 'name' in value &&\n 'message' in value\n )\n return value as Error;\n return new Error(String(value));\n}\n\nfunction liveAudio(stream: MediaStream | null | undefined) {\n return stream?.getAudioTracks().some((track) => track.readyState === 'live')\n ? stream\n : null;\n}\n\n/**\n * Headless speech-to-text for a single speaker: record, then transcribe to editable text.\n * Owns the mic lifecycle; every async step is tied to a session id so stale results are dropped.\n */\nexport function useDictation({\n onText,\n transcribe,\n onError,\n maxDurationSeconds = 300,\n getStream,\n}: UseDictationOptions): UseDictationResult {\n const [status, setStatus] = React.useState<DictationStatus>('idle');\n const [error, setError] = React.useState<Error | null>(null);\n const [elapsedMs, setElapsedMs] = React.useState(0);\n\n const callbacksRef = React.useRef({ onText, onError, transcribe, getStream });\n callbacksRef.current = { onText, onError, transcribe, getStream };\n const maxMsRef = React.useRef(maxDurationSeconds * 1000);\n maxMsRef.current = maxDurationSeconds * 1000;\n\n const sessionRef = React.useRef(0);\n const busyRef = React.useRef(false);\n const mountedRef = React.useRef(true);\n // Only streams the hook opened; a host-shared stream is never stored here, so it is never stopped.\n const streamRef = React.useRef<MediaStream | null>(null);\n const recorderRef = React.useRef<MediaRecorder | null>(null);\n const timerRef = React.useRef<ReturnType<typeof setInterval> | null>(null);\n const abortRef = React.useRef<AbortController | null>(null);\n\n const releaseMic = React.useCallback(() => {\n if (timerRef.current) clearInterval(timerRef.current);\n timerRef.current = null;\n streamRef.current?.getTracks().forEach((track) => track.stop());\n streamRef.current = null;\n }, []);\n\n const fail = React.useCallback(\n (err: unknown) => {\n releaseMic();\n recorderRef.current = null;\n busyRef.current = false;\n const e = toError(err);\n if (mountedRef.current) {\n setError(e);\n setStatus('error');\n }\n callbacksRef.current.onError?.(e);\n },\n [releaseMic]\n );\n\n const finish = React.useCallback(\n async (session: number, chunks: Blob[], mimeType: string) => {\n // A stale session must not touch refs that may already belong to a new one.\n if (session !== sessionRef.current) return;\n releaseMic();\n recorderRef.current = null;\n\n const audio = new Blob(chunks, { type: mimeType });\n if (audio.size === 0) {\n busyRef.current = false;\n if (mountedRef.current) setStatus('idle');\n return;\n }\n\n const controller = new AbortController();\n abortRef.current = controller;\n if (mountedRef.current) setStatus('transcribing');\n try {\n const run = callbacksRef.current.transcribe ?? onDeviceTranscribe;\n const text = (await run(audio, controller.signal)).trim();\n if (session !== sessionRef.current || controller.signal.aborted) return;\n busyRef.current = false;\n abortRef.current = null;\n if (mountedRef.current) setStatus('idle');\n if (text) callbacksRef.current.onText(text);\n } catch (err) {\n if (session !== sessionRef.current || controller.signal.aborted) return;\n abortRef.current = null;\n fail(err);\n }\n },\n [releaseMic, fail]\n );\n\n const cancel = React.useCallback(() => {\n sessionRef.current += 1;\n abortRef.current?.abort();\n abortRef.current = null;\n const recorder = recorderRef.current;\n recorderRef.current = null;\n if (recorder && recorder.state !== 'inactive') recorder.stop();\n releaseMic();\n busyRef.current = false;\n if (mountedRef.current) {\n setStatus('idle');\n setError(null);\n setElapsedMs(0);\n }\n }, [releaseMic]);\n\n const stop = React.useCallback(() => {\n const recorder = recorderRef.current;\n if (recorder) {\n // Already inactive means onstop is queued; cancelling here would drop the take.\n if (recorder.state !== 'inactive') recorder.stop();\n } else if (busyRef.current && !abortRef.current) {\n // Still waiting on the permission prompt: nothing recorded yet.\n cancel();\n }\n }, [cancel]);\n\n const start = React.useCallback(async () => {\n if (busyRef.current) return;\n busyRef.current = true;\n const session = ++sessionRef.current;\n setStatus('idle');\n setError(null);\n setElapsedMs(0);\n\n let shared: MediaStream | null;\n try {\n shared = liveAudio(callbacksRef.current.getStream?.());\n } catch (err) {\n fail(err);\n return;\n }\n if (\n typeof MediaRecorder === 'undefined' ||\n (!shared &&\n (typeof navigator === 'undefined' ||\n !navigator.mediaDevices?.getUserMedia))\n ) {\n fail(new Error('Microphone is not available in this context'));\n return;\n }\n\n let stream: MediaStream;\n if (shared) {\n stream = shared;\n } else {\n try {\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (err) {\n if (session === sessionRef.current) fail(err);\n return;\n }\n if (session !== sessionRef.current) {\n // Cancelled or unmounted while the permission prompt was open.\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n streamRef.current = stream;\n }\n\n let recorder: MediaRecorder;\n try {\n recorder = new MediaRecorder(stream);\n } catch (err) {\n fail(err);\n return;\n }\n\n const chunks: Blob[] = [];\n recorder.ondataavailable = (e) => {\n if (e.data.size > 0) chunks.push(e.data);\n };\n recorder.onstop = () => {\n void finish(session, chunks, recorder.mimeType || 'audio/webm');\n };\n recorder.onerror = (e) => {\n if (session !== sessionRef.current) return;\n sessionRef.current += 1;\n recorderRef.current = null;\n fail((e as Event & { error?: unknown }).error ?? e);\n };\n // Shared tracks live longer than one take; MediaRecorder stops by itself if they end.\n if (!shared) {\n stream.getAudioTracks().forEach((track) => {\n track.addEventListener('ended', () => {\n if (recorderRef.current === recorder && recorder.state !== 'inactive')\n recorder.stop();\n });\n });\n }\n\n recorderRef.current = recorder;\n try {\n recorder.start();\n } catch (err) {\n recorderRef.current = null;\n fail(err);\n return;\n }\n setStatus('recording');\n\n const startedAt = Date.now();\n timerRef.current = setInterval(() => {\n const elapsed = Date.now() - startedAt;\n setElapsedMs(elapsed);\n if (maxMsRef.current > 0 && elapsed >= maxMsRef.current) {\n if (recorder.state !== 'inactive') recorder.stop();\n }\n }, 250);\n }, [fail, finish]);\n\n React.useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n cancel();\n };\n }, [cancel]);\n\n return { status, error, elapsedMs, start, stop, cancel };\n}\n","import * as React from 'react';\nimport {\n RecordButton,\n formatDuration,\n type RecordButtonSize,\n type RecordButtonState,\n type RecordButtonVariant,\n} from '../RecordButton';\nimport { cn } from '../../utils/cn';\nimport {\n useDictation,\n type DictationStatus,\n type UseDictationOptions,\n} from './useDictation';\n\nexport interface DictationButtonLabels {\n start: string;\n stop: string;\n listening: string;\n transcribing: string;\n error: string;\n micBlocked: string;\n}\n\nexport const defaultDictationLabels: DictationButtonLabels = {\n start: 'Start dictation',\n stop: 'Stop dictation',\n listening: 'Listening…',\n transcribing: 'Transcribing…',\n error: 'Dictation failed. Try again.',\n micBlocked: 'Microphone access is blocked.',\n};\n\nexport interface DictationButtonProps\n extends\n UseDictationOptions,\n Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n // The ARIA attributes follow the dictation state; customise text through `labels`.\n | 'children'\n | 'onClick'\n | 'onError'\n | 'disabled'\n | 'className'\n | 'aria-label'\n | 'aria-pressed'\n | 'aria-busy'\n > {\n disabled?: boolean;\n size?: RecordButtonSize;\n variant?: RecordButtonVariant;\n /** Show `m:ss` while recording. */\n showDuration?: boolean;\n /** Show the listening/transcribing/error caption. It is always announced to screen readers. */\n showStatus?: boolean;\n /** Override any user-facing string (i18n). */\n labels?: Partial<DictationButtonLabels>;\n className?: string;\n}\n\nconst buttonState: Record<DictationStatus, RecordButtonState> = {\n idle: 'idle',\n recording: 'recording',\n transcribing: 'processing',\n error: 'error',\n};\n\n/**\n * Mic button that turns speech into editable text via `onText`. Never sends anything itself.\n * Escape while recording discards the take.\n */\nexport const DictationButton = React.forwardRef<\n HTMLButtonElement,\n DictationButtonProps\n>(\n (\n {\n onText,\n transcribe,\n onError,\n maxDurationSeconds,\n getStream,\n disabled = false,\n size = 'sm',\n variant = 'ghost',\n showDuration = false,\n showStatus = false,\n labels: labelOverrides,\n className,\n ...rest\n },\n ref\n ) => {\n const labels = { ...defaultDictationLabels, ...labelOverrides };\n const { status, error, elapsedMs, start, stop, cancel } = useDictation({\n onText,\n transcribe,\n onError,\n maxDurationSeconds,\n getStream,\n });\n\n // Covers a pending permission prompt (status still idle) and transcribing, not just recording.\n React.useEffect(() => {\n if (disabled) cancel();\n }, [disabled, cancel]);\n\n // Document-level so Escape works while focus stays in the composer.\n React.useEffect(() => {\n if (status !== 'recording') return;\n const onEscape = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || e.defaultPrevented) return;\n e.preventDefault();\n cancel();\n };\n document.addEventListener('keydown', onEscape);\n return () => document.removeEventListener('keydown', onEscape);\n }, [status, cancel]);\n\n const errorText =\n error?.name === 'NotAllowedError' ? labels.micBlocked : labels.error;\n const statusText =\n status === 'recording'\n ? labels.listening\n : status === 'transcribing'\n ? labels.transcribing\n : status === 'error'\n ? errorText\n : '';\n const ariaLabel =\n status === 'recording'\n ? labels.stop\n : status === 'transcribing'\n ? labels.transcribing\n : status === 'error'\n ? errorText\n : labels.start;\n\n return (\n <div\n data-slot=\"dictation-button\"\n className={cn('inline-flex items-center gap-2', className)}\n >\n <RecordButton\n {...rest}\n ref={ref}\n state={disabled ? 'disabled' : buttonState[status]}\n disabled={disabled}\n size={size}\n variant={variant}\n aria-label={ariaLabel}\n onClick={() => {\n if (status === 'recording') stop();\n else void start();\n }}\n />\n {showDuration && status === 'recording' && (\n <span\n data-slot=\"dictation-button-duration\"\n className=\"text-destructive font-mono text-xs tabular-nums\"\n >\n {formatDuration(elapsedMs / 1000)}\n </span>\n )}\n <span\n role=\"status\"\n data-slot=\"dictation-button-status\"\n className={cn(\n 'text-xs',\n status === 'error' ? 'text-destructive' : 'text-muted-foreground',\n !showStatus && 'sr-only'\n )}\n >\n {statusText}\n </span>\n </div>\n );\n }\n);\n\nDictationButton.displayName = 'DictationButton';\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/brands/index.ts"],"names":[],"mappings":";;;AAkCO,IAAM,MAAA,GAAS;AAAA,EACpB,QAAA,EAAU,MAAM,OAAO,uBAAY,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,CAAA;AAAA,EAChE,IAAA,EAAM,MAAM,OAAO,mBAAQ,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,CAAA;AAAA,EACpD,OAAA,EAAS,MAAM,OAAO,wBAAW,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,YAAY,CAAA;AAAA,EAC7D,mBAAA,EAAqB,MACnB,OAAO,kCAAqB,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,qBAAqB,CAAA;AAAA,EACnE,MAAA,EAAQ,MAAM,OAAO,uBAAU,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,CAAA;AAAA,EAC1D,MAAA,EAAQ,MAAM,OAAO,qBAAU,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,CAAA;AAAA,EAC1D,UAAA,EAAY,MAAM,OAAO,2BAAc,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,eAAe,CAAA;AAAA,EACtE,QAAA,EAAU,MAAM,OAAO,yBAAY,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa;AAClE","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/brands/index.ts"],"names":[],"mappings":";;;AAkCO,IAAM,MAAA,GAAS;AAAA,EACpB,QAAA,EAAU,MAAM,OAAO,uBAAY,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,CAAA;AAAA,EAChE,IAAA,EAAM,MAAM,OAAO,mBAAQ,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,CAAA;AAAA,EACpD,OAAA,EAAS,MAAM,OAAO,wBAAW,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,YAAY,CAAA;AAAA,EAC7D,mBAAA,EAAqB,MACnB,OAAO,kCAAqB,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,qBAAqB,CAAA;AAAA,EACnE,MAAA,EAAQ,MAAM,OAAO,uBAAU,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,CAAA;AAAA,EAC1D,MAAA,EAAQ,MAAM,OAAO,qBAAU,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,CAAA;AAAA,EAC1D,UAAA,EAAY,MAAM,OAAO,2BAAc,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,eAAe,CAAA;AAAA,EACtE,QAAA,EAAU,MAAM,OAAO,yBAAY,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa;AAClE","file":"chunk-AWSSWNJY.cjs","sourcesContent":["/**\n * @mieweb/ui Brand System\n *\n * Export all brand-related types, utilities, and configurations.\n */\n\n// Types and utilities\nexport type {\n BrandConfig,\n BrandColors,\n BrandTypography,\n BrandBorderRadius,\n BrandBoxShadow,\n ColorScale,\n SemanticColors,\n} from './types';\n\nexport {\n generateBrandCSS,\n generateTailwindTheme,\n createBrandPreset,\n} from './types';\n\n// Brand configurations\nexport { bluehiveBrand } from './bluehive';\nexport { ccmeBrand } from './ccme';\nexport { defaultBrand } from './default';\nexport { enterpriseHealthBrand } from './enterprise-health';\nexport { miewebBrand } from './mieweb';\nexport { ozwellBrand } from './ozwell';\nexport { wagglelineBrand } from './waggleline';\nexport { webchartBrand } from './webchart';\n\n// All brands for iteration\nexport const brands = {\n bluehive: () => import('./bluehive').then((m) => m.bluehiveBrand),\n ccme: () => import('./ccme').then((m) => m.ccmeBrand),\n default: () => import('./default').then((m) => m.defaultBrand),\n 'enterprise-health': () =>\n import('./enterprise-health').then((m) => m.enterpriseHealthBrand),\n mieweb: () => import('./mieweb').then((m) => m.miewebBrand),\n ozwell: () => import('./ozwell').then((m) => m.ozwellBrand),\n waggleline: () => import('./waggleline').then((m) => m.wagglelineBrand),\n webchart: () => import('./webchart').then((m) => m.webchartBrand),\n} as const;\n"]}
|
|
@@ -1085,6 +1085,7 @@ function ComposerSelector({
|
|
|
1085
1085
|
options,
|
|
1086
1086
|
value,
|
|
1087
1087
|
onChange,
|
|
1088
|
+
actions,
|
|
1088
1089
|
label,
|
|
1089
1090
|
triggerLabel,
|
|
1090
1091
|
icon,
|
|
@@ -1094,7 +1095,7 @@ function ComposerSelector({
|
|
|
1094
1095
|
const [open, setOpen] = React.useState(false);
|
|
1095
1096
|
if (disabled && open) setOpen(false);
|
|
1096
1097
|
const selected = options.find((option) => option.id === value) ?? null;
|
|
1097
|
-
return /* @__PURE__ */
|
|
1098
|
+
return /* @__PURE__ */ jsxs(
|
|
1098
1099
|
Dropdown,
|
|
1099
1100
|
{
|
|
1100
1101
|
placement: "top-start",
|
|
@@ -1116,32 +1117,49 @@ function ComposerSelector({
|
|
|
1116
1117
|
]
|
|
1117
1118
|
}
|
|
1118
1119
|
),
|
|
1119
|
-
children:
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1120
|
+
children: [
|
|
1121
|
+
options.map((option) => /* @__PURE__ */ jsx(
|
|
1122
|
+
DropdownItem,
|
|
1123
|
+
{
|
|
1124
|
+
icon: option.icon,
|
|
1125
|
+
role: "menuitemradio",
|
|
1126
|
+
"aria-checked": option.id === value,
|
|
1127
|
+
onClick: () => {
|
|
1128
|
+
setOpen(false);
|
|
1129
|
+
onChange?.(option.id);
|
|
1130
|
+
},
|
|
1131
|
+
children: /* @__PURE__ */ jsxs("span", { className: "flex w-full items-center justify-between gap-2", children: [
|
|
1132
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
1133
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate", children: option.label }),
|
|
1134
|
+
option.description && /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-neutral-500 dark:text-neutral-400", children: option.description })
|
|
1135
|
+
] }),
|
|
1136
|
+
option.id === value && /* @__PURE__ */ jsx(
|
|
1137
|
+
Check,
|
|
1138
|
+
{
|
|
1139
|
+
className: "text-primary-600 dark:text-primary-400 h-4 w-4 shrink-0",
|
|
1140
|
+
"aria-hidden": "true"
|
|
1141
|
+
}
|
|
1142
|
+
)
|
|
1143
|
+
] })
|
|
1128
1144
|
},
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
+
option.id
|
|
1146
|
+
)),
|
|
1147
|
+
actions && actions.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1148
|
+
/* @__PURE__ */ jsx(DropdownSeparator, {}),
|
|
1149
|
+
actions.map((action) => /* @__PURE__ */ jsx(
|
|
1150
|
+
DropdownItem,
|
|
1151
|
+
{
|
|
1152
|
+
icon: action.icon,
|
|
1153
|
+
onClick: () => {
|
|
1154
|
+
setOpen(false);
|
|
1155
|
+
action.onSelect();
|
|
1156
|
+
},
|
|
1157
|
+
children: action.label
|
|
1158
|
+
},
|
|
1159
|
+
action.id
|
|
1160
|
+
))
|
|
1161
|
+
] })
|
|
1162
|
+
]
|
|
1145
1163
|
}
|
|
1146
1164
|
);
|
|
1147
1165
|
}
|
|
@@ -2001,6 +2019,6 @@ function notifyComposerMigrationOnce(surface) {
|
|
|
2001
2019
|
);
|
|
2002
2020
|
}
|
|
2003
2021
|
|
|
2004
|
-
export { AttachmentPicker, AttachmentPreviewItem, CameraButton, ChatComposer, ComposerModelSelector, DEFAULT_ACCEPTED_FILE_TYPES, DEFAULT_MAX_FILE_SIZE, DragDropZone, generateAttachmentId, getFileType, notifyComposerMigrationOnce, validateFile };
|
|
2005
|
-
//# sourceMappingURL=chunk-
|
|
2006
|
-
//# sourceMappingURL=chunk-
|
|
2022
|
+
export { AttachmentPicker, AttachmentPreviewItem, CameraButton, ChatComposer, ComposerModelSelector, ComposerSelector, DEFAULT_ACCEPTED_FILE_TYPES, DEFAULT_MAX_FILE_SIZE, DragDropZone, generateAttachmentId, getFileType, notifyComposerMigrationOnce, validateFile };
|
|
2023
|
+
//# sourceMappingURL=chunk-DAAJ3B2T.js.map
|
|
2024
|
+
//# sourceMappingURL=chunk-DAAJ3B2T.js.map
|