@mieweb/ui 0.11.0-dev.6 → 0.11.0-dev.8
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/ag-grid.cjs +11 -2
- package/dist/ag-grid.cjs.map +1 -1
- package/dist/ag-grid.d.cts +8 -0
- package/dist/ag-grid.d.ts +8 -0
- package/dist/ag-grid.js +11 -2
- package/dist/ag-grid.js.map +1 -1
- package/dist/brands/index.cjs +12 -12
- package/dist/brands/index.js +3 -3
- 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-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-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.d.cts +2 -2
- package/dist/components/SuperChat/index.d.ts +2 -2
- package/dist/components/SuperChat/plugins/index.d.cts +2 -2
- package/dist/components/SuperChat/plugins/index.d.ts +2 -2
- package/dist/index.cjs +221 -844
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -1
- package/dist/index.d.ts +63 -1
- package/dist/index.js +25 -716
- 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-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"]}
|
|
@@ -323,5 +323,5 @@ var MarkdownRenderer = ({
|
|
|
323
323
|
};
|
|
324
324
|
|
|
325
325
|
export { CodeBlock, MarkdownRenderer, highlightCode, useMarkdown };
|
|
326
|
-
//# sourceMappingURL=chunk-
|
|
327
|
-
//# sourceMappingURL=chunk-
|
|
326
|
+
//# sourceMappingURL=chunk-DSQCYX7L.js.map
|
|
327
|
+
//# sourceMappingURL=chunk-DSQCYX7L.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/Markdown/useMarkdown.ts","../src/components/Markdown/CodeBlock.tsx","../src/components/Markdown/MarkdownRenderer.tsx"],"names":["useMemo","jsx"],"mappings":"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","file":"chunk-QJ2L5RU6.js","sourcesContent":["/** useMarkdown — converts markdown to sanitised HTML with hljs highlighting and special fence blocks. */\nimport DOMPurify from 'dompurify';\nimport hljs from 'highlight.js/lib/core';\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\nimport bash from 'highlight.js/lib/languages/bash';\nimport css from 'highlight.js/lib/languages/css';\nimport javascript from 'highlight.js/lib/languages/javascript';\nimport json from 'highlight.js/lib/languages/json';\nimport markdown from 'highlight.js/lib/languages/markdown';\nimport python from 'highlight.js/lib/languages/python';\nimport sql from 'highlight.js/lib/languages/sql';\nimport typescript from 'highlight.js/lib/languages/typescript';\nimport xml from 'highlight.js/lib/languages/xml';\nimport yaml from 'highlight.js/lib/languages/yaml';\nimport { marked, type MarkedOptions, type Tokens } from 'marked';\nimport { useCallback, useMemo, useRef } from 'react';\n\nhljs.registerLanguage('javascript', javascript);\nhljs.registerLanguage('typescript', typescript);\nhljs.registerLanguage('python', python);\nhljs.registerLanguage('json', json);\nhljs.registerLanguage('xml', xml);\nhljs.registerLanguage('html', xml);\nhljs.registerLanguage('css', css);\nhljs.registerLanguage('bash', bash);\nhljs.registerLanguage('shell', bash);\nhljs.registerLanguage('sql', sql);\nhljs.registerLanguage('yaml', yaml);\nhljs.registerLanguage('yml', yaml);\nhljs.registerLanguage('markdown', markdown);\nhljs.registerLanguage('md', markdown);\n\nconst lazyLanguages: Record<string, () => Promise<unknown>> = {\n java: () => import('highlight.js/lib/languages/java'),\n cpp: () => import('highlight.js/lib/languages/cpp'),\n 'c++': () => import('highlight.js/lib/languages/cpp'),\n c: () => import('highlight.js/lib/languages/c'),\n csharp: () => import('highlight.js/lib/languages/csharp'),\n php: () => import('highlight.js/lib/languages/php'),\n ruby: () => import('highlight.js/lib/languages/ruby'),\n go: () => import('highlight.js/lib/languages/go'),\n rust: () => import('highlight.js/lib/languages/rust'),\n perl: () => import('highlight.js/lib/languages/perl'),\n r: () => import('highlight.js/lib/languages/r'),\n makefile: () => import('highlight.js/lib/languages/makefile'),\n dockerfile: () => import('highlight.js/lib/languages/dockerfile'),\n};\n\nconst loadedLanguages = new Set<string>([\n 'javascript',\n 'typescript',\n 'python',\n 'json',\n 'xml',\n 'html',\n 'css',\n 'bash',\n 'shell',\n 'sql',\n 'yaml',\n 'yml',\n 'markdown',\n 'md',\n]);\n\nasync function ensureLanguage(lang: string): Promise<boolean> {\n const key = lang.toLowerCase();\n if (loadedLanguages.has(key)) return true;\n const loader = lazyLanguages[key];\n if (!loader) return false;\n try {\n const mod = (await loader()) as { default: unknown };\n hljs.registerLanguage(\n key,\n mod.default as Parameters<typeof hljs.registerLanguage>[1]\n );\n loadedLanguages.add(key);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function highlightCode(code: string, lang?: string): string {\n if (lang && loadedLanguages.has(lang.toLowerCase())) {\n try {\n return hljs.highlight(code, { language: lang.toLowerCase() }).value;\n } catch {\n /* fall through */\n }\n }\n try {\n const result = hljs.highlightAuto(code);\n if (result.relevance > 5) return result.value;\n } catch {\n /* fall through */\n }\n return escapeHtml(code);\n}\n\nfunction sanitise(html: string): string {\n return DOMPurify.sanitize(html, {\n ADD_ATTR: [\n 'target',\n 'rel',\n 'data-block-type',\n 'data-block-id',\n 'data-code',\n 'data-lang',\n 'data-md-fence',\n ],\n }) as string;\n}\n\n// Use globalThis to prevent duplicate DOMPurify hook registration across HMR reloads.\nconst _g = globalThis as unknown as Record<string, boolean>;\n// Guard against SSR: DOMPurify hooks operate on the DOM, so only register in the browser.\nif (typeof document !== 'undefined' && !_g.__markdownAnchorHookInstalled) {\n _g.__markdownAnchorHookInstalled = true;\n DOMPurify.addHook('afterSanitizeAttributes', (node: Element) => {\n const isAnchor =\n (typeof HTMLAnchorElement !== 'undefined' &&\n node instanceof HTMLAnchorElement) ||\n node.nodeName === 'A';\n if (isAnchor) {\n node.setAttribute('target', '_blank');\n node.setAttribute('rel', 'noopener noreferrer');\n }\n });\n}\n\nlet blockCounter = 0;\n\nfunction createRenderer() {\n const renderer = new marked.Renderer();\n\n renderer.code = function (this: unknown, token: Tokens.Code) {\n const { text: code, lang = '' } = token;\n const normalised = lang.toLowerCase().trim();\n const blockId = `block-${++blockCounter}`;\n const encodedCode = encodeURIComponent(code);\n const sentinel = 'data-md-fence=\"1\"';\n\n switch (normalised) {\n case 'mermaid':\n return `<div data-block-type=\"mermaid\" data-block-id=\"${blockId}\" data-code=\"${encodedCode}\" ${sentinel}></div>`;\n case 'csv':\n return `<div data-block-type=\"csv\" data-block-id=\"${blockId}\" data-code=\"${encodedCode}\" ${sentinel}></div>`;\n case 'survey':\n return `<div data-block-type=\"survey\" data-block-id=\"${blockId}\" data-code=\"${encodedCode}\" ${sentinel}></div>`;\n case 'html':\n return `<div data-block-type=\"html-preview\" data-block-id=\"${blockId}\" data-code=\"${encodedCode}\" ${sentinel}></div>`;\n default:\n return `<div data-block-type=\"code\" data-block-id=\"${blockId}\" data-code=\"${encodedCode}\" data-lang=\"${escapeHtml(normalised)}\" ${sentinel}></div>`;\n }\n };\n\n renderer.link = function (this: unknown, token: Tokens.Link) {\n const { href, title, tokens } = token;\n const text =\n this && typeof (this as Record<string, unknown>).parser === 'object'\n ? (\n this as {\n parser: { parseInline: (t: Tokens.Link['tokens']) => string };\n }\n ).parser.parseInline(tokens)\n : token.text;\n const titleAttr = title ? ` title=\"${escapeHtml(title)}\"` : '';\n return `<a href=\"${escapeHtml(href)}\" target=\"_blank\" rel=\"noopener noreferrer\"${titleAttr}>${text}</a>`;\n };\n\n renderer.table = function (this: unknown, token: Tokens.Table) {\n const headerCells = token.header\n .map((cell) => {\n const content =\n this && typeof (this as Record<string, unknown>).parser === 'object'\n ? (\n this as {\n parser: {\n parseInline: (t: Tokens.TableCell['tokens']) => string;\n };\n }\n ).parser.parseInline(cell.tokens)\n : cell.text;\n return `<th>${content}</th>`;\n })\n .join('');\n\n const bodyRows = token.rows\n .map((row) => {\n const cells = row\n .map((cell) => {\n const content =\n this &&\n typeof (this as Record<string, unknown>).parser === 'object'\n ? (\n this as {\n parser: {\n parseInline: (t: Tokens.TableCell['tokens']) => string;\n };\n }\n ).parser.parseInline(cell.tokens)\n : cell.text;\n return `<td>${content}</td>`;\n })\n .join('');\n return `<tr>${cells}</tr>`;\n })\n .join('');\n\n return `<div class=\"overflow-x-auto\"><table class=\"min-w-full divide-y divide-neutral-200 dark:divide-neutral-700\"><thead><tr>${headerCells}</tr></thead><tbody class=\"divide-y divide-neutral-200 dark:divide-neutral-700\">${bodyRows}</tbody></table></div>`;\n };\n\n return renderer;\n}\n\nfunction makeMarkedOptions(): MarkedOptions {\n return { gfm: true, breaks: true, renderer: createRenderer() };\n}\n\nconst CACHE_MAX = 100;\nconst renderCache = new Map<string, string>();\nfunction cacheSet(key: string, value: string) {\n if (renderCache.size >= CACHE_MAX) {\n renderCache.delete(renderCache.keys().next().value as string);\n }\n renderCache.set(key, value);\n}\n\nexport interface UseMarkdownResult {\n /** Render markdown text to sanitised HTML string */\n render: (text: string, cacheKey?: string) => string;\n /** Async-render that lazy-loads needed languages first */\n renderAsync: (text: string, cacheKey?: string) => Promise<string>;\n /** Clear the render cache */\n clearCache: () => void;\n}\n\nexport function useMarkdown(): UseMarkdownResult {\n const cacheRef = useRef(renderCache);\n\n const render = useCallback((text: string, cacheKey?: string): string => {\n if (!text) return '';\n const key = cacheKey ?? text;\n const cached = cacheRef.current.get(key);\n if (cached) return cached;\n\n const raw = marked.parse(text, {\n ...makeMarkedOptions(),\n async: false,\n }) as string;\n const clean = sanitise(raw);\n cacheSet(key, clean);\n return clean;\n }, []);\n\n const renderAsync = useCallback(\n async (text: string, cacheKey?: string): Promise<string> => {\n if (!text) return '';\n const key = cacheKey ?? text;\n const cached = cacheRef.current.get(key);\n if (cached) return cached;\n\n // Allow `+`, `-`, `.` in language identifiers (c++, objective-c, bash-session, etc.)\n const fenceRegex = /^```([\\w+\\-.]+)\\s*$/gm;\n let match: RegExpExecArray | null;\n const langs: string[] = [];\n while ((match = fenceRegex.exec(text)) !== null) {\n langs.push(match[1]);\n }\n await Promise.all(langs.map((l) => ensureLanguage(l)));\n\n const raw = await marked.parse(text, makeMarkedOptions());\n const clean = sanitise(raw);\n cacheSet(key, clean);\n return clean;\n },\n []\n );\n\n const clearCache = useCallback(() => {\n cacheRef.current.clear();\n }, []);\n\n return useMemo(\n () => ({ render, renderAsync, clearCache }),\n [render, renderAsync, clearCache]\n );\n}\n","/**\n * CodeBlock — Renders a syntax-highlighted code fence using FenceBlock.\n *\n * Used by `MarkdownRenderer` for plain (non-special) fenced code blocks.\n * Highlighting is performed synchronously via the `highlightCode` helper from\n * `useMarkdown`; if the language was not pre-registered with highlight.js the\n * helper falls back to escaped raw text.\n */\nimport React, { useMemo } from 'react';\n\nimport { FenceBlock } from './FenceBlock';\nimport { highlightCode } from './useMarkdown';\n\ninterface CodeBlockProps {\n code: string;\n language?: string;\n}\n\nexport const CodeBlock: React.FC<CodeBlockProps> = ({ code, language }) => {\n const html = useMemo(() => highlightCode(code, language), [code, language]);\n\n return (\n <FenceBlock code={code} language={language}>\n {/* Focusable so keyboard users can horizontally scroll long lines\n (axe: scrollable-region-focusable). */}\n <pre\n className=\"m-0 overflow-x-auto !bg-transparent p-3 text-sm\"\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- scrollable code needs keyboard focus\n tabIndex={0}\n role=\"region\"\n aria-label={language ? `${language} code` : 'Code'}\n >\n <code\n className=\"hljs bg-transparent p-0\"\n dangerouslySetInnerHTML={{ __html: html }}\n />\n </pre>\n </FenceBlock>\n );\n};\n","/**\n * MarkdownRenderer — renders markdown with React components for special fence blocks.\n */\nimport React, { Suspense, lazy, useEffect, useState } from 'react';\n\nimport './styles.css';\nimport { CodeBlock } from './CodeBlock';\nimport { useMarkdown } from './useMarkdown';\n\n// Block components are lazy-loaded so optional peer deps (papaparse, js-yaml, mermaid)\n// aren't bundled unless actually rendered.\nconst CsvBlock = lazy(() =>\n import('./CsvBlock').then((m) => ({ default: m.CsvBlock }))\n);\nconst HtmlPreviewBlock = lazy(() =>\n import('./HtmlPreviewBlock').then((m) => ({ default: m.HtmlPreviewBlock }))\n);\nconst MermaidBlock = lazy(() =>\n import('./MermaidBlock').then((m) => ({ default: m.MermaidBlock }))\n);\nconst SurveyBlock = lazy(() =>\n import('./SurveyBlock').then((m) => ({ default: m.SurveyBlock }))\n);\n\nconst BlockFallback: React.FC = () => (\n <div className=\"flex items-center justify-center p-4 text-sm text-neutral-500\">\n Loading…\n </div>\n);\n\nexport interface MarkdownRendererProps {\n /** Raw markdown text */\n text: string;\n /** Cache key (e.g., message._id + revisionNumber) */\n cacheKey?: string;\n /** Additional CSS classes */\n className?: string;\n /** Whether the message is still streaming (may have incomplete blocks) */\n streaming?: boolean;\n}\n\n// Matches fence block markers emitted by useMarkdown (sentinel data-md-fence prevents raw-HTML injection)\nconst BLOCK_RE =\n /<div data-block-type=\"([^\"]+)\" data-block-id=\"([^\"]+)\" data-code=\"([^\"]*)\"([^>]*\\bdata-md-fence=\"1\"[^>]*)><\\/div>/g;\n\ninterface HtmlSegment {\n html: string;\n}\ninterface BlockSegment {\n type: string;\n id: string;\n code: string;\n lang?: string;\n}\ntype Segment = HtmlSegment | BlockSegment;\n\nfunction splitHtml(html: string): Segment[] {\n const segments: Segment[] = [];\n let last = 0;\n for (const m of html.matchAll(BLOCK_RE)) {\n if (m.index > last) segments.push({ html: html.slice(last, m.index) });\n const langMatch = m[4]?.match(/data-lang=\"([^\"]*)\"/)?.[1];\n segments.push({\n type: m[1],\n id: m[2],\n code: (() => {\n try {\n return decodeURIComponent(m[3]);\n } catch {\n return m[3];\n }\n })(),\n lang: langMatch ?? undefined,\n });\n last = m.index + m[0].length;\n }\n if (last < html.length) segments.push({ html: html.slice(last) });\n return segments;\n}\n\nfunction renderBlock(seg: BlockSegment): React.ReactNode {\n const wrap = (node: React.ReactNode) => (\n <Suspense key={seg.id} fallback={<BlockFallback />}>\n {node}\n </Suspense>\n );\n switch (seg.type) {\n case 'mermaid':\n return wrap(<MermaidBlock code={seg.code} id={seg.id} />);\n case 'csv':\n return wrap(<CsvBlock code={seg.code} id={seg.id} />);\n case 'survey':\n return wrap(<SurveyBlock code={seg.code} id={seg.id} />);\n case 'html-preview':\n return wrap(<HtmlPreviewBlock code={seg.code} id={seg.id} />);\n case 'code':\n return wrap(\n <CodeBlock code={seg.code} language={seg.lang || undefined} />\n );\n default:\n return null;\n }\n}\n\nexport const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({\n text,\n cacheKey,\n className = '',\n streaming = false,\n}) => {\n const { render, renderAsync } = useMarkdown();\n const [html, setHtml] = useState('');\n\n useEffect(() => {\n let cancelled = false;\n\n // While streaming, append text length to the cache key so each delta\n // produces a fresh render rather than a stale hit on the messageId.\n const effectiveKey = streaming\n ? cacheKey\n ? `${cacheKey}::${text.length}`\n : undefined\n : cacheKey;\n\n async function doRender() {\n const rendered = await renderAsync(text, effectiveKey);\n if (!cancelled) setHtml(rendered);\n }\n\n if (streaming) {\n setHtml(render(text, effectiveKey));\n } else {\n void doRender();\n }\n\n return () => {\n cancelled = true;\n };\n }, [text, cacheKey, streaming, render, renderAsync]);\n\n const segments = splitHtml(html);\n const proseClass = `md-prose ${className}`.trim();\n\n return (\n <div className={proseClass}>\n {segments.map((seg, i) =>\n 'html' in seg ? (\n <div key={i} dangerouslySetInnerHTML={{ __html: seg.html }} />\n ) : (\n renderBlock(seg)\n )\n )}\n </div>\n );\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/Markdown/useMarkdown.ts","../src/components/Markdown/CodeBlock.tsx","../src/components/Markdown/MarkdownRenderer.tsx"],"names":["useMemo","jsx"],"mappings":"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","file":"chunk-DSQCYX7L.js","sourcesContent":["/** useMarkdown — converts markdown to sanitised HTML with hljs highlighting and special fence blocks. */\nimport DOMPurify from 'dompurify';\nimport hljs from 'highlight.js/lib/core';\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\nimport bash from 'highlight.js/lib/languages/bash';\nimport css from 'highlight.js/lib/languages/css';\nimport javascript from 'highlight.js/lib/languages/javascript';\nimport json from 'highlight.js/lib/languages/json';\nimport markdown from 'highlight.js/lib/languages/markdown';\nimport python from 'highlight.js/lib/languages/python';\nimport sql from 'highlight.js/lib/languages/sql';\nimport typescript from 'highlight.js/lib/languages/typescript';\nimport xml from 'highlight.js/lib/languages/xml';\nimport yaml from 'highlight.js/lib/languages/yaml';\nimport { marked, type MarkedOptions, type Tokens } from 'marked';\nimport { useCallback, useMemo, useRef } from 'react';\n\nhljs.registerLanguage('javascript', javascript);\nhljs.registerLanguage('typescript', typescript);\nhljs.registerLanguage('python', python);\nhljs.registerLanguage('json', json);\nhljs.registerLanguage('xml', xml);\nhljs.registerLanguage('html', xml);\nhljs.registerLanguage('css', css);\nhljs.registerLanguage('bash', bash);\nhljs.registerLanguage('shell', bash);\nhljs.registerLanguage('sql', sql);\nhljs.registerLanguage('yaml', yaml);\nhljs.registerLanguage('yml', yaml);\nhljs.registerLanguage('markdown', markdown);\nhljs.registerLanguage('md', markdown);\n\nconst lazyLanguages: Record<string, () => Promise<unknown>> = {\n java: () => import('highlight.js/lib/languages/java'),\n cpp: () => import('highlight.js/lib/languages/cpp'),\n 'c++': () => import('highlight.js/lib/languages/cpp'),\n c: () => import('highlight.js/lib/languages/c'),\n csharp: () => import('highlight.js/lib/languages/csharp'),\n php: () => import('highlight.js/lib/languages/php'),\n ruby: () => import('highlight.js/lib/languages/ruby'),\n go: () => import('highlight.js/lib/languages/go'),\n rust: () => import('highlight.js/lib/languages/rust'),\n perl: () => import('highlight.js/lib/languages/perl'),\n r: () => import('highlight.js/lib/languages/r'),\n makefile: () => import('highlight.js/lib/languages/makefile'),\n dockerfile: () => import('highlight.js/lib/languages/dockerfile'),\n};\n\nconst loadedLanguages = new Set<string>([\n 'javascript',\n 'typescript',\n 'python',\n 'json',\n 'xml',\n 'html',\n 'css',\n 'bash',\n 'shell',\n 'sql',\n 'yaml',\n 'yml',\n 'markdown',\n 'md',\n]);\n\nasync function ensureLanguage(lang: string): Promise<boolean> {\n const key = lang.toLowerCase();\n if (loadedLanguages.has(key)) return true;\n const loader = lazyLanguages[key];\n if (!loader) return false;\n try {\n const mod = (await loader()) as { default: unknown };\n hljs.registerLanguage(\n key,\n mod.default as Parameters<typeof hljs.registerLanguage>[1]\n );\n loadedLanguages.add(key);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function highlightCode(code: string, lang?: string): string {\n if (lang && loadedLanguages.has(lang.toLowerCase())) {\n try {\n return hljs.highlight(code, { language: lang.toLowerCase() }).value;\n } catch {\n /* fall through */\n }\n }\n try {\n const result = hljs.highlightAuto(code);\n if (result.relevance > 5) return result.value;\n } catch {\n /* fall through */\n }\n return escapeHtml(code);\n}\n\nfunction sanitise(html: string): string {\n return DOMPurify.sanitize(html, {\n ADD_ATTR: [\n 'target',\n 'rel',\n 'data-block-type',\n 'data-block-id',\n 'data-code',\n 'data-lang',\n 'data-md-fence',\n ],\n }) as string;\n}\n\n// Use globalThis to prevent duplicate DOMPurify hook registration across HMR reloads.\nconst _g = globalThis as unknown as Record<string, boolean>;\n// Guard against SSR: DOMPurify hooks operate on the DOM, so only register in the browser.\nif (typeof document !== 'undefined' && !_g.__markdownAnchorHookInstalled) {\n _g.__markdownAnchorHookInstalled = true;\n DOMPurify.addHook('afterSanitizeAttributes', (node: Element) => {\n const isAnchor =\n (typeof HTMLAnchorElement !== 'undefined' &&\n node instanceof HTMLAnchorElement) ||\n node.nodeName === 'A';\n if (isAnchor) {\n node.setAttribute('target', '_blank');\n node.setAttribute('rel', 'noopener noreferrer');\n }\n });\n}\n\nlet blockCounter = 0;\n\nfunction createRenderer() {\n const renderer = new marked.Renderer();\n\n renderer.code = function (this: unknown, token: Tokens.Code) {\n const { text: code, lang = '' } = token;\n const normalised = lang.toLowerCase().trim();\n const blockId = `block-${++blockCounter}`;\n const encodedCode = encodeURIComponent(code);\n const sentinel = 'data-md-fence=\"1\"';\n\n switch (normalised) {\n case 'mermaid':\n return `<div data-block-type=\"mermaid\" data-block-id=\"${blockId}\" data-code=\"${encodedCode}\" ${sentinel}></div>`;\n case 'csv':\n return `<div data-block-type=\"csv\" data-block-id=\"${blockId}\" data-code=\"${encodedCode}\" ${sentinel}></div>`;\n case 'survey':\n return `<div data-block-type=\"survey\" data-block-id=\"${blockId}\" data-code=\"${encodedCode}\" ${sentinel}></div>`;\n case 'html':\n return `<div data-block-type=\"html-preview\" data-block-id=\"${blockId}\" data-code=\"${encodedCode}\" ${sentinel}></div>`;\n default:\n return `<div data-block-type=\"code\" data-block-id=\"${blockId}\" data-code=\"${encodedCode}\" data-lang=\"${escapeHtml(normalised)}\" ${sentinel}></div>`;\n }\n };\n\n renderer.link = function (this: unknown, token: Tokens.Link) {\n const { href, title, tokens } = token;\n const text =\n this && typeof (this as Record<string, unknown>).parser === 'object'\n ? (\n this as {\n parser: { parseInline: (t: Tokens.Link['tokens']) => string };\n }\n ).parser.parseInline(tokens)\n : token.text;\n const titleAttr = title ? ` title=\"${escapeHtml(title)}\"` : '';\n return `<a href=\"${escapeHtml(href)}\" target=\"_blank\" rel=\"noopener noreferrer\"${titleAttr}>${text}</a>`;\n };\n\n renderer.table = function (this: unknown, token: Tokens.Table) {\n const headerCells = token.header\n .map((cell) => {\n const content =\n this && typeof (this as Record<string, unknown>).parser === 'object'\n ? (\n this as {\n parser: {\n parseInline: (t: Tokens.TableCell['tokens']) => string;\n };\n }\n ).parser.parseInline(cell.tokens)\n : cell.text;\n return `<th>${content}</th>`;\n })\n .join('');\n\n const bodyRows = token.rows\n .map((row) => {\n const cells = row\n .map((cell) => {\n const content =\n this &&\n typeof (this as Record<string, unknown>).parser === 'object'\n ? (\n this as {\n parser: {\n parseInline: (t: Tokens.TableCell['tokens']) => string;\n };\n }\n ).parser.parseInline(cell.tokens)\n : cell.text;\n return `<td>${content}</td>`;\n })\n .join('');\n return `<tr>${cells}</tr>`;\n })\n .join('');\n\n return `<div class=\"overflow-x-auto\"><table class=\"min-w-full divide-y divide-neutral-200 dark:divide-neutral-700\"><thead><tr>${headerCells}</tr></thead><tbody class=\"divide-y divide-neutral-200 dark:divide-neutral-700\">${bodyRows}</tbody></table></div>`;\n };\n\n return renderer;\n}\n\nfunction makeMarkedOptions(): MarkedOptions {\n return { gfm: true, breaks: true, renderer: createRenderer() };\n}\n\nconst CACHE_MAX = 100;\nconst renderCache = new Map<string, string>();\nfunction cacheSet(key: string, value: string) {\n if (renderCache.size >= CACHE_MAX) {\n renderCache.delete(renderCache.keys().next().value as string);\n }\n renderCache.set(key, value);\n}\n\nexport interface UseMarkdownResult {\n /** Render markdown text to sanitised HTML string */\n render: (text: string, cacheKey?: string) => string;\n /** Async-render that lazy-loads needed languages first */\n renderAsync: (text: string, cacheKey?: string) => Promise<string>;\n /** Clear the render cache */\n clearCache: () => void;\n}\n\nexport function useMarkdown(): UseMarkdownResult {\n const cacheRef = useRef(renderCache);\n\n const render = useCallback((text: string, cacheKey?: string): string => {\n if (!text) return '';\n const key = cacheKey ?? text;\n const cached = cacheRef.current.get(key);\n if (cached) return cached;\n\n const raw = marked.parse(text, {\n ...makeMarkedOptions(),\n async: false,\n }) as string;\n const clean = sanitise(raw);\n cacheSet(key, clean);\n return clean;\n }, []);\n\n const renderAsync = useCallback(\n async (text: string, cacheKey?: string): Promise<string> => {\n if (!text) return '';\n const key = cacheKey ?? text;\n const cached = cacheRef.current.get(key);\n if (cached) return cached;\n\n // Allow `+`, `-`, `.` in language identifiers (c++, objective-c, bash-session, etc.)\n const fenceRegex = /^```([\\w+\\-.]+)\\s*$/gm;\n let match: RegExpExecArray | null;\n const langs: string[] = [];\n while ((match = fenceRegex.exec(text)) !== null) {\n langs.push(match[1]);\n }\n await Promise.all(langs.map((l) => ensureLanguage(l)));\n\n const raw = await marked.parse(text, makeMarkedOptions());\n const clean = sanitise(raw);\n cacheSet(key, clean);\n return clean;\n },\n []\n );\n\n const clearCache = useCallback(() => {\n cacheRef.current.clear();\n }, []);\n\n return useMemo(\n () => ({ render, renderAsync, clearCache }),\n [render, renderAsync, clearCache]\n );\n}\n","/**\n * CodeBlock — Renders a syntax-highlighted code fence using FenceBlock.\n *\n * Used by `MarkdownRenderer` for plain (non-special) fenced code blocks.\n * Highlighting is performed synchronously via the `highlightCode` helper from\n * `useMarkdown`; if the language was not pre-registered with highlight.js the\n * helper falls back to escaped raw text.\n */\nimport React, { useMemo } from 'react';\n\nimport { FenceBlock } from './FenceBlock';\nimport { highlightCode } from './useMarkdown';\n\ninterface CodeBlockProps {\n code: string;\n language?: string;\n}\n\nexport const CodeBlock: React.FC<CodeBlockProps> = ({ code, language }) => {\n const html = useMemo(() => highlightCode(code, language), [code, language]);\n\n return (\n <FenceBlock code={code} language={language}>\n {/* Focusable so keyboard users can horizontally scroll long lines\n (axe: scrollable-region-focusable). */}\n <pre\n className=\"m-0 overflow-x-auto !bg-transparent p-3 text-sm\"\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- scrollable code needs keyboard focus\n tabIndex={0}\n role=\"region\"\n aria-label={language ? `${language} code` : 'Code'}\n >\n <code\n className=\"hljs bg-transparent p-0\"\n dangerouslySetInnerHTML={{ __html: html }}\n />\n </pre>\n </FenceBlock>\n );\n};\n","/**\n * MarkdownRenderer — renders markdown with React components for special fence blocks.\n */\nimport React, { Suspense, lazy, useEffect, useState } from 'react';\n\nimport './styles.css';\nimport { CodeBlock } from './CodeBlock';\nimport { useMarkdown } from './useMarkdown';\n\n// Block components are lazy-loaded so optional peer deps (papaparse, js-yaml, mermaid)\n// aren't bundled unless actually rendered.\nconst CsvBlock = lazy(() =>\n import('./CsvBlock').then((m) => ({ default: m.CsvBlock }))\n);\nconst HtmlPreviewBlock = lazy(() =>\n import('./HtmlPreviewBlock').then((m) => ({ default: m.HtmlPreviewBlock }))\n);\nconst MermaidBlock = lazy(() =>\n import('./MermaidBlock').then((m) => ({ default: m.MermaidBlock }))\n);\nconst SurveyBlock = lazy(() =>\n import('./SurveyBlock').then((m) => ({ default: m.SurveyBlock }))\n);\n\nconst BlockFallback: React.FC = () => (\n <div className=\"flex items-center justify-center p-4 text-sm text-neutral-500\">\n Loading…\n </div>\n);\n\nexport interface MarkdownRendererProps {\n /** Raw markdown text */\n text: string;\n /** Cache key (e.g., message._id + revisionNumber) */\n cacheKey?: string;\n /** Additional CSS classes */\n className?: string;\n /** Whether the message is still streaming (may have incomplete blocks) */\n streaming?: boolean;\n}\n\n// Matches fence block markers emitted by useMarkdown (sentinel data-md-fence prevents raw-HTML injection)\nconst BLOCK_RE =\n /<div data-block-type=\"([^\"]+)\" data-block-id=\"([^\"]+)\" data-code=\"([^\"]*)\"([^>]*\\bdata-md-fence=\"1\"[^>]*)><\\/div>/g;\n\ninterface HtmlSegment {\n html: string;\n}\ninterface BlockSegment {\n type: string;\n id: string;\n code: string;\n lang?: string;\n}\ntype Segment = HtmlSegment | BlockSegment;\n\nfunction splitHtml(html: string): Segment[] {\n const segments: Segment[] = [];\n let last = 0;\n for (const m of html.matchAll(BLOCK_RE)) {\n if (m.index > last) segments.push({ html: html.slice(last, m.index) });\n const langMatch = m[4]?.match(/data-lang=\"([^\"]*)\"/)?.[1];\n segments.push({\n type: m[1],\n id: m[2],\n code: (() => {\n try {\n return decodeURIComponent(m[3]);\n } catch {\n return m[3];\n }\n })(),\n lang: langMatch ?? undefined,\n });\n last = m.index + m[0].length;\n }\n if (last < html.length) segments.push({ html: html.slice(last) });\n return segments;\n}\n\nfunction renderBlock(seg: BlockSegment): React.ReactNode {\n const wrap = (node: React.ReactNode) => (\n <Suspense key={seg.id} fallback={<BlockFallback />}>\n {node}\n </Suspense>\n );\n switch (seg.type) {\n case 'mermaid':\n return wrap(<MermaidBlock code={seg.code} id={seg.id} />);\n case 'csv':\n return wrap(<CsvBlock code={seg.code} id={seg.id} />);\n case 'survey':\n return wrap(<SurveyBlock code={seg.code} id={seg.id} />);\n case 'html-preview':\n return wrap(<HtmlPreviewBlock code={seg.code} id={seg.id} />);\n case 'code':\n return wrap(\n <CodeBlock code={seg.code} language={seg.lang || undefined} />\n );\n default:\n return null;\n }\n}\n\nexport const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({\n text,\n cacheKey,\n className = '',\n streaming = false,\n}) => {\n const { render, renderAsync } = useMarkdown();\n const [html, setHtml] = useState('');\n\n useEffect(() => {\n let cancelled = false;\n\n // While streaming, append text length to the cache key so each delta\n // produces a fresh render rather than a stale hit on the messageId.\n const effectiveKey = streaming\n ? cacheKey\n ? `${cacheKey}::${text.length}`\n : undefined\n : cacheKey;\n\n async function doRender() {\n const rendered = await renderAsync(text, effectiveKey);\n if (!cancelled) setHtml(rendered);\n }\n\n if (streaming) {\n setHtml(render(text, effectiveKey));\n } else {\n void doRender();\n }\n\n return () => {\n cancelled = true;\n };\n }, [text, cacheKey, streaming, render, renderAsync]);\n\n const segments = splitHtml(html);\n const proseClass = `md-prose ${className}`.trim();\n\n return (\n <div className={proseClass}>\n {segments.map((seg, i) =>\n 'html' in seg ? (\n <div key={i} dangerouslySetInnerHTML={{ __html: seg.html }} />\n ) : (\n renderBlock(seg)\n )\n )}\n </div>\n );\n};\n"]}
|
|
@@ -287,6 +287,7 @@ var RecordButton = React__namespace.forwardRef(
|
|
|
287
287
|
maxDuration = 0,
|
|
288
288
|
mimeType = "audio/webm",
|
|
289
289
|
onClick,
|
|
290
|
+
"aria-label": ariaLabel,
|
|
290
291
|
...props
|
|
291
292
|
}, ref) => {
|
|
292
293
|
const [internalState, setInternalState] = React__namespace.useState("idle");
|
|
@@ -497,7 +498,7 @@ var RecordButton = React__namespace.forwardRef(
|
|
|
497
498
|
getStateStyles(effectiveState, variant),
|
|
498
499
|
className
|
|
499
500
|
),
|
|
500
|
-
"aria-label": getAriaLabel(),
|
|
501
|
+
"aria-label": ariaLabel ?? getAriaLabel(),
|
|
501
502
|
"aria-pressed": effectiveState === "recording" ? true : void 0,
|
|
502
503
|
"aria-busy": effectiveState === "processing" ? true : void 0,
|
|
503
504
|
children: [
|
|
@@ -532,5 +533,5 @@ RecordButton.displayName = "RecordButton";
|
|
|
532
533
|
exports.RecordButton = RecordButton;
|
|
533
534
|
exports.formatDuration = formatDuration;
|
|
534
535
|
exports.recordButtonVariants = recordButtonVariants;
|
|
535
|
-
//# sourceMappingURL=chunk-
|
|
536
|
-
//# sourceMappingURL=chunk-
|
|
536
|
+
//# sourceMappingURL=chunk-HIYQ3M5V.cjs.map
|
|
537
|
+
//# sourceMappingURL=chunk-HIYQ3M5V.cjs.map
|