@scalemule/nextjs 0.1.18 → 0.1.20
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/README.md +3 -1
- package/dist/index.d.mts +31 -2
- package/dist/index.d.ts +31 -2
- package/dist/index.js +287 -3
- package/dist/index.mjs +286 -4
- package/dist/server/index.js +678 -637
- package/dist/server/index.mjs +679 -638
- package/dist/server/ledvery.js +5 -1
- package/dist/server/ledvery.mjs +5 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -7784,6 +7784,7 @@ function ScaleMuleProvider({
|
|
|
7784
7784
|
photo: baseClient.photo,
|
|
7785
7785
|
video: baseClient.video,
|
|
7786
7786
|
audio: baseClient.audio,
|
|
7787
|
+
tts: baseClient.tts,
|
|
7787
7788
|
mediaPolicy: effectiveMediaPolicy,
|
|
7788
7789
|
user,
|
|
7789
7790
|
setUser: handleSetUser,
|
|
@@ -9172,7 +9173,7 @@ function useAudio() {
|
|
|
9172
9173
|
return null;
|
|
9173
9174
|
}
|
|
9174
9175
|
try {
|
|
9175
|
-
const details = await client.get(`/v1/
|
|
9176
|
+
const details = await client.get(`/v1/audio/${audioId}`);
|
|
9176
9177
|
if (details.status === "ready" && details.url) {
|
|
9177
9178
|
return details.url;
|
|
9178
9179
|
}
|
|
@@ -9234,7 +9235,7 @@ function useAudio() {
|
|
|
9234
9235
|
const enriched = await Promise.all(
|
|
9235
9236
|
audioFiles.map(async (file) => {
|
|
9236
9237
|
try {
|
|
9237
|
-
const details = await client.get(`/v1/
|
|
9238
|
+
const details = await client.get(`/v1/audio/${file.id}`);
|
|
9238
9239
|
return {
|
|
9239
9240
|
audio_id: details.id,
|
|
9240
9241
|
file_id: file.id,
|
|
@@ -9270,7 +9271,7 @@ function useAudio() {
|
|
|
9270
9271
|
setError(null);
|
|
9271
9272
|
try {
|
|
9272
9273
|
const [audioDeleteResult, storageDeleteResult] = await Promise.allSettled([
|
|
9273
|
-
client.delete(`/v1/
|
|
9274
|
+
client.delete(`/v1/audio/${fileId}`),
|
|
9274
9275
|
client.delete(`/v1/storage/files/${fileId}`)
|
|
9275
9276
|
]);
|
|
9276
9277
|
if (storageDeleteResult.status === "rejected" && !isNotFoundError(storageDeleteResult.reason)) {
|
|
@@ -9297,6 +9298,63 @@ function useAudio() {
|
|
|
9297
9298
|
loading
|
|
9298
9299
|
};
|
|
9299
9300
|
}
|
|
9301
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
9302
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["ready", "failed"]);
|
|
9303
|
+
function useTtsJob(jobId, options) {
|
|
9304
|
+
const { tts } = useScaleMule();
|
|
9305
|
+
const [job, setJob] = useState(null);
|
|
9306
|
+
const [loading, setLoading] = useState(false);
|
|
9307
|
+
const [error, setError] = useState(null);
|
|
9308
|
+
const refresh = useCallback(async () => {
|
|
9309
|
+
if (!jobId) {
|
|
9310
|
+
setJob(null);
|
|
9311
|
+
return null;
|
|
9312
|
+
}
|
|
9313
|
+
setLoading(true);
|
|
9314
|
+
setError(null);
|
|
9315
|
+
try {
|
|
9316
|
+
const result = await tts.getJob(jobId);
|
|
9317
|
+
if (!result) {
|
|
9318
|
+
const apiError = { code: "tts_job_error", message: "Failed to load TTS job", status: 500 };
|
|
9319
|
+
setError(apiError);
|
|
9320
|
+
return null;
|
|
9321
|
+
}
|
|
9322
|
+
if (result.error || !result.data) {
|
|
9323
|
+
const apiError = result.error ?? { code: "tts_job_error", message: "Failed to load TTS job", status: 500 };
|
|
9324
|
+
setError(apiError);
|
|
9325
|
+
return null;
|
|
9326
|
+
}
|
|
9327
|
+
setJob(result.data);
|
|
9328
|
+
return result.data;
|
|
9329
|
+
} finally {
|
|
9330
|
+
setLoading(false);
|
|
9331
|
+
}
|
|
9332
|
+
}, [jobId, tts]);
|
|
9333
|
+
useEffect(() => {
|
|
9334
|
+
if (!jobId || options?.enabled === false) {
|
|
9335
|
+
setJob(null);
|
|
9336
|
+
setLoading(false);
|
|
9337
|
+
return;
|
|
9338
|
+
}
|
|
9339
|
+
let cancelled = false;
|
|
9340
|
+
let timeoutId = null;
|
|
9341
|
+
const poll = async () => {
|
|
9342
|
+
const next = await refresh();
|
|
9343
|
+
if (cancelled || !next || TERMINAL_STATUSES.has(next.status)) {
|
|
9344
|
+
return;
|
|
9345
|
+
}
|
|
9346
|
+
timeoutId = window.setTimeout(poll, options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
9347
|
+
};
|
|
9348
|
+
poll();
|
|
9349
|
+
return () => {
|
|
9350
|
+
cancelled = true;
|
|
9351
|
+
if (timeoutId != null) {
|
|
9352
|
+
window.clearTimeout(timeoutId);
|
|
9353
|
+
}
|
|
9354
|
+
};
|
|
9355
|
+
}, [jobId, options?.enabled, options?.pollIntervalMs, refresh]);
|
|
9356
|
+
return { job, loading, error, refresh };
|
|
9357
|
+
}
|
|
9300
9358
|
function useMedia() {
|
|
9301
9359
|
const { storage, photo, video, audio, mediaPolicy: providerDefaultPolicy } = useScaleMule();
|
|
9302
9360
|
const [uploading, setUploading] = useState(false);
|
|
@@ -11210,6 +11268,230 @@ function FeedbackWidget(props) {
|
|
|
11210
11268
|
) })
|
|
11211
11269
|
] });
|
|
11212
11270
|
}
|
|
11271
|
+
var SPEEDS = [1, 1.25, 1.5, 2];
|
|
11272
|
+
function formatTime(seconds) {
|
|
11273
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
11274
|
+
return "0:00";
|
|
11275
|
+
}
|
|
11276
|
+
const wholeSeconds = Math.floor(seconds);
|
|
11277
|
+
const minutes = Math.floor(wholeSeconds / 60);
|
|
11278
|
+
const remainder = wholeSeconds % 60;
|
|
11279
|
+
return `${minutes}:${remainder.toString().padStart(2, "0")}`;
|
|
11280
|
+
}
|
|
11281
|
+
function getNextSpeed(currentSpeed) {
|
|
11282
|
+
const currentIndex = SPEEDS.findIndex((speed) => speed === currentSpeed);
|
|
11283
|
+
if (currentIndex === -1) {
|
|
11284
|
+
return SPEEDS[0];
|
|
11285
|
+
}
|
|
11286
|
+
return SPEEDS[(currentIndex + 1) % SPEEDS.length];
|
|
11287
|
+
}
|
|
11288
|
+
function NarrationPlayer({
|
|
11289
|
+
audio,
|
|
11290
|
+
className,
|
|
11291
|
+
providerLabel = "ScaleMule",
|
|
11292
|
+
narrationLabel = "AI-Narrated",
|
|
11293
|
+
refreshing = false,
|
|
11294
|
+
onRefresh,
|
|
11295
|
+
onPlaybackError
|
|
11296
|
+
}) {
|
|
11297
|
+
const audioRef = useRef(null);
|
|
11298
|
+
const scrubRef = useRef(null);
|
|
11299
|
+
const [playing, setPlaying] = useState(false);
|
|
11300
|
+
const [currentTime, setCurrentTime] = useState(0);
|
|
11301
|
+
const [duration, setDuration] = useState(audio.duration_ms ? audio.duration_ms / 1e3 : 0);
|
|
11302
|
+
const [speed, setSpeed] = useState(1.5);
|
|
11303
|
+
const peaks = useMemo(() => {
|
|
11304
|
+
if (!Array.isArray(audio.waveform_peaks)) {
|
|
11305
|
+
return [];
|
|
11306
|
+
}
|
|
11307
|
+
const sampled = audio.waveform_peaks.filter((_, index) => index % 3 === 0).slice(0, 220);
|
|
11308
|
+
return sampled.length > 0 ? sampled : audio.waveform_peaks.slice(0, 220);
|
|
11309
|
+
}, [audio.waveform_peaks]);
|
|
11310
|
+
useEffect(() => {
|
|
11311
|
+
const element = audioRef.current;
|
|
11312
|
+
if (!element) return;
|
|
11313
|
+
setCurrentTime(0);
|
|
11314
|
+
setPlaying(false);
|
|
11315
|
+
element.pause();
|
|
11316
|
+
element.currentTime = 0;
|
|
11317
|
+
}, [audio.url]);
|
|
11318
|
+
useEffect(() => {
|
|
11319
|
+
const element = audioRef.current;
|
|
11320
|
+
if (!element) return;
|
|
11321
|
+
element.playbackRate = speed;
|
|
11322
|
+
element.preservesPitch = true;
|
|
11323
|
+
}, [speed]);
|
|
11324
|
+
function togglePlayback() {
|
|
11325
|
+
const element = audioRef.current;
|
|
11326
|
+
if (!element) return;
|
|
11327
|
+
if (element.paused) {
|
|
11328
|
+
element.play().catch(() => {
|
|
11329
|
+
setPlaying(false);
|
|
11330
|
+
onPlaybackError?.();
|
|
11331
|
+
});
|
|
11332
|
+
return;
|
|
11333
|
+
}
|
|
11334
|
+
element.pause();
|
|
11335
|
+
}
|
|
11336
|
+
function seekTo(clientX) {
|
|
11337
|
+
const element = audioRef.current;
|
|
11338
|
+
const scrubber = scrubRef.current;
|
|
11339
|
+
if (!element || !scrubber || !duration) return;
|
|
11340
|
+
const bounds = scrubber.getBoundingClientRect();
|
|
11341
|
+
const ratio = Math.max(0, Math.min(1, (clientX - bounds.left) / bounds.width));
|
|
11342
|
+
const nextTime = ratio * duration;
|
|
11343
|
+
element.currentTime = nextTime;
|
|
11344
|
+
setCurrentTime(nextTime);
|
|
11345
|
+
}
|
|
11346
|
+
const progress = duration > 0 ? Math.min(1, currentTime / duration) : 0;
|
|
11347
|
+
const expiresLabel = audio.expires_at ? new Date(audio.expires_at).toLocaleTimeString([], {
|
|
11348
|
+
hour: "numeric",
|
|
11349
|
+
minute: "2-digit"
|
|
11350
|
+
}) : null;
|
|
11351
|
+
if (!audio.url) {
|
|
11352
|
+
return null;
|
|
11353
|
+
}
|
|
11354
|
+
return /* @__PURE__ */ jsxs(
|
|
11355
|
+
"div",
|
|
11356
|
+
{
|
|
11357
|
+
className: "rounded-[1.75rem] border border-slate-200/80 bg-[linear-gradient(180deg,#f8fbff_0%,#edf4ff_100%)] p-4 text-slate-900 shadow-[0_22px_70px_rgba(4,12,24,0.28)] " + (className ?? ""),
|
|
11358
|
+
children: [
|
|
11359
|
+
/* @__PURE__ */ jsx(
|
|
11360
|
+
"audio",
|
|
11361
|
+
{
|
|
11362
|
+
ref: audioRef,
|
|
11363
|
+
src: audio.url ?? void 0,
|
|
11364
|
+
preload: "metadata",
|
|
11365
|
+
onLoadedMetadata: (event) => {
|
|
11366
|
+
setDuration(event.currentTarget.duration || 0);
|
|
11367
|
+
},
|
|
11368
|
+
onTimeUpdate: (event) => {
|
|
11369
|
+
setCurrentTime(event.currentTarget.currentTime);
|
|
11370
|
+
},
|
|
11371
|
+
onPlay: () => setPlaying(true),
|
|
11372
|
+
onPause: () => setPlaying(false),
|
|
11373
|
+
onEnded: () => setPlaying(false),
|
|
11374
|
+
onError: () => onPlaybackError?.()
|
|
11375
|
+
}
|
|
11376
|
+
),
|
|
11377
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-center", children: [
|
|
11378
|
+
/* @__PURE__ */ jsx(
|
|
11379
|
+
"button",
|
|
11380
|
+
{
|
|
11381
|
+
type: "button",
|
|
11382
|
+
onClick: togglePlayback,
|
|
11383
|
+
"aria-label": playing ? "Pause narration" : "Play narration",
|
|
11384
|
+
className: "flex h-[4.5rem] w-[4.5rem] shrink-0 items-center justify-center rounded-full bg-slate-950 text-white shadow-[0_10px_28px_rgba(15,23,42,0.25)] transition hover:bg-slate-800",
|
|
11385
|
+
children: playing ? /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", className: "h-7 w-7 fill-current", "aria-hidden": "true", children: [
|
|
11386
|
+
/* @__PURE__ */ jsx("rect", { x: "6", y: "5", width: "4", height: "14", rx: "1.2" }),
|
|
11387
|
+
/* @__PURE__ */ jsx("rect", { x: "14", y: "5", width: "4", height: "14", rx: "1.2" })
|
|
11388
|
+
] }) : /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "ml-1 h-7 w-7 fill-current", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M7 5.2c0-1.02 1.12-1.65 2-.98l10.2 7.78a1.24 1.24 0 0 1 0 1.98L9 21.76c-.88.67-2-.04-2-1.08V5.2Z" }) })
|
|
11389
|
+
}
|
|
11390
|
+
),
|
|
11391
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
11392
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 text-[0.8rem] font-medium text-slate-500", children: [
|
|
11393
|
+
/* @__PURE__ */ jsx("span", { children: formatTime(currentTime / speed) }),
|
|
11394
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
11395
|
+
/* @__PURE__ */ jsx("span", { className: "rounded-full bg-blue-100 px-3 py-1 text-[0.65rem] font-semibold uppercase tracking-[0.24em] text-blue-700", children: narrationLabel }),
|
|
11396
|
+
/* @__PURE__ */ jsx("span", { children: formatTime(duration / speed) })
|
|
11397
|
+
] })
|
|
11398
|
+
] }),
|
|
11399
|
+
/* @__PURE__ */ jsx(
|
|
11400
|
+
"div",
|
|
11401
|
+
{
|
|
11402
|
+
ref: scrubRef,
|
|
11403
|
+
role: "slider",
|
|
11404
|
+
"aria-label": "Narration progress",
|
|
11405
|
+
"aria-valuemin": 0,
|
|
11406
|
+
"aria-valuemax": Math.max(1, Math.floor(duration)),
|
|
11407
|
+
"aria-valuenow": Math.floor(currentTime),
|
|
11408
|
+
tabIndex: 0,
|
|
11409
|
+
onClick: (event) => {
|
|
11410
|
+
seekTo(event.clientX);
|
|
11411
|
+
},
|
|
11412
|
+
className: "relative mt-4 h-12 cursor-pointer overflow-hidden rounded-[1rem] bg-slate-200/90 px-1",
|
|
11413
|
+
children: peaks.length > 0 ? /* @__PURE__ */ jsx(
|
|
11414
|
+
"svg",
|
|
11415
|
+
{
|
|
11416
|
+
viewBox: `0 0 ${peaks.length} 40`,
|
|
11417
|
+
preserveAspectRatio: "none",
|
|
11418
|
+
className: "pointer-events-none absolute inset-0 h-full w-full",
|
|
11419
|
+
children: peaks.map((peak, index) => {
|
|
11420
|
+
const height = Math.max(2, Math.round(Math.abs(peak) * 34));
|
|
11421
|
+
const y = (40 - height) / 2;
|
|
11422
|
+
const filled = index / peaks.length < progress;
|
|
11423
|
+
return /* @__PURE__ */ jsx(
|
|
11424
|
+
"rect",
|
|
11425
|
+
{
|
|
11426
|
+
x: index,
|
|
11427
|
+
y,
|
|
11428
|
+
width: 0.82,
|
|
11429
|
+
height,
|
|
11430
|
+
rx: 0.4,
|
|
11431
|
+
className: filled ? "fill-blue-600" : "fill-blue-300"
|
|
11432
|
+
},
|
|
11433
|
+
`${audio.id}-${index}`
|
|
11434
|
+
);
|
|
11435
|
+
})
|
|
11436
|
+
}
|
|
11437
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11438
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-slate-200/90" }),
|
|
11439
|
+
/* @__PURE__ */ jsx(
|
|
11440
|
+
"div",
|
|
11441
|
+
{
|
|
11442
|
+
className: "absolute inset-y-0 left-0 bg-blue-600",
|
|
11443
|
+
style: { width: `${progress * 100}%` }
|
|
11444
|
+
}
|
|
11445
|
+
)
|
|
11446
|
+
] })
|
|
11447
|
+
}
|
|
11448
|
+
),
|
|
11449
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-2 text-[0.72rem] font-medium text-slate-500", children: [
|
|
11450
|
+
/* @__PURE__ */ jsxs("span", { className: "rounded-full border border-slate-200 bg-white/85 px-3 py-1 text-slate-700", children: [
|
|
11451
|
+
"Provider: ",
|
|
11452
|
+
providerLabel
|
|
11453
|
+
] }),
|
|
11454
|
+
duration > 0 && /* @__PURE__ */ jsxs("span", { className: "rounded-full border border-slate-200 bg-white/65 px-3 py-1", children: [
|
|
11455
|
+
"Length ",
|
|
11456
|
+
formatTime(duration / speed)
|
|
11457
|
+
] }),
|
|
11458
|
+
expiresLabel && /* @__PURE__ */ jsxs("span", { className: "rounded-full border border-slate-200 bg-white/65 px-3 py-1", children: [
|
|
11459
|
+
"Expires ",
|
|
11460
|
+
expiresLabel
|
|
11461
|
+
] })
|
|
11462
|
+
] })
|
|
11463
|
+
] }),
|
|
11464
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 self-start md:self-center", children: [
|
|
11465
|
+
/* @__PURE__ */ jsxs(
|
|
11466
|
+
"button",
|
|
11467
|
+
{
|
|
11468
|
+
type: "button",
|
|
11469
|
+
onClick: () => {
|
|
11470
|
+
setSpeed((currentSpeed) => getNextSpeed(currentSpeed));
|
|
11471
|
+
},
|
|
11472
|
+
className: "rounded-full border border-slate-300 bg-white/90 px-4 py-2 text-base font-semibold text-slate-700 shadow-sm hover:bg-white",
|
|
11473
|
+
children: [
|
|
11474
|
+
speed,
|
|
11475
|
+
"\xD7"
|
|
11476
|
+
]
|
|
11477
|
+
}
|
|
11478
|
+
),
|
|
11479
|
+
onRefresh && /* @__PURE__ */ jsx(
|
|
11480
|
+
"button",
|
|
11481
|
+
{
|
|
11482
|
+
type: "button",
|
|
11483
|
+
onClick: onRefresh,
|
|
11484
|
+
disabled: refreshing,
|
|
11485
|
+
className: "rounded-full border border-slate-300 bg-white/75 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-white disabled:cursor-not-allowed disabled:opacity-60",
|
|
11486
|
+
children: refreshing ? "Refreshing\u2026" : "Refresh"
|
|
11487
|
+
}
|
|
11488
|
+
)
|
|
11489
|
+
] })
|
|
11490
|
+
] })
|
|
11491
|
+
]
|
|
11492
|
+
}
|
|
11493
|
+
);
|
|
11494
|
+
}
|
|
11213
11495
|
|
|
11214
11496
|
// src/validation.ts
|
|
11215
11497
|
var phoneCountries = [
|
|
@@ -11502,4 +11784,4 @@ function createSafeLogger(prefix) {
|
|
|
11502
11784
|
};
|
|
11503
11785
|
}
|
|
11504
11786
|
|
|
11505
|
-
export { FeedbackWidget, ScaleMuleApiError, ScaleMuleClient2 as ScaleMuleClient, ScaleMuleMedia, ScaleMuleProvider, composePhone, createClient, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAudio, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useFileStatus, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
|
|
11787
|
+
export { FeedbackWidget, NarrationPlayer, ScaleMuleApiError, ScaleMuleClient2 as ScaleMuleClient, ScaleMuleMedia, ScaleMuleProvider, composePhone, createClient, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAudio, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useFileStatus, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useTtsJob, useUser, validateForm, validators };
|