@arbidocs/react 0.3.173 → 0.3.175
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/chunk-7IOCMCC5.cjs +99 -0
- package/dist/chunk-7IOCMCC5.cjs.map +1 -0
- package/dist/{chunk-5DV7FW6T.cjs → chunk-FIPS3VPH.cjs} +497 -8
- package/dist/chunk-FIPS3VPH.cjs.map +1 -0
- package/dist/{chunk-PAJGQHFM.js → chunk-I256BUCX.js} +492 -11
- package/dist/chunk-I256BUCX.js.map +1 -0
- package/dist/chunk-JKO3NR5L.js +93 -0
- package/dist/chunk-JKO3NR5L.js.map +1 -0
- package/dist/index.cjs +723 -543
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +228 -2
- package/dist/index.d.ts +228 -2
- package/dist/index.js +372 -223
- package/dist/index.js.map +1 -1
- package/dist/ui/index.cjs +115 -107
- package/dist/ui/index.d.cts +216 -154
- package/dist/ui/index.d.ts +216 -154
- package/dist/ui/index.js +1 -1
- package/dist/viewers/index.cjs +14 -58
- package/dist/viewers/index.cjs.map +1 -1
- package/dist/viewers/index.js +3 -53
- package/dist/viewers/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-5DV7FW6T.cjs.map +0 -1
- package/dist/chunk-PAJGQHFM.js.map +0 -1
- package/dist/chunk-Q6XAXIT3.js +0 -43
- package/dist/chunk-Q6XAXIT3.js.map +0 -1
- package/dist/chunk-RWCMW36I.cjs +0 -47
- package/dist/chunk-RWCMW36I.cjs.map +0 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var browser = require('@arbidocs/sdk/browser');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/ArbiProvider.tsx
|
|
8
|
+
var ArbiContext = react.createContext(null);
|
|
9
|
+
function ArbiProvider({ children, ...options }) {
|
|
10
|
+
const arbi = react.useMemo(
|
|
11
|
+
() => new browser.Arbi(options),
|
|
12
|
+
// Stable on url + deploymentDomain — re-creating the instance on every render would be wrong
|
|
13
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
14
|
+
[options.url, options.deploymentDomain, options.credentials]
|
|
15
|
+
);
|
|
16
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ArbiContext.Provider, { value: arbi, children });
|
|
17
|
+
}
|
|
18
|
+
function useArbi() {
|
|
19
|
+
const arbi = react.useContext(ArbiContext);
|
|
20
|
+
if (!arbi) {
|
|
21
|
+
throw new Error("useArbi() must be used within an <ArbiProvider>");
|
|
22
|
+
}
|
|
23
|
+
return arbi;
|
|
24
|
+
}
|
|
25
|
+
function AudioViewer({ blob, title }) {
|
|
26
|
+
const [objectUrl, setObjectUrl] = react.useState("");
|
|
27
|
+
react.useEffect(() => {
|
|
28
|
+
const url = URL.createObjectURL(blob);
|
|
29
|
+
setObjectUrl(url);
|
|
30
|
+
return () => URL.revokeObjectURL(url);
|
|
31
|
+
}, [blob]);
|
|
32
|
+
if (!objectUrl) return null;
|
|
33
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
34
|
+
"div",
|
|
35
|
+
{
|
|
36
|
+
className: "h-full w-full flex items-center justify-center p-4 bg-muted/30",
|
|
37
|
+
"data-testid": "audio-viewer",
|
|
38
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("audio", { src: objectUrl, controls: true, title, className: "w-full max-w-lg" })
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function ImageViewer({ blob, alt }) {
|
|
43
|
+
const [objectUrl, setObjectUrl] = react.useState("");
|
|
44
|
+
react.useEffect(() => {
|
|
45
|
+
const url = URL.createObjectURL(blob);
|
|
46
|
+
setObjectUrl(url);
|
|
47
|
+
return () => URL.revokeObjectURL(url);
|
|
48
|
+
}, [blob]);
|
|
49
|
+
if (!objectUrl) return null;
|
|
50
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
51
|
+
"div",
|
|
52
|
+
{
|
|
53
|
+
className: "h-full w-full overflow-auto flex items-center justify-center p-4 bg-muted/30",
|
|
54
|
+
"data-testid": "image-viewer",
|
|
55
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
56
|
+
"img",
|
|
57
|
+
{
|
|
58
|
+
src: objectUrl,
|
|
59
|
+
alt,
|
|
60
|
+
className: "max-w-full max-h-full object-contain rounded shadow-sm",
|
|
61
|
+
"data-testid": "image-viewer-img"
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
function VideoViewer({ blob, title }) {
|
|
68
|
+
const [objectUrl, setObjectUrl] = react.useState("");
|
|
69
|
+
react.useEffect(() => {
|
|
70
|
+
const url = URL.createObjectURL(blob);
|
|
71
|
+
setObjectUrl(url);
|
|
72
|
+
return () => URL.revokeObjectURL(url);
|
|
73
|
+
}, [blob]);
|
|
74
|
+
if (!objectUrl) return null;
|
|
75
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
76
|
+
"div",
|
|
77
|
+
{
|
|
78
|
+
className: "h-full w-full overflow-auto flex items-center justify-center p-4 bg-muted/30",
|
|
79
|
+
"data-testid": "video-viewer",
|
|
80
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
81
|
+
"video",
|
|
82
|
+
{
|
|
83
|
+
src: objectUrl,
|
|
84
|
+
controls: true,
|
|
85
|
+
title,
|
|
86
|
+
className: "max-w-full max-h-full rounded shadow-sm"
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
exports.ArbiProvider = ArbiProvider;
|
|
94
|
+
exports.AudioViewer = AudioViewer;
|
|
95
|
+
exports.ImageViewer = ImageViewer;
|
|
96
|
+
exports.VideoViewer = VideoViewer;
|
|
97
|
+
exports.useArbi = useArbi;
|
|
98
|
+
//# sourceMappingURL=chunk-7IOCMCC5.cjs.map
|
|
99
|
+
//# sourceMappingURL=chunk-7IOCMCC5.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ArbiProvider.tsx","../src/useArbi.ts","../src/viewers/AudioViewer.tsx","../src/viewers/ImageViewer.tsx","../src/viewers/VideoViewer.tsx"],"names":["createContext","useMemo","Arbi","useContext","useState","useEffect","jsx"],"mappings":"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","file":"chunk-7IOCMCC5.cjs","sourcesContent":["/**\n * ArbiProvider — React context that holds an Arbi instance.\n *\n * Usage:\n * <ArbiProvider url=\"https://arbi.mycompany.com\">\n * <App />\n * </ArbiProvider>\n */\n\nimport { createContext, useMemo, type ReactNode } from 'react'\nimport { Arbi, type ArbiOptions } from '@arbidocs/sdk/browser'\n\nexport const ArbiContext = createContext<Arbi | null>(null)\n\nexport interface ArbiProviderProps extends ArbiOptions {\n children: ReactNode\n}\n\nexport function ArbiProvider({ children, ...options }: ArbiProviderProps) {\n const arbi = useMemo(\n () => new Arbi(options),\n // Stable on url + deploymentDomain — re-creating the instance on every render would be wrong\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [options.url, options.deploymentDomain, options.credentials]\n )\n\n return <ArbiContext.Provider value={arbi}>{children}</ArbiContext.Provider>\n}\n","/**\n * Hook to access the Arbi instance from the nearest ArbiProvider.\n */\n\nimport { useContext } from 'react'\nimport type { Arbi } from '@arbidocs/sdk/browser'\nimport { ArbiContext } from './ArbiProvider'\n\nexport function useArbi(): Arbi {\n const arbi = useContext(ArbiContext)\n if (!arbi) {\n throw new Error('useArbi() must be used within an <ArbiProvider>')\n }\n return arbi\n}\n","import { useEffect, useState } from 'react'\n\nexport interface AudioViewerProps {\n blob: Blob\n title: string\n}\n\n/** Renders an audio blob with the browser's native transport controls. */\nexport function AudioViewer({ blob, title }: AudioViewerProps) {\n const [objectUrl, setObjectUrl] = useState<string>('')\n\n useEffect(() => {\n const url = URL.createObjectURL(blob)\n setObjectUrl(url)\n return () => URL.revokeObjectURL(url)\n }, [blob])\n\n if (!objectUrl) return null\n\n return (\n <div\n className=\"h-full w-full flex items-center justify-center p-4 bg-muted/30\"\n data-testid=\"audio-viewer\"\n >\n <audio src={objectUrl} controls title={title} className=\"w-full max-w-lg\" />\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\n\nexport interface ImageViewerProps {\n blob: Blob\n alt: string\n}\n\n/** Renders an image blob, scaled to fit its container. */\nexport function ImageViewer({ blob, alt }: ImageViewerProps) {\n const [objectUrl, setObjectUrl] = useState<string>('')\n\n useEffect(() => {\n const url = URL.createObjectURL(blob)\n setObjectUrl(url)\n return () => URL.revokeObjectURL(url)\n }, [blob])\n\n if (!objectUrl) return null\n\n return (\n <div\n className=\"h-full w-full overflow-auto flex items-center justify-center p-4 bg-muted/30\"\n data-testid=\"image-viewer\"\n >\n <img\n src={objectUrl}\n alt={alt}\n className=\"max-w-full max-h-full object-contain rounded shadow-sm\"\n data-testid=\"image-viewer-img\"\n />\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\n\nexport interface VideoViewerProps {\n blob: Blob\n title: string\n}\n\n/** Renders a video blob with the browser's native transport controls. */\nexport function VideoViewer({ blob, title }: VideoViewerProps) {\n const [objectUrl, setObjectUrl] = useState<string>('')\n\n useEffect(() => {\n const url = URL.createObjectURL(blob)\n setObjectUrl(url)\n return () => URL.revokeObjectURL(url)\n }, [blob])\n\n if (!objectUrl) return null\n\n return (\n <div\n className=\"h-full w-full overflow-auto flex items-center justify-center p-4 bg-muted/30\"\n data-testid=\"video-viewer\"\n >\n <video\n src={objectUrl}\n controls\n title={title}\n className=\"max-w-full max-h-full rounded shadow-sm\"\n />\n </div>\n )\n}\n"]}
|
|
@@ -442,6 +442,86 @@ var TooltipContent = React2__namespace.forwardRef(
|
|
|
442
442
|
}
|
|
443
443
|
);
|
|
444
444
|
TooltipContent.displayName = "TooltipContent";
|
|
445
|
+
function IconToggle({
|
|
446
|
+
icon: Icon2,
|
|
447
|
+
isActive,
|
|
448
|
+
onToggle,
|
|
449
|
+
colors,
|
|
450
|
+
fillWhenActive = false,
|
|
451
|
+
disabled = false,
|
|
452
|
+
disabledTooltip,
|
|
453
|
+
showLabel = false,
|
|
454
|
+
label,
|
|
455
|
+
tooltipActive,
|
|
456
|
+
tooltipInactive,
|
|
457
|
+
tooltipMixed,
|
|
458
|
+
isMixed = false,
|
|
459
|
+
showTooltip = true,
|
|
460
|
+
"data-testid": dataTestId
|
|
461
|
+
}) {
|
|
462
|
+
const handleClick = (e) => {
|
|
463
|
+
e.stopPropagation();
|
|
464
|
+
onToggle(!isActive);
|
|
465
|
+
};
|
|
466
|
+
const { bg: bgColorClass } = colors;
|
|
467
|
+
const tooltipText = disabled ? disabledTooltip : isMixed ? tooltipMixed : isActive ? tooltipActive : tooltipInactive;
|
|
468
|
+
const { text: textColorClass } = colors;
|
|
469
|
+
const button = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
470
|
+
"button",
|
|
471
|
+
{
|
|
472
|
+
type: "button",
|
|
473
|
+
onClick: handleClick,
|
|
474
|
+
disabled,
|
|
475
|
+
"data-testid": dataTestId,
|
|
476
|
+
"aria-pressed": isMixed ? "mixed" : isActive,
|
|
477
|
+
className: `
|
|
478
|
+
group/toggle relative inline-flex items-center h-4 w-8 rounded-full
|
|
479
|
+
transition-colors cursor-pointer
|
|
480
|
+
disabled:opacity-50 disabled:cursor-not-allowed
|
|
481
|
+
${isActive ? bgColorClass : isMixed ? "bg-gray-400" : "bg-muted"}
|
|
482
|
+
`,
|
|
483
|
+
children: [
|
|
484
|
+
isActive && !disabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 w-4 h-4 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minus, { className: "w-2.5 h-2.5 text-white/70 hidden group-hover/toggle:block" }) }),
|
|
485
|
+
!isActive && !disabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-0 w-4 h-4 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "w-2.5 h-2.5 text-muted-foreground hidden group-hover/toggle:block" }) }),
|
|
486
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
487
|
+
"div",
|
|
488
|
+
{
|
|
489
|
+
className: `
|
|
490
|
+
absolute h-4 w-4 rounded-full bg-white shadow-sm
|
|
491
|
+
flex items-center justify-center
|
|
492
|
+
transition-transform duration-200
|
|
493
|
+
${isActive ? "translate-x-4" : "translate-x-0"}
|
|
494
|
+
`,
|
|
495
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
496
|
+
Icon2,
|
|
497
|
+
{
|
|
498
|
+
className: `w-2.5 h-2.5 ${isActive ? textColorClass : "text-muted-foreground"} ${isActive && fillWhenActive ? "fill-current" : ""}`
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
)
|
|
503
|
+
]
|
|
504
|
+
}
|
|
505
|
+
) });
|
|
506
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-2", children: [
|
|
507
|
+
showLabel && label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-sm text-primary", children: label }),
|
|
508
|
+
tooltipText && showTooltip ? /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
509
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, onFocusCapture: (e) => e.stopPropagation(), children: button }),
|
|
510
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: tooltipText }) })
|
|
511
|
+
] }) : button
|
|
512
|
+
] });
|
|
513
|
+
}
|
|
514
|
+
var DictateContext = React2.createContext(null);
|
|
515
|
+
function DictateProvider({
|
|
516
|
+
transcribe,
|
|
517
|
+
onError,
|
|
518
|
+
children
|
|
519
|
+
}) {
|
|
520
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DictateContext.Provider, { value: { transcribe, onError }, children });
|
|
521
|
+
}
|
|
522
|
+
function useDictateContext() {
|
|
523
|
+
return React2.useContext(DictateContext);
|
|
524
|
+
}
|
|
445
525
|
var Avatar = React2__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
446
526
|
AvatarPrimitive__namespace.Root,
|
|
447
527
|
{
|
|
@@ -617,21 +697,384 @@ var CardFooter = React2__namespace.forwardRef(
|
|
|
617
697
|
)
|
|
618
698
|
);
|
|
619
699
|
CardFooter.displayName = "CardFooter";
|
|
700
|
+
|
|
701
|
+
// src/documents/media-error.ts
|
|
702
|
+
function classifyMediaError(err, kind) {
|
|
703
|
+
const label = kind === "camera" ? "Camera" : "Microphone";
|
|
704
|
+
const name = err instanceof DOMException ? err.name : void 0;
|
|
705
|
+
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
706
|
+
return `${label} access is blocked. Allow ${kind} permission in your browser settings to use this feature.`;
|
|
707
|
+
}
|
|
708
|
+
if (name === "NotFoundError" || name === "DevicesNotFoundError") {
|
|
709
|
+
return `No ${kind} found on this device.`;
|
|
710
|
+
}
|
|
711
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
712
|
+
return `${kind === "camera" ? "Camera" : "Voice"} input is not supported in this browser.`;
|
|
713
|
+
}
|
|
714
|
+
return `Could not access the ${kind}. Please try again.`;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/documents/useAudioRecorder.ts
|
|
718
|
+
var LEVEL_UPDATE_INTERVAL_MS = 50;
|
|
719
|
+
function useAudioRecorder() {
|
|
720
|
+
const [state, setState] = React2.useState("idle");
|
|
721
|
+
const [level, setLevel] = React2.useState(0);
|
|
722
|
+
const [elapsedMs, setElapsedMs] = React2.useState(0);
|
|
723
|
+
const [blob, setBlob] = React2.useState(null);
|
|
724
|
+
const [mimeType, setMimeType] = React2.useState(null);
|
|
725
|
+
const [error, setError] = React2.useState(null);
|
|
726
|
+
const mediaRecorderRef = React2.useRef(null);
|
|
727
|
+
const chunksRef = React2.useRef([]);
|
|
728
|
+
const streamRef = React2.useRef(null);
|
|
729
|
+
const stateRef = React2.useRef("idle");
|
|
730
|
+
stateRef.current = state;
|
|
731
|
+
const segmentStartRef = React2.useRef(0);
|
|
732
|
+
const accumulatedMsRef = React2.useRef(0);
|
|
733
|
+
const audioContextRef = React2.useRef(null);
|
|
734
|
+
const analyserRef = React2.useRef(null);
|
|
735
|
+
const levelRafRef = React2.useRef(null);
|
|
736
|
+
const stopMeter = React2.useCallback(() => {
|
|
737
|
+
if (levelRafRef.current !== null) cancelAnimationFrame(levelRafRef.current);
|
|
738
|
+
levelRafRef.current = null;
|
|
739
|
+
analyserRef.current = null;
|
|
740
|
+
void audioContextRef.current?.close().catch(() => {
|
|
741
|
+
});
|
|
742
|
+
audioContextRef.current = null;
|
|
743
|
+
}, []);
|
|
744
|
+
const startMeter = React2.useCallback((stream) => {
|
|
745
|
+
const audioContext = new AudioContext();
|
|
746
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
747
|
+
const analyser = audioContext.createAnalyser();
|
|
748
|
+
analyser.fftSize = 256;
|
|
749
|
+
source.connect(analyser);
|
|
750
|
+
audioContextRef.current = audioContext;
|
|
751
|
+
analyserRef.current = analyser;
|
|
752
|
+
const data = new Uint8Array(analyser.frequencyBinCount);
|
|
753
|
+
let lastUpdate = 0;
|
|
754
|
+
const tick = (t) => {
|
|
755
|
+
const active = analyserRef.current;
|
|
756
|
+
if (!active) return;
|
|
757
|
+
if (t - lastUpdate >= LEVEL_UPDATE_INTERVAL_MS) {
|
|
758
|
+
lastUpdate = t;
|
|
759
|
+
active.getByteTimeDomainData(data);
|
|
760
|
+
let sumSquares = 0;
|
|
761
|
+
for (let i = 0; i < data.length; i++) {
|
|
762
|
+
const centered = (data[i] - 128) / 128;
|
|
763
|
+
sumSquares += centered * centered;
|
|
764
|
+
}
|
|
765
|
+
const rms = Math.sqrt(sumSquares / data.length);
|
|
766
|
+
setLevel(Math.sqrt(Math.min(1, rms * 8)));
|
|
767
|
+
setElapsedMs(accumulatedMsRef.current + (t - segmentStartRef.current));
|
|
768
|
+
}
|
|
769
|
+
levelRafRef.current = requestAnimationFrame(tick);
|
|
770
|
+
};
|
|
771
|
+
levelRafRef.current = requestAnimationFrame(tick);
|
|
772
|
+
}, []);
|
|
773
|
+
const stopTracks = React2.useCallback(() => {
|
|
774
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
775
|
+
streamRef.current = null;
|
|
776
|
+
}, []);
|
|
777
|
+
React2.useEffect(
|
|
778
|
+
() => () => {
|
|
779
|
+
stopTracks();
|
|
780
|
+
stopMeter();
|
|
781
|
+
},
|
|
782
|
+
[stopTracks, stopMeter]
|
|
783
|
+
);
|
|
784
|
+
const start = React2.useCallback(async () => {
|
|
785
|
+
if (stateRef.current === "recording" || stateRef.current === "paused") return;
|
|
786
|
+
setError(null);
|
|
787
|
+
try {
|
|
788
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
789
|
+
streamRef.current = stream;
|
|
790
|
+
const mt = MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
|
|
791
|
+
const recorder = new MediaRecorder(stream, { mimeType: mt });
|
|
792
|
+
chunksRef.current = [];
|
|
793
|
+
recorder.ondataavailable = (e) => {
|
|
794
|
+
if (e.data.size > 0) chunksRef.current.push(e.data);
|
|
795
|
+
};
|
|
796
|
+
mediaRecorderRef.current = recorder;
|
|
797
|
+
setMimeType(mt);
|
|
798
|
+
setBlob(null);
|
|
799
|
+
accumulatedMsRef.current = 0;
|
|
800
|
+
segmentStartRef.current = performance.now();
|
|
801
|
+
recorder.start();
|
|
802
|
+
setState("recording");
|
|
803
|
+
startMeter(stream);
|
|
804
|
+
} catch (err) {
|
|
805
|
+
stopTracks();
|
|
806
|
+
setError(classifyMediaError(err, "microphone"));
|
|
807
|
+
}
|
|
808
|
+
}, [startMeter, stopTracks]);
|
|
809
|
+
const pause = React2.useCallback(() => {
|
|
810
|
+
const recorder = mediaRecorderRef.current;
|
|
811
|
+
if (!recorder || recorder.state !== "recording") return;
|
|
812
|
+
recorder.pause();
|
|
813
|
+
accumulatedMsRef.current += performance.now() - segmentStartRef.current;
|
|
814
|
+
stopMeter();
|
|
815
|
+
setLevel(0);
|
|
816
|
+
setState("paused");
|
|
817
|
+
}, [stopMeter]);
|
|
818
|
+
const resume = React2.useCallback(() => {
|
|
819
|
+
const recorder = mediaRecorderRef.current;
|
|
820
|
+
if (!recorder || recorder.state !== "paused") return;
|
|
821
|
+
recorder.resume();
|
|
822
|
+
segmentStartRef.current = performance.now();
|
|
823
|
+
setState("recording");
|
|
824
|
+
if (streamRef.current) startMeter(streamRef.current);
|
|
825
|
+
}, [startMeter]);
|
|
826
|
+
const stop = React2.useCallback(() => {
|
|
827
|
+
const recorder = mediaRecorderRef.current;
|
|
828
|
+
if (!recorder || recorder.state === "inactive") return;
|
|
829
|
+
const finalElapsed = recorder.state === "paused" ? accumulatedMsRef.current : accumulatedMsRef.current + (performance.now() - segmentStartRef.current);
|
|
830
|
+
stopMeter();
|
|
831
|
+
recorder.onstop = () => {
|
|
832
|
+
stopTracks();
|
|
833
|
+
const finalBlob = chunksRef.current.length ? new Blob(chunksRef.current, { type: recorder.mimeType }) : null;
|
|
834
|
+
setBlob(finalBlob);
|
|
835
|
+
setElapsedMs(finalElapsed);
|
|
836
|
+
setLevel(0);
|
|
837
|
+
setState("stopped");
|
|
838
|
+
};
|
|
839
|
+
recorder.stop();
|
|
840
|
+
}, [stopMeter, stopTracks]);
|
|
841
|
+
const reset = React2.useCallback(() => {
|
|
842
|
+
setState("idle");
|
|
843
|
+
setBlob(null);
|
|
844
|
+
setElapsedMs(0);
|
|
845
|
+
setLevel(0);
|
|
846
|
+
setMimeType(null);
|
|
847
|
+
setError(null);
|
|
848
|
+
}, []);
|
|
849
|
+
return { state, level, elapsedMs, blob, mimeType, error, start, pause, resume, stop, reset };
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// src/documents/format-elapsed.ts
|
|
853
|
+
function formatElapsed(ms) {
|
|
854
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
855
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
856
|
+
const seconds = totalSeconds % 60;
|
|
857
|
+
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
858
|
+
}
|
|
859
|
+
var BAR_WEIGHTS = [
|
|
860
|
+
0.25,
|
|
861
|
+
0.32,
|
|
862
|
+
0.48,
|
|
863
|
+
0.62,
|
|
864
|
+
0.82,
|
|
865
|
+
0.56,
|
|
866
|
+
0.92,
|
|
867
|
+
0.72,
|
|
868
|
+
1,
|
|
869
|
+
0.86,
|
|
870
|
+
0.62,
|
|
871
|
+
0.96,
|
|
872
|
+
0.76,
|
|
873
|
+
0.5,
|
|
874
|
+
0.84,
|
|
875
|
+
0.66,
|
|
876
|
+
0.42,
|
|
877
|
+
0.56,
|
|
878
|
+
0.36,
|
|
879
|
+
0.28,
|
|
880
|
+
0.22
|
|
881
|
+
];
|
|
882
|
+
var MIN_BAR_PX = 3;
|
|
883
|
+
var MAX_BAR_PX = 40;
|
|
884
|
+
function MicWaveformIndicator({
|
|
885
|
+
level,
|
|
886
|
+
elapsedMs,
|
|
887
|
+
paused = false
|
|
888
|
+
}) {
|
|
889
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3", "data-testid": "mic-waveform-indicator", children: [
|
|
890
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
891
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-2 w-2", children: [
|
|
892
|
+
!paused && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-red-500 opacity-75" }),
|
|
893
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
894
|
+
"span",
|
|
895
|
+
{
|
|
896
|
+
className: `relative inline-flex h-2 w-2 rounded-full ${paused ? "bg-muted-foreground" : "bg-red-500"}`
|
|
897
|
+
}
|
|
898
|
+
)
|
|
899
|
+
] }),
|
|
900
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
901
|
+
"span",
|
|
902
|
+
{
|
|
903
|
+
className: `text-xs font-semibold uppercase tracking-wide ${paused ? "text-muted-foreground" : "text-red-500"}`,
|
|
904
|
+
children: paused ? "Paused" : "Rec"
|
|
905
|
+
}
|
|
906
|
+
),
|
|
907
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-xs tabular-nums text-muted-foreground", children: formatElapsed(elapsedMs) })
|
|
908
|
+
] }),
|
|
909
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-10 items-end gap-[3px]", "data-testid": "mic-waveform-indicator-bars", children: BAR_WEIGHTS.map((weight, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
910
|
+
"span",
|
|
911
|
+
{
|
|
912
|
+
className: "w-1 rounded-full bg-secondary transition-[height] duration-75 ease-out",
|
|
913
|
+
style: {
|
|
914
|
+
height: Math.min(MAX_BAR_PX, Math.max(MIN_BAR_PX, level * weight * MAX_BAR_PX))
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
i
|
|
918
|
+
)) })
|
|
919
|
+
] });
|
|
920
|
+
}
|
|
921
|
+
function MicRecordingOverlay({
|
|
922
|
+
level,
|
|
923
|
+
elapsedMs,
|
|
924
|
+
paused,
|
|
925
|
+
caption = "Keep button pressed while speaking"
|
|
926
|
+
}) {
|
|
927
|
+
if (typeof document === "undefined") return null;
|
|
928
|
+
return reactDom.createPortal(
|
|
929
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
930
|
+
"div",
|
|
931
|
+
{
|
|
932
|
+
"aria-hidden": true,
|
|
933
|
+
className: "pointer-events-none fixed inset-0 z-[2147483647] flex items-center justify-center",
|
|
934
|
+
"data-testid": "mic-recording-overlay",
|
|
935
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-72 flex-col items-center gap-3 rounded-2xl bg-background/95 px-6 py-5 shadow-2xl ring-1 ring-border backdrop-blur-sm", children: [
|
|
936
|
+
/* @__PURE__ */ jsxRuntime.jsx(MicWaveformIndicator, { level, elapsedMs, paused }),
|
|
937
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-center text-sm font-medium text-muted-foreground", children: caption })
|
|
938
|
+
] })
|
|
939
|
+
}
|
|
940
|
+
),
|
|
941
|
+
document.body
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
var HOLD_THRESHOLD_MS = 350;
|
|
945
|
+
function InlineMicButton({
|
|
946
|
+
focused,
|
|
947
|
+
onTranscript,
|
|
948
|
+
onError,
|
|
949
|
+
transcribe,
|
|
950
|
+
className
|
|
951
|
+
}) {
|
|
952
|
+
const recorder = useAudioRecorder();
|
|
953
|
+
const [isTranscribing, setIsTranscribing] = React2.useState(false);
|
|
954
|
+
const [latched, setLatched] = React2.useState(false);
|
|
955
|
+
const pressedAtRef = React2.useRef(0);
|
|
956
|
+
React2.useEffect(() => {
|
|
957
|
+
if (recorder.error) onError?.(recorder.error);
|
|
958
|
+
}, [recorder.error]);
|
|
959
|
+
React2.useEffect(() => {
|
|
960
|
+
if (recorder.state !== "stopped" || !recorder.blob) return;
|
|
961
|
+
const blob = recorder.blob;
|
|
962
|
+
setIsTranscribing(true);
|
|
963
|
+
transcribe(blob).then((text) => {
|
|
964
|
+
if (text.trim()) onTranscript(text.trim());
|
|
965
|
+
}).catch((err) => {
|
|
966
|
+
onError?.(err instanceof Error ? err.message : "Could not transcribe audio.");
|
|
967
|
+
}).finally(() => {
|
|
968
|
+
setIsTranscribing(false);
|
|
969
|
+
setLatched(false);
|
|
970
|
+
recorder.reset();
|
|
971
|
+
});
|
|
972
|
+
}, [recorder.state, recorder.blob]);
|
|
973
|
+
const isRecording = recorder.state === "recording";
|
|
974
|
+
const isBusy = isTranscribing;
|
|
975
|
+
if (!focused && !isRecording && !isBusy) return null;
|
|
976
|
+
const handlePointerDown = (e) => {
|
|
977
|
+
e.preventDefault();
|
|
978
|
+
if (isRecording) return;
|
|
979
|
+
pressedAtRef.current = Date.now();
|
|
980
|
+
void recorder.start();
|
|
981
|
+
};
|
|
982
|
+
const handlePointerUp = () => {
|
|
983
|
+
if (!isRecording) return;
|
|
984
|
+
if (latched) {
|
|
985
|
+
recorder.stop();
|
|
986
|
+
setLatched(false);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const held = Date.now() - pressedAtRef.current;
|
|
990
|
+
if (held >= HOLD_THRESHOLD_MS) {
|
|
991
|
+
recorder.stop();
|
|
992
|
+
} else {
|
|
993
|
+
setLatched(true);
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
const handlePointerLeave = () => {
|
|
997
|
+
if (isRecording && !latched) recorder.stop();
|
|
998
|
+
};
|
|
999
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1000
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1001
|
+
"button",
|
|
1002
|
+
{
|
|
1003
|
+
type: "button",
|
|
1004
|
+
onPointerDown: handlePointerDown,
|
|
1005
|
+
onPointerUp: handlePointerUp,
|
|
1006
|
+
onPointerLeave: handlePointerLeave,
|
|
1007
|
+
onPointerCancel: handlePointerLeave,
|
|
1008
|
+
disabled: isBusy,
|
|
1009
|
+
title: isRecording ? "Release to stop, or tap again if hands-free" : "Hold, or tap to dictate",
|
|
1010
|
+
"data-testid": "inline-mic-button",
|
|
1011
|
+
className: `inline-flex items-center justify-center rounded-full transition-colors disabled:pointer-events-none ${isRecording ? "h-7 w-7 bg-red-500 text-white shadow-sm hover:bg-red-600" : isBusy ? "h-6 w-6 text-muted-foreground" : "h-6 w-6 text-muted-foreground/50 hover:bg-muted hover:text-muted-foreground"} ${className ?? ""}`,
|
|
1012
|
+
children: isBusy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : isRecording ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, { className: "h-3 w-3 fill-current" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "h-3.5 w-3.5" })
|
|
1013
|
+
}
|
|
1014
|
+
),
|
|
1015
|
+
isRecording && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1016
|
+
MicRecordingOverlay,
|
|
1017
|
+
{
|
|
1018
|
+
level: recorder.level,
|
|
1019
|
+
elapsedMs: recorder.elapsedMs,
|
|
1020
|
+
caption: latched ? "Tap the mic again to stop" : "Release to stop, or keep holding"
|
|
1021
|
+
}
|
|
1022
|
+
)
|
|
1023
|
+
] });
|
|
1024
|
+
}
|
|
620
1025
|
var Input = React2__namespace.forwardRef(
|
|
621
|
-
({ className, type, ...props },
|
|
622
|
-
|
|
1026
|
+
({ className, type, dictate, onFocus, onBlur, ...props }, forwardedRef) => {
|
|
1027
|
+
const dictateCtx = useDictateContext();
|
|
1028
|
+
const [focused, setFocused] = React2__namespace.useState(false);
|
|
1029
|
+
const innerRef = React2__namespace.useRef(null);
|
|
1030
|
+
React2__namespace.useImperativeHandle(forwardedRef, () => innerRef.current);
|
|
1031
|
+
const showDictate = dictate && !!dictateCtx && !props.disabled && !props.readOnly;
|
|
1032
|
+
const input = /* @__PURE__ */ jsxRuntime.jsx(
|
|
623
1033
|
"input",
|
|
624
1034
|
{
|
|
625
1035
|
type,
|
|
626
1036
|
"data-slot": "input",
|
|
627
1037
|
className: cn(
|
|
628
1038
|
"flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground/60 focus-visible:outline-none focus-visible:border-ring/50 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
1039
|
+
showDictate && "pr-9",
|
|
629
1040
|
className
|
|
630
1041
|
),
|
|
631
|
-
ref,
|
|
1042
|
+
ref: innerRef,
|
|
1043
|
+
onFocus: (e) => {
|
|
1044
|
+
setFocused(true);
|
|
1045
|
+
onFocus?.(e);
|
|
1046
|
+
},
|
|
1047
|
+
onBlur: (e) => {
|
|
1048
|
+
setFocused(false);
|
|
1049
|
+
onBlur?.(e);
|
|
1050
|
+
},
|
|
632
1051
|
...props
|
|
633
1052
|
}
|
|
634
1053
|
);
|
|
1054
|
+
if (!showDictate) return input;
|
|
1055
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full", children: [
|
|
1056
|
+
input,
|
|
1057
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1058
|
+
InlineMicButton,
|
|
1059
|
+
{
|
|
1060
|
+
focused,
|
|
1061
|
+
transcribe: dictateCtx.transcribe,
|
|
1062
|
+
onError: dictateCtx.onError,
|
|
1063
|
+
className: "absolute right-1.5 top-1/2 -translate-y-1/2",
|
|
1064
|
+
onTranscript: (text) => {
|
|
1065
|
+
const el = innerRef.current;
|
|
1066
|
+
if (!el) return;
|
|
1067
|
+
const next = el.value ? `${el.value} ${text}` : text;
|
|
1068
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
1069
|
+
window.HTMLInputElement.prototype,
|
|
1070
|
+
"value"
|
|
1071
|
+
)?.set;
|
|
1072
|
+
setter?.call(el, next);
|
|
1073
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
)
|
|
1077
|
+
] });
|
|
635
1078
|
}
|
|
636
1079
|
);
|
|
637
1080
|
Input.displayName = "Input";
|
|
@@ -660,18 +1103,56 @@ var Separator = React2__namespace.forwardRef(({ className, orientation = "horizo
|
|
|
660
1103
|
));
|
|
661
1104
|
Separator.displayName = SeparatorPrimitive__namespace.Root.displayName;
|
|
662
1105
|
var Textarea = React2__namespace.forwardRef(
|
|
663
|
-
({ className, ...props },
|
|
664
|
-
|
|
1106
|
+
({ className, dictate, onFocus, onBlur, ...props }, forwardedRef) => {
|
|
1107
|
+
const dictateCtx = useDictateContext();
|
|
1108
|
+
const [focused, setFocused] = React2__namespace.useState(false);
|
|
1109
|
+
const innerRef = React2__namespace.useRef(null);
|
|
1110
|
+
React2__namespace.useImperativeHandle(forwardedRef, () => innerRef.current);
|
|
1111
|
+
const showDictate = dictate && !!dictateCtx && !props.disabled && !props.readOnly;
|
|
1112
|
+
const textarea = /* @__PURE__ */ jsxRuntime.jsx(
|
|
665
1113
|
"textarea",
|
|
666
1114
|
{
|
|
667
1115
|
className: cn(
|
|
668
1116
|
"flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground/60 focus-visible:outline-none focus-visible:border-ring/50 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
1117
|
+
showDictate && "pr-9",
|
|
669
1118
|
className
|
|
670
1119
|
),
|
|
671
|
-
ref,
|
|
1120
|
+
ref: innerRef,
|
|
1121
|
+
onFocus: (e) => {
|
|
1122
|
+
setFocused(true);
|
|
1123
|
+
onFocus?.(e);
|
|
1124
|
+
},
|
|
1125
|
+
onBlur: (e) => {
|
|
1126
|
+
setFocused(false);
|
|
1127
|
+
onBlur?.(e);
|
|
1128
|
+
},
|
|
672
1129
|
...props
|
|
673
1130
|
}
|
|
674
1131
|
);
|
|
1132
|
+
if (!showDictate) return textarea;
|
|
1133
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full", children: [
|
|
1134
|
+
textarea,
|
|
1135
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1136
|
+
InlineMicButton,
|
|
1137
|
+
{
|
|
1138
|
+
focused,
|
|
1139
|
+
transcribe: dictateCtx.transcribe,
|
|
1140
|
+
onError: dictateCtx.onError,
|
|
1141
|
+
className: "absolute right-1.5 top-1.5",
|
|
1142
|
+
onTranscript: (text) => {
|
|
1143
|
+
const el = innerRef.current;
|
|
1144
|
+
if (!el) return;
|
|
1145
|
+
const next = el.value ? `${el.value} ${text}` : text;
|
|
1146
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
1147
|
+
window.HTMLTextAreaElement.prototype,
|
|
1148
|
+
"value"
|
|
1149
|
+
)?.set;
|
|
1150
|
+
setter?.call(el, next);
|
|
1151
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
)
|
|
1155
|
+
] });
|
|
675
1156
|
}
|
|
676
1157
|
);
|
|
677
1158
|
Textarea.displayName = "Textarea";
|
|
@@ -1411,6 +1892,7 @@ exports.DialogOverlay = DialogOverlay;
|
|
|
1411
1892
|
exports.DialogPortal = DialogPortal;
|
|
1412
1893
|
exports.DialogTitle = DialogTitle;
|
|
1413
1894
|
exports.DialogTrigger = DialogTrigger;
|
|
1895
|
+
exports.DictateProvider = DictateProvider;
|
|
1414
1896
|
exports.DropdownMenu = DropdownMenu;
|
|
1415
1897
|
exports.DropdownMenuCheckboxItem = DropdownMenuCheckboxItem;
|
|
1416
1898
|
exports.DropdownMenuContent = DropdownMenuContent;
|
|
@@ -1429,10 +1911,14 @@ exports.DropdownMenuTrigger = DropdownMenuTrigger;
|
|
|
1429
1911
|
exports.HoverCard = HoverCard;
|
|
1430
1912
|
exports.HoverCardContent = HoverCardContent;
|
|
1431
1913
|
exports.HoverCardTrigger = HoverCardTrigger;
|
|
1914
|
+
exports.IconToggle = IconToggle;
|
|
1915
|
+
exports.InlineMicButton = InlineMicButton;
|
|
1432
1916
|
exports.Input = Input;
|
|
1433
1917
|
exports.Label = Label;
|
|
1434
1918
|
exports.MarkdownRenderer = MarkdownRenderer;
|
|
1435
1919
|
exports.MarkdownStreamingContext = MarkdownStreamingContext;
|
|
1920
|
+
exports.MicRecordingOverlay = MicRecordingOverlay;
|
|
1921
|
+
exports.MicWaveformIndicator = MicWaveformIndicator;
|
|
1436
1922
|
exports.Popover = Popover;
|
|
1437
1923
|
exports.PopoverAnchor = PopoverAnchor;
|
|
1438
1924
|
exports.PopoverContent = PopoverContent;
|
|
@@ -1478,9 +1964,12 @@ exports.TooltipTrigger = TooltipTrigger;
|
|
|
1478
1964
|
exports.badgeVariants = badgeVariants;
|
|
1479
1965
|
exports.buttonVariants = buttonVariants;
|
|
1480
1966
|
exports.cardVariants = cardVariants;
|
|
1967
|
+
exports.classifyMediaError = classifyMediaError;
|
|
1481
1968
|
exports.cn = cn;
|
|
1969
|
+
exports.formatElapsed = formatElapsed;
|
|
1482
1970
|
exports.markdownBaseComponents = markdownBaseComponents;
|
|
1483
1971
|
exports.splitMarkdownBlocks = splitMarkdownBlocks;
|
|
1484
1972
|
exports.toggleVariants = toggleVariants;
|
|
1485
|
-
|
|
1486
|
-
//# sourceMappingURL=chunk-
|
|
1973
|
+
exports.useAudioRecorder = useAudioRecorder;
|
|
1974
|
+
//# sourceMappingURL=chunk-FIPS3VPH.cjs.map
|
|
1975
|
+
//# sourceMappingURL=chunk-FIPS3VPH.cjs.map
|