@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
|
@@ -2,11 +2,11 @@ import { clsx } from 'clsx';
|
|
|
2
2
|
import { twMerge } from 'tailwind-merge';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import * as React2 from 'react';
|
|
5
|
-
import React2__default, { createContext, useMemo, useState } from 'react';
|
|
5
|
+
import React2__default, { createContext, useMemo, useContext, useState, useRef, useEffect, useCallback } from 'react';
|
|
6
6
|
import Markdown from 'react-markdown';
|
|
7
7
|
import remarkGfm from 'remark-gfm';
|
|
8
8
|
import rehypeRaw from 'rehype-raw';
|
|
9
|
-
import { Circle, Check, X, ChevronRight, ChevronDown, ChevronUp, Search, Copy } from 'lucide-react';
|
|
9
|
+
import { Circle, Check, X, ChevronRight, ChevronDown, ChevronUp, Search, Loader2, Square, Mic, Copy, Minus, Plus } from 'lucide-react';
|
|
10
10
|
import { createPortal } from 'react-dom';
|
|
11
11
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
12
12
|
import { cva } from 'class-variance-authority';
|
|
@@ -399,6 +399,86 @@ var TooltipContent = React2.forwardRef(
|
|
|
399
399
|
}
|
|
400
400
|
);
|
|
401
401
|
TooltipContent.displayName = "TooltipContent";
|
|
402
|
+
function IconToggle({
|
|
403
|
+
icon: Icon2,
|
|
404
|
+
isActive,
|
|
405
|
+
onToggle,
|
|
406
|
+
colors,
|
|
407
|
+
fillWhenActive = false,
|
|
408
|
+
disabled = false,
|
|
409
|
+
disabledTooltip,
|
|
410
|
+
showLabel = false,
|
|
411
|
+
label,
|
|
412
|
+
tooltipActive,
|
|
413
|
+
tooltipInactive,
|
|
414
|
+
tooltipMixed,
|
|
415
|
+
isMixed = false,
|
|
416
|
+
showTooltip = true,
|
|
417
|
+
"data-testid": dataTestId
|
|
418
|
+
}) {
|
|
419
|
+
const handleClick = (e) => {
|
|
420
|
+
e.stopPropagation();
|
|
421
|
+
onToggle(!isActive);
|
|
422
|
+
};
|
|
423
|
+
const { bg: bgColorClass } = colors;
|
|
424
|
+
const tooltipText = disabled ? disabledTooltip : isMixed ? tooltipMixed : isActive ? tooltipActive : tooltipInactive;
|
|
425
|
+
const { text: textColorClass } = colors;
|
|
426
|
+
const button = /* @__PURE__ */ jsx("div", { className: "inline-flex items-center gap-2", children: /* @__PURE__ */ jsxs(
|
|
427
|
+
"button",
|
|
428
|
+
{
|
|
429
|
+
type: "button",
|
|
430
|
+
onClick: handleClick,
|
|
431
|
+
disabled,
|
|
432
|
+
"data-testid": dataTestId,
|
|
433
|
+
"aria-pressed": isMixed ? "mixed" : isActive,
|
|
434
|
+
className: `
|
|
435
|
+
group/toggle relative inline-flex items-center h-4 w-8 rounded-full
|
|
436
|
+
transition-colors cursor-pointer
|
|
437
|
+
disabled:opacity-50 disabled:cursor-not-allowed
|
|
438
|
+
${isActive ? bgColorClass : isMixed ? "bg-gray-400" : "bg-muted"}
|
|
439
|
+
`,
|
|
440
|
+
children: [
|
|
441
|
+
isActive && !disabled && /* @__PURE__ */ jsx("div", { className: "absolute left-0 w-4 h-4 flex items-center justify-center", children: /* @__PURE__ */ jsx(Minus, { className: "w-2.5 h-2.5 text-white/70 hidden group-hover/toggle:block" }) }),
|
|
442
|
+
!isActive && !disabled && /* @__PURE__ */ jsx("div", { className: "absolute right-0 w-4 h-4 flex items-center justify-center", children: /* @__PURE__ */ jsx(Plus, { className: "w-2.5 h-2.5 text-muted-foreground hidden group-hover/toggle:block" }) }),
|
|
443
|
+
/* @__PURE__ */ jsx(
|
|
444
|
+
"div",
|
|
445
|
+
{
|
|
446
|
+
className: `
|
|
447
|
+
absolute h-4 w-4 rounded-full bg-white shadow-sm
|
|
448
|
+
flex items-center justify-center
|
|
449
|
+
transition-transform duration-200
|
|
450
|
+
${isActive ? "translate-x-4" : "translate-x-0"}
|
|
451
|
+
`,
|
|
452
|
+
children: /* @__PURE__ */ jsx(
|
|
453
|
+
Icon2,
|
|
454
|
+
{
|
|
455
|
+
className: `w-2.5 h-2.5 ${isActive ? textColorClass : "text-muted-foreground"} ${isActive && fillWhenActive ? "fill-current" : ""}`
|
|
456
|
+
}
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
)
|
|
460
|
+
]
|
|
461
|
+
}
|
|
462
|
+
) });
|
|
463
|
+
return /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2", children: [
|
|
464
|
+
showLabel && label && /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm text-primary", children: label }),
|
|
465
|
+
tooltipText && showTooltip ? /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
466
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, onFocusCapture: (e) => e.stopPropagation(), children: button }),
|
|
467
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: tooltipText }) })
|
|
468
|
+
] }) : button
|
|
469
|
+
] });
|
|
470
|
+
}
|
|
471
|
+
var DictateContext = createContext(null);
|
|
472
|
+
function DictateProvider({
|
|
473
|
+
transcribe,
|
|
474
|
+
onError,
|
|
475
|
+
children
|
|
476
|
+
}) {
|
|
477
|
+
return /* @__PURE__ */ jsx(DictateContext.Provider, { value: { transcribe, onError }, children });
|
|
478
|
+
}
|
|
479
|
+
function useDictateContext() {
|
|
480
|
+
return useContext(DictateContext);
|
|
481
|
+
}
|
|
402
482
|
var Avatar = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
403
483
|
AvatarPrimitive.Root,
|
|
404
484
|
{
|
|
@@ -574,21 +654,384 @@ var CardFooter = React2.forwardRef(
|
|
|
574
654
|
)
|
|
575
655
|
);
|
|
576
656
|
CardFooter.displayName = "CardFooter";
|
|
657
|
+
|
|
658
|
+
// src/documents/media-error.ts
|
|
659
|
+
function classifyMediaError(err, kind) {
|
|
660
|
+
const label = kind === "camera" ? "Camera" : "Microphone";
|
|
661
|
+
const name = err instanceof DOMException ? err.name : void 0;
|
|
662
|
+
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
663
|
+
return `${label} access is blocked. Allow ${kind} permission in your browser settings to use this feature.`;
|
|
664
|
+
}
|
|
665
|
+
if (name === "NotFoundError" || name === "DevicesNotFoundError") {
|
|
666
|
+
return `No ${kind} found on this device.`;
|
|
667
|
+
}
|
|
668
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
669
|
+
return `${kind === "camera" ? "Camera" : "Voice"} input is not supported in this browser.`;
|
|
670
|
+
}
|
|
671
|
+
return `Could not access the ${kind}. Please try again.`;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// src/documents/useAudioRecorder.ts
|
|
675
|
+
var LEVEL_UPDATE_INTERVAL_MS = 50;
|
|
676
|
+
function useAudioRecorder() {
|
|
677
|
+
const [state, setState] = useState("idle");
|
|
678
|
+
const [level, setLevel] = useState(0);
|
|
679
|
+
const [elapsedMs, setElapsedMs] = useState(0);
|
|
680
|
+
const [blob, setBlob] = useState(null);
|
|
681
|
+
const [mimeType, setMimeType] = useState(null);
|
|
682
|
+
const [error, setError] = useState(null);
|
|
683
|
+
const mediaRecorderRef = useRef(null);
|
|
684
|
+
const chunksRef = useRef([]);
|
|
685
|
+
const streamRef = useRef(null);
|
|
686
|
+
const stateRef = useRef("idle");
|
|
687
|
+
stateRef.current = state;
|
|
688
|
+
const segmentStartRef = useRef(0);
|
|
689
|
+
const accumulatedMsRef = useRef(0);
|
|
690
|
+
const audioContextRef = useRef(null);
|
|
691
|
+
const analyserRef = useRef(null);
|
|
692
|
+
const levelRafRef = useRef(null);
|
|
693
|
+
const stopMeter = useCallback(() => {
|
|
694
|
+
if (levelRafRef.current !== null) cancelAnimationFrame(levelRafRef.current);
|
|
695
|
+
levelRafRef.current = null;
|
|
696
|
+
analyserRef.current = null;
|
|
697
|
+
void audioContextRef.current?.close().catch(() => {
|
|
698
|
+
});
|
|
699
|
+
audioContextRef.current = null;
|
|
700
|
+
}, []);
|
|
701
|
+
const startMeter = useCallback((stream) => {
|
|
702
|
+
const audioContext = new AudioContext();
|
|
703
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
704
|
+
const analyser = audioContext.createAnalyser();
|
|
705
|
+
analyser.fftSize = 256;
|
|
706
|
+
source.connect(analyser);
|
|
707
|
+
audioContextRef.current = audioContext;
|
|
708
|
+
analyserRef.current = analyser;
|
|
709
|
+
const data = new Uint8Array(analyser.frequencyBinCount);
|
|
710
|
+
let lastUpdate = 0;
|
|
711
|
+
const tick = (t) => {
|
|
712
|
+
const active = analyserRef.current;
|
|
713
|
+
if (!active) return;
|
|
714
|
+
if (t - lastUpdate >= LEVEL_UPDATE_INTERVAL_MS) {
|
|
715
|
+
lastUpdate = t;
|
|
716
|
+
active.getByteTimeDomainData(data);
|
|
717
|
+
let sumSquares = 0;
|
|
718
|
+
for (let i = 0; i < data.length; i++) {
|
|
719
|
+
const centered = (data[i] - 128) / 128;
|
|
720
|
+
sumSquares += centered * centered;
|
|
721
|
+
}
|
|
722
|
+
const rms = Math.sqrt(sumSquares / data.length);
|
|
723
|
+
setLevel(Math.sqrt(Math.min(1, rms * 8)));
|
|
724
|
+
setElapsedMs(accumulatedMsRef.current + (t - segmentStartRef.current));
|
|
725
|
+
}
|
|
726
|
+
levelRafRef.current = requestAnimationFrame(tick);
|
|
727
|
+
};
|
|
728
|
+
levelRafRef.current = requestAnimationFrame(tick);
|
|
729
|
+
}, []);
|
|
730
|
+
const stopTracks = useCallback(() => {
|
|
731
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
732
|
+
streamRef.current = null;
|
|
733
|
+
}, []);
|
|
734
|
+
useEffect(
|
|
735
|
+
() => () => {
|
|
736
|
+
stopTracks();
|
|
737
|
+
stopMeter();
|
|
738
|
+
},
|
|
739
|
+
[stopTracks, stopMeter]
|
|
740
|
+
);
|
|
741
|
+
const start = useCallback(async () => {
|
|
742
|
+
if (stateRef.current === "recording" || stateRef.current === "paused") return;
|
|
743
|
+
setError(null);
|
|
744
|
+
try {
|
|
745
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
746
|
+
streamRef.current = stream;
|
|
747
|
+
const mt = MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
|
|
748
|
+
const recorder = new MediaRecorder(stream, { mimeType: mt });
|
|
749
|
+
chunksRef.current = [];
|
|
750
|
+
recorder.ondataavailable = (e) => {
|
|
751
|
+
if (e.data.size > 0) chunksRef.current.push(e.data);
|
|
752
|
+
};
|
|
753
|
+
mediaRecorderRef.current = recorder;
|
|
754
|
+
setMimeType(mt);
|
|
755
|
+
setBlob(null);
|
|
756
|
+
accumulatedMsRef.current = 0;
|
|
757
|
+
segmentStartRef.current = performance.now();
|
|
758
|
+
recorder.start();
|
|
759
|
+
setState("recording");
|
|
760
|
+
startMeter(stream);
|
|
761
|
+
} catch (err) {
|
|
762
|
+
stopTracks();
|
|
763
|
+
setError(classifyMediaError(err, "microphone"));
|
|
764
|
+
}
|
|
765
|
+
}, [startMeter, stopTracks]);
|
|
766
|
+
const pause = useCallback(() => {
|
|
767
|
+
const recorder = mediaRecorderRef.current;
|
|
768
|
+
if (!recorder || recorder.state !== "recording") return;
|
|
769
|
+
recorder.pause();
|
|
770
|
+
accumulatedMsRef.current += performance.now() - segmentStartRef.current;
|
|
771
|
+
stopMeter();
|
|
772
|
+
setLevel(0);
|
|
773
|
+
setState("paused");
|
|
774
|
+
}, [stopMeter]);
|
|
775
|
+
const resume = useCallback(() => {
|
|
776
|
+
const recorder = mediaRecorderRef.current;
|
|
777
|
+
if (!recorder || recorder.state !== "paused") return;
|
|
778
|
+
recorder.resume();
|
|
779
|
+
segmentStartRef.current = performance.now();
|
|
780
|
+
setState("recording");
|
|
781
|
+
if (streamRef.current) startMeter(streamRef.current);
|
|
782
|
+
}, [startMeter]);
|
|
783
|
+
const stop = useCallback(() => {
|
|
784
|
+
const recorder = mediaRecorderRef.current;
|
|
785
|
+
if (!recorder || recorder.state === "inactive") return;
|
|
786
|
+
const finalElapsed = recorder.state === "paused" ? accumulatedMsRef.current : accumulatedMsRef.current + (performance.now() - segmentStartRef.current);
|
|
787
|
+
stopMeter();
|
|
788
|
+
recorder.onstop = () => {
|
|
789
|
+
stopTracks();
|
|
790
|
+
const finalBlob = chunksRef.current.length ? new Blob(chunksRef.current, { type: recorder.mimeType }) : null;
|
|
791
|
+
setBlob(finalBlob);
|
|
792
|
+
setElapsedMs(finalElapsed);
|
|
793
|
+
setLevel(0);
|
|
794
|
+
setState("stopped");
|
|
795
|
+
};
|
|
796
|
+
recorder.stop();
|
|
797
|
+
}, [stopMeter, stopTracks]);
|
|
798
|
+
const reset = useCallback(() => {
|
|
799
|
+
setState("idle");
|
|
800
|
+
setBlob(null);
|
|
801
|
+
setElapsedMs(0);
|
|
802
|
+
setLevel(0);
|
|
803
|
+
setMimeType(null);
|
|
804
|
+
setError(null);
|
|
805
|
+
}, []);
|
|
806
|
+
return { state, level, elapsedMs, blob, mimeType, error, start, pause, resume, stop, reset };
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/documents/format-elapsed.ts
|
|
810
|
+
function formatElapsed(ms) {
|
|
811
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
812
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
813
|
+
const seconds = totalSeconds % 60;
|
|
814
|
+
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
815
|
+
}
|
|
816
|
+
var BAR_WEIGHTS = [
|
|
817
|
+
0.25,
|
|
818
|
+
0.32,
|
|
819
|
+
0.48,
|
|
820
|
+
0.62,
|
|
821
|
+
0.82,
|
|
822
|
+
0.56,
|
|
823
|
+
0.92,
|
|
824
|
+
0.72,
|
|
825
|
+
1,
|
|
826
|
+
0.86,
|
|
827
|
+
0.62,
|
|
828
|
+
0.96,
|
|
829
|
+
0.76,
|
|
830
|
+
0.5,
|
|
831
|
+
0.84,
|
|
832
|
+
0.66,
|
|
833
|
+
0.42,
|
|
834
|
+
0.56,
|
|
835
|
+
0.36,
|
|
836
|
+
0.28,
|
|
837
|
+
0.22
|
|
838
|
+
];
|
|
839
|
+
var MIN_BAR_PX = 3;
|
|
840
|
+
var MAX_BAR_PX = 40;
|
|
841
|
+
function MicWaveformIndicator({
|
|
842
|
+
level,
|
|
843
|
+
elapsedMs,
|
|
844
|
+
paused = false
|
|
845
|
+
}) {
|
|
846
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", "data-testid": "mic-waveform-indicator", children: [
|
|
847
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
848
|
+
/* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2", children: [
|
|
849
|
+
!paused && /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-red-500 opacity-75" }),
|
|
850
|
+
/* @__PURE__ */ jsx(
|
|
851
|
+
"span",
|
|
852
|
+
{
|
|
853
|
+
className: `relative inline-flex h-2 w-2 rounded-full ${paused ? "bg-muted-foreground" : "bg-red-500"}`
|
|
854
|
+
}
|
|
855
|
+
)
|
|
856
|
+
] }),
|
|
857
|
+
/* @__PURE__ */ jsx(
|
|
858
|
+
"span",
|
|
859
|
+
{
|
|
860
|
+
className: `text-xs font-semibold uppercase tracking-wide ${paused ? "text-muted-foreground" : "text-red-500"}`,
|
|
861
|
+
children: paused ? "Paused" : "Rec"
|
|
862
|
+
}
|
|
863
|
+
),
|
|
864
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs tabular-nums text-muted-foreground", children: formatElapsed(elapsedMs) })
|
|
865
|
+
] }),
|
|
866
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-10 items-end gap-[3px]", "data-testid": "mic-waveform-indicator-bars", children: BAR_WEIGHTS.map((weight, i) => /* @__PURE__ */ jsx(
|
|
867
|
+
"span",
|
|
868
|
+
{
|
|
869
|
+
className: "w-1 rounded-full bg-secondary transition-[height] duration-75 ease-out",
|
|
870
|
+
style: {
|
|
871
|
+
height: Math.min(MAX_BAR_PX, Math.max(MIN_BAR_PX, level * weight * MAX_BAR_PX))
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
i
|
|
875
|
+
)) })
|
|
876
|
+
] });
|
|
877
|
+
}
|
|
878
|
+
function MicRecordingOverlay({
|
|
879
|
+
level,
|
|
880
|
+
elapsedMs,
|
|
881
|
+
paused,
|
|
882
|
+
caption = "Keep button pressed while speaking"
|
|
883
|
+
}) {
|
|
884
|
+
if (typeof document === "undefined") return null;
|
|
885
|
+
return createPortal(
|
|
886
|
+
/* @__PURE__ */ jsx(
|
|
887
|
+
"div",
|
|
888
|
+
{
|
|
889
|
+
"aria-hidden": true,
|
|
890
|
+
className: "pointer-events-none fixed inset-0 z-[2147483647] flex items-center justify-center",
|
|
891
|
+
"data-testid": "mic-recording-overlay",
|
|
892
|
+
children: /* @__PURE__ */ 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: [
|
|
893
|
+
/* @__PURE__ */ jsx(MicWaveformIndicator, { level, elapsedMs, paused }),
|
|
894
|
+
/* @__PURE__ */ jsx("p", { className: "text-center text-sm font-medium text-muted-foreground", children: caption })
|
|
895
|
+
] })
|
|
896
|
+
}
|
|
897
|
+
),
|
|
898
|
+
document.body
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
var HOLD_THRESHOLD_MS = 350;
|
|
902
|
+
function InlineMicButton({
|
|
903
|
+
focused,
|
|
904
|
+
onTranscript,
|
|
905
|
+
onError,
|
|
906
|
+
transcribe,
|
|
907
|
+
className
|
|
908
|
+
}) {
|
|
909
|
+
const recorder = useAudioRecorder();
|
|
910
|
+
const [isTranscribing, setIsTranscribing] = useState(false);
|
|
911
|
+
const [latched, setLatched] = useState(false);
|
|
912
|
+
const pressedAtRef = useRef(0);
|
|
913
|
+
useEffect(() => {
|
|
914
|
+
if (recorder.error) onError?.(recorder.error);
|
|
915
|
+
}, [recorder.error]);
|
|
916
|
+
useEffect(() => {
|
|
917
|
+
if (recorder.state !== "stopped" || !recorder.blob) return;
|
|
918
|
+
const blob = recorder.blob;
|
|
919
|
+
setIsTranscribing(true);
|
|
920
|
+
transcribe(blob).then((text) => {
|
|
921
|
+
if (text.trim()) onTranscript(text.trim());
|
|
922
|
+
}).catch((err) => {
|
|
923
|
+
onError?.(err instanceof Error ? err.message : "Could not transcribe audio.");
|
|
924
|
+
}).finally(() => {
|
|
925
|
+
setIsTranscribing(false);
|
|
926
|
+
setLatched(false);
|
|
927
|
+
recorder.reset();
|
|
928
|
+
});
|
|
929
|
+
}, [recorder.state, recorder.blob]);
|
|
930
|
+
const isRecording = recorder.state === "recording";
|
|
931
|
+
const isBusy = isTranscribing;
|
|
932
|
+
if (!focused && !isRecording && !isBusy) return null;
|
|
933
|
+
const handlePointerDown = (e) => {
|
|
934
|
+
e.preventDefault();
|
|
935
|
+
if (isRecording) return;
|
|
936
|
+
pressedAtRef.current = Date.now();
|
|
937
|
+
void recorder.start();
|
|
938
|
+
};
|
|
939
|
+
const handlePointerUp = () => {
|
|
940
|
+
if (!isRecording) return;
|
|
941
|
+
if (latched) {
|
|
942
|
+
recorder.stop();
|
|
943
|
+
setLatched(false);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const held = Date.now() - pressedAtRef.current;
|
|
947
|
+
if (held >= HOLD_THRESHOLD_MS) {
|
|
948
|
+
recorder.stop();
|
|
949
|
+
} else {
|
|
950
|
+
setLatched(true);
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
const handlePointerLeave = () => {
|
|
954
|
+
if (isRecording && !latched) recorder.stop();
|
|
955
|
+
};
|
|
956
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
957
|
+
/* @__PURE__ */ jsx(
|
|
958
|
+
"button",
|
|
959
|
+
{
|
|
960
|
+
type: "button",
|
|
961
|
+
onPointerDown: handlePointerDown,
|
|
962
|
+
onPointerUp: handlePointerUp,
|
|
963
|
+
onPointerLeave: handlePointerLeave,
|
|
964
|
+
onPointerCancel: handlePointerLeave,
|
|
965
|
+
disabled: isBusy,
|
|
966
|
+
title: isRecording ? "Release to stop, or tap again if hands-free" : "Hold, or tap to dictate",
|
|
967
|
+
"data-testid": "inline-mic-button",
|
|
968
|
+
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 ?? ""}`,
|
|
969
|
+
children: isBusy ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : isRecording ? /* @__PURE__ */ jsx(Square, { className: "h-3 w-3 fill-current" }) : /* @__PURE__ */ jsx(Mic, { className: "h-3.5 w-3.5" })
|
|
970
|
+
}
|
|
971
|
+
),
|
|
972
|
+
isRecording && /* @__PURE__ */ jsx(
|
|
973
|
+
MicRecordingOverlay,
|
|
974
|
+
{
|
|
975
|
+
level: recorder.level,
|
|
976
|
+
elapsedMs: recorder.elapsedMs,
|
|
977
|
+
caption: latched ? "Tap the mic again to stop" : "Release to stop, or keep holding"
|
|
978
|
+
}
|
|
979
|
+
)
|
|
980
|
+
] });
|
|
981
|
+
}
|
|
577
982
|
var Input = React2.forwardRef(
|
|
578
|
-
({ className, type, ...props },
|
|
579
|
-
|
|
983
|
+
({ className, type, dictate, onFocus, onBlur, ...props }, forwardedRef) => {
|
|
984
|
+
const dictateCtx = useDictateContext();
|
|
985
|
+
const [focused, setFocused] = React2.useState(false);
|
|
986
|
+
const innerRef = React2.useRef(null);
|
|
987
|
+
React2.useImperativeHandle(forwardedRef, () => innerRef.current);
|
|
988
|
+
const showDictate = dictate && !!dictateCtx && !props.disabled && !props.readOnly;
|
|
989
|
+
const input = /* @__PURE__ */ jsx(
|
|
580
990
|
"input",
|
|
581
991
|
{
|
|
582
992
|
type,
|
|
583
993
|
"data-slot": "input",
|
|
584
994
|
className: cn(
|
|
585
995
|
"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",
|
|
996
|
+
showDictate && "pr-9",
|
|
586
997
|
className
|
|
587
998
|
),
|
|
588
|
-
ref,
|
|
999
|
+
ref: innerRef,
|
|
1000
|
+
onFocus: (e) => {
|
|
1001
|
+
setFocused(true);
|
|
1002
|
+
onFocus?.(e);
|
|
1003
|
+
},
|
|
1004
|
+
onBlur: (e) => {
|
|
1005
|
+
setFocused(false);
|
|
1006
|
+
onBlur?.(e);
|
|
1007
|
+
},
|
|
589
1008
|
...props
|
|
590
1009
|
}
|
|
591
1010
|
);
|
|
1011
|
+
if (!showDictate) return input;
|
|
1012
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
|
|
1013
|
+
input,
|
|
1014
|
+
/* @__PURE__ */ jsx(
|
|
1015
|
+
InlineMicButton,
|
|
1016
|
+
{
|
|
1017
|
+
focused,
|
|
1018
|
+
transcribe: dictateCtx.transcribe,
|
|
1019
|
+
onError: dictateCtx.onError,
|
|
1020
|
+
className: "absolute right-1.5 top-1/2 -translate-y-1/2",
|
|
1021
|
+
onTranscript: (text) => {
|
|
1022
|
+
const el = innerRef.current;
|
|
1023
|
+
if (!el) return;
|
|
1024
|
+
const next = el.value ? `${el.value} ${text}` : text;
|
|
1025
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
1026
|
+
window.HTMLInputElement.prototype,
|
|
1027
|
+
"value"
|
|
1028
|
+
)?.set;
|
|
1029
|
+
setter?.call(el, next);
|
|
1030
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
)
|
|
1034
|
+
] });
|
|
592
1035
|
}
|
|
593
1036
|
);
|
|
594
1037
|
Input.displayName = "Input";
|
|
@@ -617,18 +1060,56 @@ var Separator = React2.forwardRef(({ className, orientation = "horizontal", deco
|
|
|
617
1060
|
));
|
|
618
1061
|
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
619
1062
|
var Textarea = React2.forwardRef(
|
|
620
|
-
({ className, ...props },
|
|
621
|
-
|
|
1063
|
+
({ className, dictate, onFocus, onBlur, ...props }, forwardedRef) => {
|
|
1064
|
+
const dictateCtx = useDictateContext();
|
|
1065
|
+
const [focused, setFocused] = React2.useState(false);
|
|
1066
|
+
const innerRef = React2.useRef(null);
|
|
1067
|
+
React2.useImperativeHandle(forwardedRef, () => innerRef.current);
|
|
1068
|
+
const showDictate = dictate && !!dictateCtx && !props.disabled && !props.readOnly;
|
|
1069
|
+
const textarea = /* @__PURE__ */ jsx(
|
|
622
1070
|
"textarea",
|
|
623
1071
|
{
|
|
624
1072
|
className: cn(
|
|
625
1073
|
"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",
|
|
1074
|
+
showDictate && "pr-9",
|
|
626
1075
|
className
|
|
627
1076
|
),
|
|
628
|
-
ref,
|
|
1077
|
+
ref: innerRef,
|
|
1078
|
+
onFocus: (e) => {
|
|
1079
|
+
setFocused(true);
|
|
1080
|
+
onFocus?.(e);
|
|
1081
|
+
},
|
|
1082
|
+
onBlur: (e) => {
|
|
1083
|
+
setFocused(false);
|
|
1084
|
+
onBlur?.(e);
|
|
1085
|
+
},
|
|
629
1086
|
...props
|
|
630
1087
|
}
|
|
631
1088
|
);
|
|
1089
|
+
if (!showDictate) return textarea;
|
|
1090
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
|
|
1091
|
+
textarea,
|
|
1092
|
+
/* @__PURE__ */ jsx(
|
|
1093
|
+
InlineMicButton,
|
|
1094
|
+
{
|
|
1095
|
+
focused,
|
|
1096
|
+
transcribe: dictateCtx.transcribe,
|
|
1097
|
+
onError: dictateCtx.onError,
|
|
1098
|
+
className: "absolute right-1.5 top-1.5",
|
|
1099
|
+
onTranscript: (text) => {
|
|
1100
|
+
const el = innerRef.current;
|
|
1101
|
+
if (!el) return;
|
|
1102
|
+
const next = el.value ? `${el.value} ${text}` : text;
|
|
1103
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
1104
|
+
window.HTMLTextAreaElement.prototype,
|
|
1105
|
+
"value"
|
|
1106
|
+
)?.set;
|
|
1107
|
+
setter?.call(el, next);
|
|
1108
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
)
|
|
1112
|
+
] });
|
|
632
1113
|
}
|
|
633
1114
|
);
|
|
634
1115
|
Textarea.displayName = "Textarea";
|
|
@@ -1333,6 +1814,6 @@ function AiMarkdown({
|
|
|
1333
1814
|
);
|
|
1334
1815
|
}
|
|
1335
1816
|
|
|
1336
|
-
export { AiMarkdown, Avatar, AvatarFallback, AvatarImage, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Collapsible, CollapsibleContent2 as CollapsibleContent, CollapsibleTrigger2 as CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, HoverCard, HoverCardContent, HoverCardTrigger, Input, Label, MarkdownRenderer, MarkdownStreamingContext, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Slider, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, cardVariants, cn, markdownBaseComponents, splitMarkdownBlocks, toggleVariants };
|
|
1337
|
-
//# sourceMappingURL=chunk-
|
|
1338
|
-
//# sourceMappingURL=chunk-
|
|
1817
|
+
export { AiMarkdown, Avatar, AvatarFallback, AvatarImage, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Collapsible, CollapsibleContent2 as CollapsibleContent, CollapsibleTrigger2 as CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DictateProvider, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, HoverCard, HoverCardContent, HoverCardTrigger, IconToggle, InlineMicButton, Input, Label, MarkdownRenderer, MarkdownStreamingContext, MicRecordingOverlay, MicWaveformIndicator, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Slider, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, cardVariants, classifyMediaError, cn, formatElapsed, markdownBaseComponents, splitMarkdownBlocks, toggleVariants, useAudioRecorder };
|
|
1818
|
+
//# sourceMappingURL=chunk-I256BUCX.js.map
|
|
1819
|
+
//# sourceMappingURL=chunk-I256BUCX.js.map
|