@ai-matrx/media 0.13.0 → 0.15.0
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/CHANGELOG.md +37 -0
- package/dist/react.cjs +201 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +59 -2
- package/dist/react.d.ts +59 -2
- package/dist/react.js +210 -1
- package/dist/react.js.map +1 -1
- package/dist/speech.cjs +1689 -0
- package/dist/speech.cjs.map +1 -0
- package/dist/speech.d.cts +565 -0
- package/dist/speech.d.ts +565 -0
- package/dist/speech.js +1683 -0
- package/dist/speech.js.map +1 -0
- package/dist/tts-config-JbF86KIt.d.cts +149 -0
- package/dist/tts-config-JbF86KIt.d.ts +149 -0
- package/dist/voices.cjs +481 -0
- package/dist/voices.cjs.map +1 -1
- package/dist/voices.d.cts +16 -47
- package/dist/voices.d.ts +16 -47
- package/dist/voices.js +489 -0
- package/dist/voices.js.map +1 -1
- package/package.json +17 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @ai-matrx/media
|
|
2
2
|
|
|
3
|
+
## 0.15.0 — THE one speech engine (`/speech`) + ReadAloudButton compact size
|
|
4
|
+
|
|
5
|
+
- New `@ai-matrx/media/speech`: `speak()`, the app-wide playback queue (`enqueuePlayback`,
|
|
6
|
+
`pausePlayback`, `resumePlayback`, `skipPlayback`, `playPlaybackItem`, `removePlaybackItem`,
|
|
7
|
+
`clearPlayback`, `subscribePlayback`, …), the playback lock (`claimPlayback` / `releasePlayback`),
|
|
8
|
+
the Cartesia streaming lane (`connectCartesiaTts`, `cartesiaAdapter`, `ProviderSessionError`),
|
|
9
|
+
`SinkAwarePlayer` + output-device routing, and the iOS/WebKit unlock (`primeAudioOutput`,
|
|
10
|
+
`installAudioUnlockListeners`) — moved from matrx-frontend's audio system, which is the reference.
|
|
11
|
+
A host wires it once: `configureSpeech({ getCartesiaToken, resolveVoiceSettings,
|
|
12
|
+
reportProviderFailure?, notifyError?, beforeStart?, describeStartRefusal?, onEngage?, adapters?,
|
|
13
|
+
defaultEngine?, completeRequest?, resolvePronunciations?, invalidateCartesiaToken? })`.
|
|
14
|
+
- `@ai-matrx/media/react`: `useSpeech`, `useReadAloud(text)` (the read-aloud button's whole wiring:
|
|
15
|
+
`status`, `error`, `onPress`), `usePlaybackSnapshot`, `readAloudStatusOf` — over the same queue
|
|
16
|
+
instance (`/react` imports `/speech`, never a bundled copy).
|
|
17
|
+
- New dependency `@cartesia/cartesia-js` ^4.2.0.
|
|
18
|
+
- Consumer action: delete the app's own queue / lock / Cartesia connection / player / unlock and
|
|
19
|
+
import them from `/speech`; call `configureSpeech` once at start-up.
|
|
20
|
+
|
|
21
|
+
- `ReadAloudButton` `size?: "default" | "compact"` (`ReadAloudButtonSize` exported): "compact" is the
|
|
22
|
+
message-footer cell (32px box, 28px pill — the tap system's group geometry) instead of the 38px
|
|
23
|
+
default box. Geometry only; it wins over `variant`.
|
|
24
|
+
- Consumer action: a message footer that drew the old 24px speaker passes `size="compact"`.
|
|
25
|
+
|
|
26
|
+
## 0.14.0 — Read-aloud lives here (speech text, languages, button, language picker)
|
|
27
|
+
|
|
28
|
+
- `@ai-matrx/media/voices`: `parseMarkdownToText` (+ `normalizeSpeechAbbreviations`,
|
|
29
|
+
`normalizeSpeechBlanks`, `COMMON_ABBREVIATION_EXPANSIONS`, `SpeechPronunciation`) — THE one
|
|
30
|
+
markdown → spoken-text conversion (tables read as a sentence, initialisms letter by letter,
|
|
31
|
+
pronunciations), moved from matrx-frontend; `READ_ALOUD_LANGUAGES` / `readAloudLanguage` — the
|
|
32
|
+
website's 15 voice languages plus the extension's Persian, each with its own name.
|
|
33
|
+
- `@ai-matrx/media/react`: `ReadAloudButton` (one look for idle / connecting / playing / paused /
|
|
34
|
+
failed; the host's engine reports `status` and handles `onPress`) and
|
|
35
|
+
`ReadAloudLanguagePicker` (controlled quick picker).
|
|
36
|
+
- New sibling deps: `@ai-matrx/content-ir` (table + code-span rules), `@ai-matrx/tap-target`.
|
|
37
|
+
- Consumer action: replace an app copy of the speech parser / speaker button / language picker
|
|
38
|
+
with these; render `ReadAloudButton` inside a `TooltipProvider` (every app shell has one).
|
|
39
|
+
|
|
3
40
|
|
|
4
41
|
|
|
5
42
|
## 0.13.0 — DB types come from @ai-matrx/data/types
|
package/dist/react.cjs
CHANGED
|
@@ -56,6 +56,8 @@ __export(react_exports, {
|
|
|
56
56
|
PenToolIcon: () => PenToolIcon,
|
|
57
57
|
PlayIcon: () => PlayIcon,
|
|
58
58
|
PlusIcon: () => PlusIcon,
|
|
59
|
+
ReadAloudButton: () => ReadAloudButton,
|
|
60
|
+
ReadAloudLanguagePicker: () => ReadAloudLanguagePicker,
|
|
59
61
|
SearchIcon: () => SearchIcon,
|
|
60
62
|
Settings2Icon: () => Settings2Icon,
|
|
61
63
|
Share2Icon: () => Share2Icon,
|
|
@@ -68,7 +70,11 @@ __export(react_exports, {
|
|
|
68
70
|
VolumeXIcon: () => VolumeXIcon,
|
|
69
71
|
XIcon: () => XIcon,
|
|
70
72
|
cn: () => cn,
|
|
71
|
-
|
|
73
|
+
readAloudStatusOf: () => readAloudStatusOf,
|
|
74
|
+
useMediaDropzone: () => useMediaDropzone,
|
|
75
|
+
usePlaybackSnapshot: () => usePlaybackSnapshot,
|
|
76
|
+
useReadAloud: () => useReadAloud,
|
|
77
|
+
useSpeech: () => useSpeech
|
|
72
78
|
});
|
|
73
79
|
module.exports = __toCommonJS(react_exports);
|
|
74
80
|
|
|
@@ -2531,4 +2537,198 @@ function MediaLightbox({
|
|
|
2531
2537
|
}
|
|
2532
2538
|
);
|
|
2533
2539
|
}
|
|
2540
|
+
|
|
2541
|
+
// src/react/read-aloud-button.tsx
|
|
2542
|
+
var import_buttons = require("@ai-matrx/tap-target/buttons");
|
|
2543
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
2544
|
+
function ReadAloudButton({
|
|
2545
|
+
status,
|
|
2546
|
+
onPress,
|
|
2547
|
+
error,
|
|
2548
|
+
disabled = false,
|
|
2549
|
+
variant: requestedVariant,
|
|
2550
|
+
size = "default",
|
|
2551
|
+
className,
|
|
2552
|
+
label = "Read aloud"
|
|
2553
|
+
}) {
|
|
2554
|
+
const variant = size === "compact" ? "group" : requestedVariant;
|
|
2555
|
+
if (status === "playing") {
|
|
2556
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_buttons.PauseTapButton, { variant, onClick: onPress, disabled, ariaLabel: "Pause", className });
|
|
2557
|
+
}
|
|
2558
|
+
if (status === "error") {
|
|
2559
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2560
|
+
import_buttons.TriangleAlertTapButton,
|
|
2561
|
+
{
|
|
2562
|
+
variant,
|
|
2563
|
+
onClick: onPress,
|
|
2564
|
+
disabled,
|
|
2565
|
+
ariaLabel: error || "Read aloud failed. Press to try again.",
|
|
2566
|
+
className
|
|
2567
|
+
}
|
|
2568
|
+
);
|
|
2569
|
+
}
|
|
2570
|
+
const loading = status === "loading";
|
|
2571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2572
|
+
import_buttons.Volume2TapButton,
|
|
2573
|
+
{
|
|
2574
|
+
variant,
|
|
2575
|
+
onClick: onPress,
|
|
2576
|
+
disabled: disabled || loading,
|
|
2577
|
+
ariaLabel: loading ? "Connecting\u2026" : status === "paused" ? "Resume" : label,
|
|
2578
|
+
className
|
|
2579
|
+
}
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
// src/react/use-speech.ts
|
|
2584
|
+
var import_react17 = require("react");
|
|
2585
|
+
var import_speech = require("@ai-matrx/media/speech");
|
|
2586
|
+
var cached = (0, import_speech.getPlaybackSnapshot)();
|
|
2587
|
+
function subscribe(onChange) {
|
|
2588
|
+
return (0, import_speech.subscribePlayback)((snapshot) => {
|
|
2589
|
+
cached = snapshot;
|
|
2590
|
+
onChange();
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
var read = () => cached;
|
|
2594
|
+
function usePlaybackSnapshot() {
|
|
2595
|
+
return (0, import_react17.useSyncExternalStore)(subscribe, read, read);
|
|
2596
|
+
}
|
|
2597
|
+
function useSpeech({ adoptText, ...request } = {}) {
|
|
2598
|
+
const { items, currentId } = usePlaybackSnapshot();
|
|
2599
|
+
const [itemId, setItemId] = (0, import_react17.useState)(null);
|
|
2600
|
+
const adoptedId = !itemId && adoptText && currentId ? items.find(
|
|
2601
|
+
(i) => i.id === currentId && i.text === adoptText && (i.status === "playing" || i.status === "paused" || i.status === "loading")
|
|
2602
|
+
)?.id ?? null : null;
|
|
2603
|
+
const effectiveId = itemId ?? adoptedId;
|
|
2604
|
+
const { engine, voice, purpose, processMarkdown = true, label, dictionarySurfaceKey, language, speed } = request;
|
|
2605
|
+
const speak = (0, import_react17.useCallback)(
|
|
2606
|
+
(text) => {
|
|
2607
|
+
const { id } = (0, import_speech.speak)({
|
|
2608
|
+
text,
|
|
2609
|
+
engine,
|
|
2610
|
+
voice,
|
|
2611
|
+
purpose,
|
|
2612
|
+
processMarkdown,
|
|
2613
|
+
label,
|
|
2614
|
+
dictionarySurfaceKey,
|
|
2615
|
+
language,
|
|
2616
|
+
speed
|
|
2617
|
+
});
|
|
2618
|
+
setItemId(id);
|
|
2619
|
+
return id;
|
|
2620
|
+
},
|
|
2621
|
+
[engine, voice, purpose, processMarkdown, label, dictionarySurfaceKey, language, speed]
|
|
2622
|
+
);
|
|
2623
|
+
const item = effectiveId ? items.find((i) => i.id === effectiveId) : void 0;
|
|
2624
|
+
const status = item?.status ?? null;
|
|
2625
|
+
return {
|
|
2626
|
+
speak,
|
|
2627
|
+
status,
|
|
2628
|
+
error: item?.error ?? null,
|
|
2629
|
+
itemId,
|
|
2630
|
+
isMine: effectiveId !== null && effectiveId === currentId,
|
|
2631
|
+
pause: import_speech.pausePlayback,
|
|
2632
|
+
resume: import_speech.resumePlayback,
|
|
2633
|
+
skip: import_speech.skipPlayback,
|
|
2634
|
+
remove: import_speech.removePlaybackItem,
|
|
2635
|
+
playItem: import_speech.playPlaybackItem
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2638
|
+
function readAloudStatusOf(status) {
|
|
2639
|
+
if (status === "playing") return "playing";
|
|
2640
|
+
if (status === "paused") return "paused";
|
|
2641
|
+
if (status === "loading" || status === "queued") return "loading";
|
|
2642
|
+
if (status === "error") return "error";
|
|
2643
|
+
return "idle";
|
|
2644
|
+
}
|
|
2645
|
+
function useReadAloud(text, options = {}) {
|
|
2646
|
+
const { speak, status, error, pause, resume } = useSpeech({ adoptText: text, ...options });
|
|
2647
|
+
const state = readAloudStatusOf(status);
|
|
2648
|
+
const onPress = (0, import_react17.useCallback)(() => {
|
|
2649
|
+
if (state === "loading") return;
|
|
2650
|
+
if (state === "playing") void pause();
|
|
2651
|
+
else if (state === "paused") void resume();
|
|
2652
|
+
else if (text.trim()) speak(text);
|
|
2653
|
+
}, [state, text, speak, pause, resume]);
|
|
2654
|
+
return { status: state, error, onPress, speak };
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
// src/react/read-aloud-language-picker.tsx
|
|
2658
|
+
var import_design_system = require("@ai-matrx/design-system");
|
|
2659
|
+
var import_lucide_react = require("lucide-react");
|
|
2660
|
+
var import_react18 = require("react");
|
|
2661
|
+
|
|
2662
|
+
// src/voices/read-aloud-languages.ts
|
|
2663
|
+
var READ_ALOUD_LANGUAGES = [
|
|
2664
|
+
{ code: "en", englishName: "English", nativeName: "English" },
|
|
2665
|
+
{ code: "es", englishName: "Spanish", nativeName: "Espa\xF1ol" },
|
|
2666
|
+
{ code: "fr", englishName: "French", nativeName: "Fran\xE7ais" },
|
|
2667
|
+
{ code: "de", englishName: "German", nativeName: "Deutsch" },
|
|
2668
|
+
{ code: "it", englishName: "Italian", nativeName: "Italiano" },
|
|
2669
|
+
{ code: "pt", englishName: "Portuguese", nativeName: "Portugu\xEAs" },
|
|
2670
|
+
{ code: "zh", englishName: "Chinese", nativeName: "\u4E2D\u6587" },
|
|
2671
|
+
{ code: "ja", englishName: "Japanese", nativeName: "\u65E5\u672C\u8A9E" },
|
|
2672
|
+
{ code: "ko", englishName: "Korean", nativeName: "\uD55C\uAD6D\uC5B4" },
|
|
2673
|
+
{ code: "ru", englishName: "Russian", nativeName: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439" },
|
|
2674
|
+
{ code: "hi", englishName: "Hindi", nativeName: "\u0939\u093F\u0928\u094D\u0926\u0940" },
|
|
2675
|
+
{ code: "nl", englishName: "Dutch", nativeName: "Nederlands" },
|
|
2676
|
+
{ code: "pl", englishName: "Polish", nativeName: "Polski" },
|
|
2677
|
+
{ code: "sv", englishName: "Swedish", nativeName: "Svenska" },
|
|
2678
|
+
{ code: "tr", englishName: "Turkish", nativeName: "T\xFCrk\xE7e" },
|
|
2679
|
+
{ code: "fa", englishName: "Persian", nativeName: "\u0641\u0627\u0631\u0633\u06CC" }
|
|
2680
|
+
];
|
|
2681
|
+
function readAloudLanguage(code) {
|
|
2682
|
+
return READ_ALOUD_LANGUAGES.find((l) => l.code === code) ?? READ_ALOUD_LANGUAGES[0];
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
// src/react/read-aloud-language-picker.tsx
|
|
2686
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2687
|
+
function ReadAloudLanguagePicker({ value, onChange, className }) {
|
|
2688
|
+
const [open, setOpen] = (0, import_react18.useState)(false);
|
|
2689
|
+
const selected = readAloudLanguage(value);
|
|
2690
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_design_system.Popover, { open, onOpenChange: setOpen, children: [
|
|
2691
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2692
|
+
import_design_system.Button,
|
|
2693
|
+
{
|
|
2694
|
+
variant: "ghost",
|
|
2695
|
+
size: "icon",
|
|
2696
|
+
className: cn("size-7 text-muted-foreground", className),
|
|
2697
|
+
title: `Voice language: ${selected.englishName}`,
|
|
2698
|
+
"aria-label": `Voice language, currently ${selected.englishName}`,
|
|
2699
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react.Languages, { className: "size-3.5" })
|
|
2700
|
+
}
|
|
2701
|
+
) }),
|
|
2702
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_design_system.PopoverContent, { className: "max-h-80 w-56 overflow-y-auto p-1", align: "end", children: [
|
|
2703
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "px-2.5 py-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: "Voice language" }),
|
|
2704
|
+
READ_ALOUD_LANGUAGES.map((option) => {
|
|
2705
|
+
const isSelected = option.code === selected.code;
|
|
2706
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2707
|
+
"button",
|
|
2708
|
+
{
|
|
2709
|
+
type: "button",
|
|
2710
|
+
role: "menuitemradio",
|
|
2711
|
+
"aria-checked": isSelected,
|
|
2712
|
+
onClick: () => {
|
|
2713
|
+
onChange(option.code);
|
|
2714
|
+
setOpen(false);
|
|
2715
|
+
},
|
|
2716
|
+
className: cn(
|
|
2717
|
+
"flex w-full items-center gap-2.5 rounded-md px-2.5 py-1.5 text-left transition-colors hover:bg-accent",
|
|
2718
|
+
isSelected && "bg-accent"
|
|
2719
|
+
),
|
|
2720
|
+
children: [
|
|
2721
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "min-w-0 flex-1", children: [
|
|
2722
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "block text-sm", children: option.nativeName }),
|
|
2723
|
+
option.nativeName !== option.englishName ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "block text-[11px] text-muted-foreground", children: option.englishName }) : null
|
|
2724
|
+
] }),
|
|
2725
|
+
isSelected ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react.Check, { className: "size-3.5 shrink-0 text-primary" }) : null
|
|
2726
|
+
]
|
|
2727
|
+
},
|
|
2728
|
+
option.code
|
|
2729
|
+
);
|
|
2730
|
+
})
|
|
2731
|
+
] })
|
|
2732
|
+
] });
|
|
2733
|
+
}
|
|
2534
2734
|
//# sourceMappingURL=react.cjs.map
|